import { type AttachOursClientOptions, type OursClient } from '@ours.network/sdk/client'; import { type LockDeps } from './atomic-file.js'; import type { ResolvedRole } from './config.js'; export type ReservationKind = 'role' | 'identity'; export interface Reservation { kind: ReservationKind; name: string; } export declare class CreationConflictError extends Error { constructor(message: string); } /** * Artifacts a transaction created, newest last. Rollback walks this in reverse, * and may only delete what THIS transaction made — an object that already * existed is never touched. */ export interface JournalEntry { stage: string; undo(): void | Promise; } export interface CreationDeps { lock?: LockDeps; log?(line: string): void; /** Reserve the ours identity name. Injectable so tests need no daemon. */ identityRegistry?: IdentityRegistry; /** Verify/create the ours identity. Injectable so tests need no daemon. */ identityProvisioner?: IdentityProvisioner; /** Descriptive progress around the existing transaction; never a second workflow. */ onStage?(stage: CreationCoreStage, evidence?: Record): void; } export type CreationCoreStage = 'reserving' | 'checking_identity' | 'writing_role' | 'registering_supervisor' | 'starting_temp'; /** * The contract the ours daemon must satisfy for identity names to be reserved * atomically across ALL of its clients, not just across fleet processes. * * `check-then-create` is not atomic across processes, which is the whole point: * two spawns can both observe a free identity name and both create it. The * daemon is the only component that sees every client, so only the daemon can * make the reservation authoritative. */ export interface IdentityRegistry { /** Claim `name`. Returns false if it is already taken or reserved. */ reserve(name: string): Promise; /** Give the claim back (rollback). Must be safe to call on an unheld name. */ release(name: string): Promise; } /** * Host-local identity reservation: atomic across every ours-fleet process on * this host, because it is taken under the same host-wide creation lock as the * role name. * * It is NOT atomic against other clients of the same ours daemon — another tool * creating the identity between our reservation and our creation would still * win. Closing that needs a reserve/commit/release operation in the daemon * itself; see the release notes. */ export declare const hostLocalIdentityRegistry: IdentityRegistry; export interface CreationTransaction { /** Record an artifact this transaction created, with how to undo it. */ record(entry: JournalEntry): void; /** Stages recorded so far, in order. */ readonly stages: string[]; } /** * Run `body` inside a creation transaction. * * Under one host-wide lock: both names are reserved, then `body` builds the * role. If anything throws, every recorded stage is undone in reverse order and * both reservations are released, so the names can be reused immediately. On * success the reservations are released too — the role's own config and state * are the durable record from then on. * * Rollback errors are collected and reported, never allowed to hide the failure * that caused the rollback. */ export declare function withCreationTransaction(names: { role: string; identity: string; }, body: (tx: CreationTransaction) => Promise, deps?: CreationDeps): Promise; /** Forget reservations left behind by a process that died mid-transaction. */ export declare function clearStaleReservations(olderThanMs?: number, now?: number): number; /** * Identity provisioning. The fleet must know — before the harness starts * — whether the role's identity exists, and create it when it does not. * * `exists()` is answered through the typed SDK daemon inventory. `create()` is * intentionally still a seam: fleet preserves application-owned identity * creation/binding, especially the session-owned lifetime of temporary roles. * Its absence is reported rather than papered over. */ export interface IdentityProvisioner { /** Does this identity exist? `unknown` when the daemon could not be asked. */ exists(name: string): Promise; /** Create it, publishing bio/persona through the same path. Absent = cannot. */ create?(name: string, profile: IdentityProvisionProfile): Promise; /** * Undo a `create` during rollback. Only ever called for an identity THIS * transaction created; absent means "cannot", and the orphan is reported. */ remove?(name: string): Promise; } export interface IdentityProvisionProfile { bio?: string; persona?: string; /** Permanent role identities are locally discoverable; control identities opt out. */ exposeLocal?: boolean; /** Permanent role identities accept sibling introductions; control identities opt out. */ localAutoAccept?: boolean; } export type IdentityGuarantee = { state: 'verified'; evidence: 'verified'; detail: string; } | { state: 'created'; evidence: 'missing'; detail: string; } | { state: 'unverified'; evidence: 'missing' | 'unknown'; detail: string; }; /** Reconcile every permanent identity a role's supervisor owns before launch. */ export declare function reconcilePermanentRoleIdentities(role: ResolvedRole, provisioner?: IdentityProvisioner, log?: (line: string) => void, knownRoleGuarantee?: IdentityGuarantee['state']): Promise<'verified' | 'created'>; /** * Establish the identity before the role's service is enabled. * * Returns what was actually GUARANTEED, so the generated briefing can say * something true instead of asserting a "predefined" identity nobody checked — * the failure a real agent hit on its first boot, having been told to bind an * identity that did not exist. */ export declare function ensureIdentity(name: string, profile: IdentityProvisionProfile, provisioner: IdentityProvisioner | undefined, log?: (line: string) => void): Promise; /** * Ask the running ours daemon whether an identity exists through SDK 2's * coherence-checking attach path. Answers `unknown` rather than guessing when * the daemon cannot be reached — an unreachable daemon is not evidence that * the identity is missing. * * Permanent identities can be created before a harness starts. Creation binds * the provisioning lease, so it is always released before the role or channel * takes ownership. Temporary callers disable creation: a temporary identity is * owned and deleted by the exact connector lease that created it, and there is * no safe lease-transfer operation in the current SDK. */ type IdentityInventoryClient = Pick & Partial>; export interface DaemonIdentityProvisionerOptions { /** False for temporary roles/watchdogs whose connector must own creation. */ createPermanent?: boolean; /** True only for inventory checks performed by a temporary lifecycle. */ acceptTemporaryExisting?: boolean; } export declare function daemonIdentityProvisioner(env?: NodeJS.ProcessEnv, attachClient?: (options: AttachOursClientOptions) => Promise, options?: DaemonIdentityProvisionerOptions): IdentityProvisioner; /** Inventory-only variant for session-owned temporary identity lifecycles. */ export declare function daemonIdentityInventoryProvisioner(env?: NodeJS.ProcessEnv, attachClient?: (options: AttachOursClientOptions) => Promise): IdentityProvisioner; /** Atomically write a bare Agent file, journalling it for rollback. */ export declare function writeRoleFile(tx: CreationTransaction, file: string, contents: string): void; /** Where a setting's effective value came from. */ export type ProvenanceSource = 'cli' | 'agent-template' | 'fleet-default' | 'caller-role' | 'built-in'; export interface ProvenanceEntry { value: unknown; source: ProvenanceSource; } export interface CreationProvenance { version: 1; /** The command that created the role, without its arguments. */ command: string; fleetVersion: string; /** * Build id of the artifact that created the role. Two installs can report the * same fleetVersion and resolve the same fleet.yaml differently, so the semver * alone does not identify what actually ran. `unknown` for a pre-provenance build. */ fleetBuild: string; createdAt: string; lifetime: 'permanent' | 'temporary'; role: string; /** Additive correlation for non-CLI creation surfaces; never contains request data. */ surface?: 'cli' | 'web' | 'agent'; creationActionId?: string; /** Managed role which requested creation through its supervisor proxy. */ callerRole?: string; /** Effective settings, each tagged with where its value came from. */ settings: Record; } export declare const CREATION_PROVENANCE_FILE = "creation.json"; /** * Record HOW a role was created, so nobody has to remember. * * Six months on, "why does this role have `approval: allow`?" is unanswerable: * the resolved config shows the value but not whether an operator typed it, a * fleet default supplied it, or it fell through to a built-in. Those have very * different implications for whether it is safe to change. * * Deliberately excluded: `env`, `bio`, `persona`, and `harness_options`. The * first two can carry credentials, and this file exists to be read — it must * never become a place secrets accumulate. */ export declare function buildProvenance(o: { role: string; lifetime: 'permanent' | 'temporary'; fleetVersion: string; now?: Date; settings: Record; surface?: 'cli' | 'web' | 'agent'; creationActionId?: string; callerRole?: string; }): CreationProvenance; /** Write the provenance record atomically, before the role is started. */ export declare function writeProvenance(stateDir: string, p: CreationProvenance): void; /** Read the provenance a role was created with, if it has one. */ export declare function readProvenance(stateDir: string): CreationProvenance | undefined; /** * One line when the artifact reporting on a role is not the one that created it. * * A role carries its creating build in creation.json. If a different build now * manages it, the role's fleet.yaml may mean something different than it did at * creation — and because both can report the same semver, nothing else says so. */ export declare function creationBuildNote(p: CreationProvenance): string | undefined; /** One concise line per non-built-in setting, for the post-creation summary. */ export declare function formatProvenance(p: CreationProvenance): string[]; /** Classify one setting: an explicit CLI value, a fleet default, or built-in. */ export declare function provenanceOf(cliValue: unknown, fleetDefault: unknown, builtIn?: unknown): ProvenanceEntry; export {};