@forgezero/agent 0.1.79 → 0.1.81

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/dist/provision.js CHANGED
@@ -1649,6 +1649,7 @@ function phasePipeline(definition, phase, profile, executeRelease = false) {
1649
1649
  // src/deployment-connectivity.ts
1650
1650
  import { createHash as createHash3 } from "node:crypto";
1651
1651
  import { mkdirSync as mkdirSync4, renameSync as renameSync4, writeFileSync as writeFileSync4 } from "node:fs";
1652
+ import { createConnection } from "node:net";
1652
1653
  import { dirname as dirname4 } from "node:path";
1653
1654
 
1654
1655
  // src/process-input.ts
@@ -1701,6 +1702,21 @@ var defaultHost = {
1701
1702
  const [stdout, stderr, exitCode] = await Promise.all([new Response(child.stdout).text(), new Response(child.stderr).text(), child.exited]);
1702
1703
  return { exitCode, output: `${stdout}${stderr}` };
1703
1704
  },
1705
+ probe: (address, port, timeoutMs) => new Promise((resolve3) => {
1706
+ const socket = createConnection({ host: address, port });
1707
+ let settled = false;
1708
+ const finish = (value) => {
1709
+ if (settled)
1710
+ return;
1711
+ settled = true;
1712
+ socket.destroy();
1713
+ resolve3(value);
1714
+ };
1715
+ socket.setTimeout(timeoutMs);
1716
+ socket.once("connect", () => finish(true));
1717
+ socket.once("timeout", () => finish(false));
1718
+ socket.once("error", () => finish(false));
1719
+ }),
1704
1720
  sleep: (ms) => Bun.sleep(ms)
1705
1721
  };
1706
1722
  function validate(request) {
@@ -1715,7 +1731,7 @@ function validate(request) {
1715
1731
  } else if (request.capabilities.public)
1716
1732
  throw new Error("unexpected public deployment capability");
1717
1733
  if (privateIntent?.mode === "cloudflare-warp") {
1718
- if (!NAME2.test(privateIntent.credential) || privateIntent.network.length < 1 || privateIntent.network.length > 128 || !request.capabilities.private || !UUID.test(request.capabilities.private.connectorId) || !TOKEN.test(request.capabilities.private.connectorToken)) {
1734
+ if (!NAME2.test(privateIntent.credential) || privateIntent.network.length < 1 || privateIntent.network.length > 128 || !request.capabilities.private || !UUID.test(request.capabilities.private.connectorId) || !UUID.test(request.capabilities.private.siteRouteId) || !TOKEN.test(request.capabilities.private.connectorToken)) {
1719
1735
  throw new Error("private deployment connectivity is malformed");
1720
1736
  }
1721
1737
  } else if (request.capabilities.private)
@@ -1725,6 +1741,22 @@ async function applyDeploymentConnectivity(request, host = defaultHost) {
1725
1741
  validate(request);
1726
1742
  const id = idFor(request.key);
1727
1743
  const evidence = { key: request.key };
1744
+ const topology = request.topology;
1745
+ if (topology) {
1746
+ const values = [topology.localRelayAddress, ...topology.localPeerAddresses, ...topology.remoteRelayAddresses];
1747
+ const identities = [
1748
+ topology.nodeIdentity,
1749
+ ...topology.localPeerIdentities,
1750
+ ...topology.remoteRelayIdentities,
1751
+ ...topology.memberIdentities
1752
+ ];
1753
+ if (!/^[A-Za-z0-9.-]{1,253}$/.test(topology.site) || !/^(?:\d{1,3}\.){3}0\/24$/.test(topology.siteCidr) || values.some((value) => !/^(?:\d{1,3}\.){3}\d{1,3}$/.test(value)) || identities.some((value) => !/^[A-Za-z0-9.-]{1,253}$/.test(value)) || topology.localPeerIdentities.length !== topology.localPeerAddresses.length || topology.remoteRelayIdentities.length !== topology.remoteRelayAddresses.length || topology.memberIdentities.length < 1 || topology.memberIdentities.length > 1024 || new Set(topology.memberIdentities).size !== topology.memberIdentities.length || !topology.memberIdentities.includes(topology.nodeIdentity) || !/^sha256:[a-f0-9]{64}$/.test(topology.generation) || (topology.role === "member" ? topology.remoteRelayAddresses.length !== 0 : topology.remoteSiteCidrs.length !== topology.remoteRelayAddresses.length) || topology.remoteSiteCidrs.some((value) => !/^(?:\d{1,3}\.){3}0\/24$/.test(value)) || new Set(topology.remoteSiteCidrs).size !== topology.remoteSiteCidrs.length || !Number.isSafeInteger(topology.healthPort) || topology.healthPort < 1 || topology.healthPort > 65535 || topology.routedTcpPorts.length < 1 || topology.routedTcpPorts.length > 64 || new Set(topology.routedTcpPorts).size !== topology.routedTcpPorts.length || !topology.routedTcpPorts.includes(topology.healthPort) || topology.routedTcpPorts.some((port) => !Number.isSafeInteger(port) || port < 1 || port > 65535) || topology.role === "member" && topology.transport !== "private-lan") {
1754
+ throw new Error("deployment topology is malformed");
1755
+ }
1756
+ if (request.intent.private?.mode === "cloudflare-warp" !== (topology.transport === "cloudflare-warp")) {
1757
+ throw new Error("deployment topology transport differs from private connectivity intent");
1758
+ }
1759
+ }
1728
1760
  if (request.intent.public?.mode === "cloudflare-tunnel") {
1729
1761
  const capability = request.capabilities.public;
1730
1762
  const credentialName = `FZ_TUNNEL_${id.toUpperCase()}`;
@@ -1762,6 +1794,12 @@ WantedBy=multi-user.target
1762
1794
  const capability = request.capabilities.private;
1763
1795
  const credentialPath = "/etc/forgezero/creds/CF_WARP_CONNECTOR_TOKEN.cred";
1764
1796
  const unit = "forgezero-deployment-mesh.service";
1797
+ const forwarding = "/etc/sysctl.d/99-forgezero-mesh.conf";
1798
+ host.write(forwarding, `net.ipv4.ip_forward = 1
1799
+ net.ipv6.conf.all.forwarding = 1
1800
+ net.ipv6.conf.all.accept_ra = 2
1801
+ `, 420);
1802
+ await checked2(host, ["/usr/sbin/sysctl", "-p", forwarding], "Mesh subnet forwarding");
1765
1803
  await host.seal("CF_WARP_CONNECTOR_TOKEN", credentialPath, capability.connectorToken);
1766
1804
  host.write(`/etc/systemd/system/${unit}`, `[Unit]
1767
1805
  Description=ForgeZero deployment Mesh/WARP connector
@@ -1796,6 +1834,95 @@ WantedBy=multi-user.target
1796
1834
  if (!evidence.private)
1797
1835
  throw new Error("WARP connector readiness failed");
1798
1836
  }
1837
+ if (topology) {
1838
+ const syncAddresses = topology.role === "relay" ? [...topology.localPeerAddresses, ...topology.remoteRelayAddresses] : [topology.localRelayAddress];
1839
+ const topologyEpoch = `topology-${topology.generation.slice("sha256:".length, "sha256:".length + 48)}`;
1840
+ host.write("/etc/forgezero/deployment-topology.env", [
1841
+ `FZ_TOPOLOGY_NODE_IDENTITY=${topology.nodeIdentity}`,
1842
+ `FZ_TOPOLOGY_PEER_ADDRESSES=${[...new Set(syncAddresses)].join(",")}`,
1843
+ `FZ_TOPOLOGY_MEMBERS=${topology.memberIdentities.join(",")}`,
1844
+ `FZ_TOPOLOGY_EPOCH=${topologyEpoch}`,
1845
+ `FZ_TOPOLOGY_SITE=${topology.site}`,
1846
+ `FZ_TOPOLOGY_ROLE=${topology.role}`,
1847
+ `FZ_TOPOLOGY_TRANSPORT=${topology.transport}`,
1848
+ ""
1849
+ ].join(`
1850
+ `), 384);
1851
+ const routeUnit = "forgezero-deployment-topology.service";
1852
+ const routeUnitPath = `/etc/systemd/system/${routeUnit}`;
1853
+ const routePairs = topology.role === "member" ? topology.remoteSiteCidrs.map((network) => ({ network, via: topology.localRelayAddress })) : topology.transport === "private-lan" ? topology.remoteSiteCidrs.map((network, index) => ({ network, via: topology.remoteRelayAddresses[index] })) : [];
1854
+ for (const { via } of routePairs) {
1855
+ await checked2(host, ["/usr/sbin/ip", "route", "get", via], `private route gateway ${via}`);
1856
+ }
1857
+ await host.exec(["/usr/bin/systemctl", "disable", "--now", routeUnit]);
1858
+ const forwarding = topology.role !== "member" && topology.remoteSiteCidrs.length > 0 ? `ExecStart=/usr/sbin/sysctl -w net.ipv4.ip_forward=1
1859
+ ` : "";
1860
+ const starts = routePairs.map(({ network, via }) => `ExecStart=/usr/sbin/ip route replace ${network} via ${via}`).join(`
1861
+ `);
1862
+ const stops = routePairs.map(({ network, via }) => `ExecStop=-/usr/sbin/ip route del ${network} via ${via}`).join(`
1863
+ `);
1864
+ const noOp = !forwarding && !starts ? `ExecStart=/usr/bin/true
1865
+ ` : "";
1866
+ host.write(routeUnitPath, `[Unit]
1867
+ Description=ForgeZero fenced deployment site routing
1868
+ After=network-online.target${topology.transport === "cloudflare-warp" ? " forgezero-deployment-mesh.service" : ""}
1869
+ Wants=network-online.target
1870
+
1871
+ [Service]
1872
+ Type=oneshot
1873
+ RemainAfterExit=yes
1874
+ ${forwarding}${noOp}${starts}${starts ? `
1875
+ ` : ""}${stops}${stops ? `
1876
+ ` : ""}
1877
+ [Install]
1878
+ WantedBy=multi-user.target
1879
+ `, 420);
1880
+ for (const source of [...new Set([topology.siteCidr, ...topology.remoteSiteCidrs])]) {
1881
+ for (const port of topology.routedTcpPorts) {
1882
+ await checked2(host, ["/usr/sbin/ufw", "allow", "from", source, "to", "any", "port", String(port), "proto", "tcp"], `private service ${source}:${port}`);
1883
+ }
1884
+ }
1885
+ if (topology.role !== "member")
1886
+ for (const remoteCidr of topology.remoteSiteCidrs) {
1887
+ for (const port of topology.routedTcpPorts) {
1888
+ await checked2(host, [
1889
+ "/usr/sbin/ufw",
1890
+ "route",
1891
+ "allow",
1892
+ "proto",
1893
+ "tcp",
1894
+ "from",
1895
+ topology.siteCidr,
1896
+ "to",
1897
+ remoteCidr,
1898
+ "port",
1899
+ String(port)
1900
+ ], `private routed service ${topology.siteCidr}->${remoteCidr}:${port}`);
1901
+ }
1902
+ }
1903
+ await checked2(host, ["/usr/bin/systemctl", "daemon-reload"], "topology daemon reload");
1904
+ await checked2(host, ["/usr/bin/systemctl", "enable", "--now", routeUnit], "topology routes");
1905
+ const probes = topology.role === "member" ? [topology.localRelayAddress] : topology.remoteRelayAddresses;
1906
+ for (const address of [...new Set(probes)]) {
1907
+ await checked2(host, ["/usr/sbin/ip", "route", "get", address], `private route ${address}`);
1908
+ let ready = false;
1909
+ for (let attempt = 0;attempt < 10; attempt += 1) {
1910
+ if (await host.probe(address, topology.healthPort, 2000)) {
1911
+ ready = true;
1912
+ break;
1913
+ }
1914
+ await host.sleep(1000);
1915
+ }
1916
+ if (!ready)
1917
+ throw new Error(`private topology relay ${address}:${topology.healthPort} is unreachable`);
1918
+ }
1919
+ evidence.topology = {
1920
+ site: topology.site,
1921
+ siteCidr: topology.siteCidr,
1922
+ role: topology.role,
1923
+ probed: [...new Set(probes)]
1924
+ };
1925
+ }
1799
1926
  return evidence;
1800
1927
  }
1801
1928
 
@@ -2520,7 +2647,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
2520
2647
  }
2521
2648
 
2522
2649
  // src/version.ts
2523
- var VERSION3 = "0.1.79";
2650
+ var VERSION3 = "0.1.81";
2524
2651
 
2525
2652
  // src/egress-policy.ts
2526
2653
  import { realpathSync as realpathSync3 } from "node:fs";
@@ -971,6 +971,7 @@ function phasePipeline(definition, phase, profile, executeRelease = false) {
971
971
  // src/deployment-connectivity.ts
972
972
  import { createHash as createHash2 } from "node:crypto";
973
973
  import { mkdirSync as mkdirSync2, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
974
+ import { createConnection } from "node:net";
974
975
  import { dirname as dirname2 } from "node:path";
975
976
 
976
977
  // src/process-input.ts
@@ -1023,6 +1024,21 @@ var defaultHost = {
1023
1024
  const [stdout, stderr, exitCode] = await Promise.all([new Response(child.stdout).text(), new Response(child.stderr).text(), child.exited]);
1024
1025
  return { exitCode, output: `${stdout}${stderr}` };
1025
1026
  },
1027
+ probe: (address, port, timeoutMs) => new Promise((resolve) => {
1028
+ const socket = createConnection({ host: address, port });
1029
+ let settled = false;
1030
+ const finish = (value) => {
1031
+ if (settled)
1032
+ return;
1033
+ settled = true;
1034
+ socket.destroy();
1035
+ resolve(value);
1036
+ };
1037
+ socket.setTimeout(timeoutMs);
1038
+ socket.once("connect", () => finish(true));
1039
+ socket.once("timeout", () => finish(false));
1040
+ socket.once("error", () => finish(false));
1041
+ }),
1026
1042
  sleep: (ms) => Bun.sleep(ms)
1027
1043
  };
1028
1044
  function validate(request) {
@@ -1037,7 +1053,7 @@ function validate(request) {
1037
1053
  } else if (request.capabilities.public)
1038
1054
  throw new Error("unexpected public deployment capability");
1039
1055
  if (privateIntent?.mode === "cloudflare-warp") {
1040
- if (!NAME2.test(privateIntent.credential) || privateIntent.network.length < 1 || privateIntent.network.length > 128 || !request.capabilities.private || !UUID.test(request.capabilities.private.connectorId) || !TOKEN.test(request.capabilities.private.connectorToken)) {
1056
+ if (!NAME2.test(privateIntent.credential) || privateIntent.network.length < 1 || privateIntent.network.length > 128 || !request.capabilities.private || !UUID.test(request.capabilities.private.connectorId) || !UUID.test(request.capabilities.private.siteRouteId) || !TOKEN.test(request.capabilities.private.connectorToken)) {
1041
1057
  throw new Error("private deployment connectivity is malformed");
1042
1058
  }
1043
1059
  } else if (request.capabilities.private)
@@ -1047,6 +1063,22 @@ async function applyDeploymentConnectivity(request, host = defaultHost) {
1047
1063
  validate(request);
1048
1064
  const id = idFor(request.key);
1049
1065
  const evidence = { key: request.key };
1066
+ const topology = request.topology;
1067
+ if (topology) {
1068
+ const values = [topology.localRelayAddress, ...topology.localPeerAddresses, ...topology.remoteRelayAddresses];
1069
+ const identities = [
1070
+ topology.nodeIdentity,
1071
+ ...topology.localPeerIdentities,
1072
+ ...topology.remoteRelayIdentities,
1073
+ ...topology.memberIdentities
1074
+ ];
1075
+ if (!/^[A-Za-z0-9.-]{1,253}$/.test(topology.site) || !/^(?:\d{1,3}\.){3}0\/24$/.test(topology.siteCidr) || values.some((value) => !/^(?:\d{1,3}\.){3}\d{1,3}$/.test(value)) || identities.some((value) => !/^[A-Za-z0-9.-]{1,253}$/.test(value)) || topology.localPeerIdentities.length !== topology.localPeerAddresses.length || topology.remoteRelayIdentities.length !== topology.remoteRelayAddresses.length || topology.memberIdentities.length < 1 || topology.memberIdentities.length > 1024 || new Set(topology.memberIdentities).size !== topology.memberIdentities.length || !topology.memberIdentities.includes(topology.nodeIdentity) || !/^sha256:[a-f0-9]{64}$/.test(topology.generation) || (topology.role === "member" ? topology.remoteRelayAddresses.length !== 0 : topology.remoteSiteCidrs.length !== topology.remoteRelayAddresses.length) || topology.remoteSiteCidrs.some((value) => !/^(?:\d{1,3}\.){3}0\/24$/.test(value)) || new Set(topology.remoteSiteCidrs).size !== topology.remoteSiteCidrs.length || !Number.isSafeInteger(topology.healthPort) || topology.healthPort < 1 || topology.healthPort > 65535 || topology.routedTcpPorts.length < 1 || topology.routedTcpPorts.length > 64 || new Set(topology.routedTcpPorts).size !== topology.routedTcpPorts.length || !topology.routedTcpPorts.includes(topology.healthPort) || topology.routedTcpPorts.some((port) => !Number.isSafeInteger(port) || port < 1 || port > 65535) || topology.role === "member" && topology.transport !== "private-lan") {
1076
+ throw new Error("deployment topology is malformed");
1077
+ }
1078
+ if (request.intent.private?.mode === "cloudflare-warp" !== (topology.transport === "cloudflare-warp")) {
1079
+ throw new Error("deployment topology transport differs from private connectivity intent");
1080
+ }
1081
+ }
1050
1082
  if (request.intent.public?.mode === "cloudflare-tunnel") {
1051
1083
  const capability = request.capabilities.public;
1052
1084
  const credentialName = `FZ_TUNNEL_${id.toUpperCase()}`;
@@ -1084,6 +1116,12 @@ WantedBy=multi-user.target
1084
1116
  const capability = request.capabilities.private;
1085
1117
  const credentialPath = "/etc/forgezero/creds/CF_WARP_CONNECTOR_TOKEN.cred";
1086
1118
  const unit = "forgezero-deployment-mesh.service";
1119
+ const forwarding = "/etc/sysctl.d/99-forgezero-mesh.conf";
1120
+ host.write(forwarding, `net.ipv4.ip_forward = 1
1121
+ net.ipv6.conf.all.forwarding = 1
1122
+ net.ipv6.conf.all.accept_ra = 2
1123
+ `, 420);
1124
+ await checked(host, ["/usr/sbin/sysctl", "-p", forwarding], "Mesh subnet forwarding");
1087
1125
  await host.seal("CF_WARP_CONNECTOR_TOKEN", credentialPath, capability.connectorToken);
1088
1126
  host.write(`/etc/systemd/system/${unit}`, `[Unit]
1089
1127
  Description=ForgeZero deployment Mesh/WARP connector
@@ -1118,6 +1156,95 @@ WantedBy=multi-user.target
1118
1156
  if (!evidence.private)
1119
1157
  throw new Error("WARP connector readiness failed");
1120
1158
  }
1159
+ if (topology) {
1160
+ const syncAddresses = topology.role === "relay" ? [...topology.localPeerAddresses, ...topology.remoteRelayAddresses] : [topology.localRelayAddress];
1161
+ const topologyEpoch = `topology-${topology.generation.slice("sha256:".length, "sha256:".length + 48)}`;
1162
+ host.write("/etc/forgezero/deployment-topology.env", [
1163
+ `FZ_TOPOLOGY_NODE_IDENTITY=${topology.nodeIdentity}`,
1164
+ `FZ_TOPOLOGY_PEER_ADDRESSES=${[...new Set(syncAddresses)].join(",")}`,
1165
+ `FZ_TOPOLOGY_MEMBERS=${topology.memberIdentities.join(",")}`,
1166
+ `FZ_TOPOLOGY_EPOCH=${topologyEpoch}`,
1167
+ `FZ_TOPOLOGY_SITE=${topology.site}`,
1168
+ `FZ_TOPOLOGY_ROLE=${topology.role}`,
1169
+ `FZ_TOPOLOGY_TRANSPORT=${topology.transport}`,
1170
+ ""
1171
+ ].join(`
1172
+ `), 384);
1173
+ const routeUnit = "forgezero-deployment-topology.service";
1174
+ const routeUnitPath = `/etc/systemd/system/${routeUnit}`;
1175
+ const routePairs = topology.role === "member" ? topology.remoteSiteCidrs.map((network) => ({ network, via: topology.localRelayAddress })) : topology.transport === "private-lan" ? topology.remoteSiteCidrs.map((network, index) => ({ network, via: topology.remoteRelayAddresses[index] })) : [];
1176
+ for (const { via } of routePairs) {
1177
+ await checked(host, ["/usr/sbin/ip", "route", "get", via], `private route gateway ${via}`);
1178
+ }
1179
+ await host.exec(["/usr/bin/systemctl", "disable", "--now", routeUnit]);
1180
+ const forwarding = topology.role !== "member" && topology.remoteSiteCidrs.length > 0 ? `ExecStart=/usr/sbin/sysctl -w net.ipv4.ip_forward=1
1181
+ ` : "";
1182
+ const starts = routePairs.map(({ network, via }) => `ExecStart=/usr/sbin/ip route replace ${network} via ${via}`).join(`
1183
+ `);
1184
+ const stops = routePairs.map(({ network, via }) => `ExecStop=-/usr/sbin/ip route del ${network} via ${via}`).join(`
1185
+ `);
1186
+ const noOp = !forwarding && !starts ? `ExecStart=/usr/bin/true
1187
+ ` : "";
1188
+ host.write(routeUnitPath, `[Unit]
1189
+ Description=ForgeZero fenced deployment site routing
1190
+ After=network-online.target${topology.transport === "cloudflare-warp" ? " forgezero-deployment-mesh.service" : ""}
1191
+ Wants=network-online.target
1192
+
1193
+ [Service]
1194
+ Type=oneshot
1195
+ RemainAfterExit=yes
1196
+ ${forwarding}${noOp}${starts}${starts ? `
1197
+ ` : ""}${stops}${stops ? `
1198
+ ` : ""}
1199
+ [Install]
1200
+ WantedBy=multi-user.target
1201
+ `, 420);
1202
+ for (const source of [...new Set([topology.siteCidr, ...topology.remoteSiteCidrs])]) {
1203
+ for (const port of topology.routedTcpPorts) {
1204
+ await checked(host, ["/usr/sbin/ufw", "allow", "from", source, "to", "any", "port", String(port), "proto", "tcp"], `private service ${source}:${port}`);
1205
+ }
1206
+ }
1207
+ if (topology.role !== "member")
1208
+ for (const remoteCidr of topology.remoteSiteCidrs) {
1209
+ for (const port of topology.routedTcpPorts) {
1210
+ await checked(host, [
1211
+ "/usr/sbin/ufw",
1212
+ "route",
1213
+ "allow",
1214
+ "proto",
1215
+ "tcp",
1216
+ "from",
1217
+ topology.siteCidr,
1218
+ "to",
1219
+ remoteCidr,
1220
+ "port",
1221
+ String(port)
1222
+ ], `private routed service ${topology.siteCidr}->${remoteCidr}:${port}`);
1223
+ }
1224
+ }
1225
+ await checked(host, ["/usr/bin/systemctl", "daemon-reload"], "topology daemon reload");
1226
+ await checked(host, ["/usr/bin/systemctl", "enable", "--now", routeUnit], "topology routes");
1227
+ const probes = topology.role === "member" ? [topology.localRelayAddress] : topology.remoteRelayAddresses;
1228
+ for (const address of [...new Set(probes)]) {
1229
+ await checked(host, ["/usr/sbin/ip", "route", "get", address], `private route ${address}`);
1230
+ let ready = false;
1231
+ for (let attempt = 0;attempt < 10; attempt += 1) {
1232
+ if (await host.probe(address, topology.healthPort, 2000)) {
1233
+ ready = true;
1234
+ break;
1235
+ }
1236
+ await host.sleep(1000);
1237
+ }
1238
+ if (!ready)
1239
+ throw new Error(`private topology relay ${address}:${topology.healthPort} is unreachable`);
1240
+ }
1241
+ evidence.topology = {
1242
+ site: topology.site,
1243
+ siteCidr: topology.siteCidr,
1244
+ role: topology.role,
1245
+ probed: [...new Set(probes)]
1246
+ };
1247
+ }
1121
1248
  return evidence;
1122
1249
  }
1123
1250
 
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** One package version shared by both public binaries. Pinned to package.json by tests. */
2
- export declare const VERSION = "0.1.79";
2
+ export declare const VERSION = "0.1.81";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgezero/agent",
3
- "version": "0.1.79",
3
+ "version": "0.1.81",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "check": "tsc --noEmit",
@@ -88,6 +88,10 @@
88
88
  "types": "./dist/deployment-targets.d.ts",
89
89
  "default": "./dist/deployment-targets.js"
90
90
  },
91
+ "./deployment-topology": {
92
+ "types": "./dist/deployment-topology.d.ts",
93
+ "default": "./dist/deployment-topology.js"
94
+ },
91
95
  "./deployment-connectivity": {
92
96
  "types": "./dist/deployment-connectivity.d.ts",
93
97
  "default": "./dist/deployment-connectivity.js"