@forgezero/agent 0.1.53 → 0.1.56

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.
@@ -4,6 +4,7 @@ import {
4
4
  accessSync,
5
5
  chmodSync,
6
6
  copyFileSync,
7
+ existsSync,
7
8
  mkdtempSync,
8
9
  mkdirSync,
9
10
  readFileSync,
@@ -24,6 +25,7 @@ var OS_CATALOG = [
24
25
  ];
25
26
  var SOFTWARE_CATALOG = [
26
27
  { id: "bun", version: "1.3.14", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
28
+ { id: "docker", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
27
29
  { id: "nginx", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
28
30
  { id: "arangodb", version: "3.11.14", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
29
31
  { id: "cloudflared", version: "2026.7.3", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
@@ -33,6 +35,7 @@ var SOFTWARE_CATALOG = [
33
35
  ];
34
36
  var UBUNTU_2604_X64 = [
35
37
  { requirement: { id: "bun", version: "1.3.14" } },
38
+ { requirement: { id: "docker", version: "ubuntu-26.04" } },
36
39
  { requirement: { id: "nginx", version: "ubuntu-26.04" } },
37
40
  { requirement: { id: "arangodb", version: "3.11.14" } },
38
41
  { requirement: { id: "cloudflared", version: "2026.7.3" } },
@@ -41,6 +44,15 @@ var UBUNTU_2604_X64 = [
41
44
  { requirement: { id: "openssh-client", version: "ubuntu-26.04" } }
42
45
  ];
43
46
  var path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
47
+ var DOCKER_DAEMON_PATH = "/etc/docker/daemon.json";
48
+ var DOCKER_DAEMON_CONFIG = `${JSON.stringify({
49
+ "data-root": "/var/lib/docker",
50
+ "storage-driver": "overlay2",
51
+ "live-restore": true,
52
+ "log-driver": "local",
53
+ "log-opts": { "max-size": "10m", "max-file": "3" }
54
+ }, null, 2)}
55
+ `;
44
56
  var softwareSpawnFailure = (cause, argv) => cause.code === "ENOENT" ? { exitCode: 127, output: `executable is absent: ${argv[0]}` } : undefined;
45
57
  var runSoftwareCommand = async (argv, env = {}) => {
46
58
  let child;
@@ -83,6 +95,15 @@ async function executeSoftwareOperation(operation) {
83
95
  if (operation.kind === "check") {
84
96
  if (software === "bun")
85
97
  return run(["/usr/local/bin/bun", "--version"]).then((r) => ({ ...r, exitCode: successful(r, /^1\.3\.14\s*$/m) ? 0 : 1 }));
98
+ if (software === "docker") {
99
+ const binary = await run(["/usr/bin/docker", "--version"]);
100
+ if (!successful(binary, /Docker version/))
101
+ return { ...binary, exitCode: 1 };
102
+ if (!existsSync(DOCKER_DAEMON_PATH) || readFileSync(DOCKER_DAEMON_PATH, "utf8") !== DOCKER_DAEMON_CONFIG)
103
+ return { exitCode: 1, output: "Docker daemon policy is absent or differs from the reviewed ForgeZero policy" };
104
+ const active = await run(["/usr/bin/systemctl", "is-active", "--quiet", "docker.service"]);
105
+ return active.exitCode === 0 ? { exitCode: 0, output: binary.output } : active;
106
+ }
86
107
  if (software === "nginx") {
87
108
  const binary = await run(["/usr/sbin/nginx", "-v"]);
88
109
  return binary.exitCode === 0 ? run(["/usr/bin/systemctl", "is-active", "--quiet", "nginx.service"]) : binary;
@@ -115,11 +136,22 @@ async function executeSoftwareOperation(operation) {
115
136
  return { exitCode: 1, output: cause instanceof Error ? cause.message : String(cause) };
116
137
  }
117
138
  }
118
- if (software === "nginx" || software === "ufw" || software === "openssh-client") {
119
- const installed = await aptInstall(software === "openssh-client" ? "openssh-client" : software);
120
- if (installed.exitCode !== 0 || software !== "nginx")
139
+ if (software === "docker" || software === "nginx" || software === "ufw" || software === "openssh-client") {
140
+ const packageName = software === "openssh-client" ? "openssh-client" : software === "docker" ? "docker.io" : software;
141
+ const installed = await aptInstall(packageName);
142
+ if (installed.exitCode !== 0 || software !== "nginx" && software !== "docker")
121
143
  return installed;
122
- return run(["/usr/bin/systemctl", "enable", "--now", "nginx.service"]);
144
+ if (software === "docker") {
145
+ mkdirSync("/etc/docker", { recursive: true, mode: 493 });
146
+ if (existsSync(DOCKER_DAEMON_PATH) && readFileSync(DOCKER_DAEMON_PATH, "utf8") !== DOCKER_DAEMON_CONFIG) {
147
+ return { exitCode: 2, output: "refusing to overwrite an existing Docker daemon configuration that differs from the reviewed ForgeZero policy" };
148
+ }
149
+ if (!existsSync(DOCKER_DAEMON_PATH))
150
+ writeFileSync(DOCKER_DAEMON_PATH, DOCKER_DAEMON_CONFIG, { mode: 420, flag: "wx" });
151
+ const enabled = await run(["/usr/bin/systemctl", "enable", "docker.service"]);
152
+ return enabled.exitCode === 0 ? run(["/usr/bin/systemctl", "restart", "docker.service"]) : enabled;
153
+ }
154
+ return run(["/usr/bin/systemctl", "enable", "--now", `${software}.service`]);
123
155
  }
124
156
  const directory = mkdtempSync(join(tmpdir(), "forgezero-software-"));
125
157
  try {
@@ -204,7 +236,7 @@ function validateSoftwareRequirements(value, _options = {}) {
204
236
  if (Object.keys(row).some((key) => key !== "id" && key !== "version")) {
205
237
  throw new Error("software requirement contains an unknown field");
206
238
  }
207
- if (!["bun", "nginx", "arangodb", "cloudflared", "cloudflare-warp", "ufw", "openssh-client"].includes(String(row.id)) || typeof row.version !== "string" || !/^[A-Za-z0-9][A-Za-z0-9.-]{0,31}$/.test(row.version)) {
239
+ if (!["bun", "docker", "nginx", "arangodb", "cloudflared", "cloudflare-warp", "ufw", "openssh-client"].includes(String(row.id)) || typeof row.version !== "string" || !/^[A-Za-z0-9][A-Za-z0-9.-]{0,31}$/.test(row.version)) {
208
240
  throw new Error("software requirement coordinate is invalid");
209
241
  }
210
242
  const requirement = { id: row.id, version: row.version };
@@ -678,7 +710,7 @@ function phasePipeline(definition, phase, profile, executeRelease = false) {
678
710
 
679
711
  // src/deploy-file.ts
680
712
  import { createHash as createHash2 } from "node:crypto";
681
- import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
713
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
682
714
  import { basename, join as join2 } from "node:path";
683
715
  var DEPLOY_FILE = ".fz/deploy.json";
684
716
  var DEPLOY_TODO_PREFIX = "ForgeZero pipeline TODO:";
@@ -699,17 +731,17 @@ var safeName = (value) => {
699
731
  };
700
732
  function packageHints(root) {
701
733
  const packagePath = join2(root, "package.json");
702
- if (!existsSync(packagePath))
703
- return { bun: existsSync(join2(root, "bun.lock")) };
734
+ if (!existsSync2(packagePath))
735
+ return { bun: existsSync2(join2(root, "bun.lock")) };
704
736
  try {
705
737
  const manifest = JSON.parse(readFileSync2(packagePath, "utf8"));
706
738
  return {
707
739
  name: typeof manifest.name === "string" ? manifest.name : undefined,
708
740
  build: typeof manifest.scripts?.build === "string" ? ["bun", "run", "build"] : undefined,
709
- bun: existsSync(join2(root, "bun.lock")) || existsSync(join2(root, "bun.lockb"))
741
+ bun: existsSync2(join2(root, "bun.lock")) || existsSync2(join2(root, "bun.lockb"))
710
742
  };
711
743
  } catch {
712
- return { bun: existsSync(join2(root, "bun.lock")) };
744
+ return { bun: existsSync2(join2(root, "bun.lock")) };
713
745
  }
714
746
  }
715
747
  var blocker = (instruction) => ["fz-agent", "pipeline-todo", `${DEPLOY_TODO_PREFIX} ${instruction}`];
@@ -746,7 +778,7 @@ function defaultDeployFile(root, options = {}) {
746
778
  }
747
779
  function inspectDeployFile(root, options = {}) {
748
780
  const path2 = join2(root, DEPLOY_FILE);
749
- if (!existsSync(path2))
781
+ if (!existsSync2(path2))
750
782
  throw new Error(`${DEPLOY_FILE} does not exist; run \`fz deploy init\`.`);
751
783
  const raw = JSON.parse(readFileSync2(path2, "utf8"));
752
784
  const definition = parseDeployDefinition(raw, options);
@@ -772,7 +804,7 @@ function inspectDeployFile(root, options = {}) {
772
804
  }
773
805
  function initializeDeployFile(root, options = {}) {
774
806
  const path2 = join2(root, DEPLOY_FILE);
775
- if (existsSync(path2) && !options.force) {
807
+ if (existsSync2(path2) && !options.force) {
776
808
  throw new Error(`${DEPLOY_FILE} already exists; use --force only when replacing it deliberately.`);
777
809
  }
778
810
  const raw = defaultDeployFile(root, options);
@@ -0,0 +1,65 @@
1
+ import type { Condition, JsonObject, JsonValue } from './deploy';
2
+ import { type DeploymentPlan, type PlannedStage, type PlannedStep } from './deploy-plan';
3
+ export type PlanStepStatus = 'pending' | 'running' | 'succeeded' | 'failed' | 'skipped' | 'compensated' | 'manual-intervention';
4
+ export type ActionFailureKind = 'network' | 'timeout' | 'provider-unavailable' | 'rate-limited' | 'conflict' | 'refused' | 'failed';
5
+ export declare class DeploymentActionError extends Error {
6
+ readonly kind: ActionFailureKind;
7
+ constructor(kind: ActionFailureKind, message: string);
8
+ }
9
+ export interface ActionExecutionResult {
10
+ outputs?: JsonObject;
11
+ changed?: boolean;
12
+ evidence?: JsonObject;
13
+ }
14
+ export interface ActionContext {
15
+ plan: DeploymentPlan;
16
+ workflow: string;
17
+ stage: PlannedStage;
18
+ step: PlannedStep;
19
+ inputs: Readonly<Record<string, JsonValue>>;
20
+ resolved: JsonObject;
21
+ attempt: number;
22
+ signal: AbortSignal;
23
+ }
24
+ export type ActionHandler = (context: ActionContext) => Promise<ActionExecutionResult>;
25
+ export interface StepExecutionRecord {
26
+ id: string;
27
+ stage: string;
28
+ action: string;
29
+ status: PlanStepStatus;
30
+ attempts: number;
31
+ startedAtTs?: number;
32
+ finishedAtTs?: number;
33
+ outputs: JsonObject;
34
+ changed: boolean;
35
+ error?: {
36
+ kind: ActionFailureKind;
37
+ message: string;
38
+ };
39
+ evidence?: JsonObject;
40
+ }
41
+ export interface PlanRunResult {
42
+ workflow: string;
43
+ ok: boolean;
44
+ status: 'succeeded' | 'failed' | 'manual-intervention';
45
+ steps: readonly StepExecutionRecord[];
46
+ }
47
+ interface EvaluationState {
48
+ inputs: Readonly<Record<string, JsonValue>>;
49
+ steps: Map<string, StepExecutionRecord>;
50
+ plan: DeploymentPlan;
51
+ }
52
+ export declare function evaluateCondition(value: Condition, state: EvaluationState): boolean;
53
+ export interface RunDeploymentPlanOptions {
54
+ plan: DeploymentPlan | unknown;
55
+ workflow: string;
56
+ inputs?: Readonly<Record<string, JsonValue>>;
57
+ actions: Readonly<Record<string, ActionHandler>>;
58
+ now?: () => number;
59
+ sleep?: (ms: number) => Promise<void>;
60
+ lock?: <T>(group: string, limit: number, run: () => Promise<T>) => Promise<T>;
61
+ /** Control-plane placement filter; false records a deterministic skip. */
62
+ shouldRun?: (step: PlannedStep) => boolean;
63
+ }
64
+ export declare function runDeploymentPlan(options: RunDeploymentPlanOptions): Promise<PlanRunResult>;
65
+ export {};