/** * How a machine becomes one that can run the agent — as DATA, with no transport. * * There are two ways this ever happens and they must not be two implementations: * * fz agent install an operator, on the box, executing locally * the platform enrolling a compute, executing the same steps over SSH * * So nothing here connects to anything. A check is a command string plus a * predicate over its output; a plan is an ordered list of them. `fz` runs them * with `Bun.spawn`, the control plane runs them through an SSH session, and * because it is the same list, a machine provisioned by hand and one provisioned * by the platform end up identical. * * This was briefly two implementations — a `Check`-based preflight inside the * API that could run remotely, and a separate local-only planner in the CLI that * called `existsSync` directly and could not. Two answers to "is this box ready" * is one answer nobody can trust, and the local one would have been the one that * drifted, because it is the one somebody runs while debugging. * * ## Every command must be non-interactive and repeatable * * Non-interactive because there is nobody at the terminal on the SSH path, and a * prompt there does not fail — it HANGS, which is much worse. Repeatable because * enrolment is retried: a half-provisioned box that cannot be re-provisioned is * a box somebody rebuilds by hand. */ import { type BootstrapDeploymentIntent } from './bootstrap-deployment-intent'; export interface Check { /** Stable display label; execution uses the typed operation. */ command: string; operation: CapabilityOperation; /** True when the output proves the property. */ satisfied: (stdout: string, exitCode: number) => boolean; /** Shown when it fails. Says what to do, not merely what is wrong. */ remedy: string; } export type CapabilityOperation = { kind: 'path-exists'; path: string; nodeType: 'file' | 'directory'; } | { kind: 'version'; argv: readonly string[]; }; /** * Semver-ish floor check tolerant of `v` prefixes and build suffixes. * * Version output is not a standard. `v1.2.3`, `1.2.3-rc1` and * `bun 1.2.3 (abc123)` all mean the same thing to a human and nothing to a * naive comparison, and a preflight that rejects a good box is a preflight * people learn to skip. */ export declare function atLeast(version: string, floor: string): boolean; export type AgentMode = 'attested' | 'enrolled'; /** * What the machine can prove about itself. * * Expressed as checks rather than as filesystem calls precisely so the answer is * the same whether it was gathered on the box or over SSH. A control plane that * decided `attested` from something it could only observe locally would be * deciding it from nothing at all. */ export declare const CAPABILITY_CHECKS: Record & { snpGuest: Check; systemd: Check; bun: Check; python: Check; }; export type CapabilityId = 'snpGuest' | 'systemd' | 'bun' | 'python'; /** The answers, however they were gathered. */ export type Capabilities = Record; /** * Which posture the agent runs in. * * Derived, never chosen. An operator picking `attested` would make attestation * a claim, and a claim is the one thing an attestation must not be. */ export declare const modeFor: (capabilities: Pick) => AgentMode; export declare const reasonFor: (mode: AgentMode) => string; export interface UnitOptions { mode: AgentMode; socketPath: string; /** Internal blue/green handover unit; never enables outbound claim intake. */ handoverCandidate?: boolean; /** Internal exact backend override used by the candidate generation. */ backendSocketPath?: string; /** Legacy/dev fallback only. Production uses `seedCredentialPath`. */ seedPath?: string; /** Host-bound encrypted credential loaded by systemd for this unit only. */ seedCredentialPath?: string; gitCredentialPath?: string; /** Non-secret OpenSSH public half reported during compute enrolment. */ gitPublicKeyPath?: string; /** Generate a unique Ed25519 deploy identity on this machine when absent. */ generateGitIdentity?: boolean; controlSocketPath?: string; repository?: string; branch?: string; /** Attended release-generation-one artifact; both coordinates are required together. */ bootstrapBundlePath?: string; bootstrapBundleManifestPath?: string; /** Exact reviewed workload identity; never derived from reusable guest enrolment. */ bootstrapDeploymentIntent?: BootstrapDeploymentIntent; profile?: string; deployRoot?: string; /** Stable deployment identity for a privileged runtime profile. */ deploymentKey?: string; publicApiUrl?: string; /** Agent-only platform authority state; never exposed as a workload input. */ platformSecretAuthorityState?: 'bootstrap' | 'activating' | 'managed' | 'recovery'; /** Non-secret phase values, named explicitly instead of inheriting the unit environment. */ deploymentEnvironment?: Record; /** Pipeline secret name -> encrypted systemd credential source. */ deploymentCredentials?: Record; /** Enable authenticated outbound deployment claims after enrolment. */ pullDeployments?: boolean; /** Production/deployed service boundary. Omit for an ordinary local developer install. */ enforceEgress?: boolean; /** The immediately preceding bootstrap/enrol phase already installed host packages. */ egressPrerequisitesReady?: boolean; /** Provisioning-owned runner health endpoints; repository commands cannot change them. */ runnerLoopbackPorts?: readonly number[]; /** Local platform API ports needed by the credential-bearing Agent. */ agentLoopbackPorts?: readonly number[]; /** Vetted public TCP surface for project commands. Defaults to HTTPS only. */ runnerPublicTcpPorts?: readonly number[]; /** Enable PQ-authenticated lifecycle claims through a constrained root helper. */ pullMigrations?: boolean; /** Claim API-owned SSH bootstrap jobs; the private key stays a systemd credential on this runner. */ pullBootstrap?: boolean; bootstrapSshCredentialPath?: string; /** Optional imported private key. When absent the runner generates its own. */ bootstrapSshSourcePath?: string; /** Non-secret authorized_keys line reported during enrolment. */ bootstrapSshPublicKeyPath?: string; bootstrapTargetTelemetryEndpoint?: string; /** Root-owned declarative lifecycle profile; never supplied by a migration claim. */ lifecycleProfilePath?: string; lifecycleHelperSocketPath?: string; /** Optional cross-network overlay. All three values are required together. */ warpOrganization?: string; warpClientIdCredentialPath?: string; warpClientSecretCredentialPath?: string; /** Non-secret coordinates bound through the PQ-signed enrolment request. */ cloudflareAccountId?: string; cloudflareTunnelId?: string; cloudflareVirtualNetworkId?: string; cloudflareWarpPolicyId?: string; /** Where `fz-agent` ended up. `bun add -g` puts it on PATH. */ binPath?: string; /** Package-owned binary copied to binPath before hardened units start. */ sourceBinPath?: string; user?: string; apiUrl?: string; project?: string; environment?: string; /** One-time direct-compute enrolment, consumed before the long-running agent. */ enrolTokenSourcePath?: string; enrolTokenCredentialPath?: string; enrolStatePath?: string; nodeLabel?: string; /** Stable API ingress identity bound by the signed one-time enrolment. */ nodeHostname?: string; /** Provisioning-owned public HTTPS OTLP coordinate. Production fails closed when absent. */ telemetryEndpoint?: string; } export declare const DEPLOYMENT_RUNNER_USER = "forgezero-runner"; export declare const APPLICATION_RUNTIME_USER = "forgezero-app"; export declare const DEPLOYMENT_GROUP = "forgezero-deploy"; export declare const VAULT_GROUP = "forgezero-vault"; export declare const LIFECYCLE_GROUP = "forgezero-lifecycle"; export declare const DEPLOYMENT_RUNNER_UNIT_PATH = "/etc/systemd/system/forgezero-deploy-runner.service"; export declare const AGENT_SOCKET_UNIT_PATH = "/etc/systemd/system/forgezero-agent.socket"; export declare const AGENT_SOCKET_PROXY_UNIT_PATH = "/etc/systemd/system/forgezero-agent-proxy.service"; export declare const AGENT_CANDIDATE_UNIT_PATH = "/etc/systemd/system/forgezero-agent-candidate.service"; export declare const DEPLOYMENT_RUNNER_SOCKET = "/run/forgezero-deploy/runner.sock"; export declare const ENROLMENT_UNIT_PATH = "/etc/systemd/system/forgezero-agent-enrol.service"; export declare const LIFECYCLE_HELPER_UNIT_PATH = "/etc/systemd/system/forgezero-lifecycle-helper.service"; export declare const LIFECYCLE_HELPER_SOCKET = "/run/forgezero-lifecycle/helper.sock"; export declare const WARP_CONFIG_UNIT_PATH = "/etc/systemd/system/forgezero-warp-config.service"; export declare const WARP_SERVICE_DROP_IN_PATH = "/etc/systemd/system/warp-svc.service.d/forgezero.conf"; export declare const AGENT_EGRESS_UNIT_PATH = "/etc/systemd/system/forgezero-agent-egress.service"; export declare const DEFAULT_RUNNER_PUBLIC_TCP_PORTS: readonly [443]; /** Root-owned policy monitor. The credential-bearing Agent is bound to it. */ export declare function agentEgressUnit(options: Pick): string; /** Root may execute only package-owned OS strategies selected by id + version. */ export declare function softwareHelperUnit(options: Pick): string; /** Fixed root boundary for verified, health-gated Agent replacement. */ export declare function agentUpdateHelperUnit(options: Pick): string; /** * PID 1 owns the stable application endpoint. * * New connections wait in the kernel while the credential-bearing Agent drains * and a health-gated replacement starts. This is supervision, not a background * terminal, and it works identically on platform and tenant computes. */ export declare function agentSocketUnit(options: Pick): string; /** * Bun does not support inheriting systemd's listening file descriptor. Keep * PID 1 as the owner of the stable application socket and let systemd's small, * credential-free proxy bridge it to the Agent-owned backend Unix socket. */ export declare function agentSocketProxyUnit(options: Pick): string; export declare function agentBackendSocketPath(publicSocketPath: string): string; /** Stable symlink resolved by systemd-socket-proxyd for every new connection. */ export declare function agentRoutingSocketPath(publicSocketPath: string): string; /** Isolated backend owned only by the verified candidate generation. */ export declare function agentCandidateSocketPath(publicSocketPath: string): string; export declare function warpConfigUnit(options: Pick): string; export declare function warpServiceDropIn(): string; export declare function lifecycleHelperUnit(options: Pick): string; /** * Exchange a tenant-issued one-time capability before the durable agent starts. * * The main unit never names the capability, so deleting the encrypted blob * after success cannot break a later reboot. A durable non-secret binding makes * the oneshot skip; a failed exchange keeps the encrypted token for a retry. */ export declare function agentEnrolmentUnit(options: UnitOptions): string; export declare function deploymentRunnerUnit(options: Pick): string; /** * A systemd unit for the agent. * * The hardening is not decoration. This process holds a node signing key and a * project-scoped vault replica in memory, so the two things worth spending * effort on are keeping that memory out of a core dump and keeping the * filesystem read-only. */ export declare function agentUnit(options: UnitOptions): string; /** * A separately supervised, credential-equivalent candidate. It loads and proves * the durable node binding and RAM Vault replica, but candidate mode never * starts heartbeat, claim, migration, bootstrap or deployment intake. */ export declare function agentCandidateUnit(options: UnitOptions): string; export interface Step { /** Stable display, never executed. */ command: string; /** Closed typed operation executed with native fs and fixed argv only. */ operation: ProvisionOperation; /** What it is for, in an operator's words. */ label: string; /** A step that may fail without failing the provision. */ optional?: boolean; } export interface FixedHostCommand { argv: readonly string[]; acceptedExitCodes?: readonly number[]; } export interface DirectorySpec { path: string; mode: number; owner?: string; group?: string; } export type ProvisionOperation = { kind: 'commands'; commands: readonly FixedHostCommand[]; } | { kind: 'directories'; directories: readonly DirectorySpec[]; } | { kind: 'install-runtime'; source: string; binary: string; version: string; } | { kind: 'ensure-seed'; credential: string; } | { kind: 'ensure-git-identity'; credential: string; publicKey: string; } | { kind: 'ensure-bootstrap-ssh-identity'; credential: string; publicKey: string; source?: string; } | { kind: 'ensure-enrolment'; state: string; source: string; credential: string; } | { kind: 'wait-socket'; path: string; attempts: number; intervalMs: number; } | { kind: 'verify-file'; path: string; } | { kind: 'verify-egress'; deploymentEnabled: boolean; runnerPublicTcpPorts: readonly number[]; runnerLoopbackPorts: readonly number[]; agentLoopbackPorts: readonly number[]; } | { kind: 'verify-resolved-stub'; } | { kind: 'install-warp'; } | { kind: 'verify-warp'; }; export interface ProvisionPlan { mode: AgentMode; reason: string; unitPath: string; unit: string; auxiliaryUnits: readonly { path: string; unit: string; }[]; socketPath: string; user: string; steps: readonly Step[]; } export declare const UNIT_PATH = "/etc/systemd/system/forgezero-agent.service"; /** * Everything that has to happen, in order, on a machine that is going to run * the agent. * * The last two steps are the reason this is not just "write a file and start * it". A unit that starts and immediately exits is `enabled` and * `active (exited)`, which reads as success at a glance — so the plan checks the * service is running AND that the socket applications actually need exists. */ export declare function planProvision(options: UnitOptions): ProvisionPlan;