@forgezero/agent 0.1.73 → 0.1.75

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 (44) hide show
  1. package/README.md +565 -61
  2. package/dist/agent-heartbeat.d.ts +25 -1
  3. package/dist/agent-heartbeat.js +45 -5
  4. package/dist/bootstrap.d.ts +2 -0
  5. package/dist/bootstrap.js +22 -4
  6. package/dist/community-rehearsal-host.js +208 -9
  7. package/dist/credential-schema.d.ts +1 -1
  8. package/dist/credential-schema.js +1 -1
  9. package/dist/definition.js +206 -7
  10. package/dist/deploy-actions.d.ts +7 -0
  11. package/dist/deploy-compiler.js +191 -10
  12. package/dist/deploy-file.js +206 -7
  13. package/dist/deploy-plan-runner.d.ts +3 -0
  14. package/dist/deploy-plan-runner.js +190 -9
  15. package/dist/deploy-plan.js +187 -8
  16. package/dist/deploy-providers.d.ts +19 -0
  17. package/dist/deploy.d.ts +89 -3
  18. package/dist/deploy.js +49 -2
  19. package/dist/deployment-connectivity.d.ts +63 -0
  20. package/dist/deployment-connectivity.js +148 -0
  21. package/dist/deployment-pull.d.ts +2 -0
  22. package/dist/deployment-targets.d.ts +9 -0
  23. package/dist/deployment-targets.js +141 -0
  24. package/dist/deployment.d.ts +69 -0
  25. package/dist/fz-agent.js +1112 -302
  26. package/dist/fz.js +246 -18
  27. package/dist/index.d.ts +1 -0
  28. package/dist/metal-bootstrap.js +1 -1
  29. package/dist/metal-helper-socket.js +210 -11
  30. package/dist/metal-provision.js +210 -11
  31. package/dist/operator-bootstrap.js +22 -4
  32. package/dist/platform-bootstrap-runtime.d.ts +1 -0
  33. package/dist/platform-bootstrap-runtime.js +216 -10
  34. package/dist/platform-fleet-verification.js +523 -150
  35. package/dist/platform-genesis.js +206 -7
  36. package/dist/provision.js +465 -99
  37. package/dist/service-supervisor.d.ts +6 -0
  38. package/dist/software-helper.d.ts +3 -0
  39. package/dist/software-helper.js +465 -98
  40. package/dist/software.d.ts +4 -1
  41. package/dist/software.js +210 -8
  42. package/dist/ubuntu.js +206 -7
  43. package/dist/version.d.ts +1 -1
  44. package/package.json +12 -4
@@ -38,6 +38,13 @@ function validateBuiltInAction(definition, context, where) {
38
38
  throw new Error(`${where}.with.requirements must name existing requirements`);
39
39
  return;
40
40
  }
41
+ if (definition.uses === "forgezero.network/ensure@1") {
42
+ exact(withValue, ["target"], `${where}.with`);
43
+ if (typeof withValue.target !== "string" || !Object.values(context.components).some((entry) => entry.target === withValue.target)) {
44
+ throw new Error(`${where}.with.target must name a target used by a component`);
45
+ }
46
+ return;
47
+ }
41
48
  if (definition.uses === "forgezero.exec/argv@1") {
42
49
  const allowed = ["component", "argv", "credentials"];
43
50
  const unknown = Object.keys(withValue).filter((key) => !allowed.includes(key));
@@ -93,6 +100,8 @@ function validateBuiltInProvider(requirement2, where) {
93
100
  const expected = {
94
101
  "forgezero.bun": { capability: "runtime.javascript", versions: ["1.3.14"], keys: ["installScope"] },
95
102
  "forgezero.docker": { capability: "runtime.container", versions: ["ubuntu-24.04", "ubuntu-26.04"], keys: ["installScope", "storageDriver", "dataRoot", "liveRestore", "logDriver", "defaultNetwork", "rootless"] },
103
+ "forgezero.containerd": { capability: "runtime.container", versions: ["ubuntu-24.04", "ubuntu-26.04"], keys: ["installScope", "runtimeClass", "namespace", "snapshotter"] },
104
+ "forgezero.kata": { capability: "runtime.container", versions: ["4.0.0"], keys: ["installScope", "runtimeClass", "namespace", "hypervisor", "confidentialGuest"] },
96
105
  "forgezero.nginx": { capability: "proxy.http", versions: ["ubuntu-24.04", "ubuntu-26.04"], keys: ["installScope", "websocket", "maximumBodyMiB", "workerProcesses", "workerConnections"] },
97
106
  "forgezero.arangodb": { capability: "database.arangodb", versions: ["3.11.14"], keys: ["installScope", "runtime", "dataPath", "bind"] },
98
107
  "forgezero.cloudflared": { capability: "network.public-tunnel", versions: ["2026.7.3"], keys: ["installScope", "mode"] },
@@ -113,6 +122,10 @@ function validateBuiltInProvider(requirement2, where) {
113
122
  if (row2.storageDriver !== "overlay2" || row2.dataRoot !== "/var/lib/docker" || row2.liveRestore !== true || row2.logDriver !== "local" || row2.defaultNetwork !== "bridge" || row2.rootless !== false)
114
123
  throw new Error(`${where} Docker config is unsupported by the fixed host strategy`);
115
124
  }
125
+ if (requirement2.provider === "forgezero.containerd" && (row2.runtimeClass !== "runc" || row2.namespace !== "forgezero" || row2.snapshotter !== "overlayfs"))
126
+ throw new Error(`${where} containerd config is unsupported by the fixed host strategy`);
127
+ if (requirement2.provider === "forgezero.kata" && (row2.runtimeClass !== "kata-qemu-snp" || row2.namespace !== "forgezero" || row2.hypervisor !== "qemu" || row2.confidentialGuest !== "sev-snp"))
128
+ throw new Error(`${where} Kata config is unsupported by the fixed host strategy`);
116
129
  if (requirement2.provider === "forgezero.nginx" && (typeof row2.websocket !== "boolean" || !Number.isInteger(row2.maximumBodyMiB) || Number(row2.maximumBodyMiB) < 1 || Number(row2.maximumBodyMiB) > 1024 || !(row2.workerProcesses === "auto" || Number.isInteger(row2.workerProcesses)) || !Number.isInteger(row2.workerConnections)))
117
130
  throw new Error(`${where} Nginx config is invalid`);
118
131
  if (requirement2.provider === "forgezero.arangodb" && (row2.runtime !== "native" || !absolute(row2.dataPath) || row2.bind !== "private"))
@@ -190,7 +203,7 @@ function reference(value, where) {
190
203
  const row2 = object(value, where);
191
204
  exact3(row2, ["$ref"], where);
192
205
  const coordinate = boundedString(row2.$ref, `${where}.$ref`, 256);
193
- if (!/^(inputs\.[a-z][A-Za-z0-9-]{0,62}|steps\.[a-z][A-Za-z0-9-]{0,62}\.(status|outputs\.[a-z][A-Za-z0-9-]{0,62})|components\.[a-z][A-Za-z0-9-]{0,62}\.[a-z][A-Za-z0-9-]{0,62}|targets\.[a-z][A-Za-z0-9-]{0,62}\.[a-z][A-Za-z0-9-]{0,62}|deployment\.[a-z][A-Za-z0-9-]{0,62})$/.test(coordinate)) {
206
+ if (!/^(inputs\.[a-z][A-Za-z0-9-]{0,62}|steps\.[a-z][A-Za-z0-9-]{0,62}\.(status|outputs\.[a-z][A-Za-z0-9-]{0,62})|components\.[a-z][A-Za-z0-9-]{0,62}\.[a-z][A-Za-z0-9-]{0,62}|targets\.[a-z][A-Za-z0-9-]{0,62}\.[a-z][A-Za-z0-9-]{0,62}|deployment\.[a-z][A-Za-z0-9-]{0,62}|execution\.(targetName|slot|releaseExecutor|runtime|cpuCores|memoryMiB|storageGiB))$/.test(coordinate)) {
194
207
  fail(where, "contains an unsupported reference.");
195
208
  }
196
209
  return { $ref: coordinate };
@@ -430,10 +443,9 @@ function validateNetwork(value, where) {
430
443
  named(container.network, `${where}.container.network`);
431
444
  }
432
445
  }
433
- function validateComponent(value, where, targetNames, requirements) {
446
+ function validateComponent(value, where, targets, requirements) {
434
447
  const row2 = object(value, where);
435
- if (!targetNames.has(String(row2.target)))
436
- fail(`${where}.target`, "must name an existing target.");
448
+ const selectedTarget = targets.get(String(row2.target)) ?? fail(`${where}.target`, "must name an existing target.");
437
449
  if (row2.kind === "application") {
438
450
  exact3(row2, ["kind", "target", "runtime", "service", "resources", "storage", "network", "rollout"], where);
439
451
  const runtime = object(row2.runtime, `${where}.runtime`);
@@ -444,7 +456,9 @@ function validateComponent(value, where, targetNames, requirements) {
444
456
  exact3(runtime, ["kind", "provider", "requirement", "argv"], `${where}.runtime`);
445
457
  validateArgv(runtime.argv, `${where}.runtime.argv`);
446
458
  } else if (runtime.kind === "container") {
447
- exact3(runtime, ["kind", "provider", "requirement", "image", "entrypoint", "security"], `${where}.runtime`);
459
+ exact3(runtime, ["kind", "provider", "requirement", "runtimeClass", "image", "entrypoint", "security"], `${where}.runtime`);
460
+ if (runtime.runtimeClass !== undefined && !["runc", "kata-qemu-snp"].includes(String(runtime.runtimeClass)))
461
+ fail(`${where}.runtime.runtimeClass`, "is unsupported.");
448
462
  const image = object(runtime.image, `${where}.runtime.image`);
449
463
  exact3(image, ["source"], `${where}.runtime.image`);
450
464
  const source = object(image.source, `${where}.runtime.image.source`);
@@ -478,6 +492,23 @@ function validateComponent(value, where, targetNames, requirements) {
478
492
  fail(`${where}.runtime.requirement`, "must provide runtime.container.");
479
493
  if (runtimeRequirement && runtime.kind === "native" && !runtimeRequirement.capability.startsWith("runtime."))
480
494
  fail(`${where}.runtime.requirement`, "must provide a runtime capability.");
495
+ const execution = selectedTarget.allocation.execution;
496
+ if (execution === "native" && runtime.kind !== "native")
497
+ fail(`${where}.runtime.kind`, "must be native for the selected target.");
498
+ if (execution !== "native" && runtime.kind !== "container")
499
+ fail(`${where}.runtime.kind`, "must be container for the selected target.");
500
+ if (runtime.kind === "container" && execution === "oci-runc") {
501
+ if (!["forgezero.containerd", "forgezero.docker"].includes(String(runtime.provider)))
502
+ fail(`${where}.runtime.provider`, "must be forgezero.containerd for an OCI runc target.");
503
+ if (runtime.runtimeClass !== undefined && runtime.runtimeClass !== "runc")
504
+ fail(`${where}.runtime.runtimeClass`, "must be runc for the selected target.");
505
+ }
506
+ if (runtime.kind === "container" && execution === "oci-kata-qemu-snp") {
507
+ if (runtime.provider !== "forgezero.kata")
508
+ fail(`${where}.runtime.provider`, "must be forgezero.kata for a Kata QEMU SNP target.");
509
+ if (runtime.runtimeClass !== "kata-qemu-snp")
510
+ fail(`${where}.runtime.runtimeClass`, "must be kata-qemu-snp for the selected target.");
511
+ }
481
512
  const service = object(row2.service, `${where}.service`);
482
513
  exact3(service, ["protocol", "port", "health", "websocket", "maximumConnections"], `${where}.service`);
483
514
  if (service.protocol !== "http")
@@ -509,6 +540,18 @@ function validateComponent(value, where, targetNames, requirements) {
509
540
  const network = object(row2.network, `${where}.network`);
510
541
  if (network.container === undefined)
511
542
  fail(`${where}.network.container`, "must select bridge or reviewed host networking.");
543
+ if (selectedTarget.allocation.sharing === "shared") {
544
+ if (object(runtime.security, `${where}.runtime.security`).root !== "read-only")
545
+ fail(`${where}.runtime.security.root`, "must be read-only on a shared compute.");
546
+ if (network.container.mode !== "bridge")
547
+ fail(`${where}.network.container.mode`, "must be bridge on a shared compute.");
548
+ if (Array.isArray(row2.storage) && row2.storage.some((mount) => mount.class !== "ephemeral")) {
549
+ fail(`${where}.storage`, "may contain only size-bounded tmpfs mounts on a shared compute.");
550
+ }
551
+ }
552
+ }
553
+ if (runtime.kind === "native" && selectedTarget.allocation.sharing !== "exclusive") {
554
+ fail(`${where}.target`, "native applications require an exclusive compute.");
512
555
  }
513
556
  if (row2.storage !== undefined) {
514
557
  if (!Array.isArray(row2.storage) || row2.storage.length > 32)
@@ -545,6 +588,8 @@ function validateComponent(value, where, targetNames, requirements) {
545
588
  fail(`${where}.runtime.mode`, "must match the provider runtime mode.");
546
589
  if (runtime.mode !== "native")
547
590
  fail(`${where}.runtime.mode`, "must be native in deployment plan v1.");
591
+ if (selectedTarget.allocation.execution !== "native")
592
+ fail(`${where}.target`, "must select a native execution target for database components.");
548
593
  const topology = object(row2.topology, `${where}.topology`);
549
594
  if (topology.mode === "standalone")
550
595
  exact3(topology, ["mode"], `${where}.topology`);
@@ -741,15 +786,20 @@ function compileDeployment(sourceValue) {
741
786
  if (targetEntries.length < 1 || targetEntries.length > 64)
742
787
  fail("deployment.spec.targets", "must contain 1 to 64 targets.");
743
788
  const targetNames = new Set(targetEntries.map(([targetName]) => named(targetName, "deployment.spec.targets key")));
789
+ const targetDefinitions = new Map;
744
790
  for (const [targetName, value] of targetEntries) {
745
791
  const target = object(value, `deployment.spec.targets.${targetName}`);
746
- exact3(target, ["kind", "selector", "cardinality", "placement"], `deployment.spec.targets.${targetName}`);
792
+ exact3(target, ["kind", "selector", "cardinality", "allocation", "provisioning", "connectivity", "placement"], `deployment.spec.targets.${targetName}`);
747
793
  if (target.kind !== "compute")
748
794
  fail(`deployment.spec.targets.${targetName}.kind`, "must be compute.");
749
795
  const selector = object(target.selector, `deployment.spec.targets.${targetName}.selector`);
750
- exact3(selector, ["profiles", "confidentialCompute", "labels"], `deployment.spec.targets.${targetName}.selector`);
796
+ exact3(selector, ["profiles", "operatingSystem", "confidentialCompute", "labels"], `deployment.spec.targets.${targetName}.selector`);
751
797
  if (!Array.isArray(selector.profiles) || selector.profiles.length < 1 || selector.profiles.length > 32 || selector.profiles.some((profile) => typeof profile !== "string" || !NAME.test(profile)) || new Set(selector.profiles).size !== selector.profiles.length)
752
798
  fail(`deployment.spec.targets.${targetName}.selector.profiles`, "is invalid.");
799
+ const operatingSystem = object(selector.operatingSystem, `deployment.spec.targets.${targetName}.selector.operatingSystem`);
800
+ exact3(operatingSystem, ["id", "version", "architecture"], `deployment.spec.targets.${targetName}.selector.operatingSystem`);
801
+ if (operatingSystem.id !== "ubuntu" || !["24.04", "26.04"].includes(String(operatingSystem.version)) || operatingSystem.architecture !== "x64")
802
+ fail(`deployment.spec.targets.${targetName}.selector.operatingSystem`, "is unsupported.");
753
803
  if (selector.confidentialCompute !== undefined && !["required", "preferred", "disabled"].includes(String(selector.confidentialCompute)))
754
804
  fail(`deployment.spec.targets.${targetName}.selector.confidentialCompute`, "is unsupported.");
755
805
  if (selector.labels !== undefined)
@@ -762,8 +812,108 @@ function compileDeployment(sourceValue) {
762
812
  integer(cardinality.desired, `deployment.spec.targets.${targetName}.cardinality.desired`, minimum, maximum);
763
813
  else
764
814
  reference(cardinality.desired, `deployment.spec.targets.${targetName}.cardinality.desired`);
815
+ const allocation = object(target.allocation, `deployment.spec.targets.${targetName}.allocation`);
816
+ exact3(allocation, ["execution", "sharing", "resources", "existing"], `deployment.spec.targets.${targetName}.allocation`);
817
+ if (!["native", "oci-runc", "oci-kata-qemu-snp"].includes(String(allocation.execution)))
818
+ fail(`deployment.spec.targets.${targetName}.allocation.execution`, "is unsupported.");
819
+ if (!["exclusive", "shared"].includes(String(allocation.sharing)))
820
+ fail(`deployment.spec.targets.${targetName}.allocation.sharing`, "is unsupported.");
821
+ if (!["prefer", "require"].includes(String(allocation.existing)))
822
+ fail(`deployment.spec.targets.${targetName}.allocation.existing`, "is unsupported.");
823
+ const capacity = object(allocation.resources, `deployment.spec.targets.${targetName}.allocation.resources`);
824
+ exact3(capacity, ["cpuCores", "memoryMiB", "storageGiB"], `deployment.spec.targets.${targetName}.allocation.resources`);
825
+ const capacityValue = (value2, where, minimumValue, maximumValue) => {
826
+ if (typeof value2 === "number")
827
+ integer(value2, where, minimumValue, maximumValue);
828
+ else
829
+ reference(value2, where);
830
+ };
831
+ capacityValue(capacity.cpuCores, `deployment.spec.targets.${targetName}.allocation.resources.cpuCores`, 1, 1024);
832
+ capacityValue(capacity.memoryMiB, `deployment.spec.targets.${targetName}.allocation.resources.memoryMiB`, 128, 4194304);
833
+ capacityValue(capacity.storageGiB, `deployment.spec.targets.${targetName}.allocation.resources.storageGiB`, 1, 1048576);
834
+ if (allocation.execution === "oci-kata-qemu-snp" && selector.confidentialCompute !== "required")
835
+ fail(`deployment.spec.targets.${targetName}.selector.confidentialCompute`, "must be required for Kata QEMU SNP.");
836
+ if ((allocation.execution === "oci-kata-qemu-snp" || selector.confidentialCompute === "required") && operatingSystem.version !== "26.04")
837
+ fail(`deployment.spec.targets.${targetName}.selector.operatingSystem.version`, "must be Ubuntu 26.04 for SEV-SNP.");
838
+ if (allocation.execution !== "oci-kata-qemu-snp" && selector.confidentialCompute === "required" && allocation.execution !== "native")
839
+ fail(`deployment.spec.targets.${targetName}.allocation.execution`, "cannot satisfy required confidential compute.");
840
+ if (target.provisioning !== undefined) {
841
+ const provisioning = object(target.provisioning, `deployment.spec.targets.${targetName}.provisioning`);
842
+ exact3(provisioning, ["planKey", "regionKey", "imageKey", "environmentKey", "ownership", "metalHostname", "maxMonthlySpendMinor"], `deployment.spec.targets.${targetName}.provisioning`);
843
+ for (const key of ["planKey", "regionKey", "imageKey", "environmentKey"])
844
+ named(provisioning[key], `deployment.spec.targets.${targetName}.provisioning.${key}`);
845
+ if (!["platform", "tenant-metal"].includes(String(provisioning.ownership)) || provisioning.ownership === "tenant-metal" !== Boolean(provisioning.metalHostname))
846
+ fail(`deployment.spec.targets.${targetName}.provisioning.ownership`, "must bind platform capacity or one tenant metal hostname.");
847
+ if (provisioning.metalHostname !== undefined && !/^(?=.{1,253}$)[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$/.test(String(provisioning.metalHostname)))
848
+ fail(`deployment.spec.targets.${targetName}.provisioning.metalHostname`, "is invalid.");
849
+ if (typeof provisioning.maxMonthlySpendMinor !== "string" || !/^\d{1,18}$/.test(provisioning.maxMonthlySpendMinor))
850
+ fail(`deployment.spec.targets.${targetName}.provisioning.maxMonthlySpendMinor`, "must be a bounded decimal minor-unit amount.");
851
+ }
852
+ if (allocation.existing === "prefer" && target.provisioning === undefined)
853
+ fail(`deployment.spec.targets.${targetName}.provisioning`, "is required when placement may provision after reuse.");
854
+ if (allocation.existing === "require" && target.provisioning !== undefined)
855
+ fail(`deployment.spec.targets.${targetName}.provisioning`, "must be omitted for reuse-only placement.");
856
+ if (target.connectivity !== undefined) {
857
+ const connectivity = object(target.connectivity, `deployment.spec.targets.${targetName}.connectivity`);
858
+ exact3(connectivity, ["private", "public"], `deployment.spec.targets.${targetName}.connectivity`);
859
+ const declaredPrivate = connectivity.private === undefined ? undefined : object(connectivity.private, `deployment.spec.targets.${targetName}.connectivity.private`);
860
+ if (declaredPrivate?.mode === "cloudflare-warp" && allocation.sharing !== "exclusive")
861
+ fail(`deployment.spec.targets.${targetName}.allocation.sharing`, "must be exclusive for a Cloudflare WARP connector.");
862
+ if (connectivity.private !== undefined) {
863
+ const privateNetwork = declaredPrivate;
864
+ if (privateNetwork.mode === "cloudflare-warp") {
865
+ exact3(privateNetwork, ["mode", "credential", "network"], `deployment.spec.targets.${targetName}.connectivity.private`);
866
+ if (!credentials[named(privateNetwork.credential, `deployment.spec.targets.${targetName}.connectivity.private.credential`)])
867
+ fail(`deployment.spec.targets.${targetName}.connectivity.private.credential`, "must name a declared credential.");
868
+ if (typeof privateNetwork.network === "string")
869
+ named(privateNetwork.network, `deployment.spec.targets.${targetName}.connectivity.private.network`);
870
+ else
871
+ reference(privateNetwork.network, `deployment.spec.targets.${targetName}.connectivity.private.network`);
872
+ } else {
873
+ exact3(privateNetwork, ["mode"], `deployment.spec.targets.${targetName}.connectivity.private`);
874
+ if (!["disabled", "private-lan"].includes(String(privateNetwork.mode)))
875
+ fail(`deployment.spec.targets.${targetName}.connectivity.private.mode`, "is unsupported.");
876
+ }
877
+ }
878
+ if (connectivity.public !== undefined) {
879
+ const publicNetwork = object(connectivity.public, `deployment.spec.targets.${targetName}.connectivity.public`);
880
+ if (publicNetwork.mode === "cloudflare-tunnel") {
881
+ exact3(publicNetwork, ["mode", "credential", "zone", "hostname"], `deployment.spec.targets.${targetName}.connectivity.public`);
882
+ if (!credentials[named(publicNetwork.credential, `deployment.spec.targets.${targetName}.connectivity.public.credential`)])
883
+ fail(`deployment.spec.targets.${targetName}.connectivity.public.credential`, "must name a declared credential.");
884
+ if (typeof publicNetwork.zone === "string") {
885
+ if (!/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(publicNetwork.zone))
886
+ fail(`deployment.spec.targets.${targetName}.connectivity.public.zone`, "is invalid.");
887
+ } else
888
+ reference(publicNetwork.zone, `deployment.spec.targets.${targetName}.connectivity.public.zone`);
889
+ const hostname = object(publicNetwork.hostname, `deployment.spec.targets.${targetName}.connectivity.public.hostname`);
890
+ const value2 = (coordinate, where, pattern) => {
891
+ if (typeof coordinate === "string") {
892
+ if (!pattern.test(coordinate))
893
+ fail(where, "is invalid.");
894
+ } else
895
+ reference(coordinate, where);
896
+ };
897
+ if (hostname.mode === "static") {
898
+ exact3(hostname, ["mode", "label"], `deployment.spec.targets.${targetName}.connectivity.public.hostname`);
899
+ value2(hostname.label, `deployment.spec.targets.${targetName}.connectivity.public.hostname.label`, /^[a-z0-9](?:[a-z0-9-]{0,48}[a-z0-9])?$/);
900
+ } else if (hostname.mode === "indexed") {
901
+ exact3(hostname, ["mode", "prefix", "startAt"], `deployment.spec.targets.${targetName}.connectivity.public.hostname`);
902
+ value2(hostname.prefix, `deployment.spec.targets.${targetName}.connectivity.public.hostname.prefix`, /^[a-z0-9](?:[a-z0-9-]{0,48}[a-z0-9])?$/);
903
+ if (hostname.startAt !== undefined)
904
+ integer(hostname.startAt, `deployment.spec.targets.${targetName}.connectivity.public.hostname.startAt`, 0, 1e6);
905
+ } else
906
+ fail(`deployment.spec.targets.${targetName}.connectivity.public.hostname.mode`, "is unsupported.");
907
+ } else {
908
+ exact3(publicNetwork, ["mode"], `deployment.spec.targets.${targetName}.connectivity.public`);
909
+ if (publicNetwork.mode !== "disabled")
910
+ fail(`deployment.spec.targets.${targetName}.connectivity.public.mode`, "is unsupported.");
911
+ }
912
+ }
913
+ }
765
914
  if (target.placement !== undefined)
766
915
  jsonValue(target.placement, `deployment.spec.targets.${targetName}.placement`);
916
+ targetDefinitions.set(targetName, target);
767
917
  }
768
918
  const requirements = object(spec.requirements ?? {}, "deployment.spec.requirements");
769
919
  if (Object.keys(requirements).length > 64)
@@ -777,13 +927,34 @@ function compileDeployment(sourceValue) {
777
927
  const key = `${requirementValue.provider}@${requirementValue.contract}:${requirementValue.capability}`;
778
928
  requiredProviders.set(key, { provider: requirementValue.provider, contract: requirementValue.contract, capability: requirementValue.capability });
779
929
  }
930
+ for (const [targetName, target] of targetDefinitions) {
931
+ if (target.connectivity?.public?.mode === "cloudflare-tunnel" && ![...requirementDefinitions.values()].some(({ capability }) => capability === "network.public-tunnel")) {
932
+ fail(`deployment.spec.targets.${targetName}.connectivity.public`, "requires one network.public-tunnel provider requirement.");
933
+ }
934
+ if (target.connectivity?.private?.mode === "cloudflare-warp" && ![...requirementDefinitions.values()].some(({ capability }) => capability === "network.private")) {
935
+ fail(`deployment.spec.targets.${targetName}.connectivity.private`, "requires one network.private provider requirement.");
936
+ }
937
+ }
780
938
  const components = object(spec.components, "deployment.spec.components");
781
939
  const componentEntries = Object.entries(components);
782
940
  if (componentEntries.length < 1 || componentEntries.length > 128)
783
941
  fail("deployment.spec.components", "must contain 1 to 128 components.");
784
942
  for (const [componentName, value] of componentEntries) {
785
943
  named(componentName, "deployment.spec.components key");
786
- validateComponent(value, `deployment.spec.components.${componentName}`, targetNames, requirementDefinitions);
944
+ validateComponent(value, `deployment.spec.components.${componentName}`, targetDefinitions, requirementDefinitions);
945
+ }
946
+ for (const [targetName, target] of targetDefinitions) {
947
+ if (target.connectivity?.public?.mode !== "cloudflare-tunnel")
948
+ continue;
949
+ const publicApplications = componentEntries.filter(([, component2]) => {
950
+ const candidate = component2;
951
+ return candidate.kind === "application" && candidate.target === targetName && candidate.network?.public?.mode === "cloudflare-tunnel";
952
+ });
953
+ if (publicApplications.length !== 1)
954
+ fail(`deployment.spec.targets.${targetName}.connectivity.public`, "requires exactly one application on the target with network.public cloudflare-tunnel.");
955
+ const application = publicApplications[0][1];
956
+ if (application.kind !== "application" || !application.network?.ingress?.stablePort)
957
+ fail(`deployment.spec.components.${publicApplications[0][0]}.network.ingress`, "requires one stablePort for Tunnel ingress.");
787
958
  }
788
959
  const actionContext = {
789
960
  components,
@@ -825,6 +996,14 @@ function compileDeployment(sourceValue) {
825
996
  });
826
997
  plannedWorkflows.push({ id: workflowName, ...workflowValue.concurrency ? { concurrency: workflowValue.concurrency } : {}, stages: plannedStages });
827
998
  }
999
+ const deployWorkflow = plannedWorkflows.find(({ id }) => id === "deploy");
1000
+ for (const [targetName, target] of targetDefinitions) {
1001
+ if (!target.connectivity || target.connectivity.public?.mode !== "cloudflare-tunnel" && target.connectivity.private?.mode !== "cloudflare-warp")
1002
+ continue;
1003
+ const ensures = deployWorkflow?.stages.flatMap(({ steps }) => steps).filter((step2) => step2.uses === "forgezero.network/ensure@1" && step2.with.target === targetName && (step2.scope.kind === "each-target" || step2.scope.kind === "target-batches") && step2.scope.target === targetName) ?? [];
1004
+ if (ensures.length !== 1)
1005
+ fail(`deployment.spec.workflows.deploy`, `must contain exactly one network.ensure step scoped to connected target ${targetName}.`);
1006
+ }
828
1007
  const typedSource = source;
829
1008
  return {
830
1009
  format: DEPLOY_PLAN_FORMAT,
@@ -11,6 +11,19 @@ export interface DockerProviderConfig {
11
11
  defaultNetwork: 'bridge';
12
12
  rootless: boolean;
13
13
  }
14
+ export interface ContainerdProviderConfig {
15
+ installScope: 'host';
16
+ runtimeClass: 'runc';
17
+ namespace: 'forgezero';
18
+ snapshotter: 'overlayfs';
19
+ }
20
+ export interface KataProviderConfig {
21
+ installScope: 'host';
22
+ runtimeClass: 'kata-qemu-snp';
23
+ namespace: 'forgezero';
24
+ hypervisor: 'qemu';
25
+ confidentialGuest: 'sev-snp';
26
+ }
14
27
  export interface NginxProviderConfig {
15
28
  installScope: 'host';
16
29
  websocket: boolean;
@@ -58,6 +71,12 @@ export declare const providers: {
58
71
  readonly docker: {
59
72
  readonly require: (options?: RequirementOptions<DockerProviderConfig>) => RequirementDefinition<import("./deploy").JsonObject>;
60
73
  };
74
+ readonly containerd: {
75
+ readonly require: (options?: RequirementOptions<ContainerdProviderConfig>) => RequirementDefinition<import("./deploy").JsonObject>;
76
+ };
77
+ readonly kata: {
78
+ readonly require: (options?: RequirementOptions<KataProviderConfig>) => RequirementDefinition<import("./deploy").JsonObject>;
79
+ };
61
80
  readonly nginx: {
62
81
  readonly require: (options?: RequirementOptions<NginxProviderConfig>) => RequirementDefinition<import("./deploy").JsonObject>;
63
82
  };
package/dist/deploy.d.ts CHANGED
@@ -46,7 +46,33 @@ export type InputDefinition = {
46
46
  default?: string;
47
47
  };
48
48
  export interface ValueReference {
49
- readonly $ref: `inputs.${string}` | `steps.${string}.status` | `steps.${string}.outputs.${string}` | `components.${string}.${string}` | `targets.${string}.${string}` | `deployment.${string}`;
49
+ readonly $ref: `inputs.${string}` | `steps.${string}.status` | `steps.${string}.outputs.${string}` | `components.${string}.${string}` | `targets.${string}.${string}` | `deployment.${string}` | `execution.${string}`;
50
+ }
51
+ export type IntegerValue = number | ValueReference;
52
+ export type StringValue = string | ValueReference;
53
+ export interface ComputeConnectivityDefinition {
54
+ private?: {
55
+ mode: 'disabled' | 'private-lan';
56
+ } | {
57
+ mode: 'cloudflare-warp';
58
+ credential: string;
59
+ network: StringValue;
60
+ };
61
+ public?: {
62
+ mode: 'disabled';
63
+ } | {
64
+ mode: 'cloudflare-tunnel';
65
+ credential: string;
66
+ zone: StringValue;
67
+ hostname: {
68
+ mode: 'static';
69
+ label: StringValue;
70
+ } | {
71
+ mode: 'indexed';
72
+ prefix: StringValue;
73
+ startAt?: number;
74
+ };
75
+ };
50
76
  }
51
77
  export type ResolvableJsonValue = JsonPrimitive | ValueReference | readonly ResolvableJsonValue[] | {
52
78
  [key: string]: ResolvableJsonValue;
@@ -98,6 +124,11 @@ export interface TargetDefinition {
98
124
  kind: 'compute';
99
125
  selector: {
100
126
  profiles: readonly string[];
127
+ operatingSystem: {
128
+ id: 'ubuntu';
129
+ version: '24.04' | '26.04';
130
+ architecture: 'x64';
131
+ };
101
132
  confidentialCompute?: 'required' | 'preferred' | 'disabled';
102
133
  labels?: Readonly<Record<string, string>>;
103
134
  };
@@ -106,11 +137,62 @@ export interface TargetDefinition {
106
137
  desired: number | ValueReference;
107
138
  maximum: number;
108
139
  };
140
+ /**
141
+ * Capacity reserved for every stable target slot. This is placement intent,
142
+ * not an application cgroup limit. The API first binds an eligible idle
143
+ * compute with at least this unreserved capacity; provisioning is allowed
144
+ * only when the separately approved control-plane policy permits it.
145
+ */
146
+ allocation: {
147
+ execution: 'native' | 'oci-runc' | 'oci-kata-qemu-snp';
148
+ sharing: 'exclusive' | 'shared';
149
+ resources: {
150
+ cpuCores: IntegerValue;
151
+ memoryMiB: IntegerValue;
152
+ storageGiB: IntegerValue;
153
+ };
154
+ existing: 'prefer' | 'require';
155
+ };
156
+ /** Required only when the control plane may create capacity after reuse is exhausted. */
157
+ provisioning?: {
158
+ planKey: string;
159
+ regionKey: string;
160
+ imageKey: string;
161
+ environmentKey: string;
162
+ ownership: 'platform' | 'tenant-metal';
163
+ metalHostname?: string;
164
+ maxMonthlySpendMinor: string;
165
+ };
166
+ /** Per-slot connectivity intent. Credential names resolve through the scoped Vault, never to plan values. */
167
+ connectivity?: ComputeConnectivityDefinition;
109
168
  placement?: {
110
169
  spreadBy?: readonly ('metal' | 'region' | 'zone')[];
111
170
  antiAffinity?: readonly string[];
112
171
  };
113
172
  }
173
+ /** Compact TypeScript input normalized into the explicit wire shape above. */
174
+ export interface ComputePoolDefinition {
175
+ profiles: readonly string[];
176
+ replicas: number | {
177
+ minimum: number;
178
+ desired: number | ValueReference;
179
+ maximum: number;
180
+ };
181
+ resources: {
182
+ cpuCores: IntegerValue;
183
+ memoryMiB: IntegerValue;
184
+ storageGiB: IntegerValue;
185
+ };
186
+ os?: 'ubuntu-24.04' | 'ubuntu-26.04';
187
+ runtime?: 'native' | 'oci-runc' | 'oci-kata-qemu-snp';
188
+ isolation?: 'standard' | 'sev-snp';
189
+ sharing?: 'exclusive' | 'shared';
190
+ reuse?: 'prefer' | 'require';
191
+ provisioning?: TargetDefinition['provisioning'];
192
+ connectivity?: ComputeConnectivityDefinition;
193
+ labels?: Readonly<Record<string, string>>;
194
+ placement?: TargetDefinition['placement'];
195
+ }
114
196
  export interface RequirementDefinition<TConfig extends JsonObject = JsonObject> {
115
197
  capability: string;
116
198
  provider: string;
@@ -195,6 +277,8 @@ export type ApplicationRuntime = {
195
277
  kind: 'container';
196
278
  provider: string;
197
279
  requirement: string;
280
+ /** runc shares the compute kernel; Kata creates one measured QEMU SNP workload VM. */
281
+ runtimeClass?: 'runc' | 'kata-qemu-snp';
198
282
  image: {
199
283
  source: {
200
284
  kind: 'build';
@@ -395,6 +479,8 @@ export declare const ref: {
395
479
  component: (component: string, property: string) => ValueReference;
396
480
  target: (target: string, property: string) => ValueReference;
397
481
  deployment: (property: string) => ValueReference;
482
+ /** Secret-free control-plane assignment of the compute executing this step. */
483
+ execution: (property: "targetName" | "slot" | "releaseExecutor" | "runtime" | "cpuCores" | "memoryMiB" | "storageGiB") => ValueReference;
398
484
  };
399
485
  export declare const conditions: {
400
486
  all: (...values: Condition[]) => Condition;
@@ -425,7 +511,7 @@ export declare const credential: {
425
511
  }) => CredentialDefinition;
426
512
  };
427
513
  export declare const target: {
428
- compute: (value: Omit<TargetDefinition, "kind">) => TargetDefinition;
514
+ compute: (value: ComputePoolDefinition | Omit<TargetDefinition, "kind">) => TargetDefinition;
429
515
  };
430
516
  export declare function requirement<TConfig extends JsonObject>(value: RequirementDefinition<TConfig>): RequirementDefinition<TConfig>;
431
517
  export declare function application(value: Omit<ApplicationComponent, 'kind'>): ApplicationComponent;
@@ -436,4 +522,4 @@ export declare function workflow(value: WorkflowDefinition): WorkflowDefinition;
436
522
  export declare function defineDeployment<const T extends DeploymentSource>(definition: T): T;
437
523
  export { actions } from './deploy-actions';
438
524
  export { providers } from './deploy-providers';
439
- export type { ArangoDbProviderConfig, BunProviderConfig, CloudflaredProviderConfig, DockerProviderConfig, NginxProviderConfig, OpenSshProviderConfig, SystemdProviderConfig, UfwProviderConfig, WarpProviderConfig } from './deploy-providers';
525
+ export type { ArangoDbProviderConfig, BunProviderConfig, CloudflaredProviderConfig, ContainerdProviderConfig, DockerProviderConfig, KataProviderConfig, NginxProviderConfig, OpenSshProviderConfig, SystemdProviderConfig, UfwProviderConfig, WarpProviderConfig } from './deploy-providers';
package/dist/deploy.js CHANGED
@@ -6,6 +6,9 @@ var actions = {
6
6
  software: {
7
7
  ensure: (withValue, options) => action("forgezero.software/ensure@1", withValue, options)
8
8
  },
9
+ network: {
10
+ ensure: (withValue, options) => action("forgezero.network/ensure@1", withValue, options)
11
+ },
9
12
  exec: {
10
13
  argv: (withValue, options) => action("forgezero.exec/argv@1", withValue, options)
11
14
  },
@@ -49,6 +52,23 @@ var providers = {
49
52
  rootless: false
50
53
  })
51
54
  },
55
+ containerd: {
56
+ require: (options = { version: "ubuntu-26.04" }) => define("runtime.container", "forgezero.containerd", options, {
57
+ installScope: "host",
58
+ runtimeClass: "runc",
59
+ namespace: "forgezero",
60
+ snapshotter: "overlayfs"
61
+ })
62
+ },
63
+ kata: {
64
+ require: (options = { version: "4.0.0" }) => define("runtime.container", "forgezero.kata", options, {
65
+ installScope: "host",
66
+ runtimeClass: "kata-qemu-snp",
67
+ namespace: "forgezero",
68
+ hypervisor: "qemu",
69
+ confidentialGuest: "sev-snp"
70
+ })
71
+ },
52
72
  nginx: {
53
73
  require: (options = { version: "ubuntu-26.04" }) => define("proxy.http", "forgezero.nginx", options, {
54
74
  installScope: "host",
@@ -98,7 +118,8 @@ var ref = {
98
118
  stepOutput: (step2, output) => ({ $ref: `steps.${step2}.outputs.${output}` }),
99
119
  component: (component, property) => ({ $ref: `components.${component}.${property}` }),
100
120
  target: (target, property) => ({ $ref: `targets.${target}.${property}` }),
101
- deployment: (property) => ({ $ref: `deployment.${property}` })
121
+ deployment: (property) => ({ $ref: `deployment.${property}` }),
122
+ execution: (property) => ({ $ref: `execution.${property}` })
102
123
  };
103
124
  var conditions = {
104
125
  all: (...values) => ({ all: values }),
@@ -128,7 +149,33 @@ var credential = {
128
149
  })
129
150
  };
130
151
  var target = {
131
- compute: (value) => ({ kind: "compute", ...value })
152
+ compute: (value) => {
153
+ if ("selector" in value)
154
+ return { kind: "compute", ...value };
155
+ const replicas = typeof value.replicas === "number" ? { minimum: value.replicas, desired: value.replicas, maximum: value.replicas } : value.replicas;
156
+ const execution = value.runtime ?? "native";
157
+ const isolation = value.isolation ?? (execution === "oci-kata-qemu-snp" ? "sev-snp" : "standard");
158
+ const os = value.os ?? (isolation === "sev-snp" ? "ubuntu-26.04" : "ubuntu-24.04");
159
+ return {
160
+ kind: "compute",
161
+ selector: {
162
+ profiles: value.profiles,
163
+ operatingSystem: { id: "ubuntu", version: os === "ubuntu-26.04" ? "26.04" : "24.04", architecture: "x64" },
164
+ confidentialCompute: isolation === "sev-snp" ? "required" : "disabled",
165
+ ...value.labels ? { labels: value.labels } : {}
166
+ },
167
+ cardinality: replicas,
168
+ allocation: {
169
+ execution,
170
+ sharing: value.sharing ?? "exclusive",
171
+ resources: value.resources,
172
+ existing: value.reuse ?? "prefer"
173
+ },
174
+ ...value.provisioning ? { provisioning: value.provisioning } : {},
175
+ ...value.connectivity ? { connectivity: value.connectivity } : {},
176
+ ...value.placement ? { placement: value.placement } : {}
177
+ };
178
+ }
132
179
  };
133
180
  function requirement(value) {
134
181
  return value;
@@ -0,0 +1,63 @@
1
+ export interface DeploymentConnectivityIntent {
2
+ private?: {
3
+ mode: 'disabled' | 'private-lan';
4
+ } | {
5
+ mode: 'cloudflare-warp';
6
+ credential: string;
7
+ network: string;
8
+ };
9
+ public?: {
10
+ mode: 'disabled';
11
+ } | {
12
+ mode: 'cloudflare-tunnel';
13
+ credential: string;
14
+ hostname: string;
15
+ tunnelKey: string;
16
+ service: {
17
+ protocol: 'http';
18
+ port: number;
19
+ };
20
+ };
21
+ }
22
+ /** Connector-scoped capabilities delivered only inside the hybrid-sealed node claim. */
23
+ export interface DeploymentConnectivityCapabilities {
24
+ public?: {
25
+ tunnelId: string;
26
+ connectorToken: string;
27
+ };
28
+ private?: {
29
+ connectorId: string;
30
+ connectorToken: string;
31
+ };
32
+ }
33
+ export interface DeploymentConnectivityRequest {
34
+ key: string;
35
+ intent: DeploymentConnectivityIntent;
36
+ capabilities: DeploymentConnectivityCapabilities;
37
+ }
38
+ export interface DeploymentConnectivityEvidence {
39
+ key: string;
40
+ public?: {
41
+ hostname: string;
42
+ tunnelId: string;
43
+ unit: string;
44
+ active: true;
45
+ };
46
+ private?: {
47
+ network: string;
48
+ connectorId: string;
49
+ unit: string;
50
+ active: true;
51
+ };
52
+ }
53
+ export interface DeploymentConnectivityHost {
54
+ seal(name: string, path: string, value: string): Promise<void>;
55
+ write(path: string, content: string, mode: number): void;
56
+ exec(argv: readonly string[]): Promise<{
57
+ exitCode: number;
58
+ output: string;
59
+ }>;
60
+ sleep(ms: number): Promise<void>;
61
+ }
62
+ /** Apply only connector-scoped capabilities; the broad Cloudflare token never reaches this helper. */
63
+ export declare function applyDeploymentConnectivity(request: DeploymentConnectivityRequest, host?: DeploymentConnectivityHost): Promise<DeploymentConnectivityEvidence>;