@forgezero/agent 0.1.38 → 0.1.40

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/index.d.ts CHANGED
@@ -2,11 +2,14 @@
2
2
  import { type NodeKeyPair } from '@forgezero/runtime/identity';
3
3
  import { startAgent, type AgentOptions, type AttestationSource } from './socket';
4
4
  import type { SecretCache } from './cache';
5
+ import { type DeploymentCredentialSchema } from './credential-schema';
5
6
  export { VERSION } from './version';
6
7
  export { startAgent, handleRequest, handleApplicationRequest } from './socket';
7
8
  export type { AgentOptions, AttestationSource, Request, Response } from './socket';
8
9
  export { createSecretCache, CacheError } from './cache';
9
10
  export type { SecretCache, CacheOptions } from './cache';
11
+ export { AGENT_CREDENTIAL_LOCATIONS, AGENT_CREDENTIAL_POLICY, METAL_SYSTEMD_CREDENTIALS, credentialBinding, deploymentCredentialSchema } from './credential-schema';
12
+ export type { AgentCredentialLocation, DeploymentCredentialBinding, DeploymentCredentialSchema } from './credential-schema';
10
13
  export { createDeploymentManager, DeploymentError } from './deployment';
11
14
  export type { DeploymentManager, DeploymentOptions, DeploymentRequest, DeploymentResult } from './deployment';
12
15
  export { DEFAULT_CONTROL_SOCKET, requestControl, startControlServer } from './control';
@@ -132,6 +135,36 @@ export declare function createSystemdDeploymentSecrets(names: string | undefined
132
135
  has(name: string): boolean;
133
136
  get(name: string): Promise<string>;
134
137
  } | undefined;
138
+ /**
139
+ * Resolve deployment secrets from the project-scoped RAM Vault replica first,
140
+ * then from an explicitly allow-listed systemd credential with the same name.
141
+ * This is shared by platform and tenant deployment managers.
142
+ */
143
+ export declare function createDeploymentSecretResolver(schema: DeploymentCredentialSchema, primary: Pick<SecretCache, 'get'> | undefined, fallback: {
144
+ has(name: string): boolean;
145
+ get(name: string): Promise<string>;
146
+ } | undefined): Pick<SecretCache, 'get'> | undefined;
147
+ export type InitialVaultReplicaStatus = {
148
+ state: 'ready';
149
+ loaded: number;
150
+ failed: readonly string[];
151
+ } | {
152
+ state: 'partial';
153
+ loaded: number;
154
+ failed: readonly string[];
155
+ } | {
156
+ state: 'unavailable';
157
+ loaded: 0;
158
+ failed: readonly [];
159
+ };
160
+ /**
161
+ * Attempt the in-memory Vault replica without turning a locked or restarting
162
+ * Vault into an Agent outage. Deployment reads still try this cache first and
163
+ * may fall back only through the explicit same-name systemd schema. The sync
164
+ * loop remains active and promotes the cache to a complete replica once Vault
165
+ * is available again.
166
+ */
167
+ export declare function initializeVaultReplica(cache: Pick<SecretCache, 'load'>): Promise<InitialVaultReplicaStatus>;
135
168
  /**
136
169
  * Bring the agent up.
137
170
  *
@@ -1,5 +1,6 @@
1
1
  import { type MetalCommandResult, type MetalProvisionProfile } from './metal-provision';
2
2
  declare const PROFILE_PATH = "/etc/forgezero/metal.json";
3
+ export declare const METAL_BOOTSTRAP_STATE_PATH = "/etc/forgezero/metal.initialized.json";
3
4
  export interface MetalBootstrapConfig {
4
5
  kind: 'metal';
5
6
  metalHostname: string;
@@ -293,11 +293,12 @@ function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
293
293
  }
294
294
 
295
295
  // src/version.ts
296
- var VERSION = "0.1.38";
296
+ var VERSION = "0.1.40";
297
297
 
298
298
  // src/metal-bootstrap.ts
299
299
  var PROFILE_PATH = "/etc/forgezero/metal.json";
300
- var STATE_PATH = "/etc/forgezero/metal.initialized.json";
300
+ var METAL_BOOTSTRAP_STATE_PATH = "/etc/forgezero/metal.initialized.json";
301
+ var STATE_PATH = METAL_BOOTSTRAP_STATE_PATH;
301
302
  var SEED_CREDENTIAL_PATH = "/etc/forgezero/creds/metal-agent-seed.cred";
302
303
  var UNIT_DIRECTORY = "/etc/systemd/system";
303
304
  var AGENT_PATH = "/usr/local/bin/fz-agent";
@@ -938,5 +939,6 @@ export {
938
939
  planMetalBootstrap,
939
940
  metalBootstrapStatus,
940
941
  applyMetalBootstrap,
941
- MetalBootstrapError
942
+ MetalBootstrapError,
943
+ METAL_BOOTSTRAP_STATE_PATH
942
944
  };
@@ -82,6 +82,8 @@ export interface ApiRuntimeRenderOptions {
82
82
  greenPort: number;
83
83
  collectorUnit: string;
84
84
  credentials: SystemdCredentialSpec[];
85
+ /** Root-owned calibration override, loaded after the ordinary shared environment. */
86
+ capacityEnvironmentFile?: string;
85
87
  }
86
88
  export declare function renderPlatformApiUnits(input: ApiRuntimeRenderOptions): {
87
89
  template: string;
@@ -90,6 +92,8 @@ export declare function renderPlatformApiUnits(input: ApiRuntimeRenderOptions):
90
92
  export declare function renderPlatformNginx(input: {
91
93
  publicPort: number;
92
94
  initialSlotPort: number;
95
+ concurrencyLimit?: number;
96
+ workerDrainSeconds?: number;
93
97
  }): {
94
98
  upstream: string;
95
99
  site: string;
@@ -109,6 +113,7 @@ export interface PlatformActivationConfig {
109
113
  greenPort: number;
110
114
  healthPath: string;
111
115
  keepReleases: number;
116
+ drainDeadlineMs?: number;
112
117
  }
113
118
  /**
114
119
  * Render the fixed privilege boundary used by the credential-free deployment
@@ -131,31 +136,3 @@ export interface LocalOtlpProofPlan {
131
136
  };
132
137
  }
133
138
  export declare function planLocalOtlpProof(endpoint: string, collectorUnit: string): LocalOtlpProofPlan;
134
- export type BringupStage = 'runtime-ready' | 'invite-prepared' | 'deploy-requested' | 'deploy-healthy' | 'invite-presented' | 'complete' | 'failed';
135
- export interface PlatformBringupState {
136
- requiresFirstInvite: boolean;
137
- stage: BringupStage;
138
- inviteTokenFilePresent: boolean;
139
- lastError?: string;
140
- }
141
- export type PlatformBringupEvent = {
142
- type: 'invite-prepared';
143
- tokenFilePresent: boolean;
144
- } | {
145
- type: 'deploy-requested';
146
- } | {
147
- type: 'deploy-healthy';
148
- } | {
149
- type: 'invite-presented';
150
- } | {
151
- type: 'complete';
152
- } | {
153
- type: 'failed';
154
- error: string;
155
- };
156
- export declare function initialPlatformBringupState(requiresFirstInvite: boolean): PlatformBringupState;
157
- /** Strict persisted sequencing: genesis invite before deploy, presentation only after health. */
158
- export declare function advancePlatformBringup(state: PlatformBringupState, event: PlatformBringupEvent): PlatformBringupState;
159
- export type PlatformBringupAction = 'prepare-invite' | 'request-deploy' | 'await-deploy-health' | 'present-invite' | 'mark-complete' | 'none';
160
- export declare function nextPlatformBringupAction(state: PlatformBringupState): PlatformBringupAction;
161
- export declare function repairPlatformBringup(state: PlatformBringupState): PlatformBringupState;
@@ -218,6 +218,8 @@ function renderPlatformApiUnits(input) {
218
218
  throw new Error("Blue and green ports must differ.");
219
219
  const credentials = input.credentials.map((credential) => `LoadCredentialEncrypted=${credential.name}:${credential.encryptedPath}`).join(`
220
220
  `);
221
+ const capacityEnvironment = input.capacityEnvironmentFile ? `EnvironmentFile=-${input.capacityEnvironmentFile}
222
+ ` : "";
221
223
  const template = `[Unit]
222
224
  Description=ForgeZero (%i slot)
223
225
  After=network-online.target ${input.collectorUnit}
@@ -230,7 +232,7 @@ WorkingDirectory=${input.slotsDirectory}/%i
230
232
  Environment=NODE_ENV=production
231
233
  Environment=FZ_SLOT=%i
232
234
  EnvironmentFile=${input.sharedEnvironmentFile}
233
- ${credentials}
235
+ ${capacityEnvironment}${credentials}
234
236
  ExecStart=/usr/local/bin/bun run ${input.slotsDirectory}/%i/src/index.ts
235
237
  Restart=always
236
238
  RestartSec=2
@@ -266,16 +268,24 @@ Environment=PORT=${input.greenPort}
266
268
  function renderPlatformNginx(input) {
267
269
  boundedInteger("publicPort", input.publicPort, 1024, 65535);
268
270
  boundedInteger("initialSlotPort", input.initialSlotPort, 1024, 65535);
271
+ const concurrencyLimit = boundedInteger("concurrencyLimit", input.concurrencyLimit ?? 256, 1, 1e6);
272
+ boundedInteger("workerDrainSeconds", input.workerDrainSeconds ?? 35, 1, 300);
269
273
  if (input.publicPort === input.initialSlotPort)
270
274
  throw new Error("Edge and slot ports must differ.");
271
275
  return {
272
276
  upstream: `upstream forgezero { server 127.0.0.1:${input.initialSlotPort}; }
273
277
  `,
274
- site: `server {
278
+ site: `limit_conn_zone $server_name zone=forgezero_admission:10m;
279
+ map $http_upgrade $forgezero_connection { default upgrade; '' close; }
280
+ map $limit_conn_status $forgezero_retry_after { default ''; REJECTED 1; REJECTED_DRY_RUN 1; }
281
+ server {
275
282
  listen 127.0.0.1:${input.publicPort};
276
283
  server_name _;
277
- location ^~ /api/ { proxy_pass http://forgezero; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }
278
- location ^~ /v1/ { proxy_pass http://forgezero; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }
284
+ limit_conn forgezero_admission ${concurrencyLimit};
285
+ limit_conn_status 503;
286
+ add_header Retry-After $forgezero_retry_after always;
287
+ location ^~ /api/ { proxy_pass http://forgezero; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $forgezero_connection; proxy_read_timeout 3600s; }
288
+ location ^~ /v1/ { proxy_pass http://forgezero; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $forgezero_connection; proxy_read_timeout 3600s; }
279
289
  location / { return 404; }
280
290
  }
281
291
  `
@@ -301,6 +311,7 @@ function renderPlatformActivationFiles(input) {
301
311
  if (input.bluePort === input.greenPort)
302
312
  throw new Error("Activation slot ports must differ.");
303
313
  boundedInteger("keepReleases", input.keepReleases, 2, 100);
314
+ const drainDeadlineMs = boundedInteger("drainDeadlineMs", input.drainDeadlineMs ?? 35000, 1000, 300000);
304
315
  if (!/^\/[A-Za-z0-9/_-]{1,128}$/.test(input.healthPath) || input.healthPath.includes("..")) {
305
316
  throw new Error("Activation health path is malformed.");
306
317
  }
@@ -310,7 +321,8 @@ function renderPlatformActivationFiles(input) {
310
321
  `FZ_BLUE_PORT=${input.bluePort}`,
311
322
  `FZ_GREEN_PORT=${input.greenPort}`,
312
323
  `FZ_HEALTH_PATH=${input.healthPath}`,
313
- `FZ_KEEP_RELEASES=${input.keepReleases}`
324
+ `FZ_KEEP_RELEASES=${input.keepReleases}`,
325
+ `FZ_DRAIN_DEADLINE_MS=${drainDeadlineMs}`
314
326
  ].join(`
315
327
  `) + `
316
328
  `;
@@ -332,7 +344,14 @@ if (( ! healthy )); then systemctl stop "forgezero@\${target}.service" || true;
332
344
  upstream=/etc/nginx/conf.d/forgezero-upstream.conf; backup="$(mktemp -p /run forgezero-upstream.XXXXXX)"; [[ -f "$upstream" ]] && cp "$upstream" "$backup" || : >"$backup"
333
345
  printf 'upstream forgezero { server 127.0.0.1:%s; }\\n' "$port" >"$upstream"
334
346
  if ! nginx -t || ! nginx -s reload; then [[ -s "$backup" ]] && cp "$backup" "$upstream" || rm -f "$upstream"; rm -f "$backup"; systemctl stop "forgezero@\${target}.service" || true; [[ -n "$previous_target_link" && -d "$previous_target_link" ]] && ln -sfn "$previous_target_link" "$target_link" || rm -f "$target_link"; nginx -t >/dev/null 2>&1 && nginx -s reload || true; exit 1; fi
335
- rm -f "$backup"; printf '%s\\n' "$target" >"$slot_file"; [[ -n "$previous_slot" && "$previous_slot" != "$target" ]] && systemctl stop "forgezero@\${previous_slot}.service" || true
347
+ rm -f "$backup"; printf '%s\\n' "$target" >"$slot_file"
348
+ # New nginx workers select the new slot after reload. Keep the old slot alive
349
+ # while old workers drain in-flight requests and upgraded connections.
350
+ if [[ -n "$previous_slot" && "$previous_slot" != "$target" ]]; then
351
+ sleep_seconds="$(( (FZ_DRAIN_DEADLINE_MS + 999) / 1000 ))"
352
+ sleep "$sleep_seconds"
353
+ systemctl stop "forgezero@\${previous_slot}.service" || true
354
+ fi
336
355
  mapfile -t old < <(find "$releases" -mindepth 1 -maxdepth 1 -type d -printf '%T@ %p\\n' | sort -rn | tail -n "+$((FZ_KEEP_RELEASES + 1))" | cut -d' ' -f2-)
337
356
  for path in "\${old[@]}"; do [[ "$path" == "$release" ]] || rm -rf -- "$path"; done
338
357
  printf 'promoted %s on %s\\n' "$release" "$target"
@@ -380,83 +399,13 @@ function planLocalOtlpProof(endpoint, collectorUnit) {
380
399
  }
381
400
  };
382
401
  }
383
- function initialPlatformBringupState(requiresFirstInvite) {
384
- return { requiresFirstInvite, stage: "runtime-ready", inviteTokenFilePresent: false };
385
- }
386
- function advancePlatformBringup(state, event) {
387
- if (state.stage === "failed")
388
- throw new Error("A failed bring-up must be explicitly repaired before resuming.");
389
- if (event.type === "failed")
390
- return { ...state, stage: "failed", lastError: safeAtom("error", event.error) };
391
- if (event.type === state.stage)
392
- return state;
393
- if (event.type === "invite-prepared") {
394
- if (!state.requiresFirstInvite || state.stage !== "runtime-ready" || !event.tokenFilePresent) {
395
- throw new Error("The first invite must be durably present before it is recorded.");
396
- }
397
- return { ...state, stage: "invite-prepared", inviteTokenFilePresent: true };
398
- }
399
- if (event.type === "deploy-requested") {
400
- const expected = state.requiresFirstInvite ? "invite-prepared" : "runtime-ready";
401
- if (state.stage !== expected || state.requiresFirstInvite && !state.inviteTokenFilePresent) {
402
- throw new Error("Deployment cannot start before required invite preparation.");
403
- }
404
- return { ...state, stage: "deploy-requested" };
405
- }
406
- if (event.type === "deploy-healthy") {
407
- if (state.stage !== "deploy-requested")
408
- throw new Error("Deployment health requires a requested deployment.");
409
- return { ...state, stage: "deploy-healthy" };
410
- }
411
- if (event.type === "invite-presented") {
412
- if (!state.requiresFirstInvite || state.stage !== "deploy-healthy" || !state.inviteTokenFilePresent) {
413
- throw new Error("Invite presentation requires a healthy deployment and the original token file.");
414
- }
415
- return { ...state, stage: "invite-presented" };
416
- }
417
- if (event.type === "complete") {
418
- const expected = state.requiresFirstInvite ? "invite-presented" : "deploy-healthy";
419
- if (state.stage !== expected)
420
- throw new Error("Bring-up cannot complete before its ordered ceremony.");
421
- return { ...state, stage: "complete" };
422
- }
423
- throw new Error("Unsupported bring-up transition.");
424
- }
425
- function nextPlatformBringupAction(state) {
426
- if (state.stage === "failed" || state.stage === "complete")
427
- return "none";
428
- if (state.stage === "runtime-ready")
429
- return state.requiresFirstInvite ? "prepare-invite" : "request-deploy";
430
- if (state.stage === "invite-prepared") {
431
- if (!state.inviteTokenFilePresent)
432
- throw new Error("Recorded invite is missing; refusing implicit rotation.");
433
- return "request-deploy";
434
- }
435
- if (state.stage === "deploy-requested")
436
- return "await-deploy-health";
437
- if (state.stage === "deploy-healthy")
438
- return state.requiresFirstInvite ? "present-invite" : "mark-complete";
439
- return "mark-complete";
440
- }
441
- function repairPlatformBringup(state) {
442
- if (state.stage !== "failed")
443
- return state;
444
- if (state.requiresFirstInvite && !state.inviteTokenFilePresent) {
445
- throw new Error("Missing issued invite cannot be repaired by implicit rotation.");
446
- }
447
- return { ...state, stage: state.inviteTokenFilePresent ? "invite-prepared" : "runtime-ready", lastError: undefined };
448
- }
449
402
  export {
450
403
  validatePlatformSharedEnvironment,
451
- repairPlatformBringup,
452
404
  renderPlatformSharedEnvironment,
453
405
  renderPlatformNginx,
454
406
  renderPlatformApiUnits,
455
407
  renderPlatformActivationFiles,
456
408
  platformApiCredentialSpecs,
457
409
  planPlatformActivation,
458
- planLocalOtlpProof,
459
- nextPlatformBringupAction,
460
- initialPlatformBringupState,
461
- advancePlatformBringup
410
+ planLocalOtlpProof
462
411
  };
@@ -105,7 +105,7 @@ function defaultProjectContext(root = process.cwd()) {
105
105
  "Update a truth source instead of copying architecture or progress into another document.",
106
106
  "Never report a feature as complete without running its declared verification."
107
107
  ],
108
- nonAuthoritative: ["audit/"]
108
+ nonAuthoritative: ["docs/audit/"]
109
109
  };
110
110
  }
111
111
  function renderProjectContext(manifest) {
package/dist/provision.js CHANGED
@@ -93,6 +93,8 @@ var checked = async (run, input, label) => {
93
93
  return result;
94
94
  };
95
95
  async function validateReleaseDirectory(directory, release, run) {
96
+ chmodSync(directory, 493);
97
+ chmodSync(join(directory, "dist"), 493);
96
98
  const manifest = JSON.parse(readFileSync(join(directory, "package.json"), "utf8"));
97
99
  if (manifest.name !== release.package || manifest.version !== release.version) {
98
100
  throw new Error("agent update manifest does not match the selected release");
@@ -143,19 +145,19 @@ async function stageAgentRelease(releaseInput, options) {
143
145
  if (!timingSafeEqual(actual, expected))
144
146
  throw new Error("agent update integrity mismatch");
145
147
  writeFileSync(archive, bytes, { mode: 384, flag: "wx" });
146
- await checked(run, {
147
- command: "/usr/bin/tar",
148
- args: [
149
- "-xzf",
150
- archive,
151
- "-C",
152
- unpacked,
153
- "--strip-components=1",
154
- "package/package.json",
155
- "package/dist/fz-agent.js",
156
- "package/dist/fz.js"
157
- ]
158
- }, "agent update extraction");
148
+ for (const [member, relative] of [
149
+ ["package/package.json", "package.json"],
150
+ ["package/dist/fz-agent.js", "dist/fz-agent.js"],
151
+ ["package/dist/fz.js", "dist/fz.js"]
152
+ ]) {
153
+ const extracted = await checked(run, {
154
+ command: "/usr/bin/tar",
155
+ args: ["-xOzf", archive, member]
156
+ }, `agent update extraction of ${member}`);
157
+ const destination = join(unpacked, relative);
158
+ mkdirSync(dirname(destination), { recursive: true, mode: 448 });
159
+ writeFileSync(destination, extracted.output, { mode: 384, flag: "wx" });
160
+ }
159
161
  await validateReleaseDirectory(unpacked, release, run);
160
162
  if (!existsSync(finalDirectory)) {
161
163
  renameSync(unpacked, finalDirectory);
@@ -701,8 +703,8 @@ var UBUNTU_2604_X64 = [
701
703
  },
702
704
  {
703
705
  requirement: { id: "arangodb", version: "3.11.14" },
704
- check: `arangod --version 2>/dev/null | head -1 | grep -q '3.11.14'`,
705
- install: `tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT; ` + `curl -fsSL 'https://download.arangodb.com/arangodb311/DEBIAN/amd64/arangodb3_3.11.14-1_amd64.deb' -o "$tmp/arangodb.deb"; ` + `echo "${ARANGO_SHA256} $tmp/arangodb.deb" | sha256sum -c -; ` + `DEBIAN_FRONTEND=noninteractive dpkg -i "$tmp/arangodb.deb" >/dev/null 2>&1 || ` + `DEBIAN_FRONTEND=noninteractive apt-get -y -f install`
706
+ check: `arangod --version 2>/dev/null | head -1 | grep -q '3.11.14' && ` + `! systemctl is-active --quiet arangodb3.service && ` + `! systemctl is-enabled --quiet arangodb3.service`,
707
+ install: `tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT; ` + `curl -fsSL 'https://download.arangodb.com/arangodb311/DEBIAN/amd64/arangodb3_3.11.14-1_amd64.deb' -o "$tmp/arangodb.deb"; ` + `echo "${ARANGO_SHA256} $tmp/arangodb.deb" | sha256sum -c -; ` + `DEBIAN_FRONTEND=noninteractive dpkg -i "$tmp/arangodb.deb" >/dev/null 2>&1 || ` + `DEBIAN_FRONTEND=noninteractive apt-get -y -f install; ` + `systemctl disable --now arangodb3.service`
706
708
  },
707
709
  {
708
710
  requirement: { id: "cloudflared", version: "2026.7.3" },
@@ -891,7 +893,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
891
893
  }
892
894
 
893
895
  // src/version.ts
894
- var VERSION3 = "0.1.38";
896
+ var VERSION3 = "0.1.40";
895
897
 
896
898
  // src/egress-policy.ts
897
899
  import { realpathSync } from "node:fs";
@@ -28,8 +28,8 @@ var UBUNTU_2604_X64 = [
28
28
  },
29
29
  {
30
30
  requirement: { id: "arangodb", version: "3.11.14" },
31
- check: `arangod --version 2>/dev/null | head -1 | grep -q '3.11.14'`,
32
- install: `tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT; ` + `curl -fsSL 'https://download.arangodb.com/arangodb311/DEBIAN/amd64/arangodb3_3.11.14-1_amd64.deb' -o "$tmp/arangodb.deb"; ` + `echo "${ARANGO_SHA256} $tmp/arangodb.deb" | sha256sum -c -; ` + `DEBIAN_FRONTEND=noninteractive dpkg -i "$tmp/arangodb.deb" >/dev/null 2>&1 || ` + `DEBIAN_FRONTEND=noninteractive apt-get -y -f install`
31
+ check: `arangod --version 2>/dev/null | head -1 | grep -q '3.11.14' && ` + `! systemctl is-active --quiet arangodb3.service && ` + `! systemctl is-enabled --quiet arangodb3.service`,
32
+ install: `tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT; ` + `curl -fsSL 'https://download.arangodb.com/arangodb311/DEBIAN/amd64/arangodb3_3.11.14-1_amd64.deb' -o "$tmp/arangodb.deb"; ` + `echo "${ARANGO_SHA256} $tmp/arangodb.deb" | sha256sum -c -; ` + `DEBIAN_FRONTEND=noninteractive dpkg -i "$tmp/arangodb.deb" >/dev/null 2>&1 || ` + `DEBIAN_FRONTEND=noninteractive apt-get -y -f install; ` + `systemctl disable --now arangodb3.service`
33
33
  },
34
34
  {
35
35
  requirement: { id: "cloudflared", version: "2026.7.3" },
package/dist/software.js CHANGED
@@ -28,8 +28,8 @@ var UBUNTU_2604_X64 = [
28
28
  },
29
29
  {
30
30
  requirement: { id: "arangodb", version: "3.11.14" },
31
- check: `arangod --version 2>/dev/null | head -1 | grep -q '3.11.14'`,
32
- install: `tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT; ` + `curl -fsSL 'https://download.arangodb.com/arangodb311/DEBIAN/amd64/arangodb3_3.11.14-1_amd64.deb' -o "$tmp/arangodb.deb"; ` + `echo "${ARANGO_SHA256} $tmp/arangodb.deb" | sha256sum -c -; ` + `DEBIAN_FRONTEND=noninteractive dpkg -i "$tmp/arangodb.deb" >/dev/null 2>&1 || ` + `DEBIAN_FRONTEND=noninteractive apt-get -y -f install`
31
+ check: `arangod --version 2>/dev/null | head -1 | grep -q '3.11.14' && ` + `! systemctl is-active --quiet arangodb3.service && ` + `! systemctl is-enabled --quiet arangodb3.service`,
32
+ install: `tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT; ` + `curl -fsSL 'https://download.arangodb.com/arangodb311/DEBIAN/amd64/arangodb3_3.11.14-1_amd64.deb' -o "$tmp/arangodb.deb"; ` + `echo "${ARANGO_SHA256} $tmp/arangodb.deb" | sha256sum -c -; ` + `DEBIAN_FRONTEND=noninteractive dpkg -i "$tmp/arangodb.deb" >/dev/null 2>&1 || ` + `DEBIAN_FRONTEND=noninteractive apt-get -y -f install; ` + `systemctl disable --now arangodb3.service`
33
33
  },
34
34
  {
35
35
  requirement: { id: "cloudflared", version: "2026.7.3" },
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.38";
2
+ export declare const VERSION = "0.1.40";
package/package.json CHANGED
@@ -1,23 +1,23 @@
1
1
  {
2
2
  "name": "@forgezero/agent",
3
- "version": "0.1.38",
3
+ "version": "0.1.40",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "check": "tsc --noEmit",
7
7
  "prebuild": "rm -rf dist",
8
- "build": "bun build src/index.ts --outfile dist/fz-agent.js --target bun --format esm --external '@forgezero/access' --external '@forgezero/access/*' --external '@forgezero/runtime' --external '@forgezero/runtime/*' --external '@forgezero/vault' --external '@forgezero/vault/*' && bun build src/cli/index.ts --outfile dist/fz.js --target bun --format esm --external '@forgezero/access' --external '@forgezero/access/*' --external '@forgezero/runtime' --external '@forgezero/runtime/*' --external '@forgezero/vault' --external '@forgezero/vault/*' && 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 src/platform-bootstrap-runtime.ts --root src --outdir dist --target browser --format esm --packages external && bun build src/project-context.ts src/bootstrap.ts src/metal-bootstrap.ts src/cloudflare-bootstrap.ts src/cloudflare-edge.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/credential-schema.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 src/platform-bootstrap-runtime.ts --root src --outdir dist --target browser --format esm --packages external && bun build src/project-context.ts src/bootstrap.ts src/metal-bootstrap.ts src/cloudflare-bootstrap.ts src/cloudflare-edge.ts --root src --outdir dist --target bun --format esm --packages external && tsc --emitDeclarationOnly --declaration --noEmit false --outDir dist",
9
9
  "prepublishOnly": "bun run check && bun run build"
10
10
  },
11
11
  "devDependencies": {
12
12
  "typescript": "^5.6.0",
13
13
  "@types/bun": "latest",
14
14
  "@types/node": "^22.0.0",
15
+ "@forgezero/access": "0.1.2",
15
16
  "@noble/curves": "^2.2.0",
16
17
  "@noble/post-quantum": "^0.6.1"
17
18
  },
18
19
  "dependencies": {
19
- "@forgezero/access": "0.1.1",
20
- "@forgezero/runtime": "0.1.5",
20
+ "@forgezero/runtime": "0.1.6",
21
21
  "@forgezero/vault": "0.1.8",
22
22
  "@noble/curves": "2.2.0",
23
23
  "@noble/hashes": "2.2.0",
@@ -150,6 +150,10 @@
150
150
  "types": "./dist/bootstrap.d.ts",
151
151
  "default": "./dist/bootstrap.js"
152
152
  },
153
+ "./credential-schema": {
154
+ "types": "./dist/credential-schema.d.ts",
155
+ "default": "./dist/credential-schema.js"
156
+ },
153
157
  "./platform-bootstrap-runtime": {
154
158
  "types": "./dist/platform-bootstrap-runtime.d.ts",
155
159
  "default": "./dist/platform-bootstrap-runtime.js"
@@ -1,35 +0,0 @@
1
- import { type AgentIdentity } from '@forgezero/runtime/ssh-agent';
2
- /**
3
- * Choosing an SSH key for custody, safely.
4
- *
5
- * A real agent is not a clean room. It commonly holds forwarded keys whose
6
- * upstream connection is gone, and confirm-on-use keys that wait for a human at
7
- * a terminal nobody is sitting at. Neither fails — both **hang**, which during a
8
- * genesis ceremony looks exactly like the platform being broken.
9
- *
10
- * So every key is probed under a timeout before it is offered, and the one
11
- * actually chosen is proved deterministic before it is trusted with a share. An
12
- * agent that signs differently twice would seal a share that can never be
13
- * reopened, and that failure would surface only during recovery.
14
- */
15
- export declare const PROBE_TIMEOUT_MS = 2000;
16
- export interface UsableIdentity extends AgentIdentity {
17
- /** Milliseconds the agent took to sign. Slow keys are usually forwarded. */
18
- responseMs: number;
19
- }
20
- /**
21
- * Every Ed25519 key in the agent that actually responds.
22
- *
23
- * Returns an empty array rather than throwing when the agent holds nothing
24
- * usable — the caller has a better error to give than this function does.
25
- */
26
- export declare function usableIdentities(socketPath?: string): Promise<UsableIdentity[]>;
27
- /**
28
- * Derive the custody key for a chosen identity, having proved it reproduces.
29
- *
30
- * `assertDeterministic` signs twice and compares. It costs one extra signature
31
- * and removes the only failure mode that is invisible until recovery.
32
- */
33
- export declare function custodyKeyFor(identity: AgentIdentity, socketPath?: string): Promise<Uint8Array>;
34
- /** Match a key by fingerprint prefix, comment, or 1-based index. */
35
- export declare function selectIdentity(identities: UsableIdentity[], selector: string): UsableIdentity | null;