/** * CI/CD on the compute, through the agent — and never around it. * * A deploy needs two things at the same time: the tenant's secrets, and the * right to run commands on their machine. Every hosted CI answers that by * copying the secrets INTO the runner, which is why a compromised build step is * a compromised production credential everywhere that model is used. * * The agent already holds the secrets and already runs on the box. So the build * happens here, the secrets are handed to the step's process and to nothing * else, and nothing is ever written where a later step could read it. * * ## Attestation gates the run, not the read * * Reading a secret is gated by the socket's filesystem permissions, and that is * proportionate — an application on this box is meant to read its own config. * RUNNING A PIPELINE is different: it executes attacker-chosen commands if the * pipeline definition is attacker-chosen, so the platform has to know it is * talking to the machine it thinks it is. * * On an attested compute that is a hardware report. On one that is merely * enrolled it is the node's hybrid signature, which proves possession of a key * the platform issued and NOT what the machine is running. Both are honest * postures and the difference is recorded on the run, so a tenant can require * the stronger one — and `requireAttestation` is what makes that a refusal * rather than a preference. * * ## Secrets never reach disk, and never reach the log * * They are passed as environment to the spawned process and redacted from * captured output on the way back. The redaction is a last line, not the * mechanism: a step that deliberately prints a secret has already been given it. * What redaction buys is that a step which prints its environment while * debugging does not put a production key in a log somebody ships to a vendor. */ export type StepOutcome = 'ok' | 'failed' | 'skipped'; export interface PipelineStep { name: string; /** Exact executable and arguments. No shell parses, expands or joins these values. */ exec: readonly string[]; /** Secret names this step needs. Nothing it does not name is in its env. */ secrets?: readonly string[]; /** Run even when an earlier step failed — cleanup, teardown, notifications. */ always?: boolean; timeoutMs?: number; } export interface Pipeline { name: string; steps: readonly PipelineStep[]; /** * Refuse to run at all unless the agent can produce a hardware attestation. * * A tenant deploying something that matters sets this. It is the difference * between "a machine holding our node key ran this" and "a machine we can * prove is running the image we expect ran this". */ requireAttestation?: boolean; } export interface StepResult { name: string; outcome: StepOutcome; exitCode: number | null; /** Combined output, with every secret value replaced. */ log: string; durationMs: number; } export interface RunResult { pipeline: string; ok: boolean; /** How the machine authenticated itself for this run. Recorded, not inferred. */ assurance: 'attested' | 'enrolled'; steps: readonly StepResult[]; } export declare class PipelineError extends Error { readonly code: 'ATTESTATION_REQUIRED' | 'ATTESTATION_FAILED' | 'SECRET_MISSING'; constructor(code: 'ATTESTATION_REQUIRED' | 'ATTESTATION_FAILED' | 'SECRET_MISSING', message: string); } export interface RunOptions { pipeline: Pipeline; /** Reads one secret. The agent's replica, so this is a memory lookup. */ secret(name: string): Promise; /** Spawns a step. Injected, so a pipeline is testable without a shell. */ exec(input: { argv: readonly string[]; env: Record; timeoutMs?: number; }): Promise<{ exitCode: number; output: string; }>; /** * Produces a hardware report, or throws. The agent's `attest` operation, * which REFUSES when no source is configured rather than returning something * attestation-shaped. */ attest?: () => Promise<{ report: string; source: string; }>; now?: () => number; /** Receives each redacted terminal step as soon as it is durable locally. */ onStep?: (result: StepResult) => Promise; } /** * Replace every secret value wherever it appears. * * Longest first, so a value that contains another value does not leave the * shorter one's suffix exposed after the longer one is replaced. */ export declare function redact(text: string, values: readonly string[]): string; export declare function runPipeline(options: RunOptions): Promise;