@ankhorage/infra 6.0.0 → 7.0.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.
Files changed (20) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +1 -1
  3. package/dist/features/environment-lifecycle/adapters/outbound/createEnvironmentInfraCredentialPort.d.ts.map +1 -1
  4. package/dist/features/environment-lifecycle/adapters/outbound/createEnvironmentInfraCredentialPort.js +33 -10
  5. package/dist/features/environment-lifecycle/adapters/outbound/createEnvironmentInfraCredentialPort.js.map +1 -1
  6. package/dist/features/environment-lifecycle/application/use-cases/prepareInfraServicesAsync.d.ts +4 -0
  7. package/dist/features/environment-lifecycle/application/use-cases/prepareInfraServicesAsync.d.ts.map +1 -0
  8. package/dist/features/environment-lifecycle/application/use-cases/prepareInfraServicesAsync.js +15 -0
  9. package/dist/features/environment-lifecycle/application/use-cases/prepareInfraServicesAsync.js.map +1 -0
  10. package/dist/features/environment-lifecycle/application/use-cases/upInfraEnvironmentAsync.d.ts +1 -1
  11. package/dist/features/environment-lifecycle/application/use-cases/upInfraEnvironmentAsync.d.ts.map +1 -1
  12. package/dist/features/environment-lifecycle/application/use-cases/upInfraEnvironmentAsync.js +10 -5
  13. package/dist/features/environment-lifecycle/application/use-cases/upInfraEnvironmentAsync.js.map +1 -1
  14. package/package.json +2 -2
  15. package/src/environmentCredentialPort.test.ts +62 -0
  16. package/src/features/environment-lifecycle/adapters/outbound/createEnvironmentInfraCredentialPort.ts +55 -18
  17. package/src/features/environment-lifecycle/application/use-cases/prepareInfraServicesAsync.ts +23 -0
  18. package/src/features/environment-lifecycle/application/use-cases/upInfraEnvironmentAsync.ts +8 -4
  19. package/src/infraUp.test.ts +31 -3
  20. package/src/prepareInfraServices.test.ts +50 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @ankhorage/infra
2
2
 
3
+ ## 7.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 1778b96: Add provider-neutral service preparation before workload materialization and extend trusted control-plane credential hosts with optional lookup and explicit persistence so providers can prepare bootstrap state without runtime-specific coupling. Read-only lifecycle operations remain side-effect free.
8
+
3
9
  ## 6.0.0
4
10
 
5
11
  ### Major Changes
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  # INFRA
5
5
 
6
- ![license: MIT](././paradox/badges/license.svg) ![npm: v5.1.4](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![docs: paradox](././paradox/badges/docs.svg)
6
+ ![license: MIT](././paradox/badges/license.svg) ![npm: v6.0.0](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![docs: paradox](././paradox/badges/docs.svg)
7
7
 
8
8
  Executable infra provider and standalone CLI for Ankhorage project workflows.
9
9
 
@@ -1 +1 @@
1
- {"version":3,"file":"createEnvironmentInfraCredentialPort.d.ts","sourceRoot":"","sources":["../../../../../src/features/environment-lifecycle/adapters/outbound/createEnvironmentInfraCredentialPort.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAe,MAAM,4BAA4B,CAAC;AAKrF,sGAAsG;AACtG,wBAAgB,oCAAoC,CAClD,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GACxD,qBAAqB,CAAC,aAAa,CAAC,CAiBtC"}
1
+ {"version":3,"file":"createEnvironmentInfraCredentialPort.d.ts","sourceRoot":"","sources":["../../../../../src/features/environment-lifecycle/adapters/outbound/createEnvironmentInfraCredentialPort.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,qBAAqB,EAEtB,MAAM,4BAA4B,CAAC;AAKpC,sGAAsG;AACtG,wBAAgB,oCAAoC,CAClD,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GACxD,qBAAqB,CAAC,aAAa,CAAC,CA4BtC"}
@@ -3,23 +3,43 @@ import { normalizeInfraEnvironmentKey } from '../../utils/normalizeInfraEnvironm
3
3
  /*** Resolve execution-only control-plane credentials from exact-name or prefixed environment data. */
4
4
  export function createEnvironmentInfraCredentialPort(environment) {
5
5
  return {
6
+ findAsync(reference) {
7
+ return Promise.resolve(findCredential(environment, reference));
8
+ },
6
9
  resolveAsync(reference) {
7
- const prefixedName = `ANKH_INFRA_CREDENTIAL_${normalizeInfraEnvironmentKey(reference.name)}`;
8
- const rawValue = Reflect.get(environment, reference.name) ?? Reflect.get(environment, prefixedName);
9
- if (rawValue === undefined || rawValue.length === 0) {
10
- return Promise.resolve(createInfraFailure('infra-control-plane-credential-missing', `Control-plane credential ${reference.name} is unavailable. Set ${reference.name} or ${prefixedName}.`));
10
+ const found = findCredential(environment, reference);
11
+ if (!found.ok)
12
+ return Promise.resolve(found);
13
+ if (found.value !== null) {
14
+ return Promise.resolve({ ok: true, value: found.value, diagnostics: found.diagnostics });
11
15
  }
12
- return Promise.resolve(parseCredentialBundle(reference.name, rawValue));
16
+ const prefixedName = prefixedCredentialName(reference.name);
17
+ return Promise.resolve(createInfraFailure('infra-control-plane-credential-missing', `Control-plane credential ${reference.name} is unavailable. Set ${reference.name} or ${prefixedName}.`));
18
+ },
19
+ persistAsync(reference) {
20
+ return Promise.resolve(createInfraFailure('infra-control-plane-credential-persistence-unavailable', `Control-plane credential ${reference.name} cannot be persisted by the environment-only credential source.`));
13
21
  },
14
22
  };
15
23
  }
24
+ /*** Find and parse an optional credential bundle from the environment without treating absence as failure. */
25
+ function findCredential(environment, reference) {
26
+ const prefixedName = prefixedCredentialName(reference.name);
27
+ const rawValue = Reflect.get(environment, reference.name) ?? Reflect.get(environment, prefixedName);
28
+ if (rawValue === undefined || rawValue.length === 0) {
29
+ return { ok: true, value: null, diagnostics: [] };
30
+ }
31
+ return parseCredentialBundle(reference.name, rawValue);
32
+ }
33
+ /*** Build the canonical prefixed environment variable name for one credential reference. */
34
+ function prefixedCredentialName(name) {
35
+ return `ANKH_INFRA_CREDENTIAL_${normalizeInfraEnvironmentKey(name)}`;
36
+ }
16
37
  /*** Parse JSON credential bundles; token variables also support their conventional scalar form. */
17
38
  function parseCredentialBundle(name, rawValue) {
18
39
  try {
19
40
  const parsed = JSON.parse(rawValue);
20
- if (isRecord(parsed) && Object.values(parsed).every((value) => typeof value === 'string')) {
41
+ if (isStringRecord(parsed))
21
42
  return { ok: true, value: parsed, diagnostics: [] };
22
- }
23
43
  }
24
44
  catch {
25
45
  if (name.endsWith('_TOKEN')) {
@@ -28,8 +48,11 @@ function parseCredentialBundle(name, rawValue) {
28
48
  }
29
49
  return createInfraFailure('infra-control-plane-credential-invalid', `Control-plane credential ${name} must be a JSON object containing string fields.`);
30
50
  }
31
- /*** Narrow JSON credential bundles. */
32
- function isRecord(value) {
33
- return typeof value === 'object' && value !== null && !Array.isArray(value);
51
+ /*** Narrow credential bundle values to a string-only record. */
52
+ function isStringRecord(value) {
53
+ return (typeof value === 'object' &&
54
+ value !== null &&
55
+ !Array.isArray(value) &&
56
+ Object.values(value).every((entry) => typeof entry === 'string'));
34
57
  }
35
58
  //# sourceMappingURL=createEnvironmentInfraCredentialPort.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"createEnvironmentInfraCredentialPort.js","sourceRoot":"","sources":["../../../../../src/features/environment-lifecycle/adapters/outbound/createEnvironmentInfraCredentialPort.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,EAAE,4BAA4B,EAAE,MAAM,6CAA6C,CAAC;AAE3F,sGAAsG;AACtG,MAAM,UAAU,oCAAoC,CAClD,WAAyD;IAEzD,OAAO;QACL,YAAY,CAAC,SAAS;YACpB,MAAM,YAAY,GAAG,yBAAyB,4BAA4B,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7F,MAAM,QAAQ,GACZ,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,SAAS,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;YACrF,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACpD,OAAO,OAAO,CAAC,OAAO,CACpB,kBAAkB,CAChB,wCAAwC,EACxC,4BAA4B,SAAS,CAAC,IAAI,wBAAwB,SAAS,CAAC,IAAI,OAAO,YAAY,GAAG,CACvG,CACF,CAAC;YACJ,CAAC;YACD,OAAO,OAAO,CAAC,OAAO,CAAC,qBAAqB,CAAC,SAAS,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC1E,CAAC;KACF,CAAC;AACJ,CAAC;AAED,mGAAmG;AACnG,SAAS,qBAAqB,CAC5B,IAAY,EACZ,QAAgB;IAEhB,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC7C,IAAI,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,EAAE,CAAC;YAC1F,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAA0C,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;QAC1F,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5B,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;QACvF,CAAC;IACH,CAAC;IACD,OAAO,kBAAkB,CACvB,wCAAwC,EACxC,4BAA4B,IAAI,kDAAkD,CACnF,CAAC;AACJ,CAAC;AAED,uCAAuC;AACvC,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC","sourcesContent":["import type { InfraExecutionContext, InfraResult } from '@ankhorage/contracts/infra';\n\nimport { createInfraFailure } from '../../utils/createInfraFailure.js';\nimport { normalizeInfraEnvironmentKey } from '../../utils/normalizeInfraEnvironmentKey.js';\n\n/*** Resolve execution-only control-plane credentials from exact-name or prefixed environment data. */\nexport function createEnvironmentInfraCredentialPort(\n environment: Readonly<Record<string, string | undefined>>,\n): InfraExecutionContext['credentials'] {\n return {\n resolveAsync(reference) {\n const prefixedName = `ANKH_INFRA_CREDENTIAL_${normalizeInfraEnvironmentKey(reference.name)}`;\n const rawValue =\n Reflect.get(environment, reference.name) ?? Reflect.get(environment, prefixedName);\n if (rawValue === undefined || rawValue.length === 0) {\n return Promise.resolve(\n createInfraFailure(\n 'infra-control-plane-credential-missing',\n `Control-plane credential ${reference.name} is unavailable. Set ${reference.name} or ${prefixedName}.`,\n ),\n );\n }\n return Promise.resolve(parseCredentialBundle(reference.name, rawValue));\n },\n };\n}\n\n/*** Parse JSON credential bundles; token variables also support their conventional scalar form. */\nfunction parseCredentialBundle(\n name: string,\n rawValue: string,\n): InfraResult<Readonly<Record<string, string>>> {\n try {\n const parsed: unknown = JSON.parse(rawValue);\n if (isRecord(parsed) && Object.values(parsed).every((value) => typeof value === 'string')) {\n return { ok: true, value: parsed as Readonly<Record<string, string>>, diagnostics: [] };\n }\n } catch {\n if (name.endsWith('_TOKEN')) {\n return { ok: true, value: { token: rawValue, apiToken: rawValue }, diagnostics: [] };\n }\n }\n return createInfraFailure(\n 'infra-control-plane-credential-invalid',\n `Control-plane credential ${name} must be a JSON object containing string fields.`,\n );\n}\n\n/*** Narrow JSON credential bundles. */\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n"]}
1
+ {"version":3,"file":"createEnvironmentInfraCredentialPort.js","sourceRoot":"","sources":["../../../../../src/features/environment-lifecycle/adapters/outbound/createEnvironmentInfraCredentialPort.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,EAAE,4BAA4B,EAAE,MAAM,6CAA6C,CAAC;AAE3F,sGAAsG;AACtG,MAAM,UAAU,oCAAoC,CAClD,WAAyD;IAEzD,OAAO;QACL,SAAS,CAAC,SAAS;YACjB,OAAO,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC,CAAC;QACjE,CAAC;QACD,YAAY,CAAC,SAAS;YACpB,MAAM,KAAK,GAAG,cAAc,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;YACrD,IAAI,CAAC,KAAK,CAAC,EAAE;gBAAE,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC7C,IAAI,KAAK,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;gBACzB,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;YAC3F,CAAC;YACD,MAAM,YAAY,GAAG,sBAAsB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAC5D,OAAO,OAAO,CAAC,OAAO,CACpB,kBAAkB,CAChB,wCAAwC,EACxC,4BAA4B,SAAS,CAAC,IAAI,wBAAwB,SAAS,CAAC,IAAI,OAAO,YAAY,GAAG,CACvG,CACF,CAAC;QACJ,CAAC;QACD,YAAY,CAAC,SAAS;YACpB,OAAO,OAAO,CAAC,OAAO,CACpB,kBAAkB,CAChB,wDAAwD,EACxD,4BAA4B,SAAS,CAAC,IAAI,iEAAiE,CAC5G,CACF,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC;AAED,8GAA8G;AAC9G,SAAS,cAAc,CACrB,WAAyD,EACzD,SAAyC;IAEzC,MAAM,YAAY,GAAG,sBAAsB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAC5D,MAAM,QAAQ,GACZ,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,SAAS,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;IACrF,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;IACpD,CAAC;IACD,OAAO,qBAAqB,CAAC,SAAS,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AACzD,CAAC;AAED,4FAA4F;AAC5F,SAAS,sBAAsB,CAAC,IAAY;IAC1C,OAAO,yBAAyB,4BAA4B,CAAC,IAAI,CAAC,EAAE,CAAC;AACvE,CAAC;AAED,mGAAmG;AACnG,SAAS,qBAAqB,CAC5B,IAAY,EACZ,QAAgB;IAEhB,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC7C,IAAI,cAAc,CAAC,MAAM,CAAC;YAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;IAClF,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5B,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;QACvF,CAAC;IACH,CAAC;IACD,OAAO,kBAAkB,CACvB,wCAAwC,EACxC,4BAA4B,IAAI,kDAAkD,CACnF,CAAC;AACJ,CAAC;AAED,gEAAgE;AAChE,SAAS,cAAc,CAAC,KAAc;IACpC,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACrB,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CACjE,CAAC;AACJ,CAAC","sourcesContent":["import type {\n InfraControlPlaneCredentialRef,\n InfraExecutionContext,\n InfraResult,\n} from '@ankhorage/contracts/infra';\n\nimport { createInfraFailure } from '../../utils/createInfraFailure.js';\nimport { normalizeInfraEnvironmentKey } from '../../utils/normalizeInfraEnvironmentKey.js';\n\n/*** Resolve execution-only control-plane credentials from exact-name or prefixed environment data. */\nexport function createEnvironmentInfraCredentialPort(\n environment: Readonly<Record<string, string | undefined>>,\n): InfraExecutionContext['credentials'] {\n return {\n findAsync(reference) {\n return Promise.resolve(findCredential(environment, reference));\n },\n resolveAsync(reference) {\n const found = findCredential(environment, reference);\n if (!found.ok) return Promise.resolve(found);\n if (found.value !== null) {\n return Promise.resolve({ ok: true, value: found.value, diagnostics: found.diagnostics });\n }\n const prefixedName = prefixedCredentialName(reference.name);\n return Promise.resolve(\n createInfraFailure(\n 'infra-control-plane-credential-missing',\n `Control-plane credential ${reference.name} is unavailable. Set ${reference.name} or ${prefixedName}.`,\n ),\n );\n },\n persistAsync(reference) {\n return Promise.resolve(\n createInfraFailure(\n 'infra-control-plane-credential-persistence-unavailable',\n `Control-plane credential ${reference.name} cannot be persisted by the environment-only credential source.`,\n ),\n );\n },\n };\n}\n\n/*** Find and parse an optional credential bundle from the environment without treating absence as failure. */\nfunction findCredential(\n environment: Readonly<Record<string, string | undefined>>,\n reference: InfraControlPlaneCredentialRef,\n): InfraResult<Readonly<Record<string, string>> | null> {\n const prefixedName = prefixedCredentialName(reference.name);\n const rawValue =\n Reflect.get(environment, reference.name) ?? Reflect.get(environment, prefixedName);\n if (rawValue === undefined || rawValue.length === 0) {\n return { ok: true, value: null, diagnostics: [] };\n }\n return parseCredentialBundle(reference.name, rawValue);\n}\n\n/*** Build the canonical prefixed environment variable name for one credential reference. */\nfunction prefixedCredentialName(name: string): string {\n return `ANKH_INFRA_CREDENTIAL_${normalizeInfraEnvironmentKey(name)}`;\n}\n\n/*** Parse JSON credential bundles; token variables also support their conventional scalar form. */\nfunction parseCredentialBundle(\n name: string,\n rawValue: string,\n): InfraResult<Readonly<Record<string, string>>> {\n try {\n const parsed: unknown = JSON.parse(rawValue);\n if (isStringRecord(parsed)) return { ok: true, value: parsed, diagnostics: [] };\n } catch {\n if (name.endsWith('_TOKEN')) {\n return { ok: true, value: { token: rawValue, apiToken: rawValue }, diagnostics: [] };\n }\n }\n return createInfraFailure(\n 'infra-control-plane-credential-invalid',\n `Control-plane credential ${name} must be a JSON object containing string fields.`,\n );\n}\n\n/*** Narrow credential bundle values to a string-only record. */\nfunction isStringRecord(value: unknown): value is Readonly<Record<string, string>> {\n return (\n typeof value === 'object' &&\n value !== null &&\n !Array.isArray(value) &&\n Object.values(value).every((entry) => typeof entry === 'string')\n );\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { InfraExecutionContext, InfraResult, InfraServiceAdapter } from '@ankhorage/contracts/infra';
2
+ /*** Run optional mutating service preparation before runtime workload materialization. */
3
+ export declare function prepareInfraServicesAsync(context: InfraExecutionContext, services: readonly InfraServiceAdapter[]): Promise<InfraResult<null>>;
4
+ //# sourceMappingURL=prepareInfraServicesAsync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prepareInfraServicesAsync.d.ts","sourceRoot":"","sources":["../../../../../src/features/environment-lifecycle/application/use-cases/prepareInfraServicesAsync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,qBAAqB,EACrB,WAAW,EACX,mBAAmB,EACpB,MAAM,4BAA4B,CAAC;AAIpC,0FAA0F;AAC1F,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,qBAAqB,EAC9B,QAAQ,EAAE,SAAS,mBAAmB,EAAE,GACvC,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAS5B"}
@@ -0,0 +1,15 @@
1
+ import { appendInfraFailure } from '../../utils/appendInfraFailure.js';
2
+ /*** Run optional mutating service preparation before runtime workload materialization. */
3
+ export async function prepareInfraServicesAsync(context, services) {
4
+ const diagnostics = [];
5
+ for (const service of services) {
6
+ if (service.prepareAsync === undefined)
7
+ continue;
8
+ const prepared = await service.prepareAsync(context);
9
+ if (!prepared.ok)
10
+ return appendInfraFailure(diagnostics, prepared);
11
+ diagnostics.push(...prepared.diagnostics);
12
+ }
13
+ return { ok: true, value: null, diagnostics };
14
+ }
15
+ //# sourceMappingURL=prepareInfraServicesAsync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prepareInfraServicesAsync.js","sourceRoot":"","sources":["../../../../../src/features/environment-lifecycle/application/use-cases/prepareInfraServicesAsync.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAEvE,0FAA0F;AAC1F,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,OAA8B,EAC9B,QAAwC;IAExC,MAAM,WAAW,GAAsB,EAAE,CAAC;IAC1C,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,OAAO,CAAC,YAAY,KAAK,SAAS;YAAE,SAAS;QACjD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QACrD,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO,kBAAkB,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;QACnE,WAAW,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;AAChD,CAAC","sourcesContent":["import type {\n InfraDiagnostic,\n InfraExecutionContext,\n InfraResult,\n InfraServiceAdapter,\n} from '@ankhorage/contracts/infra';\n\nimport { appendInfraFailure } from '../../utils/appendInfraFailure.js';\n\n/*** Run optional mutating service preparation before runtime workload materialization. */\nexport async function prepareInfraServicesAsync(\n context: InfraExecutionContext,\n services: readonly InfraServiceAdapter[],\n): Promise<InfraResult<null>> {\n const diagnostics: InfraDiagnostic[] = [];\n for (const service of services) {\n if (service.prepareAsync === undefined) continue;\n const prepared = await service.prepareAsync(context);\n if (!prepared.ok) return appendInfraFailure(diagnostics, prepared);\n diagnostics.push(...prepared.diagnostics);\n }\n return { ok: true, value: null, diagnostics };\n}\n"]}
@@ -1,4 +1,4 @@
1
1
  import type { InfraOrchestrationDependencies, InfraUpOperationResult, InfraUpRequest } from '../../../../types/infraOrchestration.js';
2
- /*** Validate and reconcile compute, contributed workloads, runtime and services in dependency order. */
2
+ /*** Validate and reconcile compute, service preparation, workloads, runtime and services in order. */
3
3
  export declare function upInfraEnvironmentAsync(request: InfraUpRequest, dependencies: InfraOrchestrationDependencies): Promise<InfraUpOperationResult>;
4
4
  //# sourceMappingURL=upInfraEnvironmentAsync.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"upInfraEnvironmentAsync.d.ts","sourceRoot":"","sources":["../../../../../src/features/environment-lifecycle/application/use-cases/upInfraEnvironmentAsync.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,8BAA8B,EAC9B,sBAAsB,EACtB,cAAc,EACf,MAAM,yCAAyC,CAAC;AAUjD,wGAAwG;AACxG,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,cAAc,EACvB,YAAY,EAAE,8BAA8B,GAC3C,OAAO,CAAC,sBAAsB,CAAC,CA0EjC"}
1
+ {"version":3,"file":"upInfraEnvironmentAsync.d.ts","sourceRoot":"","sources":["../../../../../src/features/environment-lifecycle/application/use-cases/upInfraEnvironmentAsync.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,8BAA8B,EAC9B,sBAAsB,EACtB,cAAc,EACf,MAAM,yCAAyC,CAAC;AAWjD,sGAAsG;AACtG,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,cAAc,EACvB,YAAY,EAAE,8BAA8B,GAC3C,OAAO,CAAC,sBAAsB,CAAC,CA6EjC"}
@@ -4,9 +4,10 @@ import { createInfraExecutionContext } from '../../utils/createInfraExecutionCon
4
4
  import { createInfraLedger } from '../../utils/createInfraLedger.js';
5
5
  import { mergeInfraOutputs } from '../../utils/mergeInfraOutputs.js';
6
6
  import { collectInfraWorkloadsAsync } from './collectInfraWorkloadsAsync.js';
7
+ import { prepareInfraServicesAsync } from './prepareInfraServicesAsync.js';
7
8
  import { resolveInfraAdaptersAsync } from './resolveInfraAdaptersAsync.js';
8
9
  import { validateInfraAdaptersAsync } from './validateInfraAdaptersAsync.js';
9
- /*** Validate and reconcile compute, contributed workloads, runtime and services in dependency order. */
10
+ /*** Validate and reconcile compute, service preparation, workloads, runtime and services in order. */
10
11
  export async function upInfraEnvironmentAsync(request, dependencies) {
11
12
  const environment = resolveInfraEnvironment({
12
13
  manifest: request.manifest,
@@ -25,14 +26,18 @@ export async function upInfraEnvironmentAsync(request, dependencies) {
25
26
  if (!validation.ok)
26
27
  return appendInfraFailure(diagnostics, validation);
27
28
  diagnostics.push(...validation.diagnostics);
28
- const workloads = await collectInfraWorkloadsAsync(context, adapters.value.services);
29
- if (!workloads.ok)
30
- return appendInfraFailure(diagnostics, workloads);
31
- diagnostics.push(...workloads.diagnostics);
32
29
  const compute = await adapters.value.compute.ensureAsync(context, environment.value.desired.deployment.compute);
33
30
  if (!compute.ok)
34
31
  return appendInfraFailure(diagnostics, compute);
35
32
  diagnostics.push(...compute.diagnostics);
33
+ const preparation = await prepareInfraServicesAsync(context, adapters.value.services);
34
+ if (!preparation.ok)
35
+ return appendInfraFailure(diagnostics, preparation);
36
+ diagnostics.push(...preparation.diagnostics);
37
+ const workloads = await collectInfraWorkloadsAsync(context, adapters.value.services);
38
+ if (!workloads.ok)
39
+ return appendInfraFailure(diagnostics, workloads);
40
+ diagnostics.push(...workloads.diagnostics);
36
41
  const runtimeDesired = {
37
42
  selection: environment.value.desired.deployment.runtime,
38
43
  targets: compute.value.targets,
@@ -1 +1 @@
1
- {"version":3,"file":"upInfraEnvironmentAsync.js","sourceRoot":"","sources":["../../../../../src/features/environment-lifecycle/application/use-cases/upInfraEnvironmentAsync.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAClF,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,EAAE,2BAA2B,EAAE,MAAM,4CAA4C,CAAC;AACzF,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAE7E,wGAAwG;AACxG,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,OAAuB,EACvB,YAA4C;IAE5C,MAAM,WAAW,GAAG,uBAAuB,CAAC;QAC1C,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,SAAS,EAAE,IAAI;KAChB,CAAC,CAAC;IACH,IAAI,CAAC,WAAW,CAAC,EAAE;QAAE,OAAO,WAAW,CAAC;IACxC,MAAM,WAAW,GAAsB,CAAC,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC;IACpE,MAAM,QAAQ,GAAG,MAAM,yBAAyB,CAC9C,WAAW,CAAC,KAAK,CAAC,OAAO,EACzB,YAAY,CAAC,eAAe,CAC7B,CAAC;IACF,IAAI,CAAC,QAAQ,CAAC,EAAE;QAAE,OAAO,kBAAkB,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IACnE,WAAW,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAG,2BAA2B,CAAC,OAAO,EAAE,WAAW,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;IACtF,MAAM,UAAU,GAAG,MAAM,0BAA0B,CAAC,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC7E,IAAI,CAAC,UAAU,CAAC,EAAE;QAAE,OAAO,kBAAkB,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;IACvE,WAAW,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IAC5C,MAAM,SAAS,GAAG,MAAM,0BAA0B,CAAC,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACrF,IAAI,CAAC,SAAS,CAAC,EAAE;QAAE,OAAO,kBAAkB,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;IACrE,WAAW,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CACtD,OAAO,EACP,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,CAC7C,CAAC;IACF,IAAI,CAAC,OAAO,CAAC,EAAE;QAAE,OAAO,kBAAkB,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IACjE,WAAW,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACzC,MAAM,cAAc,GAAG;QACrB,SAAS,EAAE,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO;QACvD,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,OAAO;QAC9B,SAAS,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC,EAAE,GAAG,SAAS,CAAC,KAAK,CAAC;QAC/E,gBAAgB,EAAE,iBAAiB,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,IAAI,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC;KAC5F,CAAC;IACF,MAAM,iBAAiB,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IAC9F,IAAI,CAAC,iBAAiB,CAAC,EAAE;QAAE,OAAO,kBAAkB,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAC;IACrF,WAAW,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IAClF,IAAI,CAAC,OAAO,CAAC,EAAE;QAAE,OAAO,kBAAkB,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IACjE,WAAW,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,MAAM,sBAAsB,CAAC,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE;QAC9E,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO;QACxB,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO;KACzB,CAAC,CAAC;IACH,IAAI,CAAC,QAAQ,CAAC,EAAE;QAAE,OAAO,kBAAkB,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IACnE,WAAW,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC1C,MAAM,SAAS,GAAG;QAChB,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS;QAC1B,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS;QAC1B,GAAG,QAAQ,CAAC,KAAK,CAAC,SAAS;KAC5B,CAAC;IACF,MAAM,OAAO,GAAG,iBAAiB,CAC/B,OAAO,CAAC,KAAK,CAAC,OAAO,EACrB,OAAO,CAAC,KAAK,CAAC,OAAO,EACrB,QAAQ,CAAC,KAAK,CAAC,OAAO,CACvB,CAAC;IACF,MAAM,MAAM,GAAG,iBAAiB,CAAC;QAC/B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,WAAW,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE;QACjC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,OAAO;QAC9B,SAAS;QACT,OAAO;QACP,QAAQ,EAAE,OAAO,CAAC,QAAQ;KAC3B,CAAC,CAAC;IACH,OAAO;QACL,EAAE,EAAE,IAAI;QACR,KAAK,EAAE;YACL,WAAW,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE;YACjC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,OAAO;YAC9B,SAAS;YACT,OAAO;YACP,MAAM;SACP;QACD,WAAW;KACZ,CAAC;AACJ,CAAC;AAED,0FAA0F;AAC1F,KAAK,UAAU,sBAAsB,CACnC,OAA8B,EAC9B,QAAwC,EACxC,cAAsC;IAOtC,MAAM,SAAS,GAAyB,EAAE,CAAC;IAC3C,MAAM,OAAO,GAAkB,EAAE,CAAC;IAClC,MAAM,WAAW,GAAsB,EAAE,CAAC;IAC1C,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;QACrE,IAAI,CAAC,MAAM,CAAC,EAAE;YAAE,OAAO,kBAAkB,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAC/D,SAAS,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC1C,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACtC,WAAW,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,CAAC;AAClE,CAAC","sourcesContent":["import type {\n InfraDiagnostic,\n InfraExecutionContext,\n InfraOutput,\n InfraOwnedResource,\n InfraResult,\n InfraServiceAdapter,\n} from '@ankhorage/contracts/infra';\n\nimport type {\n InfraOrchestrationDependencies,\n InfraUpOperationResult,\n InfraUpRequest,\n} from '../../../../types/infraOrchestration.js';\nimport { resolveInfraEnvironment } from '../../domain/resolveInfraEnvironment.js';\nimport { appendInfraFailure } from '../../utils/appendInfraFailure.js';\nimport { createInfraExecutionContext } from '../../utils/createInfraExecutionContext.js';\nimport { createInfraLedger } from '../../utils/createInfraLedger.js';\nimport { mergeInfraOutputs } from '../../utils/mergeInfraOutputs.js';\nimport { collectInfraWorkloadsAsync } from './collectInfraWorkloadsAsync.js';\nimport { resolveInfraAdaptersAsync } from './resolveInfraAdaptersAsync.js';\nimport { validateInfraAdaptersAsync } from './validateInfraAdaptersAsync.js';\n\n/*** Validate and reconcile compute, contributed workloads, runtime and services in dependency order. */\nexport async function upInfraEnvironmentAsync(\n request: InfraUpRequest,\n dependencies: InfraOrchestrationDependencies,\n): Promise<InfraUpOperationResult> {\n const environment = resolveInfraEnvironment({\n manifest: request.manifest,\n environment: request.environment,\n operation: 'up',\n });\n if (!environment.ok) return environment;\n const diagnostics: InfraDiagnostic[] = [...environment.diagnostics];\n const adapters = await resolveInfraAdaptersAsync(\n environment.value.desired,\n dependencies.adapterResolver,\n );\n if (!adapters.ok) return appendInfraFailure(diagnostics, adapters);\n diagnostics.push(...adapters.diagnostics);\n const context = createInfraExecutionContext(request, environment.value, dependencies);\n const validation = await validateInfraAdaptersAsync(context, adapters.value);\n if (!validation.ok) return appendInfraFailure(diagnostics, validation);\n diagnostics.push(...validation.diagnostics);\n const workloads = await collectInfraWorkloadsAsync(context, adapters.value.services);\n if (!workloads.ok) return appendInfraFailure(diagnostics, workloads);\n diagnostics.push(...workloads.diagnostics);\n const compute = await adapters.value.compute.ensureAsync(\n context,\n environment.value.desired.deployment.compute,\n );\n if (!compute.ok) return appendInfraFailure(diagnostics, compute);\n diagnostics.push(...compute.diagnostics);\n const runtimeDesired = {\n selection: environment.value.desired.deployment.runtime,\n targets: compute.value.targets,\n workloads: [...(environment.value.desired.workloads ?? []), ...workloads.value],\n availableOutputs: mergeInfraOutputs(request.previous?.outputs ?? [], compute.value.outputs),\n };\n const runtimeValidation = await adapters.value.runtime.validateAsync(context, runtimeDesired);\n if (!runtimeValidation.ok) return appendInfraFailure(diagnostics, runtimeValidation);\n diagnostics.push(...runtimeValidation.diagnostics);\n const runtime = await adapters.value.runtime.ensureAsync(context, runtimeDesired);\n if (!runtime.ok) return appendInfraFailure(diagnostics, runtime);\n diagnostics.push(...runtime.diagnostics);\n const services = await reconcileServicesAsync(context, adapters.value.services, [\n ...compute.value.outputs,\n ...runtime.value.outputs,\n ]);\n if (!services.ok) return appendInfraFailure(diagnostics, services);\n diagnostics.push(...services.diagnostics);\n const resources = [\n ...compute.value.resources,\n ...runtime.value.resources,\n ...services.value.resources,\n ];\n const outputs = mergeInfraOutputs(\n compute.value.outputs,\n runtime.value.outputs,\n services.value.outputs,\n );\n const ledger = createInfraLedger({\n projectId: request.projectId,\n environment: environment.value.id,\n targets: compute.value.targets,\n resources,\n outputs,\n previous: request.previous,\n });\n return {\n ok: true,\n value: {\n environment: environment.value.id,\n targets: compute.value.targets,\n resources,\n outputs,\n ledger,\n },\n diagnostics,\n };\n}\n\n/*** Reconcile service-owned external state after its runtime workloads expose outputs. */\nasync function reconcileServicesAsync(\n context: InfraExecutionContext,\n services: readonly InfraServiceAdapter[],\n runtimeOutputs: readonly InfraOutput[],\n): Promise<\n InfraResult<{\n readonly resources: readonly InfraOwnedResource[];\n readonly outputs: readonly InfraOutput[];\n }>\n> {\n const resources: InfraOwnedResource[] = [];\n const outputs: InfraOutput[] = [];\n const diagnostics: InfraDiagnostic[] = [];\n for (const service of services) {\n const result = await service.reconcileAsync(context, runtimeOutputs);\n if (!result.ok) return appendInfraFailure(diagnostics, result);\n resources.push(...result.value.resources);\n outputs.push(...result.value.outputs);\n diagnostics.push(...result.diagnostics);\n }\n return { ok: true, value: { resources, outputs }, diagnostics };\n}\n"]}
1
+ {"version":3,"file":"upInfraEnvironmentAsync.js","sourceRoot":"","sources":["../../../../../src/features/environment-lifecycle/application/use-cases/upInfraEnvironmentAsync.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n InfraDiagnostic,\n InfraExecutionContext,\n InfraOutput,\n InfraOwnedResource,\n InfraResult,\n InfraServiceAdapter,\n} from '@ankhorage/contracts/infra';\n\nimport type {\n InfraOrchestrationDependencies,\n InfraUpOperationResult,\n InfraUpRequest,\n} from '../../../../types/infraOrchestration.js';\nimport { resolveInfraEnvironment } from '../../domain/resolveInfraEnvironment.js';\nimport { appendInfraFailure } from '../../utils/appendInfraFailure.js';\nimport { createInfraExecutionContext } from '../../utils/createInfraExecutionContext.js';\nimport { createInfraLedger } from '../../utils/createInfraLedger.js';\nimport { mergeInfraOutputs } from '../../utils/mergeInfraOutputs.js';\nimport { collectInfraWorkloadsAsync } from './collectInfraWorkloadsAsync.js';\nimport { prepareInfraServicesAsync } from './prepareInfraServicesAsync.js';\nimport { resolveInfraAdaptersAsync } from './resolveInfraAdaptersAsync.js';\nimport { validateInfraAdaptersAsync } from './validateInfraAdaptersAsync.js';\n\n/*** Validate and reconcile compute, service preparation, workloads, runtime and services in order. */\nexport async function upInfraEnvironmentAsync(\n request: InfraUpRequest,\n dependencies: InfraOrchestrationDependencies,\n): Promise<InfraUpOperationResult> {\n const environment = resolveInfraEnvironment({\n manifest: request.manifest,\n environment: request.environment,\n operation: 'up',\n });\n if (!environment.ok) return environment;\n const diagnostics: InfraDiagnostic[] = [...environment.diagnostics];\n const adapters = await resolveInfraAdaptersAsync(\n environment.value.desired,\n dependencies.adapterResolver,\n );\n if (!adapters.ok) return appendInfraFailure(diagnostics, adapters);\n diagnostics.push(...adapters.diagnostics);\n const context = createInfraExecutionContext(request, environment.value, dependencies);\n const validation = await validateInfraAdaptersAsync(context, adapters.value);\n if (!validation.ok) return appendInfraFailure(diagnostics, validation);\n diagnostics.push(...validation.diagnostics);\n const compute = await adapters.value.compute.ensureAsync(\n context,\n environment.value.desired.deployment.compute,\n );\n if (!compute.ok) return appendInfraFailure(diagnostics, compute);\n diagnostics.push(...compute.diagnostics);\n const preparation = await prepareInfraServicesAsync(context, adapters.value.services);\n if (!preparation.ok) return appendInfraFailure(diagnostics, preparation);\n diagnostics.push(...preparation.diagnostics);\n const workloads = await collectInfraWorkloadsAsync(context, adapters.value.services);\n if (!workloads.ok) return appendInfraFailure(diagnostics, workloads);\n diagnostics.push(...workloads.diagnostics);\n const runtimeDesired = {\n selection: environment.value.desired.deployment.runtime,\n targets: compute.value.targets,\n workloads: [...(environment.value.desired.workloads ?? []), ...workloads.value],\n availableOutputs: mergeInfraOutputs(request.previous?.outputs ?? [], compute.value.outputs),\n };\n const runtimeValidation = await adapters.value.runtime.validateAsync(context, runtimeDesired);\n if (!runtimeValidation.ok) return appendInfraFailure(diagnostics, runtimeValidation);\n diagnostics.push(...runtimeValidation.diagnostics);\n const runtime = await adapters.value.runtime.ensureAsync(context, runtimeDesired);\n if (!runtime.ok) return appendInfraFailure(diagnostics, runtime);\n diagnostics.push(...runtime.diagnostics);\n const services = await reconcileServicesAsync(context, adapters.value.services, [\n ...compute.value.outputs,\n ...runtime.value.outputs,\n ]);\n if (!services.ok) return appendInfraFailure(diagnostics, services);\n diagnostics.push(...services.diagnostics);\n const resources = [\n ...compute.value.resources,\n ...runtime.value.resources,\n ...services.value.resources,\n ];\n const outputs = mergeInfraOutputs(\n compute.value.outputs,\n runtime.value.outputs,\n services.value.outputs,\n );\n const ledger = createInfraLedger({\n projectId: request.projectId,\n environment: environment.value.id,\n targets: compute.value.targets,\n resources,\n outputs,\n previous: request.previous,\n });\n return {\n ok: true,\n value: {\n environment: environment.value.id,\n targets: compute.value.targets,\n resources,\n outputs,\n ledger,\n },\n diagnostics,\n };\n}\n\n/*** Reconcile service-owned external state after its runtime workloads expose outputs. */\nasync function reconcileServicesAsync(\n context: InfraExecutionContext,\n services: readonly InfraServiceAdapter[],\n runtimeOutputs: readonly InfraOutput[],\n): Promise<\n InfraResult<{\n readonly resources: readonly InfraOwnedResource[];\n readonly outputs: readonly InfraOutput[];\n }>\n> {\n const resources: InfraOwnedResource[] = [];\n const outputs: InfraOutput[] = [];\n const diagnostics: InfraDiagnostic[] = [];\n for (const service of services) {\n const result = await service.reconcileAsync(context, runtimeOutputs);\n if (!result.ok) return appendInfraFailure(diagnostics, result);\n resources.push(...result.value.resources);\n outputs.push(...result.value.outputs);\n diagnostics.push(...result.diagnostics);\n }\n return { ok: true, value: { resources, outputs }, diagnostics };\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/infra",
3
3
  "type": "module",
4
- "version": "6.0.0",
4
+ "version": "7.0.0",
5
5
  "description": "Project-aware infrastructure generation library and executable Ankh provider for Ankhorage.",
6
6
  "homepage": "https://github.com/ankhorage/infra#readme",
7
7
  "bugs": {
@@ -35,7 +35,7 @@
35
35
  "openapi"
36
36
  ],
37
37
  "dependencies": {
38
- "@ankhorage/contracts": "^20.0.0"
38
+ "@ankhorage/contracts": "^21.0.0"
39
39
  },
40
40
  "files": [
41
41
  "dist",
@@ -0,0 +1,62 @@
1
+ import type { InfraControlPlaneCredentialRef } from '@ankhorage/contracts/infra';
2
+ import { expect, it } from 'bun:test';
3
+
4
+ import { createEnvironmentInfraCredentialPort } from './features/environment-lifecycle/adapters/outbound/createEnvironmentInfraCredentialPort';
5
+
6
+ const bundleReference = {
7
+ source: 'control-plane',
8
+ name: 'FIXTURE_BOOTSTRAP',
9
+ } as const satisfies InfraControlPlaneCredentialRef;
10
+ const tokenReference = {
11
+ source: 'control-plane',
12
+ name: 'FIXTURE_TOKEN',
13
+ } as const satisfies InfraControlPlaneCredentialRef;
14
+
15
+ it('finds exact and prefixed environment credentials without mutating state', async () => {
16
+ const port = createEnvironmentInfraCredentialPort({
17
+ FIXTURE_BOOTSTRAP: JSON.stringify({ username: 'fixture', password: 'secret' }),
18
+ ANKH_INFRA_CREDENTIAL_FIXTURE_TOKEN: 'token-value',
19
+ });
20
+
21
+ expect(await port.findAsync(bundleReference)).toEqual({
22
+ ok: true,
23
+ value: { username: 'fixture', password: 'secret' },
24
+ diagnostics: [],
25
+ });
26
+ expect(await port.findAsync(tokenReference)).toEqual({
27
+ ok: true,
28
+ value: { token: 'token-value', apiToken: 'token-value' },
29
+ diagnostics: [],
30
+ });
31
+ });
32
+
33
+ it('distinguishes optional lookup from strict resolution', async () => {
34
+ const port = createEnvironmentInfraCredentialPort({});
35
+
36
+ expect(await port.findAsync(bundleReference)).toEqual({ ok: true, value: null, diagnostics: [] });
37
+ const resolved = await port.resolveAsync(bundleReference);
38
+ expect(resolved.ok).toBe(false);
39
+ expect(resolved.diagnostics).toEqual([
40
+ {
41
+ severity: 'error',
42
+ code: 'infra-control-plane-credential-missing',
43
+ message:
44
+ 'Control-plane credential FIXTURE_BOOTSTRAP is unavailable. Set FIXTURE_BOOTSTRAP or ANKH_INFRA_CREDENTIAL_FIXTURE_BOOTSTRAP.',
45
+ },
46
+ ]);
47
+ });
48
+
49
+ it('fails closed when an environment-only source is asked to persist credentials', async () => {
50
+ const port = createEnvironmentInfraCredentialPort({});
51
+ const persisted = await port.persistAsync(bundleReference, { value: 'secret' });
52
+
53
+ expect(persisted.ok).toBe(false);
54
+ expect(persisted.diagnostics).toEqual([
55
+ {
56
+ severity: 'error',
57
+ code: 'infra-control-plane-credential-persistence-unavailable',
58
+ message:
59
+ 'Control-plane credential FIXTURE_BOOTSTRAP cannot be persisted by the environment-only credential source.',
60
+ },
61
+ ]);
62
+ });
@@ -1,4 +1,8 @@
1
- import type { InfraExecutionContext, InfraResult } from '@ankhorage/contracts/infra';
1
+ import type {
2
+ InfraControlPlaneCredentialRef,
3
+ InfraExecutionContext,
4
+ InfraResult,
5
+ } from '@ankhorage/contracts/infra';
2
6
 
3
7
  import { createInfraFailure } from '../../utils/createInfraFailure.js';
4
8
  import { normalizeInfraEnvironmentKey } from '../../utils/normalizeInfraEnvironmentKey.js';
@@ -8,23 +12,53 @@ export function createEnvironmentInfraCredentialPort(
8
12
  environment: Readonly<Record<string, string | undefined>>,
9
13
  ): InfraExecutionContext['credentials'] {
10
14
  return {
15
+ findAsync(reference) {
16
+ return Promise.resolve(findCredential(environment, reference));
17
+ },
11
18
  resolveAsync(reference) {
12
- const prefixedName = `ANKH_INFRA_CREDENTIAL_${normalizeInfraEnvironmentKey(reference.name)}`;
13
- const rawValue =
14
- Reflect.get(environment, reference.name) ?? Reflect.get(environment, prefixedName);
15
- if (rawValue === undefined || rawValue.length === 0) {
16
- return Promise.resolve(
17
- createInfraFailure(
18
- 'infra-control-plane-credential-missing',
19
- `Control-plane credential ${reference.name} is unavailable. Set ${reference.name} or ${prefixedName}.`,
20
- ),
21
- );
19
+ const found = findCredential(environment, reference);
20
+ if (!found.ok) return Promise.resolve(found);
21
+ if (found.value !== null) {
22
+ return Promise.resolve({ ok: true, value: found.value, diagnostics: found.diagnostics });
22
23
  }
23
- return Promise.resolve(parseCredentialBundle(reference.name, rawValue));
24
+ const prefixedName = prefixedCredentialName(reference.name);
25
+ return Promise.resolve(
26
+ createInfraFailure(
27
+ 'infra-control-plane-credential-missing',
28
+ `Control-plane credential ${reference.name} is unavailable. Set ${reference.name} or ${prefixedName}.`,
29
+ ),
30
+ );
31
+ },
32
+ persistAsync(reference) {
33
+ return Promise.resolve(
34
+ createInfraFailure(
35
+ 'infra-control-plane-credential-persistence-unavailable',
36
+ `Control-plane credential ${reference.name} cannot be persisted by the environment-only credential source.`,
37
+ ),
38
+ );
24
39
  },
25
40
  };
26
41
  }
27
42
 
43
+ /*** Find and parse an optional credential bundle from the environment without treating absence as failure. */
44
+ function findCredential(
45
+ environment: Readonly<Record<string, string | undefined>>,
46
+ reference: InfraControlPlaneCredentialRef,
47
+ ): InfraResult<Readonly<Record<string, string>> | null> {
48
+ const prefixedName = prefixedCredentialName(reference.name);
49
+ const rawValue =
50
+ Reflect.get(environment, reference.name) ?? Reflect.get(environment, prefixedName);
51
+ if (rawValue === undefined || rawValue.length === 0) {
52
+ return { ok: true, value: null, diagnostics: [] };
53
+ }
54
+ return parseCredentialBundle(reference.name, rawValue);
55
+ }
56
+
57
+ /*** Build the canonical prefixed environment variable name for one credential reference. */
58
+ function prefixedCredentialName(name: string): string {
59
+ return `ANKH_INFRA_CREDENTIAL_${normalizeInfraEnvironmentKey(name)}`;
60
+ }
61
+
28
62
  /*** Parse JSON credential bundles; token variables also support their conventional scalar form. */
29
63
  function parseCredentialBundle(
30
64
  name: string,
@@ -32,9 +66,7 @@ function parseCredentialBundle(
32
66
  ): InfraResult<Readonly<Record<string, string>>> {
33
67
  try {
34
68
  const parsed: unknown = JSON.parse(rawValue);
35
- if (isRecord(parsed) && Object.values(parsed).every((value) => typeof value === 'string')) {
36
- return { ok: true, value: parsed as Readonly<Record<string, string>>, diagnostics: [] };
37
- }
69
+ if (isStringRecord(parsed)) return { ok: true, value: parsed, diagnostics: [] };
38
70
  } catch {
39
71
  if (name.endsWith('_TOKEN')) {
40
72
  return { ok: true, value: { token: rawValue, apiToken: rawValue }, diagnostics: [] };
@@ -46,7 +78,12 @@ function parseCredentialBundle(
46
78
  );
47
79
  }
48
80
 
49
- /*** Narrow JSON credential bundles. */
50
- function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {
51
- return typeof value === 'object' && value !== null && !Array.isArray(value);
81
+ /*** Narrow credential bundle values to a string-only record. */
82
+ function isStringRecord(value: unknown): value is Readonly<Record<string, string>> {
83
+ return (
84
+ typeof value === 'object' &&
85
+ value !== null &&
86
+ !Array.isArray(value) &&
87
+ Object.values(value).every((entry) => typeof entry === 'string')
88
+ );
52
89
  }
@@ -0,0 +1,23 @@
1
+ import type {
2
+ InfraDiagnostic,
3
+ InfraExecutionContext,
4
+ InfraResult,
5
+ InfraServiceAdapter,
6
+ } from '@ankhorage/contracts/infra';
7
+
8
+ import { appendInfraFailure } from '../../utils/appendInfraFailure.js';
9
+
10
+ /*** Run optional mutating service preparation before runtime workload materialization. */
11
+ export async function prepareInfraServicesAsync(
12
+ context: InfraExecutionContext,
13
+ services: readonly InfraServiceAdapter[],
14
+ ): Promise<InfraResult<null>> {
15
+ const diagnostics: InfraDiagnostic[] = [];
16
+ for (const service of services) {
17
+ if (service.prepareAsync === undefined) continue;
18
+ const prepared = await service.prepareAsync(context);
19
+ if (!prepared.ok) return appendInfraFailure(diagnostics, prepared);
20
+ diagnostics.push(...prepared.diagnostics);
21
+ }
22
+ return { ok: true, value: null, diagnostics };
23
+ }
@@ -18,10 +18,11 @@ import { createInfraExecutionContext } from '../../utils/createInfraExecutionCon
18
18
  import { createInfraLedger } from '../../utils/createInfraLedger.js';
19
19
  import { mergeInfraOutputs } from '../../utils/mergeInfraOutputs.js';
20
20
  import { collectInfraWorkloadsAsync } from './collectInfraWorkloadsAsync.js';
21
+ import { prepareInfraServicesAsync } from './prepareInfraServicesAsync.js';
21
22
  import { resolveInfraAdaptersAsync } from './resolveInfraAdaptersAsync.js';
22
23
  import { validateInfraAdaptersAsync } from './validateInfraAdaptersAsync.js';
23
24
 
24
- /*** Validate and reconcile compute, contributed workloads, runtime and services in dependency order. */
25
+ /*** Validate and reconcile compute, service preparation, workloads, runtime and services in order. */
25
26
  export async function upInfraEnvironmentAsync(
26
27
  request: InfraUpRequest,
27
28
  dependencies: InfraOrchestrationDependencies,
@@ -43,15 +44,18 @@ export async function upInfraEnvironmentAsync(
43
44
  const validation = await validateInfraAdaptersAsync(context, adapters.value);
44
45
  if (!validation.ok) return appendInfraFailure(diagnostics, validation);
45
46
  diagnostics.push(...validation.diagnostics);
46
- const workloads = await collectInfraWorkloadsAsync(context, adapters.value.services);
47
- if (!workloads.ok) return appendInfraFailure(diagnostics, workloads);
48
- diagnostics.push(...workloads.diagnostics);
49
47
  const compute = await adapters.value.compute.ensureAsync(
50
48
  context,
51
49
  environment.value.desired.deployment.compute,
52
50
  );
53
51
  if (!compute.ok) return appendInfraFailure(diagnostics, compute);
54
52
  diagnostics.push(...compute.diagnostics);
53
+ const preparation = await prepareInfraServicesAsync(context, adapters.value.services);
54
+ if (!preparation.ok) return appendInfraFailure(diagnostics, preparation);
55
+ diagnostics.push(...preparation.diagnostics);
56
+ const workloads = await collectInfraWorkloadsAsync(context, adapters.value.services);
57
+ if (!workloads.ok) return appendInfraFailure(diagnostics, workloads);
58
+ diagnostics.push(...workloads.diagnostics);
55
59
  const runtimeDesired = {
56
60
  selection: environment.value.desired.deployment.runtime,
57
61
  targets: compute.value.targets,
@@ -27,7 +27,7 @@ const environment = {
27
27
  const manifest = { environments: { local: environment }, modules: [] } as const;
28
28
 
29
29
  describe('environment up orchestration', () => {
30
- it('validates before mutation and reconciles compute, runtime and deduplicated services', async () => {
30
+ it('validates before mutation and prepares services before workload materialization', async () => {
31
31
  const calls: string[] = [];
32
32
  const observedRuntimeDesired: InfraRuntimeDesiredState[] = [];
33
33
  const observedServiceOutputs: InfraOutput[][] = [];
@@ -39,9 +39,11 @@ describe('environment up orchestration', () => {
39
39
  'local.validate',
40
40
  'supabase.validate',
41
41
  'cerbos.validate',
42
+ 'local.ensure',
43
+ 'supabase.prepare',
44
+ 'cerbos.prepare',
42
45
  'supabase.workloads',
43
46
  'cerbos.workloads',
44
- 'local.ensure',
45
47
  'docker-compose.validate',
46
48
  'docker-compose.ensure',
47
49
  'supabase.reconcile',
@@ -81,6 +83,27 @@ describe('environment up orchestration', () => {
81
83
  expect(calls).toEqual(['local.validate', 'supabase.validate']);
82
84
  });
83
85
 
86
+ it('stops before workload materialization when service preparation fails', async () => {
87
+ const calls: string[] = [];
88
+ const dependencies = createDependencies(calls, [], [], 'supabase.prepare');
89
+ const result = await upInfraEnvironmentAsync({ projectId: 'sample', manifest }, dependencies);
90
+
91
+ expect(result.ok).toBe(false);
92
+ expect(result.diagnostics.map(({ code }) => code)).toEqual([
93
+ 'compute-warning',
94
+ 'fixture-failure',
95
+ ]);
96
+ expect(calls).toEqual([
97
+ 'local.validate',
98
+ 'supabase.validate',
99
+ 'cerbos.validate',
100
+ 'local.ensure',
101
+ 'supabase.prepare',
102
+ ]);
103
+ expect(calls).not.toContain('supabase.workloads');
104
+ expect(calls).not.toContain('docker-compose.ensure');
105
+ });
106
+
84
107
  it('does not reconcile services after a runtime failure', async () => {
85
108
  const calls: string[] = [];
86
109
  const dependencies = createDependencies(calls, [], [], 'docker-compose.ensure');
@@ -100,7 +123,11 @@ function createDependencies(
100
123
  ): InfraOrchestrationDependencies {
101
124
  return {
102
125
  adapterResolver: createResolver(calls, runtimeDesired, serviceOutputs, failure),
103
- credentials: { resolveAsync: () => Promise.resolve(success({})) },
126
+ credentials: {
127
+ findAsync: () => Promise.resolve(success(null)),
128
+ resolveAsync: () => Promise.resolve(success({})),
129
+ persistAsync: () => Promise.resolve(success(null)),
130
+ },
104
131
  secrets: { resolveAsync: () => Promise.resolve(success('secret')) },
105
132
  };
106
133
  }
@@ -199,6 +226,7 @@ function createService(
199
226
  descriptor: descriptor as InfraServiceAdapter['descriptor'],
200
227
  validateAsync: () => recordAsync(calls, `${descriptor.id}.validate`, failure, null),
201
228
  planAsync: () => Promise.resolve(success([])),
229
+ prepareAsync: () => recordAsync(calls, `${descriptor.id}.prepare`, failure, null),
202
230
  desiredWorkloadsAsync: () =>
203
231
  recordAsync(calls, `${descriptor.id}.workloads`, failure, [workload(descriptor.id)]),
204
232
  reconcileAsync: (_context, outputs) => {
@@ -0,0 +1,50 @@
1
+ import type {
2
+ InfraExecutionContext,
3
+ InfraResult,
4
+ InfraServiceAdapter,
5
+ } from '@ankhorage/contracts/infra';
6
+ import { INFRA_ADAPTER_CATALOG } from '@ankhorage/contracts/infra';
7
+ import { expect, it } from 'bun:test';
8
+
9
+ import { prepareInfraServicesAsync } from './features/environment-lifecycle/application/use-cases/prepareInfraServicesAsync';
10
+
11
+ it('leaves services without preparation unchanged', async () => {
12
+ const service: InfraServiceAdapter = {
13
+ descriptor: INFRA_ADAPTER_CATALOG.cerbos,
14
+ validateAsync: () => success(null),
15
+ planAsync: () => success([]),
16
+ desiredWorkloadsAsync: () => success([]),
17
+ reconcileAsync: () => success({ resources: [], outputs: [] }),
18
+ statusAsync: () => success([]),
19
+ destroyAsync: () => success({ resources: [], outputs: [] }),
20
+ };
21
+
22
+ expect(await prepareInfraServicesAsync(createContext(), [service])).toEqual({
23
+ ok: true,
24
+ value: null,
25
+ diagnostics: [],
26
+ });
27
+ });
28
+
29
+ function createContext(): InfraExecutionContext {
30
+ return {
31
+ projectId: 'fixture',
32
+ environment: 'local',
33
+ desired: {
34
+ deployment: {
35
+ compute: { provider: 'local' },
36
+ runtime: { provider: 'docker-compose' },
37
+ },
38
+ },
39
+ credentials: {
40
+ findAsync: () => success(null),
41
+ resolveAsync: () => success({}),
42
+ persistAsync: () => success(null),
43
+ },
44
+ secrets: { resolveAsync: () => success('secret') },
45
+ };
46
+ }
47
+
48
+ function success<T>(value: T): Promise<InfraResult<T>> {
49
+ return Promise.resolve({ ok: true, value, diagnostics: [] });
50
+ }