@forgezero/agent 0.1.32 → 0.1.34
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/README.md +60 -5
- package/dist/agent-heartbeat.d.ts +2 -0
- package/dist/agent-heartbeat.js +27 -13
- package/dist/agent-update-helper.js +1 -0
- package/dist/attestation-client.d.ts +2 -0
- package/dist/capacity-calibration.d.ts +51 -0
- package/dist/capacity-calibration.js +119 -0
- package/dist/cli/agent-install.d.ts +6 -0
- package/dist/cli/genesis.d.ts +7 -48
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/session-store.d.ts +22 -0
- package/dist/definition.d.ts +3 -0
- package/dist/definition.js +174 -2
- package/dist/deploy-file.js +174 -2
- package/dist/deployment-pull.d.ts +2 -0
- package/dist/deployment.d.ts +14 -0
- package/dist/egress-policy.d.ts +50 -0
- package/dist/fz-agent.js +1638 -109
- package/dist/fz.js +4253 -3389
- package/dist/git-egress.d.ts +35 -0
- package/dist/index.d.ts +19 -0
- package/dist/metal-helper-socket.js +12 -1
- package/dist/metal-provision.d.ts +2 -0
- package/dist/metal-provision.js +12 -1
- package/dist/migration-pull.d.ts +2 -0
- package/dist/migration-pull.js +18 -6
- package/dist/node-vault.d.ts +2 -0
- package/dist/node-vault.js +2 -1
- package/dist/provision.d.ts +13 -1
- package/dist/provision.js +181 -6
- package/dist/provisioning-pull.d.ts +2 -0
- package/dist/provisioning-pull.js +20 -7
- package/dist/telemetry-runtime.d.ts +20 -0
- package/dist/telemetry.d.ts +56 -0
- package/dist/version.d.ts +1 -1
- package/package.json +15 -11
- package/schema/deploy-v2.json +14 -0
package/dist/provision.js
CHANGED
|
@@ -229,6 +229,7 @@ var AGENT_UPDATE_JOURNAL = "/var/lib/forgezero/agent-update.json";
|
|
|
229
229
|
var AGENT_UPDATE_RECEIPT = "/var/lib/forgezero/agent-update-receipt.json";
|
|
230
230
|
var MAX_REQUEST_BYTES = 8 * 1024;
|
|
231
231
|
var COMPUTE_HELPER_UNITS = [
|
|
232
|
+
"forgezero-agent-egress.service",
|
|
232
233
|
"forgezero-deploy-runner.service",
|
|
233
234
|
"forgezero-lifecycle-helper.service",
|
|
234
235
|
"forgezero-software-helper.service"
|
|
@@ -883,9 +884,71 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
|
|
|
883
884
|
}
|
|
884
885
|
|
|
885
886
|
// src/version.ts
|
|
886
|
-
var VERSION3 = "0.1.
|
|
887
|
+
var VERSION3 = "0.1.34";
|
|
888
|
+
|
|
889
|
+
// src/egress-policy.ts
|
|
890
|
+
import { realpathSync } from "node:fs";
|
|
891
|
+
var AGENT_EGRESS_TABLE = "forgezero_agent_egress";
|
|
892
|
+
var SYSTEMD_RESOLVED_ADDRESS = "127.0.0.53";
|
|
893
|
+
var BLOCKED_IPV4 = [
|
|
894
|
+
"0.0.0.0/8",
|
|
895
|
+
"10.0.0.0/8",
|
|
896
|
+
"100.64.0.0/10",
|
|
897
|
+
"127.0.0.0/8",
|
|
898
|
+
"168.63.129.16/32",
|
|
899
|
+
"169.254.0.0/16",
|
|
900
|
+
"172.16.0.0/12",
|
|
901
|
+
"192.0.0.0/24",
|
|
902
|
+
"192.0.2.0/24",
|
|
903
|
+
"192.88.99.0/24",
|
|
904
|
+
"192.168.0.0/16",
|
|
905
|
+
"198.18.0.0/15",
|
|
906
|
+
"198.51.100.0/24",
|
|
907
|
+
"203.0.113.0/24",
|
|
908
|
+
"224.0.0.0/4",
|
|
909
|
+
"240.0.0.0/4"
|
|
910
|
+
];
|
|
911
|
+
var BLOCKED_IPV6 = [
|
|
912
|
+
"::/128",
|
|
913
|
+
"::1/128",
|
|
914
|
+
"::ffff:0:0/96",
|
|
915
|
+
"64:ff9b::/96",
|
|
916
|
+
"64:ff9b:1::/48",
|
|
917
|
+
"100::/64",
|
|
918
|
+
"fc00::/7",
|
|
919
|
+
"fec0::/10",
|
|
920
|
+
"fe80::/10",
|
|
921
|
+
"ff00::/8",
|
|
922
|
+
"2001::/32",
|
|
923
|
+
"2001:2::/48",
|
|
924
|
+
"2001:10::/28",
|
|
925
|
+
"2001:20::/28",
|
|
926
|
+
"2001:db8::/32",
|
|
927
|
+
"2002::/16",
|
|
928
|
+
"3fff::/20"
|
|
929
|
+
];
|
|
930
|
+
var normalizeEgressTcpPorts = (ports) => {
|
|
931
|
+
for (const port of ports) {
|
|
932
|
+
if (!Number.isSafeInteger(port) || port < 1 || port > 65535) {
|
|
933
|
+
throw new Error("Agent egress policy refuses an invalid loopback TCP port.");
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
return [...new Set(ports)].sort((left, right) => left - right);
|
|
937
|
+
};
|
|
938
|
+
function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
|
|
939
|
+
const ports = normalizeEgressTcpPorts(loopbackTcpPorts);
|
|
940
|
+
return [
|
|
941
|
+
"RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6",
|
|
942
|
+
`IPAddressAllow=${SYSTEMD_RESOLVED_ADDRESS}/32`,
|
|
943
|
+
...ports.length > 0 ? ["IPAddressAllow=127.0.0.1/32", "IPAddressAllow=::1/128"] : [],
|
|
944
|
+
...BLOCKED_IPV4.map((network) => `IPAddressDeny=${network}`),
|
|
945
|
+
...BLOCKED_IPV6.map((network) => `IPAddressDeny=${network}`)
|
|
946
|
+
].join(`
|
|
947
|
+
`);
|
|
948
|
+
}
|
|
887
949
|
|
|
888
950
|
// src/provision.ts
|
|
951
|
+
import { isIP } from "node:net";
|
|
889
952
|
function atLeast(version, floor) {
|
|
890
953
|
const parse = (value) => (value.trim().replace(/^v/, "").match(/\d+/g) ?? []).slice(0, 3).map(Number);
|
|
891
954
|
const got = parse(version);
|
|
@@ -939,6 +1002,63 @@ var LIFECYCLE_HELPER_UNIT_PATH = "/etc/systemd/system/forgezero-lifecycle-helper
|
|
|
939
1002
|
var LIFECYCLE_HELPER_SOCKET = "/run/forgezero-lifecycle/helper.sock";
|
|
940
1003
|
var WARP_CONFIG_UNIT_PATH = "/etc/systemd/system/forgezero-warp-config.service";
|
|
941
1004
|
var WARP_SERVICE_DROP_IN_PATH = "/etc/systemd/system/warp-svc.service.d/forgezero.conf";
|
|
1005
|
+
var AGENT_EGRESS_UNIT_PATH = "/etc/systemd/system/forgezero-agent-egress.service";
|
|
1006
|
+
var DEFAULT_RUNNER_PUBLIC_TCP_PORTS = [443];
|
|
1007
|
+
function agentEgressUnit(options) {
|
|
1008
|
+
const bin = options.binPath ?? "fz-agent";
|
|
1009
|
+
const user = options.user ?? "forgezero";
|
|
1010
|
+
if (!/^[a-z_][a-z0-9_-]{0,30}$/.test(user))
|
|
1011
|
+
throw new Error("invalid Agent service user");
|
|
1012
|
+
const deploymentEnabled = Boolean(options.repository || options.pullDeployments);
|
|
1013
|
+
const runnerLoopbackPorts = normalizeEgressTcpPorts(options.runnerLoopbackPorts ?? []);
|
|
1014
|
+
const runnerPublicTcpPorts = normalizeEgressTcpPorts(options.runnerPublicTcpPorts ?? DEFAULT_RUNNER_PUBLIC_TCP_PORTS);
|
|
1015
|
+
if (deploymentEnabled && runnerPublicTcpPorts.length < 1) {
|
|
1016
|
+
throw new Error("deployed project runner needs at least one vetted public TCP port");
|
|
1017
|
+
}
|
|
1018
|
+
if (deploymentEnabled)
|
|
1019
|
+
systemdAgentEgressDirectives(runnerLoopbackPorts);
|
|
1020
|
+
const users = [user, ...deploymentEnabled ? [DEPLOYMENT_RUNNER_USER] : []];
|
|
1021
|
+
const runnerGrant = deploymentEnabled ? ` --loopback-user=${DEPLOYMENT_RUNNER_USER}` + runnerLoopbackPorts.map((port) => ` --loopback-tcp-port=${port}`).join("") + runnerPublicTcpPorts.map((port) => ` --public-tcp-port=${port}`).join("") : "";
|
|
1022
|
+
const policyProofs = deploymentEnabled ? [
|
|
1023
|
+
...runnerLoopbackPorts.length > 0 ? [`loopback=.*:${runnerLoopbackPorts.join(",")}`] : [],
|
|
1024
|
+
`public-tcp=${runnerPublicTcpPorts.join(",")}`
|
|
1025
|
+
].map((pattern) => `ExecStartPost=/bin/sh -c '/usr/sbin/nft --numeric list table inet ${AGENT_EGRESS_TABLE} | /usr/bin/grep -q "${pattern}"'`).join(`
|
|
1026
|
+
`) : "";
|
|
1027
|
+
return `[Unit]
|
|
1028
|
+
Description=ForgeZero Agent host egress policy
|
|
1029
|
+
Documentation=https://www.forgezero.net/docs/agent
|
|
1030
|
+
After=systemd-resolved.service nftables.service
|
|
1031
|
+
Requires=systemd-resolved.service
|
|
1032
|
+
Before=forgezero-agent-enrol.service forgezero-agent.service
|
|
1033
|
+
|
|
1034
|
+
[Service]
|
|
1035
|
+
Type=notify
|
|
1036
|
+
NotifyAccess=all
|
|
1037
|
+
User=root
|
|
1038
|
+
Group=root
|
|
1039
|
+
ExecStart=${bin} egress-policy ${users.map((name) => `--user=${name}`).join(" ")}${runnerGrant}
|
|
1040
|
+
${policyProofs}
|
|
1041
|
+
Restart=on-failure
|
|
1042
|
+
RestartSec=2
|
|
1043
|
+
LimitCORE=0
|
|
1044
|
+
NoNewPrivileges=true
|
|
1045
|
+
PrivateTmp=true
|
|
1046
|
+
ProtectSystem=strict
|
|
1047
|
+
ProtectHome=true
|
|
1048
|
+
ProtectKernelTunables=true
|
|
1049
|
+
ProtectKernelModules=true
|
|
1050
|
+
ProtectControlGroups=true
|
|
1051
|
+
RestrictSUIDSGID=true
|
|
1052
|
+
RestrictRealtime=true
|
|
1053
|
+
MemoryDenyWriteExecute=true
|
|
1054
|
+
LockPersonality=true
|
|
1055
|
+
CapabilityBoundingSet=CAP_NET_ADMIN
|
|
1056
|
+
RestrictAddressFamilies=AF_UNIX AF_NETLINK
|
|
1057
|
+
|
|
1058
|
+
[Install]
|
|
1059
|
+
WantedBy=multi-user.target
|
|
1060
|
+
`;
|
|
1061
|
+
}
|
|
942
1062
|
function softwareHelperUnit(options) {
|
|
943
1063
|
const bin = options.binPath ?? "fz-agent";
|
|
944
1064
|
return `[Unit]
|
|
@@ -1183,11 +1303,16 @@ function agentEnrolmentUnit(options) {
|
|
|
1183
1303
|
` : ""
|
|
1184
1304
|
].join("");
|
|
1185
1305
|
const stateDir = options.enrolStatePath.replace(/\/[^/]+$/, "");
|
|
1306
|
+
const egressDependency = options.enforceEgress ? `After=forgezero-agent-egress.service
|
|
1307
|
+
Requires=forgezero-agent-egress.service
|
|
1308
|
+
BindsTo=forgezero-agent-egress.service
|
|
1309
|
+
` : "";
|
|
1310
|
+
const egressDirectives = options.enforceEgress ? systemdAgentEgressDirectives() : "";
|
|
1186
1311
|
return `[Unit]
|
|
1187
1312
|
Description=Bind this machine to its ForgeZero compute
|
|
1188
1313
|
After=network-online.target
|
|
1189
1314
|
Wants=network-online.target
|
|
1190
|
-
Before=forgezero-agent.service
|
|
1315
|
+
${egressDependency}Before=forgezero-agent.service
|
|
1191
1316
|
ConditionPathExists=!${options.enrolStatePath}
|
|
1192
1317
|
|
|
1193
1318
|
[Service]
|
|
@@ -1210,6 +1335,7 @@ ProtectSystem=strict
|
|
|
1210
1335
|
ProtectHome=true
|
|
1211
1336
|
ReadWritePaths=${stateDir}
|
|
1212
1337
|
LimitCORE=0
|
|
1338
|
+
${egressDirectives}
|
|
1213
1339
|
|
|
1214
1340
|
[Install]
|
|
1215
1341
|
WantedBy=multi-user.target
|
|
@@ -1219,9 +1345,15 @@ function deploymentRunnerUnit(options) {
|
|
|
1219
1345
|
const bin = options.binPath ?? "fz-agent";
|
|
1220
1346
|
const root = options.deployRoot ?? "/opt/forgezero";
|
|
1221
1347
|
const agentUser = options.user ?? "forgezero-agent";
|
|
1348
|
+
const egressDependency = options.enforceEgress ? `After=forgezero-agent-egress.service
|
|
1349
|
+
Requires=forgezero-agent-egress.service
|
|
1350
|
+
BindsTo=forgezero-agent-egress.service
|
|
1351
|
+
` : "";
|
|
1352
|
+
const egressDirectives = options.enforceEgress ? systemdAgentEgressDirectives(options.runnerLoopbackPorts ?? []) : "";
|
|
1222
1353
|
return `[Unit]
|
|
1223
1354
|
Description=ForgeZero credential-free project command runner
|
|
1224
1355
|
Documentation=https://www.forgezero.net/docs/agent
|
|
1356
|
+
${egressDependency}
|
|
1225
1357
|
|
|
1226
1358
|
[Service]
|
|
1227
1359
|
Type=notify
|
|
@@ -1252,6 +1384,7 @@ ProtectControlGroups=true
|
|
|
1252
1384
|
RestrictRealtime=true
|
|
1253
1385
|
MemoryDenyWriteExecute=true
|
|
1254
1386
|
LockPersonality=true
|
|
1387
|
+
${egressDirectives}
|
|
1255
1388
|
ReadWritePaths=${root}/releases ${root}/runner-home
|
|
1256
1389
|
|
|
1257
1390
|
[Install]
|
|
@@ -1261,6 +1394,21 @@ WantedBy=multi-user.target
|
|
|
1261
1394
|
function agentUnit(options) {
|
|
1262
1395
|
if (!validNodeHostname(options.nodeHostname))
|
|
1263
1396
|
throw new Error("node hostname is invalid");
|
|
1397
|
+
if (!options.telemetryEndpoint) {
|
|
1398
|
+
throw new Error("compute Agent provisioning requires OTEL_EXPORTER_OTLP_ENDPOINT as an explicit public HTTPS collector coordinate");
|
|
1399
|
+
}
|
|
1400
|
+
let telemetryEndpoint;
|
|
1401
|
+
{
|
|
1402
|
+
let endpoint;
|
|
1403
|
+
try {
|
|
1404
|
+
endpoint = new URL(options.telemetryEndpoint);
|
|
1405
|
+
} catch {
|
|
1406
|
+
throw new Error("compute telemetry endpoint must be an absolute public HTTPS URL");
|
|
1407
|
+
}
|
|
1408
|
+
if (endpoint.protocol !== "https:" || endpoint.username || endpoint.password || endpoint.search || endpoint.hash || isIP(endpoint.hostname) !== 0 || !endpoint.hostname.includes(".") || endpoint.hostname === "localhost" || endpoint.hostname.endsWith(".local"))
|
|
1409
|
+
throw new Error("compute telemetry endpoint must be an absolute public HTTPS URL without credentials, query or fragment");
|
|
1410
|
+
telemetryEndpoint = endpoint.toString().replace(/\/$/, "");
|
|
1411
|
+
}
|
|
1264
1412
|
const bin = options.binPath ?? "fz-agent";
|
|
1265
1413
|
const user = options.user ?? "forgezero";
|
|
1266
1414
|
const seedCredentialPath = options.seedCredentialPath ?? "/etc/forgezero/creds/agent-seed.cred";
|
|
@@ -1309,6 +1457,7 @@ function agentUnit(options) {
|
|
|
1309
1457
|
}
|
|
1310
1458
|
}
|
|
1311
1459
|
const environment = [
|
|
1460
|
+
"NODE_ENV=production",
|
|
1312
1461
|
`FZ_SOCKET_PATH=${agentBackendSocketPath(options.socketPath)}`,
|
|
1313
1462
|
`FZ_CONTROL_SOCKET=${controlSocketPath}`,
|
|
1314
1463
|
`FZ_SEED_CREDENTIAL=agent-seed`,
|
|
@@ -1319,12 +1468,15 @@ function agentUnit(options) {
|
|
|
1319
1468
|
options.enrolStatePath ? `FZ_ENROL_STATE_FILE=${options.enrolStatePath}` : null,
|
|
1320
1469
|
options.nodeLabel ? `FZ_NODE_LABEL=${options.nodeLabel}` : null,
|
|
1321
1470
|
options.nodeHostname ? `FZ_NODE_HOSTNAME=${options.nodeHostname}` : null,
|
|
1471
|
+
`OTEL_EXPORTER_OTLP_ENDPOINT=${telemetryEndpoint}`,
|
|
1472
|
+
"OTEL_SERVICE_NAME=forgezero-agent",
|
|
1322
1473
|
options.gitPublicKeyPath ? `FZ_GIT_PUBLIC_KEY_FILE=${options.gitPublicKeyPath}` : null,
|
|
1323
1474
|
options.repository ? `FZ_DEPLOY_REPO=${options.repository}` : null,
|
|
1324
1475
|
options.branch ? `FZ_DEPLOY_BRANCH=${options.branch}` : null,
|
|
1325
1476
|
options.profile ? `FZ_DEPLOY_PROFILE=${options.profile}` : null,
|
|
1326
1477
|
options.repository && options.branch ? `FZ_DEPLOY_KEY=${options.project ?? "platform"}:${options.environment ?? "production"}` : null,
|
|
1327
1478
|
deploymentEnabled ? `FZ_DEPLOY_ROOT=${deployRoot}` : null,
|
|
1479
|
+
deploymentEnabled ? `FZ_CAPACITY_EVIDENCE_DIR=${deployRoot}/capacity` : null,
|
|
1328
1480
|
deploymentEnabled ? `FZ_DEPLOY_RUNNER_SOCKET=${DEPLOYMENT_RUNNER_SOCKET}` : null,
|
|
1329
1481
|
deploymentEnabled ? `FZ_SOFTWARE_HELPER_SOCKET=${DEFAULT_SOFTWARE_HELPER_SOCKET}` : null,
|
|
1330
1482
|
Object.keys(deploymentCredentials).length > 0 ? `FZ_DEPLOY_SYSTEMD_SECRETS=${Object.keys(deploymentCredentials).join(",")}` : null,
|
|
@@ -1343,7 +1495,7 @@ function agentUnit(options) {
|
|
|
1343
1495
|
` : "";
|
|
1344
1496
|
const projectCredentials = Object.entries(deploymentCredentials).map(([name, path]) => `LoadCredentialEncrypted=${name}:${path}`).join(`
|
|
1345
1497
|
`);
|
|
1346
|
-
const deploymentWrites = deploymentEnabled ? `ReadWritePaths=${deployRoot}/releases ${deployRoot}/agent-home ${deployRoot}/cache` : "";
|
|
1498
|
+
const deploymentWrites = deploymentEnabled ? `ReadWritePaths=${deployRoot}/releases ${deployRoot}/agent-home ${deployRoot}/cache ${deployRoot}/capacity` : "";
|
|
1347
1499
|
const supplementaryGroups = [
|
|
1348
1500
|
AGENT_UPDATE_GROUP,
|
|
1349
1501
|
deploymentEnabled ? DEPLOYMENT_GROUP : null,
|
|
@@ -1354,6 +1506,7 @@ function agentUnit(options) {
|
|
|
1354
1506
|
const after = [
|
|
1355
1507
|
"network-online.target",
|
|
1356
1508
|
"forgezero-agent-update-helper.service",
|
|
1509
|
+
options.enforceEgress ? "forgezero-agent-egress.service" : null,
|
|
1357
1510
|
deploymentEnabled ? "forgezero-deploy-runner.service" : null,
|
|
1358
1511
|
deploymentEnabled ? "forgezero-software-helper.service" : null,
|
|
1359
1512
|
lifecycleEnabled ? "forgezero-lifecycle-helper.service" : null,
|
|
@@ -1362,6 +1515,7 @@ function agentUnit(options) {
|
|
|
1362
1515
|
].filter((value) => value !== null);
|
|
1363
1516
|
const requires = [
|
|
1364
1517
|
"forgezero-agent-update-helper.service",
|
|
1518
|
+
options.enforceEgress ? "forgezero-agent-egress.service" : null,
|
|
1365
1519
|
deploymentEnabled ? "forgezero-deploy-runner.service" : null,
|
|
1366
1520
|
deploymentEnabled ? "forgezero-software-helper.service" : null,
|
|
1367
1521
|
lifecycleEnabled ? "forgezero-lifecycle-helper.service" : null,
|
|
@@ -1371,7 +1525,8 @@ function agentUnit(options) {
|
|
|
1371
1525
|
const deploymentDependency = [
|
|
1372
1526
|
`After=${after.join(" ")}`,
|
|
1373
1527
|
"Wants=network-online.target",
|
|
1374
|
-
requires.length > 0 ? `Requires=${requires.join(" ")}` : null
|
|
1528
|
+
requires.length > 0 ? `Requires=${requires.join(" ")}` : null,
|
|
1529
|
+
options.enforceEgress ? "BindsTo=forgezero-agent-egress.service" : null
|
|
1375
1530
|
].filter((value) => value !== null).join(`
|
|
1376
1531
|
`);
|
|
1377
1532
|
const snpDevice = options.mode === "attested" ? `DevicePolicy=closed
|
|
@@ -1379,6 +1534,7 @@ DeviceAllow=/dev/sev-guest rw` : "";
|
|
|
1379
1534
|
const snpPrepare = options.mode === "attested" ? `ExecStartPre=+/bin/chgrp ${VAULT_GROUP} /dev/sev-guest
|
|
1380
1535
|
ExecStartPre=+/bin/chmod 0640 /dev/sev-guest
|
|
1381
1536
|
` : "";
|
|
1537
|
+
const egressDirectives = options.enforceEgress ? systemdAgentEgressDirectives() : "";
|
|
1382
1538
|
return `[Unit]
|
|
1383
1539
|
Description=ForgeZero node agent (${options.mode})
|
|
1384
1540
|
Documentation=https://www.forgezero.net/docs/agent
|
|
@@ -1423,6 +1579,7 @@ RestrictSUIDSGID=true
|
|
|
1423
1579
|
RestrictRealtime=true
|
|
1424
1580
|
MemoryDenyWriteExecute=true
|
|
1425
1581
|
LockPersonality=true
|
|
1582
|
+
${egressDirectives}
|
|
1426
1583
|
${snpDevice}
|
|
1427
1584
|
${deploymentWrites}
|
|
1428
1585
|
|
|
@@ -1438,6 +1595,8 @@ function planProvision(options) {
|
|
|
1438
1595
|
const credentialDir = seedCredentialPath.replace(/\/[^/]+$/, "");
|
|
1439
1596
|
const deployRoot = options.deployRoot ?? "/opt/forgezero";
|
|
1440
1597
|
const deploymentEnabled = Boolean(options.repository || options.pullDeployments);
|
|
1598
|
+
const runnerLoopbackPorts = normalizeEgressTcpPorts(options.runnerLoopbackPorts ?? []);
|
|
1599
|
+
const runnerPublicTcpPorts = normalizeEgressTcpPorts(options.runnerPublicTcpPorts ?? DEFAULT_RUNNER_PUBLIC_TCP_PORTS);
|
|
1441
1600
|
const lifecycleEnabled = Boolean(options.pullMigrations && options.lifecycleProfilePath);
|
|
1442
1601
|
if (Boolean(options.pullMigrations) !== Boolean(options.lifecycleProfilePath)) {
|
|
1443
1602
|
throw new Error("migration pull and lifecycle profile must be supplied together");
|
|
@@ -1478,6 +1637,9 @@ function planProvision(options) {
|
|
|
1478
1637
|
{ path: AGENT_SOCKET_UNIT_PATH, unit: agentSocketUnit(options) },
|
|
1479
1638
|
{ path: AGENT_SOCKET_PROXY_UNIT_PATH, unit: agentSocketProxyUnit(options) },
|
|
1480
1639
|
{ path: AGENT_UPDATE_HELPER_UNIT_PATH, unit: agentUpdateHelperUnit(options) },
|
|
1640
|
+
...options.enforceEgress ? [
|
|
1641
|
+
{ path: AGENT_EGRESS_UNIT_PATH, unit: agentEgressUnit(options) }
|
|
1642
|
+
] : [],
|
|
1481
1643
|
...deploymentEnabled ? [
|
|
1482
1644
|
{ path: DEPLOYMENT_RUNNER_UNIT_PATH, unit: deploymentRunnerUnit(options) },
|
|
1483
1645
|
{ path: SOFTWARE_HELPER_UNIT_PATH, unit: softwareHelperUnit(options) }
|
|
@@ -1504,6 +1666,10 @@ function planProvision(options) {
|
|
|
1504
1666
|
socketPath: options.socketPath,
|
|
1505
1667
|
user,
|
|
1506
1668
|
steps: [
|
|
1669
|
+
...options.enforceEgress ? [{
|
|
1670
|
+
label: "Ubuntu Agent egress prerequisites",
|
|
1671
|
+
command: `. /etc/os-release; test "$ID" = ubuntu; ` + `DEBIAN_FRONTEND=noninteractive apt-get update -qq; ` + `DEBIAN_FRONTEND=noninteractive apt-get install -y nftables; ` + `systemctl enable --now systemd-resolved.service; ` + `test "$(readlink -f /etc/resolv.conf)" = /run/systemd/resolve/stub-resolv.conf; ` + `test -s /run/systemd/resolve/stub-resolv.conf`
|
|
1672
|
+
}] : [],
|
|
1507
1673
|
{
|
|
1508
1674
|
label: "vault socket access group",
|
|
1509
1675
|
command: `groupadd --system ${VAULT_GROUP} || true`
|
|
@@ -1582,7 +1748,7 @@ function planProvision(options) {
|
|
|
1582
1748
|
] : [],
|
|
1583
1749
|
...deploymentEnabled ? [{
|
|
1584
1750
|
label: "deployment directories",
|
|
1585
|
-
command: `install -d -o root -g root -m 0755 ${deployRoot} && ` + `install -d -o root -g ${DEPLOYMENT_GROUP} -m 3770 ${deployRoot}/releases && ` + `install -d -o ${user} -g ${user} -m 0750 ${deployRoot}/cache && ` + `install -d -o ${user} -g ${user} -m 0700 ${deployRoot}/agent-home && ` + `install -d -o ${DEPLOYMENT_RUNNER_USER} -g ${DEPLOYMENT_GROUP} -m 0700 ${deployRoot}/runner-home ${deployRoot}/runner-home/cache`
|
|
1751
|
+
command: `install -d -o root -g root -m 0755 ${deployRoot} && ` + `install -d -o root -g ${DEPLOYMENT_GROUP} -m 3770 ${deployRoot}/releases && ` + `install -d -o ${user} -g ${user} -m 0750 ${deployRoot}/cache && ` + `install -d -o ${user} -g ${user} -m 0700 ${deployRoot}/capacity && ` + `install -d -o ${user} -g ${user} -m 0700 ${deployRoot}/agent-home && ` + `install -d -o ${DEPLOYMENT_RUNNER_USER} -g ${DEPLOYMENT_GROUP} -m 0700 ${deployRoot}/runner-home ${deployRoot}/runner-home/cache`
|
|
1586
1752
|
}] : [],
|
|
1587
1753
|
{ label: "reload units", command: "systemctl daemon-reload" },
|
|
1588
1754
|
...deploymentEnabled ? [{
|
|
@@ -1594,6 +1760,7 @@ function planProvision(options) {
|
|
|
1594
1760
|
command: `systemctl enable ${[
|
|
1595
1761
|
"forgezero-agent.socket",
|
|
1596
1762
|
"forgezero-agent-update-helper.service",
|
|
1763
|
+
...options.enforceEgress ? ["forgezero-agent-egress.service"] : [],
|
|
1597
1764
|
...deploymentEnabled ? ["forgezero-deploy-runner.service", "forgezero-software-helper.service"] : [],
|
|
1598
1765
|
...lifecycleEnabled ? ["forgezero-lifecycle-helper.service"] : [],
|
|
1599
1766
|
...warpEnabled ? ["forgezero-warp-config.service", "warp-svc.service"] : [],
|
|
@@ -1601,6 +1768,7 @@ function planProvision(options) {
|
|
|
1601
1768
|
"forgezero-agent.service"
|
|
1602
1769
|
].join(" ")}; systemctl reset-failed forgezero-agent.service || true; systemctl restart ${[
|
|
1603
1770
|
"forgezero-agent-update-helper.service",
|
|
1771
|
+
...options.enforceEgress ? ["forgezero-agent-egress.service"] : [],
|
|
1604
1772
|
...deploymentEnabled ? ["forgezero-deploy-runner.service", "forgezero-software-helper.service"] : [],
|
|
1605
1773
|
...lifecycleEnabled ? ["forgezero-lifecycle-helper.service"] : [],
|
|
1606
1774
|
...warpEnabled ? ["forgezero-warp-config.service", "warp-svc.service"] : [],
|
|
@@ -1611,6 +1779,10 @@ function planProvision(options) {
|
|
|
1611
1779
|
label: "prove the compute binding is durable",
|
|
1612
1780
|
command: `test -s ${enrolStatePath}`
|
|
1613
1781
|
}] : [],
|
|
1782
|
+
...options.enforceEgress ? [{
|
|
1783
|
+
label: "prove the Agent egress policy is active",
|
|
1784
|
+
command: "systemctl is-active forgezero-agent-egress.service && " + "nft --numeric list table inet forgezero_agent_egress | grep -q forgezero-agent-egress-v1" + (deploymentEnabled ? ` && nft --numeric list table inet forgezero_agent_egress | grep -q 'public-tcp=${runnerPublicTcpPorts.join(",")}'` + (runnerLoopbackPorts.length > 0 ? ` && nft --numeric list table inet forgezero_agent_egress | grep -Eq 'loopback=[0-9]+:${runnerLoopbackPorts.join(",")}( |")'` : "") : "")
|
|
1785
|
+
}] : [],
|
|
1614
1786
|
{ label: "prove it is running", command: "systemctl is-active forgezero-agent.service" },
|
|
1615
1787
|
{ label: "prove the public Vault socket exists", command: awaitSocketCommand(options.socketPath) },
|
|
1616
1788
|
{ label: "prove the Agent Vault backend exists", command: awaitSocketCommand(agentBackendSocketPath(options.socketPath)) },
|
|
@@ -1652,6 +1824,7 @@ export {
|
|
|
1652
1824
|
agentSocketUnit,
|
|
1653
1825
|
agentSocketProxyUnit,
|
|
1654
1826
|
agentEnrolmentUnit,
|
|
1827
|
+
agentEgressUnit,
|
|
1655
1828
|
agentBackendSocketPath,
|
|
1656
1829
|
WARP_SERVICE_DROP_IN_PATH,
|
|
1657
1830
|
WARP_CONFIG_UNIT_PATH,
|
|
@@ -1665,7 +1838,9 @@ export {
|
|
|
1665
1838
|
DEPLOYMENT_RUNNER_UNIT_PATH,
|
|
1666
1839
|
DEPLOYMENT_RUNNER_SOCKET,
|
|
1667
1840
|
DEPLOYMENT_GROUP,
|
|
1841
|
+
DEFAULT_RUNNER_PUBLIC_TCP_PORTS,
|
|
1668
1842
|
CAPABILITY_CHECKS,
|
|
1669
1843
|
AGENT_SOCKET_UNIT_PATH,
|
|
1670
|
-
AGENT_SOCKET_PROXY_UNIT_PATH
|
|
1844
|
+
AGENT_SOCKET_PROXY_UNIT_PATH,
|
|
1845
|
+
AGENT_EGRESS_UNIT_PATH
|
|
1671
1846
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { NodeKeyPair } from '@forgezero/runtime/identity';
|
|
2
2
|
import { type SignedNodeHttpOptions } from './signed-node-http';
|
|
3
|
+
import type { AgentOperationTelemetry } from './telemetry-runtime';
|
|
3
4
|
interface RemoteProvisionClaimBase {
|
|
4
5
|
computeKey: string;
|
|
5
6
|
claimToken: string;
|
|
@@ -73,6 +74,7 @@ export interface ProvisioningPullOptions extends SignedNodeHttpOptions {
|
|
|
73
74
|
kvm: boolean;
|
|
74
75
|
helper: boolean;
|
|
75
76
|
};
|
|
77
|
+
telemetry?: AgentOperationTelemetry;
|
|
76
78
|
}
|
|
77
79
|
export type ProvisionPullResult = {
|
|
78
80
|
status: 'idle';
|
|
@@ -70,6 +70,7 @@ async function postSignedNode(options, path, body) {
|
|
|
70
70
|
// src/provisioning-pull.ts
|
|
71
71
|
class ProvisionClaimLostError extends Error {
|
|
72
72
|
}
|
|
73
|
+
var remoteOutcome = (cause) => cause instanceof SignedNodeHttpError && cause.status < 500 ? "refused" : "retryable";
|
|
73
74
|
var post = (options, operation, body) => postSignedNode(options, `v1/metal/computes/${operation}`, body);
|
|
74
75
|
async function runClaim(options, claim) {
|
|
75
76
|
const setTimer = options.setTimer ?? ((callback, ms) => setTimeout(callback, ms));
|
|
@@ -88,10 +89,11 @@ async function runClaim(options, claim) {
|
|
|
88
89
|
if (stopped || renewal)
|
|
89
90
|
return;
|
|
90
91
|
let retry;
|
|
91
|
-
|
|
92
|
+
const renew = () => post(options, "renew", {
|
|
92
93
|
computeKey: claim.computeKey,
|
|
93
94
|
claimToken: claim.claimToken
|
|
94
|
-
})
|
|
95
|
+
});
|
|
96
|
+
renewal = (options.telemetry ? options.telemetry.observe("provisioning.renew", renew, () => "success", remoteOutcome) : renew()).then((response) => {
|
|
95
97
|
expires = response.claimExpiresAtTs;
|
|
96
98
|
options.onEvent?.("lease-renewed", { computeKey: claim.computeKey, claimExpiresAtTs: expires });
|
|
97
99
|
}).catch((cause) => {
|
|
@@ -112,7 +114,8 @@ async function runClaim(options, claim) {
|
|
|
112
114
|
schedule();
|
|
113
115
|
let result;
|
|
114
116
|
try {
|
|
115
|
-
|
|
117
|
+
const run = () => options.run(claim);
|
|
118
|
+
result = options.telemetry ? await options.telemetry.observe("provisioning.apply", run) : await run();
|
|
116
119
|
} finally {
|
|
117
120
|
stopped = true;
|
|
118
121
|
clearTimer(timer);
|
|
@@ -143,7 +146,8 @@ async function complete(options, body) {
|
|
|
143
146
|
async function pullProvisioningOnce(options) {
|
|
144
147
|
if (options.metalPreflight) {
|
|
145
148
|
const report = options.metalPreflight();
|
|
146
|
-
const
|
|
149
|
+
const preflight = () => postSignedNode(options, "v1/metal/preflight", report);
|
|
150
|
+
const accepted = options.telemetry ? await options.telemetry.observe("provisioning.preflight", preflight, (value) => value.ready ? "success" : "refused", remoteOutcome) : await preflight();
|
|
147
151
|
if (options.metalHostname && accepted.hostname !== options.metalHostname) {
|
|
148
152
|
throw new Error(`metal agent: configured inventory hostname ${options.metalHostname} is bound as ${accepted.hostname}; refusing work.`);
|
|
149
153
|
}
|
|
@@ -156,7 +160,8 @@ async function pullProvisioningOnce(options) {
|
|
|
156
160
|
if (!accepted.ready)
|
|
157
161
|
return { status: "idle" };
|
|
158
162
|
}
|
|
159
|
-
const
|
|
163
|
+
const claimWork = () => post(options, "claim", {});
|
|
164
|
+
const response = options.telemetry ? await options.telemetry.observe("provisioning.claim", claimWork, (value) => value.claim ? "success" : "idle", remoteOutcome) : await claimWork();
|
|
160
165
|
if (!response.claim)
|
|
161
166
|
return { status: "idle" };
|
|
162
167
|
const claim = response.claim;
|
|
@@ -167,20 +172,28 @@ async function pullProvisioningOnce(options) {
|
|
|
167
172
|
if (cause instanceof ProvisionClaimLostError)
|
|
168
173
|
throw cause;
|
|
169
174
|
const reason = (cause instanceof Error ? cause.message : String(cause)).slice(0, 2000);
|
|
170
|
-
|
|
175
|
+
const acknowledge2 = () => complete(options, {
|
|
171
176
|
computeKey: claim.computeKey,
|
|
172
177
|
claimToken: claim.claimToken,
|
|
173
178
|
ok: false,
|
|
174
179
|
detail: reason
|
|
175
180
|
});
|
|
181
|
+
if (options.telemetry) {
|
|
182
|
+
await options.telemetry.observe("provisioning.complete", acknowledge2, () => "success", remoteOutcome);
|
|
183
|
+
} else
|
|
184
|
+
await acknowledge2();
|
|
176
185
|
return { status: "failed", claim, reason };
|
|
177
186
|
}
|
|
178
|
-
|
|
187
|
+
const acknowledge = () => complete(options, {
|
|
179
188
|
computeKey: claim.computeKey,
|
|
180
189
|
claimToken: claim.claimToken,
|
|
181
190
|
ok: true,
|
|
182
191
|
...result.guestAddress ? { guestAddress: result.guestAddress } : {}
|
|
183
192
|
});
|
|
193
|
+
if (options.telemetry) {
|
|
194
|
+
await options.telemetry.observe("provisioning.complete", acknowledge, () => "success", remoteOutcome);
|
|
195
|
+
} else
|
|
196
|
+
await acknowledge();
|
|
184
197
|
return { status: claim.action === "delete" ? "terminated" : "running", claim, result };
|
|
185
198
|
}
|
|
186
199
|
function startProvisioningPull(options) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AgentTelemetry, AgentTelemetryEvent, AgentTelemetryOperation, AgentTelemetryOutcome } from './telemetry';
|
|
2
|
+
/**
|
|
3
|
+
* Process-wide operation/state accounting layered over the deliberately small
|
|
4
|
+
* OTLP emitter. Callers still cannot attach arbitrary attributes or payloads.
|
|
5
|
+
*/
|
|
6
|
+
export declare class AgentTelemetryRuntime {
|
|
7
|
+
private readonly telemetry;
|
|
8
|
+
private readonly now;
|
|
9
|
+
private active;
|
|
10
|
+
private completed;
|
|
11
|
+
private failed;
|
|
12
|
+
private draining;
|
|
13
|
+
constructor(telemetry: AgentTelemetry, now?: () => number);
|
|
14
|
+
event(event: AgentTelemetryEvent): void;
|
|
15
|
+
setDraining(draining: boolean): void;
|
|
16
|
+
observe<T>(operation: AgentTelemetryOperation, work: () => Promise<T>, outcome?: (value: T) => AgentTelemetryOutcome, errorOutcome?: (cause: unknown) => AgentTelemetryOutcome): Promise<T>;
|
|
17
|
+
close(): Promise<void>;
|
|
18
|
+
private recordState;
|
|
19
|
+
}
|
|
20
|
+
export type AgentOperationTelemetry = Pick<AgentTelemetryRuntime, 'observe'>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency-free OTLP/HTTP telemetry for the machine Agent.
|
|
3
|
+
*
|
|
4
|
+
* Callers can name only reviewed operations, outcomes and lifecycle events.
|
|
5
|
+
* There is deliberately no field for a URL, request/response body, header,
|
|
6
|
+
* credential, tenant coordinate, command, error detail or arbitrary label.
|
|
7
|
+
*/
|
|
8
|
+
export declare const AGENT_TELEMETRY_OPERATIONS: readonly ["agent.heartbeat", "agent.update", "attestation.refresh", "deployment.claim", "deployment.run", "deployment.renew", "deployment.complete", "migration.claim", "migration.run", "migration.renew", "migration.complete", "provisioning.preflight", "provisioning.claim", "provisioning.apply", "provisioning.renew", "provisioning.complete", "vault.sync", "agent.other"];
|
|
9
|
+
export type AgentTelemetryOperation = (typeof AGENT_TELEMETRY_OPERATIONS)[number];
|
|
10
|
+
export declare const AGENT_TELEMETRY_OUTCOMES: readonly ["success", "idle", "refused", "retryable", "failed"];
|
|
11
|
+
export type AgentTelemetryOutcome = (typeof AGENT_TELEMETRY_OUTCOMES)[number];
|
|
12
|
+
export declare const AGENT_TELEMETRY_EVENTS: readonly ["agent.started", "agent.draining", "agent.stopped", "agent.update_prepared", "agent.update_recovered", "telemetry.queue_overflow"];
|
|
13
|
+
export type AgentTelemetryEvent = (typeof AGENT_TELEMETRY_EVENTS)[number];
|
|
14
|
+
export interface AgentTelemetryConfig {
|
|
15
|
+
endpoint: string;
|
|
16
|
+
serviceName: string;
|
|
17
|
+
instanceId: string;
|
|
18
|
+
environment: 'production' | 'development';
|
|
19
|
+
flushIntervalMs: number;
|
|
20
|
+
traceSampleRatio: number;
|
|
21
|
+
}
|
|
22
|
+
export interface AgentOperationObservation {
|
|
23
|
+
operation: AgentTelemetryOperation;
|
|
24
|
+
outcome: AgentTelemetryOutcome;
|
|
25
|
+
startedAtMs: number;
|
|
26
|
+
durationMs: number;
|
|
27
|
+
}
|
|
28
|
+
export interface AgentTelemetryState {
|
|
29
|
+
active: number;
|
|
30
|
+
completed: number;
|
|
31
|
+
failed: number;
|
|
32
|
+
draining: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface AgentTelemetry {
|
|
35
|
+
recordOperation(observation: AgentOperationObservation): void;
|
|
36
|
+
recordEvent(event: AgentTelemetryEvent): void;
|
|
37
|
+
recordState(state: AgentTelemetryState): void;
|
|
38
|
+
flush(): Promise<void>;
|
|
39
|
+
close(): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
42
|
+
export declare const AGENT_OTLP_EXPORT_TIMEOUT_MS = 5000;
|
|
43
|
+
export declare const AGENT_OTLP_MAX_ATTEMPTS = 2;
|
|
44
|
+
export declare const AGENT_OTLP_MAX_RETRY_DELAY_MS = 1000;
|
|
45
|
+
export declare const AGENT_TELEMETRY_MAX_SPANS = 256;
|
|
46
|
+
export declare const AGENT_TELEMETRY_MAX_LOGS = 64;
|
|
47
|
+
/** Resolve once at process startup; production refuses to run without an exporter. */
|
|
48
|
+
export declare function resolveAgentTelemetryConfig(env?: Record<string, string | undefined>): AgentTelemetryConfig | null;
|
|
49
|
+
export declare function createAgentTelemetry(input: AgentTelemetryConfig | null, options?: {
|
|
50
|
+
fetch?: FetchLike;
|
|
51
|
+
warn?: (message: string) => void;
|
|
52
|
+
now?: () => number;
|
|
53
|
+
random?: () => number;
|
|
54
|
+
wait?: (milliseconds: number) => Promise<void>;
|
|
55
|
+
}): AgentTelemetry;
|
|
56
|
+
export {};
|
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.
|
|
2
|
+
export declare const VERSION = "0.1.34";
|
package/package.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"//": "Publishing happens from an operator's machine, not CI \u2014 CLAUDE.md records that the absence of CI is deliberate. npm's `provenance` attests a tarball was built by a recognised CI provider from a named commit, so it cannot be produced here: it was set, and the first publish failed with `Automatic provenance generation not supported for provider: null`. A setting that can never be satisfied is worse than none, because it reads as a guarantee nobody is getting. Restore it the day this publishes from CI, and not before.",
|
|
3
2
|
"name": "@forgezero/agent",
|
|
4
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.34",
|
|
5
4
|
"type": "module",
|
|
6
5
|
"scripts": {
|
|
7
6
|
"check": "tsc --noEmit",
|
|
8
7
|
"prebuild": "rm -rf dist",
|
|
9
|
-
"build": "bun build src/index.ts --outfile dist/fz-agent.js --target bun --format esm && bun build src/cli/index.ts --outfile dist/fz.js --target bun --format esm && bun build src/compute.ts src/provision.ts src/subscribe.ts src/pipeline.ts src/definition.ts src/deploy-file.ts src/ssh-server.ts src/ssh-listen.ts src/provisioning-pull.ts src/migration-pull.ts src/guest-enrolment.ts src/node-vault.ts src/metal-provision.ts src/metal-helper-socket.ts src/lifecycle-helper.ts src/deployment-runner.ts src/agent-update.ts src/agent-update-helper.ts src/agent-heartbeat.ts src/software.ts src/software-helper.ts src/ubuntu.ts --root src --outdir dist --target browser --format esm --packages external && bun build src/project-context.ts --root src --outdir dist --target bun --format esm --packages external && tsc --emitDeclarationOnly --declaration --noEmit false --outDir dist",
|
|
8
|
+
"build": "bun build src/index.ts --outfile dist/fz-agent.js --target bun --format esm && bun build src/cli/index.ts --outfile dist/fz.js --target bun --format esm && bun build src/compute.ts src/provision.ts src/subscribe.ts src/pipeline.ts src/definition.ts src/deploy-file.ts src/ssh-server.ts src/ssh-listen.ts src/provisioning-pull.ts src/migration-pull.ts src/guest-enrolment.ts src/node-vault.ts src/metal-provision.ts src/metal-helper-socket.ts src/lifecycle-helper.ts src/deployment-runner.ts src/agent-update.ts src/agent-update-helper.ts src/agent-heartbeat.ts src/software.ts src/software-helper.ts src/ubuntu.ts src/capacity-calibration.ts --root src --outdir dist --target browser --format esm --packages external && bun build src/project-context.ts --root src --outdir dist --target bun --format esm --packages external && tsc --emitDeclarationOnly --declaration --noEmit false --outDir dist",
|
|
10
9
|
"prepublishOnly": "bun run check && bun run build"
|
|
11
10
|
},
|
|
12
11
|
"devDependencies": {
|
|
@@ -18,19 +17,20 @@
|
|
|
18
17
|
"@noble/post-quantum": "^0.6.1"
|
|
19
18
|
},
|
|
20
19
|
"dependencies": {
|
|
21
|
-
"@forgezero/runtime": "
|
|
22
|
-
"@forgezero/vault": "
|
|
23
|
-
"@noble/curves": "
|
|
24
|
-
"@noble/hashes": "
|
|
25
|
-
"@noble/post-quantum": "
|
|
26
|
-
"@scure/bip39": "
|
|
20
|
+
"@forgezero/runtime": "0.1.5",
|
|
21
|
+
"@forgezero/vault": "0.1.8",
|
|
22
|
+
"@noble/curves": "2.2.0",
|
|
23
|
+
"@noble/hashes": "2.2.0",
|
|
24
|
+
"@noble/post-quantum": "0.6.1",
|
|
25
|
+
"@scure/bip39": "2.2.0"
|
|
27
26
|
},
|
|
28
27
|
"bin": {
|
|
29
28
|
"fz-agent": "dist/fz-agent.js",
|
|
30
29
|
"fz": "dist/fz.js"
|
|
31
30
|
},
|
|
32
31
|
"publishConfig": {
|
|
33
|
-
"access": "public"
|
|
32
|
+
"access": "public",
|
|
33
|
+
"provenance": true
|
|
34
34
|
},
|
|
35
35
|
"files": [
|
|
36
36
|
"dist",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"repository": {
|
|
54
54
|
"type": "git",
|
|
55
55
|
"url": "git+https://github.com/forgezero-net/packages.git",
|
|
56
|
-
"directory": "
|
|
56
|
+
"directory": "agent"
|
|
57
57
|
},
|
|
58
58
|
"bugs": "https://github.com/forgezero-net/packages/issues",
|
|
59
59
|
"exports": {
|
|
@@ -133,6 +133,10 @@
|
|
|
133
133
|
"./project-context": {
|
|
134
134
|
"types": "./dist/project-context.d.ts",
|
|
135
135
|
"default": "./dist/project-context.js"
|
|
136
|
+
},
|
|
137
|
+
"./capacity-calibration": {
|
|
138
|
+
"types": "./dist/capacity-calibration.d.ts",
|
|
139
|
+
"default": "./dist/capacity-calibration.js"
|
|
136
140
|
}
|
|
137
141
|
}
|
|
138
142
|
}
|
package/schema/deploy-v2.json
CHANGED
|
@@ -32,6 +32,20 @@
|
|
|
32
32
|
"version": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9.-]{0,31}$" }
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
},
|
|
36
|
+
"capacityCalibration": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"additionalProperties": false,
|
|
39
|
+
"required": ["endpoint"],
|
|
40
|
+
"properties": {
|
|
41
|
+
"endpoint": { "type": "string", "pattern": "^http://(?:localhost|127\\.0\\.0\\.1|\\[::1\\]):[1-9][0-9]{0,4}/" },
|
|
42
|
+
"maxConcurrency": { "type": "integer", "minimum": 1, "maximum": 4096 },
|
|
43
|
+
"requestsPerWorker": { "type": "integer", "minimum": 2, "maximum": 100 },
|
|
44
|
+
"maxP95Ms": { "type": "number", "exclusiveMinimum": 0 },
|
|
45
|
+
"maxErrorRate": { "type": "number", "minimum": 0, "maximum": 0.2 },
|
|
46
|
+
"headroomRatio": { "type": "number", "minimum": 0.25, "maximum": 0.95 },
|
|
47
|
+
"requestTimeoutMs": { "type": "integer", "minimum": 100, "maximum": 30000 }
|
|
48
|
+
}
|
|
35
49
|
}
|
|
36
50
|
}
|
|
37
51
|
}
|