// The reconcile harness every provider shares. The loop below is identical for postgres, redis, // minio and umami: read the grants the control plane has written, bring each requested resource to // existence through the provider's adapter, seal and hand back the credential, and remove what is // no longer requested. A module writes ONLY the adapter — the per-service half — which is why this // lives in the sdk and the adapter lives in the module (novox/hq ADR 0044). import { readdir, readFile, writeFile, rm } from "node:fs/promises"; import { join } from "node:path"; import type { Grant, Credential } from "../contracts/index.js"; import { seal } from "../primitives/index.js"; /** What a provider implements — the only per-service code. It is handed a Grant and creates (or * removes) the resource in its own software, returning the credential a consumer receives. */ export interface Adapter { create(grant: Grant): Promise; remove(grant: Grant): Promise; } export interface ProvisionerOptions { /** Directory the control plane writes grant requests into and the harness writes credentials to. * Defaults to $GRANTS. */ grants?: string; /** Passphrase a credential is sealed to before it is written. Defaults to $MESH_SEAL_KEY. */ sealKey?: string; /** Reconcile interval in ms. Defaults to 5000. */ everyMs?: number; } export type { Grant, Credential }; /** * Run the reconcile loop for one provided resource. Returns a stop function. Never throws for a * single bad grant — it logs and keeps converging, because one consumer's failure must not stop * the others' provisioning. */ export function runProvisioner(resource: string, adapter: Adapter, opts: ProvisionerOptions = {}): () => void { const dir = opts.grants ?? envOrThrow("GRANTS"); const sealKey = opts.sealKey ?? envOrThrow("MESH_SEAL_KEY"); const everyMs = opts.everyMs ?? 5000; const applied = new Map(); // consumer -> hash of the grant last applied let stopped = false; async function reconcile(): Promise { const requested = await readGrants(dir, resource); const wantById = new Map(requested.map((g) => [g.consumer, g])); // Create or update anything requested whose grant has changed. for (const grant of requested) { const h = hash(grant); if (applied.get(grant.consumer) === h) continue; try { const cred = await adapter.create(grant); await writeSealedCredential(dir, grant, cred, sealKey); applied.set(grant.consumer, h); } catch (err) { console.error(`[provisioner:${resource}] ${grant.consumer}: create failed, will retry: ${err}`); } } // Remove anything applied that is no longer requested. Only the harness's own outputs are // touched — the credential file it wrote — never anything it did not create. for (const consumer of [...applied.keys()]) { if (wantById.has(consumer)) continue; const grant = lastGrant(dir, resource, consumer); try { await adapter.remove(grant); await rm(credentialPath(dir, resource, consumer), { force: true }); applied.delete(consumer); } catch (err) { console.error(`[provisioner:${resource}] ${consumer}: remove failed, will retry: ${err}`); } } } const tick = async (): Promise => { if (stopped) return; try { await reconcile(); } catch (err) { console.error(`[provisioner:${resource}] reconcile error: ${err}`); } if (!stopped) setTimeout(() => void tick(), everyMs); }; void tick(); return () => { stopped = true; }; } // --- grant/credential files --- async function readGrants(dir: string, resource: string): Promise { let names: string[]; try { names = await readdir(dir); } catch { return []; } const out: Grant[] = []; for (const name of names) { if (!name.endsWith(".grant.json")) continue; try { const raw = await readFile(join(dir, name), "utf8"); const g = JSON.parse(raw) as Grant; if (g.resource === resource) out.push(g); } catch (err) { console.error(`[provisioner:${resource}] unreadable grant ${name}: ${err}`); } } return out; } function credentialPath(dir: string, resource: string, consumer: string): string { return join(dir, `${consumer}.${resource}.credential`); } async function writeSealedCredential(dir: string, grant: Grant, cred: Credential, key: string): Promise { const body = JSON.stringify({ resource: grant.resource, consumer: grant.consumer, fields: cred.fields }); await writeFile(credentialPath(dir, grant.resource, grant.consumer), seal(body, key), { mode: 0o600 }); } function lastGrant(dir: string, resource: string, consumer: string): Grant { // For removal the harness needs a Grant to hand the adapter; the identity is enough to act on. return { resource, consumer, node: "", values: {} }; } // --- helpers --- function hash(g: Grant): string { return JSON.stringify([g.resource, g.consumer, g.node, g.values]); } function envOrThrow(name: string): string { const v = process.env[name]; if (!v) throw new Error(`${name} is not set — the provisioner cannot run without it`); return v; }