@ankhorage/contracts 18.0.0 → 19.1.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,23 @@
1
1
  # @ankhorage/contracts
2
2
 
3
+ ## 19.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 95ed577: add fixed published ports for public workloads
8
+
9
+ ## 19.0.0
10
+
11
+ ### Major Changes
12
+
13
+ - cb4bee3: Require compute adapters to implement `inspectAsync` and return an `InfraComputeSnapshot` without
14
+ creating, updating, or deleting resources. `ensureAsync` now returns the same canonical snapshot
15
+ shape.
16
+
17
+ This separates read-only target discovery from reconciliation so `infra plan`, `infra generate`,
18
+ and validation can resolve existing portable targets without invoking a mutating lifecycle method.
19
+ Compute-adapter consumers must add `inspectAsync(context, selection)` before upgrading.
20
+
3
21
  ## 18.0.0
4
22
 
5
23
  ### Major Changes
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  # @ankhorage/contracts
5
5
 
6
- ![license: MIT](././paradox/badges/license.svg) ![npm: v17.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)
6
+ ![license: MIT](././paradox/badges/license.svg) ![npm: v19.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
  Serializable app, action, theme, auth, and secret-store contracts for Ankhorage.
9
9
 
@@ -8,5 +8,6 @@ export declare const infraFields: {
8
8
  optionalPositiveInteger: (value: unknown) => boolean;
9
9
  optionalNonnegativeInteger: (value: unknown) => boolean;
10
10
  port: (value: unknown) => boolean;
11
+ optionalPort: (value: unknown) => boolean;
11
12
  };
12
13
  //# sourceMappingURL=infraFields.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"infraFields.d.ts","sourceRoot":"","sources":["../../src/infra/infraFields.ts"],"names":[],"mappings":"AAGA,qGAAqG;AACrG,eAAO,MAAM,WAAW;0BACA,OAAO,KAAG,OAAO;qBACtB,OAAO,KAAG,OAAO;6BACT,OAAO,KAAG,OAAO;6BAEjB,OAAO,KAAG,OAAO;gCAEd,OAAO,KAAG,OAAO;qCAEZ,OAAO,KAAG,OAAO;wCAEd,OAAO,KAAG,OAAO;kBAEvC,OAAO,KAAG,OAAO;CAEhC,CAAC"}
1
+ {"version":3,"file":"infraFields.d.ts","sourceRoot":"","sources":["../../src/infra/infraFields.ts"],"names":[],"mappings":"AAGA,qGAAqG;AACrG,eAAO,MAAM,WAAW;0BACA,OAAO,KAAG,OAAO;qBACtB,OAAO,KAAG,OAAO;6BACT,OAAO,KAAG,OAAO;6BAEjB,OAAO,KAAG,OAAO;gCAEd,OAAO,KAAG,OAAO;qCAEZ,OAAO,KAAG,OAAO;wCAEd,OAAO,KAAG,OAAO;kBAEvC,OAAO,KAAG,OAAO;0BAET,OAAO,KAAG,OAAO;CAGxC,CAAC"}
@@ -10,5 +10,7 @@ export const infraFields = {
10
10
  optionalPositiveInteger: (value) => value === undefined || (typeof value === 'number' && Number.isSafeInteger(value) && value > 0),
11
11
  optionalNonnegativeInteger: (value) => value === undefined || (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0),
12
12
  port: (value) => typeof value === 'number' && Number.isInteger(value) && value > 0 && value <= 65535,
13
+ optionalPort: (value) => value === undefined ||
14
+ (typeof value === 'number' && Number.isInteger(value) && value > 0 && value <= 65535),
13
15
  };
14
16
  //# sourceMappingURL=infraFields.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"infraFields.js","sourceRoot":"","sources":["../../src/infra/infraFields.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAE7D,qGAAqG;AACrG,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,YAAY,EAAE,CAAC,KAAc,EAAW,EAAE,CAAC,KAAK,KAAK,SAAS,IAAI,gBAAgB,CAAC,KAAK,CAAC;IACzF,OAAO,EAAE,CAAC,KAAc,EAAW,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,gBAAgB,CAAC;IAC3F,eAAe,EAAE,CAAC,KAAc,EAAW,EAAE,CAC3C,KAAK,KAAK,SAAS,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAChF,eAAe,EAAE,CAAC,KAAc,EAAW,EAAE,CAC3C,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;IACvE,kBAAkB,EAAE,CAAC,KAAc,EAAW,EAAE,CAC9C,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;IACxE,uBAAuB,EAAE,CAAC,KAAc,EAAW,EAAE,CACnD,KAAK,KAAK,SAAS,IAAI,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;IAChG,0BAA0B,EAAE,CAAC,KAAc,EAAW,EAAE,CACtD,KAAK,KAAK,SAAS,IAAI,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IACjG,IAAI,EAAE,CAAC,KAAc,EAAW,EAAE,CAChC,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,IAAI,KAAK;CACtF,CAAC","sourcesContent":["import { isStringArray } from '@ankhorage/utility/array';\nimport { isNonEmptyString } from '@ankhorage/utility/string';\n\n/*** Primitive field rules for portable Infra configuration, shared by its exact-shape validators. */\nexport const infraFields = {\n optionalText: (value: unknown): boolean => value === undefined || isNonEmptyString(value),\n strings: (value: unknown): boolean => isStringArray(value) && value.every(isNonEmptyString),\n optionalStrings: (value: unknown): boolean =>\n value === undefined || (isStringArray(value) && value.every(isNonEmptyString)),\n positiveInteger: (value: unknown): boolean =>\n typeof value === 'number' && Number.isSafeInteger(value) && value > 0,\n nonnegativeInteger: (value: unknown): boolean =>\n typeof value === 'number' && Number.isSafeInteger(value) && value >= 0,\n optionalPositiveInteger: (value: unknown): boolean =>\n value === undefined || (typeof value === 'number' && Number.isSafeInteger(value) && value > 0),\n optionalNonnegativeInteger: (value: unknown): boolean =>\n value === undefined || (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0),\n port: (value: unknown): boolean =>\n typeof value === 'number' && Number.isInteger(value) && value > 0 && value <= 65535,\n};\n"]}
1
+ {"version":3,"file":"infraFields.js","sourceRoot":"","sources":["../../src/infra/infraFields.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAE7D,qGAAqG;AACrG,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,YAAY,EAAE,CAAC,KAAc,EAAW,EAAE,CAAC,KAAK,KAAK,SAAS,IAAI,gBAAgB,CAAC,KAAK,CAAC;IACzF,OAAO,EAAE,CAAC,KAAc,EAAW,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,gBAAgB,CAAC;IAC3F,eAAe,EAAE,CAAC,KAAc,EAAW,EAAE,CAC3C,KAAK,KAAK,SAAS,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAChF,eAAe,EAAE,CAAC,KAAc,EAAW,EAAE,CAC3C,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;IACvE,kBAAkB,EAAE,CAAC,KAAc,EAAW,EAAE,CAC9C,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;IACxE,uBAAuB,EAAE,CAAC,KAAc,EAAW,EAAE,CACnD,KAAK,KAAK,SAAS,IAAI,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;IAChG,0BAA0B,EAAE,CAAC,KAAc,EAAW,EAAE,CACtD,KAAK,KAAK,SAAS,IAAI,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IACjG,IAAI,EAAE,CAAC,KAAc,EAAW,EAAE,CAChC,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,IAAI,KAAK;IACrF,YAAY,EAAE,CAAC,KAAc,EAAW,EAAE,CACxC,KAAK,KAAK,SAAS;QACnB,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,IAAI,KAAK,CAAC;CACxF,CAAC","sourcesContent":["import { isStringArray } from '@ankhorage/utility/array';\nimport { isNonEmptyString } from '@ankhorage/utility/string';\n\n/*** Primitive field rules for portable Infra configuration, shared by its exact-shape validators. */\nexport const infraFields = {\n optionalText: (value: unknown): boolean => value === undefined || isNonEmptyString(value),\n strings: (value: unknown): boolean => isStringArray(value) && value.every(isNonEmptyString),\n optionalStrings: (value: unknown): boolean =>\n value === undefined || (isStringArray(value) && value.every(isNonEmptyString)),\n positiveInteger: (value: unknown): boolean =>\n typeof value === 'number' && Number.isSafeInteger(value) && value > 0,\n nonnegativeInteger: (value: unknown): boolean =>\n typeof value === 'number' && Number.isSafeInteger(value) && value >= 0,\n optionalPositiveInteger: (value: unknown): boolean =>\n value === undefined || (typeof value === 'number' && Number.isSafeInteger(value) && value > 0),\n optionalNonnegativeInteger: (value: unknown): boolean =>\n value === undefined || (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0),\n port: (value: unknown): boolean =>\n typeof value === 'number' && Number.isInteger(value) && value > 0 && value <= 65535,\n optionalPort: (value: unknown): boolean =>\n value === undefined ||\n (typeof value === 'number' && Number.isInteger(value) && value > 0 && value <= 65535),\n};\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"isInfraEnvironmentSpec.d.ts","sourceRoot":"","sources":["../../src/infra/isInfraEnvironmentSpec.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAA0B,MAAM,wBAAwB,CAAC;AAU3F,8FAA8F;AAC9F,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,oBAAoB,CAEpF"}
1
+ {"version":3,"file":"isInfraEnvironmentSpec.d.ts","sourceRoot":"","sources":["../../src/infra/isInfraEnvironmentSpec.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAA0B,MAAM,wBAAwB,CAAC;AAU3F,8FAA8F;AAC9F,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,oBAAoB,CAIpF"}
@@ -9,7 +9,7 @@ import { isInfraShape } from './isInfraShape';
9
9
  import { isInfraWorkloadSpec } from './isInfraWorkloadSpec';
10
10
  /*** Validate sibling capabilities and their required relationships within one environment. */
11
11
  export function isInfraEnvironmentSpec(value) {
12
- return isEnvironmentShape(value) && hasServiceDependencies(value);
12
+ return (isEnvironmentShape(value) && hasServiceDependencies(value) && hasUniquePublishedPorts(value));
13
13
  }
14
14
  /*** Check all environment field shapes before inspecting canonical dependency metadata. */
15
15
  function isEnvironmentShape(value) {
@@ -86,6 +86,11 @@ function isWorkloads(value) {
86
86
  value.every(isInfraWorkloadSpec) &&
87
87
  new Set(value.map((workload) => workload.id)).size === value.length);
88
88
  }
89
+ /*** Prevent two workloads in one environment from claiming the same external listener. */
90
+ function hasUniquePublishedPorts(value) {
91
+ const ports = (value.workloads ?? []).flatMap((workload) => (workload.ports ?? []).flatMap(({ publishedPort }) => publishedPort === undefined ? [] : [publishedPort]));
92
+ return new Set(ports).size === ports.length;
93
+ }
89
94
  /*** Check catalog dependencies against selected sibling capabilities and runtime capabilities. */
90
95
  function hasServiceDependencies(value) {
91
96
  const selections = [
@@ -1 +1 @@
1
- {"version":3,"file":"isInfraEnvironmentSpec.js","sourceRoot":"","sources":["../../src/infra/isInfraEnvironmentSpec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAI7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,8FAA8F;AAC9F,MAAM,UAAU,sBAAsB,CAAC,KAAc;IACnD,OAAO,kBAAkB,CAAC,KAAK,CAAC,IAAI,sBAAsB,CAAC,KAAK,CAAC,CAAC;AACpE,CAAC;AAED,2FAA2F;AAC3F,SAAS,kBAAkB,CAAC,KAAc;IACxC,OAAO,YAAY,CAAC,KAAK,EAAE;QACzB,UAAU,EAAE,qBAAqB;QACjC,QAAQ,EAAE,CAAC,QAAQ,EAAE,EAAE,CACrB,QAAQ,KAAK,SAAS;YACtB,YAAY,CAAC,QAAQ,EAAE;gBACrB,QAAQ,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,KAAK,UAAU;gBAC/C,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,MAAM;aACxE,CAAC;QACJ,aAAa,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,SAAS,IAAI,eAAe,CAAC,OAAO,CAAC;QAC7E,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,SAAS,IAAI,eAAe,CAAC,IAAI,CAAC;QAC3D,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CACf,KAAK,KAAK,SAAS;YACnB,YAAY,CAAC,KAAK,EAAE;gBAClB,QAAQ,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,KAAK,QAAQ;gBAC7C,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,MAAM;gBAClD,QAAQ,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,KAAK,SAAS,IAAI,aAAa,CAAC,QAAQ,CAAC;aAC1E,CAAC;QACJ,WAAW,EAAE,CAAC,KAAK,EAAE,EAAE,CACrB,KAAK,KAAK,SAAS;YACnB,YAAY,CAAC,KAAK,EAAE;gBAClB,QAAQ,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,KAAK,gBAAgB;gBACrD,MAAM,EAAE,WAAW,CAAC,YAAY;aACjC,CAAC;QACJ,UAAU,EAAE,CAAC,UAAU,EAAE,EAAE,CACzB,UAAU,KAAK,SAAS;YACxB,YAAY,CAAC,UAAU,EAAE;gBACvB,MAAM,EAAE,WAAW,CAAC,YAAY;gBAChC,aAAa,EAAE,CAAC,aAAa,EAAE,EAAE,CAC/B,aAAa,KAAK,SAAS,IAAI,kBAAkB,CAAC,aAAa,CAAC;aACnE,CAAC;QACJ,SAAS,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,KAAK,SAAS,IAAI,WAAW,CAAC,SAAS,CAAC;KACjC,CAAC,CAAC;AAChD,CAAC;AAED,oGAAoG;AACpG,SAAS,kBAAkB,CAAC,KAAc;IACxC,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,CAAC;IACzF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,sFAAsF;AACtF,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,CACL,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACpB,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,EAAE,CACrB,YAAY,CAAC,MAAM,EAAE;YACnB,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CACb,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;YACpF,OAAO,EAAE,gBAAgB;SAC1B,CAAC,CACH;QACD,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI;YAC/E,KAAK,CAAC,MAAM,CACf,CAAC;AACJ,CAAC;AAED,qGAAqG;AACrG,SAAS,eAAe,CAAC,KAAc;IACrC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACnC,MAAM,MAAM,GAAG;QACb,QAAQ,EAAE,CAAC,QAAiB,EAAE,EAAE,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ;QAC5D,OAAO,EAAE,WAAW,CAAC,eAAe;KACrC,CAAC;IACF,IAAI,KAAK,CAAC,QAAQ,KAAK,UAAU;QAAE,OAAO,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACtE,OAAO,YAAY,CAAC,KAAK,EAAE;QACzB,GAAG,MAAM;QACT,QAAQ,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,KAAK,IAAI;QACzC,SAAS,EAAE,WAAW,CAAC,YAAY;QACnC,WAAW,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,WAAW,KAAK,SAAS,IAAI,oBAAoB,CAAC,WAAW,CAAC;KACV,CAAC,CAAC;AACxF,CAAC;AAED,0EAA0E;AAC1E,SAAS,WAAW,CAAC,KAAc;IACjC,OAAO,CACL,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACpB,KAAK,CAAC,KAAK,CAAC,mBAAmB,CAAC;QAChC,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,MAAM,CACpE,CAAC;AACJ,CAAC;AAED,kGAAkG;AAClG,SAAS,sBAAsB,CAAC,KAA2B;IACzD,MAAM,UAAU,GAAG;QACjB,KAAK,CAAC,QAAQ;QACd,KAAK,CAAC,IAAI;QACV,KAAK,CAAC,KAAK;QACX,KAAK,CAAC,aAAa;QACnB,KAAK,CAAC,WAAW;KAClB,CAAC;IACF,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC,IAAI,CACvD,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,QAAQ,CAC1D,CAAC;IACF,MAAM,YAAY,GAAG,IAAI,GAAG,CAAS;QACnC,SAAS;QACT,GAAG,CAAC,OAAO,EAAE,YAAY,IAAI,EAAE,CAAC;QAChC,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;aACrB,MAAM,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,EAAE,CAAC,SAAS,KAAK,SAAS,CAAC;aAClD,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC;KACrC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC,MAAM,CAAC,qBAAqB,CAAC;SACxC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,EAAE,QAAQ,KAAK,KAAK,CAAC,EAAE,CAAC,CAAC;SACnF,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AAC9F,CAAC","sourcesContent":["import { isRecord } from '@ankhorage/utility/object';\nimport { isNonEmptyString } from '@ankhorage/utility/string';\n\nimport type { InfraEnvironmentSpec, InfraObjectStorageSpec } from '../types/infraManifest';\nimport type { InfraShape } from '../types/infraValidation';\nimport { INFRA_ADAPTER_CATALOG } from './constants';\nimport { infraFields } from './infraFields';\nimport { isInfraAuthSpec } from './isInfraAuthSpec';\nimport { isInfraCredentialRef } from './isInfraCredentialRef';\nimport { isInfraDeploymentSpec } from './isInfraDeploymentSpec';\nimport { isInfraShape } from './isInfraShape';\nimport { isInfraWorkloadSpec } from './isInfraWorkloadSpec';\n\n/*** Validate sibling capabilities and their required relationships within one environment. */\nexport function isInfraEnvironmentSpec(value: unknown): value is InfraEnvironmentSpec {\n return isEnvironmentShape(value) && hasServiceDependencies(value);\n}\n\n/*** Check all environment field shapes before inspecting canonical dependency metadata. */\nfunction isEnvironmentShape(value: unknown): value is InfraEnvironmentSpec {\n return isInfraShape(value, {\n deployment: isInfraDeploymentSpec,\n database: (database) =>\n database === undefined ||\n isInfraShape(database, {\n provider: (provider) => provider === 'supabase',\n tier: (tier) => tier === undefined || tier === 'dev' || tier === 'prod',\n }),\n objectStorage: (storage) => storage === undefined || isObjectStorage(storage),\n auth: (auth) => auth === undefined || isInfraAuthSpec(auth),\n authz: (authz) =>\n authz === undefined ||\n isInfraShape(authz, {\n provider: (provider) => provider === 'cerbos',\n kind: (kind) => kind === 'ABAC' || kind === 'RBAC',\n policies: (policies) => policies === undefined || isPolicyFiles(policies),\n }),\n secretStore: (store) =>\n store === undefined ||\n isInfraShape(store, {\n provider: (provider) => provider === 'supabase-vault',\n schema: infraFields.optionalText,\n }),\n networking: (networking) =>\n networking === undefined ||\n isInfraShape(networking, {\n domain: infraFields.optionalText,\n publicBaseUrl: (publicBaseUrl) =>\n publicBaseUrl === undefined || isPublicHttpOrigin(publicBaseUrl),\n }),\n workloads: (workloads) => workloads === undefined || isWorkloads(workloads),\n } satisfies InfraShape<InfraEnvironmentSpec>);\n}\n\n/*** Public workload configuration uses one canonical absolute HTTP(S) origin without path state. */\nfunction isPublicHttpOrigin(value: unknown): boolean {\n if (!isNonEmptyString(value)) return false;\n try {\n const url = new URL(value);\n return (url.protocol === 'http:' || url.protocol === 'https:') && url.origin === value;\n } catch {\n return false;\n }\n}\n\n/*** Validate portable, unique Cerbos policy files without host paths or traversal. */\nfunction isPolicyFiles(value: unknown): boolean {\n return (\n Array.isArray(value) &&\n value.every((policy) =>\n isInfraShape(policy, {\n path: (path) =>\n isNonEmptyString(path) && !path.startsWith('/') && !path.split('/').includes('..'),\n content: isNonEmptyString,\n }),\n ) &&\n new Set(value.map((policy) => (isRecord(policy) ? policy.path : undefined))).size ===\n value.length\n );\n}\n\n/*** Object storage can vary independently from database/auth; there is no implicit auto provider. */\nfunction isObjectStorage(value: unknown): boolean {\n if (!isRecord(value)) return false;\n const common = {\n provider: (provider: unknown) => provider === value.provider,\n buckets: infraFields.optionalStrings,\n };\n if (value.provider === 'supabase') return isInfraShape(value, common);\n return isInfraShape(value, {\n ...common,\n provider: (provider) => provider === 'r2',\n accountId: infraFields.optionalText,\n credentials: (credentials) => credentials === undefined || isInfraCredentialRef(credentials),\n } satisfies InfraShape<Extract<InfraObjectStorageSpec, { readonly provider: 'r2' }>>);\n}\n\n/*** Duplicate desired workload identities cannot be reconciled safely. */\nfunction isWorkloads(value: unknown): boolean {\n return (\n Array.isArray(value) &&\n value.every(isInfraWorkloadSpec) &&\n new Set(value.map((workload) => workload.id)).size === value.length\n );\n}\n\n/*** Check catalog dependencies against selected sibling capabilities and runtime capabilities. */\nfunction hasServiceDependencies(value: InfraEnvironmentSpec): boolean {\n const selections = [\n value.database,\n value.auth,\n value.authz,\n value.objectStorage,\n value.secretStore,\n ];\n const runtime = Object.values(INFRA_ADAPTER_CATALOG).find(\n (entry) => entry.id === value.deployment.runtime.provider,\n );\n const capabilities = new Set<string>([\n 'compute',\n ...(runtime?.capabilities ?? []),\n ...Object.entries(value)\n .filter(([, selection]) => selection !== undefined)\n .map(([capability]) => capability),\n ]);\n return Object.values(INFRA_ADAPTER_CATALOG)\n .filter((entry) => selections.some((selection) => selection?.provider === entry.id))\n .every((entry) => entry.dependencies.every((dependency) => capabilities.has(dependency)));\n}\n"]}
1
+ {"version":3,"file":"isInfraEnvironmentSpec.js","sourceRoot":"","sources":["../../src/infra/isInfraEnvironmentSpec.ts"],"names":[],"mappings":"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","sourcesContent":["import { isRecord } from '@ankhorage/utility/object';\nimport { isNonEmptyString } from '@ankhorage/utility/string';\n\nimport type { InfraEnvironmentSpec, InfraObjectStorageSpec } from '../types/infraManifest';\nimport type { InfraShape } from '../types/infraValidation';\nimport { INFRA_ADAPTER_CATALOG } from './constants';\nimport { infraFields } from './infraFields';\nimport { isInfraAuthSpec } from './isInfraAuthSpec';\nimport { isInfraCredentialRef } from './isInfraCredentialRef';\nimport { isInfraDeploymentSpec } from './isInfraDeploymentSpec';\nimport { isInfraShape } from './isInfraShape';\nimport { isInfraWorkloadSpec } from './isInfraWorkloadSpec';\n\n/*** Validate sibling capabilities and their required relationships within one environment. */\nexport function isInfraEnvironmentSpec(value: unknown): value is InfraEnvironmentSpec {\n return (\n isEnvironmentShape(value) && hasServiceDependencies(value) && hasUniquePublishedPorts(value)\n );\n}\n\n/*** Check all environment field shapes before inspecting canonical dependency metadata. */\nfunction isEnvironmentShape(value: unknown): value is InfraEnvironmentSpec {\n return isInfraShape(value, {\n deployment: isInfraDeploymentSpec,\n database: (database) =>\n database === undefined ||\n isInfraShape(database, {\n provider: (provider) => provider === 'supabase',\n tier: (tier) => tier === undefined || tier === 'dev' || tier === 'prod',\n }),\n objectStorage: (storage) => storage === undefined || isObjectStorage(storage),\n auth: (auth) => auth === undefined || isInfraAuthSpec(auth),\n authz: (authz) =>\n authz === undefined ||\n isInfraShape(authz, {\n provider: (provider) => provider === 'cerbos',\n kind: (kind) => kind === 'ABAC' || kind === 'RBAC',\n policies: (policies) => policies === undefined || isPolicyFiles(policies),\n }),\n secretStore: (store) =>\n store === undefined ||\n isInfraShape(store, {\n provider: (provider) => provider === 'supabase-vault',\n schema: infraFields.optionalText,\n }),\n networking: (networking) =>\n networking === undefined ||\n isInfraShape(networking, {\n domain: infraFields.optionalText,\n publicBaseUrl: (publicBaseUrl) =>\n publicBaseUrl === undefined || isPublicHttpOrigin(publicBaseUrl),\n }),\n workloads: (workloads) => workloads === undefined || isWorkloads(workloads),\n } satisfies InfraShape<InfraEnvironmentSpec>);\n}\n\n/*** Public workload configuration uses one canonical absolute HTTP(S) origin without path state. */\nfunction isPublicHttpOrigin(value: unknown): boolean {\n if (!isNonEmptyString(value)) return false;\n try {\n const url = new URL(value);\n return (url.protocol === 'http:' || url.protocol === 'https:') && url.origin === value;\n } catch {\n return false;\n }\n}\n\n/*** Validate portable, unique Cerbos policy files without host paths or traversal. */\nfunction isPolicyFiles(value: unknown): boolean {\n return (\n Array.isArray(value) &&\n value.every((policy) =>\n isInfraShape(policy, {\n path: (path) =>\n isNonEmptyString(path) && !path.startsWith('/') && !path.split('/').includes('..'),\n content: isNonEmptyString,\n }),\n ) &&\n new Set(value.map((policy) => (isRecord(policy) ? policy.path : undefined))).size ===\n value.length\n );\n}\n\n/*** Object storage can vary independently from database/auth; there is no implicit auto provider. */\nfunction isObjectStorage(value: unknown): boolean {\n if (!isRecord(value)) return false;\n const common = {\n provider: (provider: unknown) => provider === value.provider,\n buckets: infraFields.optionalStrings,\n };\n if (value.provider === 'supabase') return isInfraShape(value, common);\n return isInfraShape(value, {\n ...common,\n provider: (provider) => provider === 'r2',\n accountId: infraFields.optionalText,\n credentials: (credentials) => credentials === undefined || isInfraCredentialRef(credentials),\n } satisfies InfraShape<Extract<InfraObjectStorageSpec, { readonly provider: 'r2' }>>);\n}\n\n/*** Duplicate desired workload identities cannot be reconciled safely. */\nfunction isWorkloads(value: unknown): boolean {\n return (\n Array.isArray(value) &&\n value.every(isInfraWorkloadSpec) &&\n new Set(value.map((workload) => workload.id)).size === value.length\n );\n}\n\n/*** Prevent two workloads in one environment from claiming the same external listener. */\nfunction hasUniquePublishedPorts(value: InfraEnvironmentSpec): boolean {\n const ports = (value.workloads ?? []).flatMap((workload) =>\n (workload.ports ?? []).flatMap(({ publishedPort }) =>\n publishedPort === undefined ? [] : [publishedPort],\n ),\n );\n return new Set(ports).size === ports.length;\n}\n\n/*** Check catalog dependencies against selected sibling capabilities and runtime capabilities. */\nfunction hasServiceDependencies(value: InfraEnvironmentSpec): boolean {\n const selections = [\n value.database,\n value.auth,\n value.authz,\n value.objectStorage,\n value.secretStore,\n ];\n const runtime = Object.values(INFRA_ADAPTER_CATALOG).find(\n (entry) => entry.id === value.deployment.runtime.provider,\n );\n const capabilities = new Set<string>([\n 'compute',\n ...(runtime?.capabilities ?? []),\n ...Object.entries(value)\n .filter(([, selection]) => selection !== undefined)\n .map(([capability]) => capability),\n ]);\n return Object.values(INFRA_ADAPTER_CATALOG)\n .filter((entry) => selections.some((selection) => selection?.provider === entry.id))\n .every((entry) => entry.dependencies.every((dependency) => capabilities.has(dependency)));\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"isInfraWorkloadSpec.d.ts","sourceRoot":"","sources":["../../src/infra/isInfraWorkloadSpec.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAMhE,0GAA0G;AAC1G,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,iBAAiB,CAkC9E"}
1
+ {"version":3,"file":"isInfraWorkloadSpec.d.ts","sourceRoot":"","sources":["../../src/infra/isInfraWorkloadSpec.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAMhE,0GAA0G;AAC1G,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,iBAAiB,CAuC9E"}
@@ -7,7 +7,7 @@ import { isInfraWorkloadHealth } from './isInfraWorkloadHealth';
7
7
  import { isInfraWorkloadValue } from './isInfraWorkloadValue';
8
8
  /*** Validate the portable desired workload; runtime-specific fields and plaintext secret objects fail. */
9
9
  export function isInfraWorkloadSpec(value) {
10
- return isInfraShape(value, {
10
+ if (!isInfraShape(value, {
11
11
  id: isNonEmptyString,
12
12
  artifact: (artifact) => isInfraShape(artifact, { kind: (kind) => kind === 'image', image: isNonEmptyString }),
13
13
  command: infraFields.optionalStrings,
@@ -32,7 +32,10 @@ export function isInfraWorkloadSpec(value) {
32
32
  exposure: (exposure) => exposure === undefined || exposure === 'internal' || exposure === 'public',
33
33
  replicas: infraFields.optionalNonnegativeInteger,
34
34
  dependsOn: infraFields.optionalStrings,
35
- });
35
+ })) {
36
+ return false;
37
+ }
38
+ return hasValidPublishedPorts(value);
36
39
  }
37
40
  /*** Named transport ports stay independent from runtime resource types. */
38
41
  function isPort(value) {
@@ -40,7 +43,24 @@ function isPort(value) {
40
43
  name: isNonEmptyString,
41
44
  port: infraFields.port,
42
45
  protocol: (protocol) => protocol === undefined || protocol === 'tcp' || protocol === 'udp',
46
+ publishedPort: infraFields.optionalPort,
47
+ });
48
+ }
49
+ /*** Require fixed external listeners to belong to one public workload replica. */
50
+ function hasValidPublishedPorts(workload) {
51
+ if (!isRecord(workload))
52
+ return false;
53
+ const ports = Array.isArray(workload.ports) ? workload.ports : [];
54
+ const published = ports.flatMap((port) => {
55
+ if (!isRecord(port) || typeof port.publishedPort !== 'number')
56
+ return [];
57
+ return [port.publishedPort];
43
58
  });
59
+ return (new Set(published).size === published.length &&
60
+ (published.length === 0 ||
61
+ (workload.exposure === 'public' &&
62
+ (workload.replicas === undefined ||
63
+ (typeof workload.replicas === 'number' && workload.replicas <= 1)))));
44
64
  }
45
65
  /*** Portable files model policy/config materialization without host filesystem access. */
46
66
  function isFile(value) {
@@ -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 return 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\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 });\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 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"]}
@@ -29,14 +29,18 @@ export interface InfraReconcileResult {
29
29
  readonly resources: readonly InfraOwnedResource[];
30
30
  readonly outputs: readonly InfraOutput[];
31
31
  }
32
+ /** Current portable compute state resolved without creating, updating or deleting resources. */
33
+ export interface InfraComputeSnapshot extends InfraReconcileResult {
34
+ readonly targets: readonly InfraComputeTarget[];
35
+ }
32
36
  export interface InfraComputeAdapter<P extends InfraComputeProviderId = InfraComputeProviderId> {
33
37
  readonly descriptor: InfraAdapterDescriptor<P>;
34
38
  /** Read-only prerequisites, config and control-plane credential validation. */
35
39
  validateAsync(context: InfraExecutionContext, selection: InfraComputeSelection<P>): Promise<InfraResult<null>>;
40
+ /** Read-only discovery for planning and stateless runtime projection. Missing compute is empty. */
41
+ inspectAsync(context: InfraExecutionContext, selection: InfraComputeSelection<P>): Promise<InfraResult<InfraComputeSnapshot>>;
36
42
  planAsync(context: InfraExecutionContext, selection: InfraComputeSelection<P>): Promise<InfraResult<readonly InfraPlanAction[]>>;
37
- ensureAsync(context: InfraExecutionContext, selection: InfraComputeSelection<P>): Promise<InfraResult<InfraReconcileResult & {
38
- readonly targets: readonly InfraComputeTarget[];
39
- }>>;
43
+ ensureAsync(context: InfraExecutionContext, selection: InfraComputeSelection<P>): Promise<InfraResult<InfraComputeSnapshot>>;
40
44
  statusAsync(context: InfraExecutionContext): Promise<InfraResult<readonly InfraResourceStatus[]>>;
41
45
  destroyAsync(context: InfraExecutionContext, request: InfraDestroyRequest): Promise<InfraResult<InfraReconcileResult>>;
42
46
  }
@@ -1 +1 @@
1
- {"version":3,"file":"infraAdapters.d.ts","sourceRoot":"","sources":["../../src/types/infraAdapters.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"infraAdapters.d.ts","sourceRoot":"","sources":["../../src/types/infraAdapters.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"infraAdapters.js","sourceRoot":"","sources":["../../src/types/infraAdapters.ts"],"names":[],"mappings":"","sourcesContent":["import type { AppEnvironmentId } from '../environments';\nimport type { INFRA_ADAPTER_CATALOG } from '../infra/constants';\nimport type {\n InfraDestroyRequest,\n InfraGeneratedArtifact,\n InfraLedger,\n InfraOutput,\n InfraOwnedResource,\n InfraPlanAction,\n InfraResourceStatus,\n InfraResult,\n} from './infraLifecycle';\nimport type {\n InfraAdapterId,\n InfraComputeProviderId,\n InfraComputeSelection,\n InfraEnvironmentSpec,\n InfraRuntimeProviderId,\n InfraRuntimeSelection,\n} from './infraManifest';\nimport type { InfraControlPlaneCredentialRef, InfraSecretReference } from './infraSecrets';\nimport type { InfraComputeTarget } from './infraTargets';\nimport type { InfraWorkloadSpec } from './infraWorkload';\n\n/** Package exports must match this exact catalog entry; optional operations are advertised explicitly. */\nexport type InfraAdapterDescriptor<P extends InfraAdapterId = InfraAdapterId> = {\n [K in P]: (typeof INFRA_ADAPTER_CATALOG)[K] & {\n readonly operations?: readonly ('suspend' | 'resume' | 'generate' | 'diagnostics')[];\n };\n}[P];\n\n/** Trusted execution-only ports. Implementations must not serialize resolved credential values. */\nexport interface InfraExecutionContext {\n readonly projectId: string;\n readonly environment: AppEnvironmentId;\n readonly desired: InfraEnvironmentSpec;\n readonly previous?: InfraLedger;\n readonly signal?: AbortSignal;\n readonly credentials: {\n resolveAsync(\n reference: InfraControlPlaneCredentialRef,\n ): Promise<InfraResult<Readonly<Record<string, string>>>>;\n };\n readonly secrets: {\n resolveAsync(reference: InfraSecretReference): Promise<InfraResult<string>>;\n };\n}\n\nexport interface InfraReconcileResult {\n readonly resources: readonly InfraOwnedResource[];\n readonly outputs: readonly InfraOutput[];\n}\n\nexport interface InfraComputeAdapter<P extends InfraComputeProviderId = InfraComputeProviderId> {\n readonly descriptor: InfraAdapterDescriptor<P>;\n /** Read-only prerequisites, config and control-plane credential validation. */\n validateAsync(\n context: InfraExecutionContext,\n selection: InfraComputeSelection<P>,\n ): Promise<InfraResult<null>>;\n planAsync(\n context: InfraExecutionContext,\n selection: InfraComputeSelection<P>,\n ): Promise<InfraResult<readonly InfraPlanAction[]>>;\n ensureAsync(\n context: InfraExecutionContext,\n selection: InfraComputeSelection<P>,\n ): Promise<\n InfraResult<InfraReconcileResult & { readonly targets: readonly InfraComputeTarget[] }>\n >;\n statusAsync(context: InfraExecutionContext): Promise<InfraResult<readonly InfraResourceStatus[]>>;\n destroyAsync(\n context: InfraExecutionContext,\n request: InfraDestroyRequest,\n ): Promise<InfraResult<InfraReconcileResult>>;\n}\n\nexport interface InfraRuntimeDesiredState<\n P extends InfraRuntimeProviderId = InfraRuntimeProviderId,\n> {\n readonly selection: InfraRuntimeSelection<P>;\n readonly targets: readonly InfraComputeTarget[];\n readonly workloads: readonly InfraWorkloadSpec[];\n /** Previously resolved outputs available to portable workload value references. */\n readonly availableOutputs: readonly InfraOutput[];\n}\n\nexport interface InfraRuntimeAdapter<P extends InfraRuntimeProviderId = InfraRuntimeProviderId> {\n readonly descriptor: InfraAdapterDescriptor<P>;\n /** Read-only prerequisites and desired-state validation; no cluster creation. */\n validateAsync(\n context: InfraExecutionContext,\n desired: InfraRuntimeDesiredState<P>,\n ): Promise<InfraResult<null>>;\n planAsync(\n context: InfraExecutionContext,\n desired: InfraRuntimeDesiredState<P>,\n ): Promise<InfraResult<readonly InfraPlanAction[]>>;\n ensureAsync(\n context: InfraExecutionContext,\n desired: InfraRuntimeDesiredState<P>,\n ): Promise<InfraResult<InfraReconcileResult>>;\n statusAsync(\n context: InfraExecutionContext,\n desired: InfraRuntimeDesiredState<P>,\n ): Promise<InfraResult<readonly InfraResourceStatus[]>>;\n suspendAsync(\n context: InfraExecutionContext,\n desired: InfraRuntimeDesiredState<P>,\n ): Promise<InfraResult<InfraReconcileResult>>;\n destroyAsync(\n context: InfraExecutionContext,\n desired: InfraRuntimeDesiredState<P>,\n request: InfraDestroyRequest,\n ): Promise<InfraResult<InfraReconcileResult>>;\n generateAsync?(\n context: InfraExecutionContext,\n desired: InfraRuntimeDesiredState<P>,\n ): Promise<InfraResult<readonly InfraGeneratedArtifact[]>>;\n}\n\n/** A service can own several capabilities but contributes one deduplicated platform instance. */\nexport interface InfraServiceAdapter {\n readonly descriptor: InfraAdapterDescriptor<'supabase' | 'cerbos' | 'r2' | 'supabase-vault'>;\n /** Read-only service config, bootstrap credentials and dependency validation. */\n validateAsync(context: InfraExecutionContext): Promise<InfraResult<null>>;\n planAsync(context: InfraExecutionContext): Promise<InfraResult<readonly InfraPlanAction[]>>;\n desiredWorkloadsAsync(\n context: InfraExecutionContext,\n ): Promise<InfraResult<readonly InfraWorkloadSpec[]>>;\n reconcileAsync(\n context: InfraExecutionContext,\n runtimeOutputs: readonly InfraOutput[],\n ): Promise<InfraResult<InfraReconcileResult>>;\n statusAsync(context: InfraExecutionContext): Promise<InfraResult<readonly InfraResourceStatus[]>>;\n destroyAsync(\n context: InfraExecutionContext,\n request: InfraDestroyRequest,\n ): Promise<InfraResult<InfraReconcileResult>>;\n suspendAsync?(context: InfraExecutionContext): Promise<InfraResult<InfraReconcileResult>>;\n}\n"]}
1
+ {"version":3,"file":"infraAdapters.js","sourceRoot":"","sources":["../../src/types/infraAdapters.ts"],"names":[],"mappings":"","sourcesContent":["import type { AppEnvironmentId } from '../environments';\nimport type { INFRA_ADAPTER_CATALOG } from '../infra/constants';\nimport type {\n InfraDestroyRequest,\n InfraGeneratedArtifact,\n InfraLedger,\n InfraOutput,\n InfraOwnedResource,\n InfraPlanAction,\n InfraResourceStatus,\n InfraResult,\n} from './infraLifecycle';\nimport type {\n InfraAdapterId,\n InfraComputeProviderId,\n InfraComputeSelection,\n InfraEnvironmentSpec,\n InfraRuntimeProviderId,\n InfraRuntimeSelection,\n} from './infraManifest';\nimport type { InfraControlPlaneCredentialRef, InfraSecretReference } from './infraSecrets';\nimport type { InfraComputeTarget } from './infraTargets';\nimport type { InfraWorkloadSpec } from './infraWorkload';\n\n/** Package exports must match this exact catalog entry; optional operations are advertised explicitly. */\nexport type InfraAdapterDescriptor<P extends InfraAdapterId = InfraAdapterId> = {\n [K in P]: (typeof INFRA_ADAPTER_CATALOG)[K] & {\n readonly operations?: readonly ('suspend' | 'resume' | 'generate' | 'diagnostics')[];\n };\n}[P];\n\n/** Trusted execution-only ports. Implementations must not serialize resolved credential values. */\nexport interface InfraExecutionContext {\n readonly projectId: string;\n readonly environment: AppEnvironmentId;\n readonly desired: InfraEnvironmentSpec;\n readonly previous?: InfraLedger;\n readonly signal?: AbortSignal;\n readonly credentials: {\n resolveAsync(\n reference: InfraControlPlaneCredentialRef,\n ): Promise<InfraResult<Readonly<Record<string, string>>>>;\n };\n readonly secrets: {\n resolveAsync(reference: InfraSecretReference): Promise<InfraResult<string>>;\n };\n}\n\nexport interface InfraReconcileResult {\n readonly resources: readonly InfraOwnedResource[];\n readonly outputs: readonly InfraOutput[];\n}\n\n/** Current portable compute state resolved without creating, updating or deleting resources. */\nexport interface InfraComputeSnapshot extends InfraReconcileResult {\n readonly targets: readonly InfraComputeTarget[];\n}\n\nexport interface InfraComputeAdapter<P extends InfraComputeProviderId = InfraComputeProviderId> {\n readonly descriptor: InfraAdapterDescriptor<P>;\n /** Read-only prerequisites, config and control-plane credential validation. */\n validateAsync(\n context: InfraExecutionContext,\n selection: InfraComputeSelection<P>,\n ): Promise<InfraResult<null>>;\n /** Read-only discovery for planning and stateless runtime projection. Missing compute is empty. */\n inspectAsync(\n context: InfraExecutionContext,\n selection: InfraComputeSelection<P>,\n ): Promise<InfraResult<InfraComputeSnapshot>>;\n planAsync(\n context: InfraExecutionContext,\n selection: InfraComputeSelection<P>,\n ): Promise<InfraResult<readonly InfraPlanAction[]>>;\n ensureAsync(\n context: InfraExecutionContext,\n selection: InfraComputeSelection<P>,\n ): Promise<InfraResult<InfraComputeSnapshot>>;\n statusAsync(context: InfraExecutionContext): Promise<InfraResult<readonly InfraResourceStatus[]>>;\n destroyAsync(\n context: InfraExecutionContext,\n request: InfraDestroyRequest,\n ): Promise<InfraResult<InfraReconcileResult>>;\n}\n\nexport interface InfraRuntimeDesiredState<\n P extends InfraRuntimeProviderId = InfraRuntimeProviderId,\n> {\n readonly selection: InfraRuntimeSelection<P>;\n readonly targets: readonly InfraComputeTarget[];\n readonly workloads: readonly InfraWorkloadSpec[];\n /** Previously resolved outputs available to portable workload value references. */\n readonly availableOutputs: readonly InfraOutput[];\n}\n\nexport interface InfraRuntimeAdapter<P extends InfraRuntimeProviderId = InfraRuntimeProviderId> {\n readonly descriptor: InfraAdapterDescriptor<P>;\n /** Read-only prerequisites and desired-state validation; no cluster creation. */\n validateAsync(\n context: InfraExecutionContext,\n desired: InfraRuntimeDesiredState<P>,\n ): Promise<InfraResult<null>>;\n planAsync(\n context: InfraExecutionContext,\n desired: InfraRuntimeDesiredState<P>,\n ): Promise<InfraResult<readonly InfraPlanAction[]>>;\n ensureAsync(\n context: InfraExecutionContext,\n desired: InfraRuntimeDesiredState<P>,\n ): Promise<InfraResult<InfraReconcileResult>>;\n statusAsync(\n context: InfraExecutionContext,\n desired: InfraRuntimeDesiredState<P>,\n ): Promise<InfraResult<readonly InfraResourceStatus[]>>;\n suspendAsync(\n context: InfraExecutionContext,\n desired: InfraRuntimeDesiredState<P>,\n ): Promise<InfraResult<InfraReconcileResult>>;\n destroyAsync(\n context: InfraExecutionContext,\n desired: InfraRuntimeDesiredState<P>,\n request: InfraDestroyRequest,\n ): Promise<InfraResult<InfraReconcileResult>>;\n generateAsync?(\n context: InfraExecutionContext,\n desired: InfraRuntimeDesiredState<P>,\n ): Promise<InfraResult<readonly InfraGeneratedArtifact[]>>;\n}\n\n/** A service can own several capabilities but contributes one deduplicated platform instance. */\nexport interface InfraServiceAdapter {\n readonly descriptor: InfraAdapterDescriptor<'supabase' | 'cerbos' | 'r2' | 'supabase-vault'>;\n /** Read-only service config, bootstrap credentials and dependency validation. */\n validateAsync(context: InfraExecutionContext): Promise<InfraResult<null>>;\n planAsync(context: InfraExecutionContext): Promise<InfraResult<readonly InfraPlanAction[]>>;\n desiredWorkloadsAsync(\n context: InfraExecutionContext,\n ): Promise<InfraResult<readonly InfraWorkloadSpec[]>>;\n reconcileAsync(\n context: InfraExecutionContext,\n runtimeOutputs: readonly InfraOutput[],\n ): Promise<InfraResult<InfraReconcileResult>>;\n statusAsync(context: InfraExecutionContext): Promise<InfraResult<readonly InfraResourceStatus[]>>;\n destroyAsync(\n context: InfraExecutionContext,\n request: InfraDestroyRequest,\n ): Promise<InfraResult<InfraReconcileResult>>;\n suspendAsync?(context: InfraExecutionContext): Promise<InfraResult<InfraReconcileResult>>;\n}\n"]}
@@ -28,6 +28,8 @@ export interface InfraWorkloadPort {
28
28
  readonly name: string;
29
29
  readonly port: number;
30
30
  readonly protocol?: 'tcp' | 'udp';
31
+ /** Exact external listener port requested for a public single-replica workload. */
32
+ readonly publishedPort?: number;
31
33
  }
32
34
  export type InfraWorkloadHealthSpec = {
33
35
  readonly intervalSeconds?: number;
@@ -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}\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 /** 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": "18.0.0",
3
+ "version": "19.1.0",
4
4
  "main": "./dist/index.js",
5
5
  "ankh": {
6
6
  "category": "contracts",
@@ -17,4 +17,7 @@ export const infraFields = {
17
17
  value === undefined || (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0),
18
18
  port: (value: unknown): boolean =>
19
19
  typeof value === 'number' && Number.isInteger(value) && value > 0 && value <= 65535,
20
+ optionalPort: (value: unknown): boolean =>
21
+ value === undefined ||
22
+ (typeof value === 'number' && Number.isInteger(value) && value > 0 && value <= 65535),
20
23
  };
@@ -13,7 +13,9 @@ import { isInfraWorkloadSpec } from './isInfraWorkloadSpec';
13
13
 
14
14
  /*** Validate sibling capabilities and their required relationships within one environment. */
15
15
  export function isInfraEnvironmentSpec(value: unknown): value is InfraEnvironmentSpec {
16
- return isEnvironmentShape(value) && hasServiceDependencies(value);
16
+ return (
17
+ isEnvironmentShape(value) && hasServiceDependencies(value) && hasUniquePublishedPorts(value)
18
+ );
17
19
  }
18
20
 
19
21
  /*** Check all environment field shapes before inspecting canonical dependency metadata. */
@@ -104,6 +106,16 @@ function isWorkloads(value: unknown): boolean {
104
106
  );
105
107
  }
106
108
 
109
+ /*** Prevent two workloads in one environment from claiming the same external listener. */
110
+ function hasUniquePublishedPorts(value: InfraEnvironmentSpec): boolean {
111
+ const ports = (value.workloads ?? []).flatMap((workload) =>
112
+ (workload.ports ?? []).flatMap(({ publishedPort }) =>
113
+ publishedPort === undefined ? [] : [publishedPort],
114
+ ),
115
+ );
116
+ return new Set(ports).size === ports.length;
117
+ }
118
+
107
119
  /*** Check catalog dependencies against selected sibling capabilities and runtime capabilities. */
108
120
  function hasServiceDependencies(value: InfraEnvironmentSpec): boolean {
109
121
  const selections = [
@@ -11,39 +11,44 @@ import { isInfraWorkloadValue } from './isInfraWorkloadValue';
11
11
 
12
12
  /*** Validate the portable desired workload; runtime-specific fields and plaintext secret objects fail. */
13
13
  export function isInfraWorkloadSpec(value: unknown): value is InfraWorkloadSpec {
14
- return isInfraShape(value, {
15
- id: isNonEmptyString,
16
- artifact: (artifact) =>
17
- isInfraShape(artifact, { kind: (kind) => kind === 'image', image: isNonEmptyString }),
18
- command: infraFields.optionalStrings,
19
- args: (args) => args === undefined || isStringArray(args),
20
- ports: (ports) =>
21
- ports === undefined ||
22
- (Array.isArray(ports) && ports.every(isPort) && hasUniqueField(ports, 'name')),
23
- environment: (environment) =>
24
- environment === undefined ||
25
- (isRecord(environment) && Object.values(environment).every(isInfraWorkloadValue)),
26
- files: (files) =>
27
- files === undefined ||
28
- (Array.isArray(files) && files.every(isFile) && hasUniqueField(files, 'path')),
29
- health: (health) => health === undefined || isInfraWorkloadHealth(health),
30
- resources: (resources) =>
31
- resources === undefined ||
32
- isInfraShape(resources, {
33
- cpuMillis: infraFields.optionalPositiveInteger,
34
- memoryMiB: infraFields.optionalPositiveInteger,
35
- }),
36
- persistence: (volumes) =>
37
- volumes === undefined ||
38
- (Array.isArray(volumes) &&
39
- volumes.every(isVolume) &&
40
- hasUniqueField(volumes, 'id') &&
41
- hasUniqueField(volumes, 'mountPath')),
42
- exposure: (exposure) =>
43
- exposure === undefined || exposure === 'internal' || exposure === 'public',
44
- replicas: infraFields.optionalNonnegativeInteger,
45
- dependsOn: infraFields.optionalStrings,
46
- } satisfies InfraShape<InfraWorkloadSpec>);
14
+ if (
15
+ !isInfraShape(value, {
16
+ id: isNonEmptyString,
17
+ artifact: (artifact) =>
18
+ isInfraShape(artifact, { kind: (kind) => kind === 'image', image: isNonEmptyString }),
19
+ command: infraFields.optionalStrings,
20
+ args: (args) => args === undefined || isStringArray(args),
21
+ ports: (ports) =>
22
+ ports === undefined ||
23
+ (Array.isArray(ports) && ports.every(isPort) && hasUniqueField(ports, 'name')),
24
+ environment: (environment) =>
25
+ environment === undefined ||
26
+ (isRecord(environment) && Object.values(environment).every(isInfraWorkloadValue)),
27
+ files: (files) =>
28
+ files === undefined ||
29
+ (Array.isArray(files) && files.every(isFile) && hasUniqueField(files, 'path')),
30
+ health: (health) => health === undefined || isInfraWorkloadHealth(health),
31
+ resources: (resources) =>
32
+ resources === undefined ||
33
+ isInfraShape(resources, {
34
+ cpuMillis: infraFields.optionalPositiveInteger,
35
+ memoryMiB: infraFields.optionalPositiveInteger,
36
+ }),
37
+ persistence: (volumes) =>
38
+ volumes === undefined ||
39
+ (Array.isArray(volumes) &&
40
+ volumes.every(isVolume) &&
41
+ hasUniqueField(volumes, 'id') &&
42
+ hasUniqueField(volumes, 'mountPath')),
43
+ exposure: (exposure) =>
44
+ exposure === undefined || exposure === 'internal' || exposure === 'public',
45
+ replicas: infraFields.optionalNonnegativeInteger,
46
+ dependsOn: infraFields.optionalStrings,
47
+ } satisfies InfraShape<InfraWorkloadSpec>)
48
+ ) {
49
+ return false;
50
+ }
51
+ return hasValidPublishedPorts(value);
47
52
  }
48
53
 
49
54
  /*** Named transport ports stay independent from runtime resource types. */
@@ -52,9 +57,27 @@ function isPort(value: unknown): boolean {
52
57
  name: isNonEmptyString,
53
58
  port: infraFields.port,
54
59
  protocol: (protocol) => protocol === undefined || protocol === 'tcp' || protocol === 'udp',
60
+ publishedPort: infraFields.optionalPort,
55
61
  });
56
62
  }
57
63
 
64
+ /*** Require fixed external listeners to belong to one public workload replica. */
65
+ function hasValidPublishedPorts(workload: unknown): boolean {
66
+ if (!isRecord(workload)) return false;
67
+ const ports = Array.isArray(workload.ports) ? workload.ports : [];
68
+ const published = ports.flatMap((port) => {
69
+ if (!isRecord(port) || typeof port.publishedPort !== 'number') return [];
70
+ return [port.publishedPort];
71
+ });
72
+ return (
73
+ new Set(published).size === published.length &&
74
+ (published.length === 0 ||
75
+ (workload.exposure === 'public' &&
76
+ (workload.replicas === undefined ||
77
+ (typeof workload.replicas === 'number' && workload.replicas <= 1))))
78
+ );
79
+ }
80
+
58
81
  /*** Portable files model policy/config materialization without host filesystem access. */
59
82
  function isFile(value: unknown): boolean {
60
83
  return isInfraShape(value, { path: isAbsoluteWorkloadPath, content: isInfraWorkloadValue });
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+
3
+ import { isInfraEnvironmentSpec, isInfraWorkloadSpec } from './infra';
4
+
5
+ const publicWorkload = {
6
+ id: 'api',
7
+ artifact: { kind: 'image', image: 'example/api:1' },
8
+ ports: [{ name: 'http', port: 8080, publishedPort: 18_080 }],
9
+ exposure: 'public',
10
+ } as const;
11
+
12
+ describe('published workload ports', () => {
13
+ it('accepts one exact external listener for a public workload', () => {
14
+ expect(isInfraWorkloadSpec(publicWorkload)).toBe(true);
15
+ expect(isInfraWorkloadSpec({ ...publicWorkload, replicas: 0 })).toBe(true);
16
+ expect(isInfraWorkloadSpec({ ...publicWorkload, replicas: 1 })).toBe(true);
17
+ });
18
+
19
+ it.each([
20
+ { ...publicWorkload, ports: [{ name: 'http', port: 80, publishedPort: 0 }] },
21
+ { ...publicWorkload, ports: [{ name: 'http', port: 80, publishedPort: 65_536 }] },
22
+ { ...publicWorkload, exposure: 'internal' },
23
+ { ...publicWorkload, replicas: 2 },
24
+ {
25
+ ...publicWorkload,
26
+ ports: [
27
+ { name: 'http', port: 80, publishedPort: 8080 },
28
+ { name: 'admin', port: 81, publishedPort: 8080 },
29
+ ],
30
+ },
31
+ ])('rejects an invalid fixed external listener: %j', (workload) => {
32
+ expect(isInfraWorkloadSpec(workload)).toBe(false);
33
+ });
34
+
35
+ it('rejects external listener collisions across workloads', () => {
36
+ expect(
37
+ isInfraEnvironmentSpec({
38
+ deployment: { compute: { provider: 'local' }, runtime: { provider: 'minikube' } },
39
+ workloads: [publicWorkload, { ...publicWorkload, id: 'admin' }],
40
+ }),
41
+ ).toBe(false);
42
+ });
43
+ });
@@ -5,12 +5,15 @@ import type {
5
5
  InfraAdapterDescriptor,
6
6
  InfraAuthSpec,
7
7
  InfraAuthzSpec,
8
+ InfraComputeAdapter,
9
+ InfraComputeSnapshot,
8
10
  InfraControlPlaneCredentialRef,
9
11
  InfraDeploymentSpec,
10
12
  InfraDestroyRequest,
11
13
  InfraManifest,
12
14
  InfraObjectStorageSpec,
13
15
  InfraOutput,
16
+ InfraResult,
14
17
  InfraRuntimeAdapter,
15
18
  InfraRuntimeDesiredState,
16
19
  InfraRuntimeProviderId,
@@ -108,3 +111,14 @@ it('requires runtime desired state for every target-dependent lifecycle operatio
108
111
 
109
112
  expect(lifecycleBoundary).toEqual([true, true, true]);
110
113
  });
114
+
115
+ it('requires read-only compute discovery separately from mutating ensure', () => {
116
+ type Adapter = InfraComputeAdapter<'local'>;
117
+
118
+ const computeBoundary: readonly [
119
+ Assignable<Awaited<ReturnType<Adapter['inspectAsync']>>, InfraResult<InfraComputeSnapshot>>,
120
+ Assignable<Awaited<ReturnType<Adapter['ensureAsync']>>, InfraResult<InfraComputeSnapshot>>,
121
+ ] = [true, true];
122
+
123
+ expect(computeBoundary).toEqual([true, true]);
124
+ });
@@ -22,7 +22,7 @@ const workload = {
22
22
  artifact: { kind: 'image', image: 'example/backend@sha256:abc' },
23
23
  command: ['server'],
24
24
  args: ['--verbose'],
25
- ports: [{ name: 'http', port: 8080 }],
25
+ ports: [{ name: 'http', port: 8080, publishedPort: 18_080 }],
26
26
  environment: {
27
27
  NODE_ENV: { kind: 'literal', value: 'production' },
28
28
  DB_PASSWORD: { kind: 'secret', reference },
@@ -51,6 +51,11 @@ export interface InfraReconcileResult {
51
51
  readonly outputs: readonly InfraOutput[];
52
52
  }
53
53
 
54
+ /** Current portable compute state resolved without creating, updating or deleting resources. */
55
+ export interface InfraComputeSnapshot extends InfraReconcileResult {
56
+ readonly targets: readonly InfraComputeTarget[];
57
+ }
58
+
54
59
  export interface InfraComputeAdapter<P extends InfraComputeProviderId = InfraComputeProviderId> {
55
60
  readonly descriptor: InfraAdapterDescriptor<P>;
56
61
  /** Read-only prerequisites, config and control-plane credential validation. */
@@ -58,6 +63,11 @@ export interface InfraComputeAdapter<P extends InfraComputeProviderId = InfraCom
58
63
  context: InfraExecutionContext,
59
64
  selection: InfraComputeSelection<P>,
60
65
  ): Promise<InfraResult<null>>;
66
+ /** Read-only discovery for planning and stateless runtime projection. Missing compute is empty. */
67
+ inspectAsync(
68
+ context: InfraExecutionContext,
69
+ selection: InfraComputeSelection<P>,
70
+ ): Promise<InfraResult<InfraComputeSnapshot>>;
61
71
  planAsync(
62
72
  context: InfraExecutionContext,
63
73
  selection: InfraComputeSelection<P>,
@@ -65,9 +75,7 @@ export interface InfraComputeAdapter<P extends InfraComputeProviderId = InfraCom
65
75
  ensureAsync(
66
76
  context: InfraExecutionContext,
67
77
  selection: InfraComputeSelection<P>,
68
- ): Promise<
69
- InfraResult<InfraReconcileResult & { readonly targets: readonly InfraComputeTarget[] }>
70
- >;
78
+ ): Promise<InfraResult<InfraComputeSnapshot>>;
71
79
  statusAsync(context: InfraExecutionContext): Promise<InfraResult<readonly InfraResourceStatus[]>>;
72
80
  destroyAsync(
73
81
  context: InfraExecutionContext,
@@ -28,6 +28,8 @@ export interface InfraWorkloadPort {
28
28
  readonly name: string;
29
29
  readonly port: number;
30
30
  readonly protocol?: 'tcp' | 'udp';
31
+ /** Exact external listener port requested for a public single-replica workload. */
32
+ readonly publishedPort?: number;
31
33
  }
32
34
 
33
35
  export type InfraWorkloadHealthSpec = {