@toon-protocol/rig 5.0.2 → 5.2.0

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.
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/ci-serve.ts","../src/ci/act-runner.ts","../src/ci/coordinator.ts","../src/ci/materialize-commit.ts","../src/ci/relay-subscription.ts","../src/ci/state.ts"],"sourcesContent":["/**\n * `rig ci serve` — run a CI coordinator (#125).\n *\n * The coordinator is an ORDINARY rig identity: the same seed-phrase chain,\n * wallet, channel, and connector configuration every paid command uses\n * (`rig identity create` under a separate `TOON_CLIENT_HOME` or account, `rig\n * entry <url>`, `rig fund`). Its spend is bounded by what its operator put in\n * its wallet — there is no separate cap. The maintainer's seed is never on\n * this host: authorization arrives as kind:9843 Service Requests on the\n * relay, and `rig ci serve` just listens.\n *\n * This module is the thin CLI shell around ../ci/coordinator.ts: flags →\n * paid session (always embedded; the daemon has no NIP-C1 routes) → Runner\n * (act on the host's Docker, or an injected one) → `startCoordinator` →\n * block until SIGINT/SIGTERM (or the injected abort signal) → clean stop.\n * Under `--once` the block also ends when the first run concludes — its\n * Workflow Result and final progress marker are on the relay — so one\n * `rig ci trigger` can be answered by one bounded `rig ci serve --once`\n * (#127); ignored or refused triggers publish nothing and do not count.\n * Everything the coordinator says goes to stderr; under `--json` stdout\n * carries exactly ONE document, emitted at start, describing what is being\n * served.\n *\n * Loaded lazily from ./ci.ts so one-shot `rig ci` verbs never pay for the\n * coordinator's imports.\n */\n\nimport { join } from 'node:path';\nimport { parseArgs } from 'node:util';\nimport {\n ActRunner,\n DEFAULT_ACT_PLATFORMS,\n type ActRunnerOptions,\n} from '../ci/act-runner.js';\nimport {\n DEFAULT_CONCURRENCY,\n DEFAULT_RUN_TIMEOUT_MS,\n LOG_UPLOAD_CAP_BYTES,\n startCoordinator,\n type CanAfford,\n type ConcludedRun,\n type CoordinatorHandle,\n type CoordinatorRepo,\n type RunCostEstimate,\n} from '../ci/coordinator.js';\nimport { liveLogTailEventBudget } from '../ci/nip-c1-events.js';\nimport { uploadChargeFor } from '../publisher.js';\nimport {\n ChannelMapStore,\n channelStatus,\n resolveChannelPaths,\n} from '../standalone/channel-map.js';\nimport type { Runner } from '../ci/runner.js';\nimport { defaultCoordinatorStateDir } from '../ci/state.js';\nimport {\n PREFERRED_GATEWAY,\n configuredGateway,\n readGatewaysFor,\n} from '../gateway-preference.js';\nimport { shaResolverFor } from '../git-sha-resolver.js';\nimport { hexToNpub, ownerToHex } from '../npub.js';\nimport type { CiDeps } from './ci.js';\nimport { rigVersion } from './dispatch.js';\nimport { emitCliError } from './errors.js';\nimport {\n identityReport,\n loadPaidSession,\n withForcedStandalone,\n} from './push.js';\nimport { WS_URL_RE } from './repo-command.js';\nimport { renderIdentityLine } from './render.js';\nimport type { StandaloneContext } from './standalone-context.js';\n\nexport const CI_SERVE_USAGE = `Usage: rig ci serve --relay <ws-url> --repo <owner>/<repo-id> [--repo …] [options]\n\nRun a CI coordinator: watch the named repos on the relay, run their\nGitHub-Actions-syntax workflows with act on this host's Docker, and publish\nNIP-C1 progress and results back to the relay as PAID writes from THIS\nidentity's channel. Every run is a handful of relay writes (one per progress\nupdate, job, and result) plus one store upload per job log and artifact.\n\nA repo is served only while one of its maintainers has published a Service\nRequest (\\`rig ci request <this-coordinator>\\`) with no later Stop; a\nmaintainer's \\`rig ci trigger\\` runs one workflow without a standing request.\nSecrets set with \\`rig ci secret\\` are injected only into runs authored by a\nmaintainer — never into a third-party pull request.\n\nOptions:\n --relay <url> the relay to watch AND publish to (ws:// or wss://)\n [required, exactly one]\n --repo <owner>/<id> a repo to serve (owner as npub or hex); repeatable\n --requester <pubkey> also accept Service Requests from this pubkey (npub\n or hex; NIP-C1 operator policy); repeatable — for\n running your own coordinator against a repo you do\n not maintain; such runs show as lower trust\n --concurrency <n> runs executed at once (default ${DEFAULT_CONCURRENCY}); more queue\n --timeout <seconds> wall-clock budget per run → timed_out\n (default ${DEFAULT_RUN_TIMEOUT_MS / 1000})\n --gateway <url> gateway that serves the store's raw bytes: where log +\n artifact links point, and the first place the\n coordinator reads objects from (<url>/raw/<txId>)\n before the shared gateway list (default:\n RIG_ARWEAVE_GATEWAY, else ${PREFERRED_GATEWAY})\n --act-bin <path> the act executable (default: RIG_ACT_BIN, else PATH)\n --platform <label=img> runs-on label → Docker image; repeatable\n (default: ubuntu-latest=${DEFAULT_ACT_PLATFORMS['ubuntu-latest']})\n --pull pull the runner image before each run (act's default)\n --no-pull never pull: run the image already on this host —\n what a locally built or otherwise private runner\n image needs, since act cannot pull it (act's own\n spelling, --pull=false, is accepted too)\n --workdir <dir> where commits are checked out for runs (default:\n <state-dir>/work)\n --once stop after the first run concludes (its Workflow\n Result and final progress marker are on the relay);\n ignored or refused triggers do not count\n --json emit ONE JSON document describing the coordinator\n at start; everything else goes to stderr\n --standalone accepted for symmetry; the coordinator always runs\n embedded (alias: --no-daemon)\n -h, --help show this help\n\nState (cursor, secret inventory) lives under\n<TOON_CLIENT_HOME|~/.toon-client>/rig-ci/<coordinator-pubkey>/ so a restart\nresumes from the last processed event. The NIP-44 secrets-key is generated\nfresh every start and never written to disk: after a restart maintainers\nre-send \\`rig ci secret set\\`; values already accepted survive.\n\nBefore each run the coordinator checks that a recorded payment channel (or,\nbefore the first channel opens, the wallet's USDC) can cover the run's writes;\notherwise it says so on stderr and starts nothing, so a run is never\nhalf-published (story 15).\n\nStop with Ctrl-C (SIGINT) or SIGTERM: active runs conclude \\`cancelled\\`.`;\n\nexport interface ServeFlags {\n relay: string;\n repos: CoordinatorRepo[];\n /** Operator-accepted requester pubkeys (lowercase hex). */\n requesters: string[];\n concurrency: number;\n timeoutMs: number;\n /** `--gateway` as given; absent → RIG_ARWEAVE_GATEWAY, else the preferred one. */\n gateway?: string;\n actBin?: string;\n platforms?: Record<string, string>;\n /**\n * `--pull` / `--no-pull`. Absent → act's own default (force-pull), which is\n * what an image pulled from a registry wants; `false` is what a runner image\n * built or loaded on this host needs (#175).\n */\n pull?: boolean;\n workdir?: string;\n /** Stop after the first run concludes. */\n once: boolean;\n json: boolean;\n}\n\nclass ServeUsageError extends Error {}\n\n/**\n * act spells the pull policy `--pull=false`, and so does the issue this flag\n * came from (#175); node's `parseArgs` would reject that on a boolean option\n * with \"does not take an argument\". Rewrite act's spelling to ours, and say\n * what to type for anything else attached to `--pull`.\n */\nfunction pullSpelling(arg: string): string {\n if (!arg.startsWith('--pull=')) return arg;\n const value = arg.slice('--pull='.length);\n if (value === 'false') return '--no-pull';\n if (value === 'true') return '--pull';\n throw new ServeUsageError(\n `--pull takes no value — use --pull or --no-pull, got ${JSON.stringify(arg)}`\n );\n}\n\nexport function parseServeArgs(args: string[]): ServeFlags | 'help' {\n const { values } = parseArgs({\n args: args.map(pullSpelling),\n options: {\n relay: { type: 'string' },\n repo: { type: 'string', multiple: true },\n requester: { type: 'string', multiple: true },\n concurrency: { type: 'string' },\n timeout: { type: 'string' },\n gateway: { type: 'string' },\n 'act-bin': { type: 'string' },\n platform: { type: 'string', multiple: true },\n pull: { type: 'boolean' },\n 'no-pull': { type: 'boolean' },\n workdir: { type: 'string' },\n once: { type: 'boolean', default: false },\n json: { type: 'boolean', default: false },\n standalone: { type: 'boolean', default: false },\n 'no-daemon': { type: 'boolean', default: false },\n help: { type: 'boolean', short: 'h', default: false },\n },\n allowPositionals: false,\n strict: true,\n });\n if (values.help) return 'help';\n if (!values.relay) throw new ServeUsageError('--relay <ws-url> is required');\n // A long-lived subscription needs a WebSocket relay URL (the http(s) relay\n // spellings other rig commands accept only work for paid publishes).\n if (!WS_URL_RE.test(values.relay)) {\n throw new ServeUsageError(\n `--relay must be a ws:// or wss:// URL, got ${JSON.stringify(values.relay)}`\n );\n }\n\n const repos: CoordinatorRepo[] = [];\n for (const spec of values.repo ?? []) {\n const slash = spec.indexOf('/');\n if (slash <= 0 || slash === spec.length - 1) {\n throw new ServeUsageError(\n `--repo expects <owner>/<repo-id>, got ${JSON.stringify(spec)}`\n );\n }\n let ownerPubkey: string;\n try {\n ownerPubkey = ownerToHex(spec.slice(0, slash));\n } catch (err) {\n throw new ServeUsageError(\n `--repo ${JSON.stringify(spec)}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n repos.push({ ownerPubkey, repoId: spec.slice(slash + 1) });\n }\n if (repos.length === 0)\n throw new ServeUsageError(\n 'at least one --repo <owner>/<repo-id> is required'\n );\n\n const requesters: string[] = [];\n for (const spec of values.requester ?? []) {\n try {\n requesters.push(ownerToHex(spec));\n } catch (err) {\n throw new ServeUsageError(\n `--requester ${JSON.stringify(spec)}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n const positiveInt = (\n flag: string,\n raw: string | undefined,\n fallback: number\n ): number => {\n if (raw === undefined) return fallback;\n const n = Number(raw);\n if (!Number.isInteger(n) || n <= 0)\n throw new ServeUsageError(`${flag} expects a positive integer`);\n return n;\n };\n const concurrency = positiveInt(\n '--concurrency',\n values.concurrency,\n DEFAULT_CONCURRENCY\n );\n const timeoutMs =\n positiveInt('--timeout', values.timeout, DEFAULT_RUN_TIMEOUT_MS / 1000) *\n 1000;\n\n let platforms: Record<string, string> | undefined;\n if (values.platform && values.platform.length > 0) {\n platforms = {};\n for (const spec of values.platform) {\n const eq = spec.indexOf('=');\n if (eq <= 0 || eq === spec.length - 1) {\n throw new ServeUsageError(\n `--platform expects <label>=<image>, got ${JSON.stringify(spec)}`\n );\n }\n platforms[spec.slice(0, eq)] = spec.slice(eq + 1);\n }\n }\n\n // The pull policy is a tri-state: neither flag leaves act's own default in\n // place, so `rig ci serve` never has to restate it.\n if (values.pull && values['no-pull']) {\n throw new ServeUsageError('--pull and --no-pull cannot both be given');\n }\n const pull = values.pull ? true : values['no-pull'] ? false : undefined;\n\n return {\n relay: values.relay,\n repos,\n requesters,\n concurrency,\n timeoutMs,\n ...(values.gateway !== undefined ? { gateway: values.gateway } : {}),\n ...(values['act-bin'] !== undefined ? { actBin: values['act-bin'] } : {}),\n ...(platforms ? { platforms } : {}),\n ...(pull !== undefined ? { pull } : {}),\n ...(values.workdir !== undefined ? { workdir: values.workdir } : {}),\n once: values.once,\n json: values.json,\n };\n}\n\n/**\n * The ActRunner options the runner-shaped flags describe. A flag the operator\n * did not give leaves no key behind, so ActRunner's own defaults (act on PATH,\n * the community ubuntu image, act's force-pull) stand.\n */\nexport function actRunnerOptions(\n flags: Pick<ServeFlags, 'actBin' | 'platforms' | 'pull'>\n): ActRunnerOptions {\n return {\n ...(flags.actBin !== undefined ? { actBin: flags.actBin } : {}),\n ...(flags.platforms ? { platforms: flags.platforms } : {}),\n ...(flags.pull !== undefined ? { pull: flags.pull } : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Affordability (story 15)\n// ---------------------------------------------------------------------------\n\n/**\n * What one run is estimated to cost, in the smallest asset unit — including\n * the worst-case cost of streaming its live log tail for the whole of\n * `runTimeoutMs` (#192).\n *\n * Per ADR-0002 the tail is ONE addressable event per run, republished on a\n * fixed cadence regardless of job count, plus one final replacement when the\n * run concludes. `liveLogTailEventBudget` is that worst-case event count —\n * shared with the coordinator's own scheduler seam so the two can never\n * disagree on the number. Folding it in here, rather than at the call site,\n * means the estimate is a function of run duration alone: neither a\n * chatty build nor a many-job workflow can move it, because the streamed\n * bytes and the job count play no part in the arithmetic. There is no\n * best-effort mode — a coordinator that cannot afford the full worst case\n * must not start the run at all (see {@link makeAffordabilityCheck}).\n */\nexport function estimateRunCost(\n estimate: RunCostEstimate,\n runTimeoutMs: number\n): bigint {\n // Logs and artifacts are metered per KiB on the store route. The\n // per-upload envelope is LOG_UPLOAD_CAP_BYTES — the coordinator never\n // uploads a job log larger than that (ADR-0003) — so this is an actual\n // bound, not a guess.\n return (\n BigInt(estimate.events) * estimate.rates.eventFee +\n BigInt(estimate.uploads) *\n uploadChargeFor(estimate.rates, LOG_UPLOAD_CAP_BYTES) +\n BigInt(liveLogTailEventBudget(runTimeoutMs)) * estimate.rates.eventFee\n );\n}\n\n/**\n * The coordinator's wallet check: a run is affordable when a recorded, still\n * open payment channel of THIS identity has `deposited − claimed ≥ cost`, or —\n * before any channel is recorded (the first paid write opens one lazily\n * from the wallet) — when the wallet holds that much USDC on some chain. An\n * unreadable wallet is NOT affordable: the coordinator must never start a\n * run it may not be able to finish publishing.\n *\n * `runTimeoutMs` is the run timeout this coordinator was started with (the\n * same value passed to `startCoordinator`) — every estimate this check\n * prices includes the worst-case live-log-tail budget for that duration\n * (#192), so a coordinator declines a run it cannot afford to stream for its\n * full timeout, not merely one it cannot afford to run.\n */\nexport function makeAffordabilityCheck(args: {\n ctx: StandaloneContext;\n env: NodeJS.ProcessEnv;\n log: (line: string) => void;\n runTimeoutMs: number;\n}): CanAfford {\n const { ctx, env, log, runTimeoutMs } = args;\n return async (estimate) => {\n const cost = estimateRunCost(estimate, runTimeoutMs);\n const store = new ChannelMapStore(resolveChannelPaths(env));\n const mine = store\n .list()\n .filter(\n (record) =>\n record.identity.toLowerCase() === ctx.ownerPubkey.toLowerCase() &&\n record.supersededAt === undefined\n );\n if (mine.length > 0) {\n let best = 0n;\n for (const record of mine) {\n const watermark = store.readWatermark(record.channelId);\n if (channelStatus(watermark) !== 'open') continue;\n if (record.depositTotal === undefined) continue;\n const remaining =\n BigInt(record.depositTotal) -\n BigInt(watermark?.cumulativeAmount ?? '0');\n if (remaining > best) best = remaining;\n }\n if (best >= cost) return true;\n log(\n `[ci] wallet check: the best open channel has ${best} available, the run needs ~${cost} — ` +\n 'fund this coordinator (rig fund / rig channel open --deposit) to resume'\n );\n return false;\n }\n if (!ctx.money) {\n log(\n '[ci] wallet check: no channel recorded and no wallet reader — refusing'\n );\n return false;\n }\n let chains;\n try {\n chains = await ctx.money.walletChainBalances();\n } catch (err) {\n log(\n `[ci] wallet check: wallet unreadable (${err instanceof Error ? err.message : String(err)}) — refusing`\n );\n return false;\n }\n let best = 0n;\n for (const chain of chains) {\n if (chain.unreadable) continue;\n for (const token of chain.tokens) {\n if (token.symbol !== undefined && !/usdc/i.test(token.symbol)) continue;\n const amount = BigInt(token.amount);\n if (amount > best) best = amount;\n }\n }\n if (best >= cost) return true;\n log(\n `[ci] wallet check: no channel recorded yet and the wallet holds ${best} USDC base units, the run needs ~${cost} — ` +\n 'fund this coordinator (rig fund) to resume'\n );\n return false;\n };\n}\n\n/**\n * Resolve on SIGINT/SIGTERM, when `signal` aborts, or — under `--once` —\n * when `firstRun` settles, whichever first. The value says why.\n */\nfunction untilStopRequested(\n signal: AbortSignal | undefined,\n firstRun?: Promise<ConcludedRun>\n): Promise<string | ConcludedRun> {\n return new Promise((resolve) => {\n let settled = false;\n const finish = (reason: string | ConcludedRun): void => {\n if (settled) return;\n settled = true;\n process.off('SIGINT', onSigint);\n process.off('SIGTERM', onSigterm);\n resolve(reason);\n };\n const onSigint = (): void => finish('SIGINT');\n const onSigterm = (): void => finish('SIGTERM');\n if (signal?.aborted) return finish('abort');\n signal?.addEventListener('abort', () => finish('abort'), { once: true });\n process.once('SIGINT', onSigint);\n process.once('SIGTERM', onSigterm);\n void firstRun?.then(finish);\n });\n}\n\nexport async function runCiServe(\n args: string[],\n deps: CiDeps\n): Promise<number> {\n // Always embedded: the coordinator signs and pays as itself.\n const forced = withForcedStandalone(deps);\n const { io } = forced;\n\n let flags: ServeFlags;\n try {\n const parsed = parseServeArgs(args);\n if (parsed === 'help') {\n io.out(CI_SERVE_USAGE);\n return 0;\n }\n flags = parsed;\n } catch (err) {\n io.err(err instanceof Error ? err.message : String(err));\n io.err(CI_SERVE_USAGE);\n return 2;\n }\n\n // The gateway that serves the store's raw bytes — `--gateway`, else\n // RIG_ARWEAVE_GATEWAY (the variable `rig push` and `rig site` honour), else\n // rig's preferred public gateway. It is where log/artifact links point AND\n // the first place the coordinator reads objects from, on the store's\n // raw-bytes route `<gateway>/raw/<txId>`, ahead of the shared public list:\n // a private or local gateway (the sandbox's answers only on /raw/) is\n // otherwise unreachable on the read path, and a public one serves the same\n // bytes there as at `/<txId>`.\n const gateway =\n configuredGateway(flags.gateway, forced.env) ?? PREFERRED_GATEWAY;\n const readGateways = readGatewaysFor(gateway);\n\n // Runner first: a missing act binary must fail BEFORE the identity is\n // loaded or anything is paid.\n let runner: Runner;\n if (forced.runner) {\n runner = forced.runner;\n } else {\n const act = new ActRunner(actRunnerOptions(flags));\n if (act.binary(forced.env) === null) {\n io.err(\n 'rig ci serve: the act executable was not found — install nektos/act ' +\n '(https://github.com/nektos/act) on this host, or point --act-bin / RIG_ACT_BIN at it. ' +\n 'Nothing was started or paid.'\n );\n return 1;\n }\n runner = act;\n }\n\n let ctx: StandaloneContext | undefined;\n let handle: CoordinatorHandle | undefined;\n try {\n const session = await loadPaidSession(forced, flags.relay);\n if (session.path !== 'standalone') {\n throw new Error('rig ci serve requires the standalone paid path');\n }\n ctx = session.ctx;\n const identity = identityReport(ctx);\n const coordinator = ctx.ownerPubkey.toLowerCase();\n const stateDir =\n forced.stateDir ?? defaultCoordinatorStateDir(forced.env, coordinator);\n const workdir = flags.workdir ?? join(stateDir, 'work');\n const version = rigVersion();\n\n // --once: the first concluded run ends the session (see the header).\n let concludeFirstRun: (run: ConcludedRun) => void = () => undefined;\n const firstRun = flags.once\n ? new Promise<ConcludedRun>((resolve) => {\n concludeFirstRun = resolve;\n })\n : undefined;\n\n handle = await startCoordinator({\n coordinatorPubkey: coordinator,\n publisher: ctx.publisher,\n relayUrl: flags.relay,\n gatewayUrl: gateway,\n gateways: readGateways,\n repos: flags.repos,\n runner,\n stateDir,\n workdir,\n version,\n concurrency: flags.concurrency,\n timeoutMs: flags.timeoutMs,\n acceptedRequesters: flags.requesters,\n canAfford:\n forced.canAfford ??\n makeAffordabilityCheck({\n ctx,\n env: forced.env,\n log: (line) => io.err(line),\n runTimeoutMs: flags.timeoutMs,\n }),\n ...(forced.webSocketFactory\n ? { webSocketFactory: forced.webSocketFactory }\n : {}),\n ...(forced.fetchFn ? { fetchFn: forced.fetchFn } : {}),\n // SHAs the object map does not cover are resolved against the SAME\n // permaweb the objects are read from, not arweave.net (#183).\n resolveSha:\n forced.resolveSha ?? shaResolverFor(flags.gateway, forced.env),\n ...(forced.clock ? { clock: forced.clock } : {}),\n ...(firstRun\n ? { onRunConcluded: (run: ConcludedRun) => concludeFirstRun(run) }\n : {}),\n log: (line) => io.err(line),\n });\n\n const npub = hexToNpub(coordinator);\n const repoLabels = flags.repos.map(\n (r) => `${hexToNpub(r.ownerPubkey)}/${r.repoId}`\n );\n if (flags.json) {\n io.emitJson({\n command: 'ci serve',\n path: 'standalone',\n identity,\n coordinator,\n coordinatorNpub: npub,\n relay: flags.relay,\n gateway,\n repos: flags.repos,\n requesters: flags.requesters,\n runner: { family: runner.family, selectors: runner.selectors },\n concurrency: flags.concurrency,\n timeoutSeconds: flags.timeoutMs / 1000,\n once: flags.once,\n stateDir,\n workdir,\n secretsKey: handle.secretsKeyPubkey,\n advertisementId: handle.advertisementId,\n version,\n });\n } else {\n io.out(renderIdentityLine(identity));\n io.out(`Coordinator: ${npub}`);\n io.out(`Relay: ${flags.relay}`);\n io.out(`Serving: ${repoLabels.join(', ')}`);\n if (flags.requesters.length > 0)\n io.out(\n `Requesters: maintainers + ${flags.requesters.map(hexToNpub).join(', ')}`\n );\n io.out(\n `Runner: ${runner.family} (${runner.selectors.join(', ')}); concurrency ${flags.concurrency}, timeout ${flags.timeoutMs / 1000}s`\n );\n io.out(`State: ${stateDir}`);\n io.out('Maintainers authorize this coordinator with:');\n io.out(` rig ci request ${npub}`);\n io.out(\n flags.once\n ? 'Listening for one run — stops after it concludes (or Ctrl-C).'\n : 'Listening — press Ctrl-C to stop.'\n );\n }\n\n const reason = await untilStopRequested(forced.signal, firstRun);\n io.err(\n typeof reason === 'string'\n ? `rig ci serve: ${reason} — stopping (active runs conclude cancelled)`\n : `rig ci serve: run ${reason.runId.slice(0, 8)} concluded ${reason.conclusion} ` +\n `(result ${reason.resultEventId.slice(0, 8)}) — --once, stopping`\n );\n await handle.stop();\n handle = undefined;\n return 0;\n } catch (err) {\n if (handle) await handle.stop().catch(() => undefined);\n return emitCliError(io, flags.json, 'ci serve', err);\n } finally {\n if (ctx) {\n try {\n await ctx.stop();\n } catch {\n // best-effort teardown\n }\n }\n }\n}\n","/**\n * ActRunner (rig#125) — the first `Runner` implementation: shell out to\n * `act` (nektos/act) against the host Docker daemon, in the coordinator's\n * own process. This is the \"coordinator is its own compute provider\" shape\n * ngit's reference coordinator uses today; a TOON-lease runner replaces it\n * later behind the same ./runner.ts interface without touching the\n * coordinator.\n *\n * What crosses the seam and how:\n *\n * - The checkout is a real git repository at the run's commit, already\n * materialized by the coordinator from the relay + Arweave. act copies\n * it into the job container itself (`-C <dir>`), so `actions/checkout`\n * steps resolve to the local copy and never reach GitHub. Other\n * GitHub-hosted actions ARE fetched by act — the accepted dependency the\n * spec names.\n * - Secrets go in as `-s NAME` with the VALUE in the child's environment,\n * never on argv (argv is world-readable via /proc). act masks them in\n * its output.\n * - The event payload (`-e <file>`) carries the ref/sha or the PR\n * head/base so `github.*` context in workflows is truthful.\n * - Logs are act's `--json` line stream, grouped per `jobID`; the\n * terminal `jobResult` field is each job's conclusion, passed through\n * as NIP-C1 wants (\"preserve the execution backend's conclusion\"). A\n * line that does not parse as act JSON — an image pull failure, a\n * refused Docker daemon, act itself crashing — never reaches the\n * durable per-job log, but is still forwarded live to `onLog` under\n * {@link CI_RUNNER_CHANNEL_KEY}, the runner channel, alongside this\n * runner's own cleanup notes (below). Per ADR-0002 the runner channel\n * is not a job: it carries no result, never concludes, and is never\n * addressable as one.\n * - Artifacts: `--artifact-server-path` makes `actions/upload-artifact`\n * work; act stores each named artifact as ONE zip under\n * `<dir>/<run>/<name>/<name>.zip`, which is unpacked here so every file\n * becomes its own `artifact` tag (NIP-C1 forbids archives).\n *\n * - Timeout/cancel: act is sent SIGTERM (then SIGKILL after a grace\n * period). act exits, but with act 0.2.89 the JOB CONTAINER it started\n * keeps running — verified with and without act's `--rm` — so a hung job\n * would spend forever on the host, exactly what the wall clock exists to\n * stop. After a run this runner killed, it therefore removes the leftover\n * containers itself: act names them deterministically\n * (`act-<workflow name>-<job name>-<sha256>`, see\n * {@link actContainerNamePrefix}), so `docker ps` by name prefix, filtered\n * to containers created after the run started, finds exactly this run's.\n * That naming also means two concurrent runs of the same workflow + job\n * on one host collide on the container name — an act limitation the\n * coordinator's concurrency setting has to respect; not solved here.\n *\n * All child-process arguments are ARRAYS (never a shell), matching the rest\n * of the package.\n */\n\nimport { execFile, spawn } from 'node:child_process';\nimport {\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n statSync,\n writeFileSync,\n} from 'node:fs';\nimport { mkdtemp, rm } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { basename, dirname, join, relative, resolve, sep } from 'node:path';\nimport { promisify } from 'node:util';\nimport { inflateRawSync } from 'node:zlib';\nimport { accessSync, constants } from 'node:fs';\nimport {\n CI_RUNNER_CHANNEL_KEY,\n type CiConclusion,\n type CiTriggerContext,\n} from './nip-c1-events.js';\nimport type {\n Runner,\n RunnerArtifact,\n RunnerJobResult,\n RunnerRequest,\n RunnerRunResult,\n} from './runner.js';\nimport { parseWorkflow, type WorkflowTriggers } from './workflows.js';\n\n/** Environment variable naming the act binary (overrides PATH lookup). */\nexport const RIG_ACT_BIN_ENV = 'RIG_ACT_BIN';\n\n/** Default `-P` platform mapping: the community act image for ubuntu-latest. */\nexport const DEFAULT_ACT_PLATFORMS: Readonly<Record<string, string>> = {\n 'ubuntu-latest': 'catthehacker/ubuntu:act-latest',\n};\n\nexport interface ActRunnerOptions {\n /** act executable; default `RIG_ACT_BIN` env, else `act` on PATH. */\n actBin?: string;\n /** `runs-on` label → Docker image (`-P label=image`). */\n platforms?: Record<string, string>;\n /** Where act's artifact server stores uploads; default a fresh temp dir per run. */\n artifactDir?: string;\n /** Pass `--pull=false` (use local images only). Default: act's own default (pull). */\n pull?: boolean;\n /** Grace period between SIGTERM and SIGKILL on timeout/cancel (ms). */\n killGraceMs?: number;\n /** Docker CLI used to remove the job containers a killed act leaves behind; default `docker`. */\n dockerBin?: string;\n /** Sink for runner-level notes (containers removed, cleanup failures). */\n warn?: (line: string) => void;\n}\n\nconst execFileAsync = promisify(execFile);\n\n// ---------------------------------------------------------------------------\n// act container naming (pure)\n// ---------------------------------------------------------------------------\n\n/**\n * act's `createContainerName` sanitizer, mirrored exactly: every\n * non-alphanumeric byte becomes `-`, then ONE pass replaces `--` with `-`\n * (so `---` becomes `--`), and the result is cut to 63 characters with\n * trailing dashes trimmed (act appends `-<sha256 hex>` of the untrimmed\n * name, which this deliberately omits).\n */\nexport function sanitizeActContainerName(raw: string): string {\n const dashed = raw.replace(/[^a-zA-Z0-9]/g, '-').replaceAll('--', '-');\n return dashed.slice(0, 63).replace(/-+$/, '');\n}\n\n/**\n * The name prefix shared by every job container act starts for a workflow:\n * `act-<workflow name>-` sanitized as act does. The workflow name is the\n * file's `name:` key, else its basename (act's own fallback), so\n * `.github/workflows/hang.yml` with no `name:` yields `act-hang-yml-` and its\n * `hang` job runs in `act-hang-yml-hang-<sha256>`.\n */\nexport function actContainerNamePrefix(workflowName: string): string {\n const prefix = `act-${workflowName}-`\n .replace(/[^a-zA-Z0-9]/g, '-')\n .replaceAll('--', '-');\n // The full name is cut at 63 before the hash; a longer prefix can never\n // match a real container, so cut it the same way (keeping the dash).\n return prefix.length > 63 ? prefix.slice(0, 63) : prefix;\n}\n\n/** Slack allowed between our clock and Docker's `Created` stamp (ms). */\nconst CONTAINER_CREATED_SLACK_MS = 5_000;\n\n// ---------------------------------------------------------------------------\n// Binary resolution\n// ---------------------------------------------------------------------------\n\nfunction isExecutable(path: string): boolean {\n try {\n accessSync(path, constants.X_OK);\n return statSync(path).isFile();\n } catch {\n return false;\n }\n}\n\n/**\n * Find an act binary: `RIG_ACT_BIN` (must exist), else the first `act` on\n * PATH. Returns null when neither is present — the coordinator refuses to\n * start and the Docker-gated test skips.\n */\nexport function resolveActBinary(\n env: NodeJS.ProcessEnv,\n explicit?: string\n): string | null {\n const candidate = explicit ?? env[RIG_ACT_BIN_ENV];\n if (candidate) return isExecutable(candidate) ? candidate : null;\n for (const dir of (env['PATH'] ?? '').split(':')) {\n if (!dir) continue;\n const p = join(dir, 'act');\n if (isExecutable(p)) return p;\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// act --json line parsing (pure)\n// ---------------------------------------------------------------------------\n\n/** One decoded line of act's `--json` stream that belongs to a job. */\nexport interface ActLine {\n jobId: string;\n /** act's display name minus the `<workflow>/` prefix and padding. */\n jobName: string;\n msg: string;\n level: string;\n /** Step stdout/stderr (as opposed to act's own narration). */\n rawOutput: boolean;\n /** Present on the job's terminal line. */\n jobResult?: string;\n /** Parsed from act's `exitcode '<n>': failure` error line. */\n exitCode?: number;\n /** Epoch milliseconds from the line's `time` field (0 when absent). */\n timeMs: number;\n}\n\nconst EXITCODE_RE = /^exitcode '(\\d+)'/;\n\n/** Decode one line; null for non-JSON lines or lines without a `jobID`. */\nexport function parseActJsonLine(line: string): ActLine | null {\n if (!line.startsWith('{')) return null;\n let obj: Record<string, unknown>;\n try {\n obj = JSON.parse(line) as Record<string, unknown>;\n } catch {\n return null;\n }\n const jobId = obj['jobID'];\n if (typeof jobId !== 'string' || jobId === '') return null;\n const display = typeof obj['job'] === 'string' ? obj['job'] : jobId;\n const slash = display.indexOf('/');\n const jobName =\n (slash >= 0 ? display.slice(slash + 1) : display).trim() || jobId;\n const msg = typeof obj['msg'] === 'string' ? obj['msg'] : '';\n const out: ActLine = {\n jobId,\n jobName,\n msg,\n level: typeof obj['level'] === 'string' ? obj['level'] : 'info',\n rawOutput: obj['raw_output'] === true,\n timeMs: typeof obj['time'] === 'string' ? Date.parse(obj['time']) || 0 : 0,\n };\n if (typeof obj['jobResult'] === 'string') out.jobResult = obj['jobResult'];\n const exit = EXITCODE_RE.exec(msg);\n if (exit) out.exitCode = Number(exit[1]);\n return out;\n}\n\nexport interface ActRunOutcome {\n /** act's exit code, or null when it was killed. */\n exitCode: number | null;\n timedOut: boolean;\n cancelled: boolean;\n}\n\nexport interface ActJobSummary {\n jobId: string;\n name: string;\n conclusion: CiConclusion;\n exitCode?: number;\n log: string;\n startedAt: number;\n finishedAt: number;\n}\n\nexport interface ActRunSummary {\n conclusion: CiConclusion;\n jobs: ActJobSummary[];\n}\n\n/**\n * act's verdict for one job, corrected for a run WE ended: act reports the\n * job it tears down on SIGTERM as `failure`, but that failure is our kill,\n * not the job's — so once the wall clock expired (or the run was cancelled)\n * only jobs that already finished cleanly keep their own conclusion.\n */\nfunction mapJobResult(\n result: string | undefined,\n outcome: ActRunOutcome\n): CiConclusion {\n if (result === 'success') return 'success';\n if (result === 'skipped') return 'skipped';\n if (outcome.timedOut) return 'timed_out';\n if (outcome.cancelled) return 'cancelled';\n switch (result) {\n case 'failure':\n return 'failure';\n case 'cancelled':\n return 'cancelled';\n default:\n return outcome.exitCode === 0 ? 'success' : 'failure';\n }\n}\n\n/**\n * Fold the job lines into per-job results and one combined conclusion.\n * The combined verdict is act's own where it has one (a failed job fails\n * the run); an unfinished run is `timed_out`/`cancelled`; no job lines at\n * all with a non-zero exit is `startup_failure` (act could not even plan\n * the workflow — parse error, missing image, bad `runs-on`).\n */\nexport function summarizeActRun(\n lines: ActLine[],\n outcome: ActRunOutcome\n): ActRunSummary {\n const byJob = new Map<string, ActLine[]>();\n for (const l of lines) {\n const list = byJob.get(l.jobId);\n if (list) list.push(l);\n else byJob.set(l.jobId, [l]);\n }\n const jobs: ActJobSummary[] = [];\n for (const [jobId, list] of byJob) {\n const terminal = list.find((l) => l.jobResult !== undefined);\n const exitLine = list.find((l) => l.exitCode !== undefined);\n const conclusion = mapJobResult(terminal?.jobResult, outcome);\n const times = list.map((l) => l.timeMs).filter((t) => t > 0);\n const startedAt =\n times.length > 0 ? Math.floor(Math.min(...times) / 1000) : 0;\n const finishedAt =\n times.length > 0 ? Math.floor(Math.max(...times) / 1000) : 0;\n const log = list\n .filter((l) => l.exitCode === undefined)\n .map((l) => (l.rawOutput || l.msg.endsWith('\\n') ? l.msg : `${l.msg}\\n`))\n .join('');\n const job: ActJobSummary = {\n jobId,\n name: list[0]?.jobName ?? jobId,\n conclusion,\n log,\n startedAt,\n finishedAt,\n };\n if (exitLine?.exitCode !== undefined) job.exitCode = exitLine.exitCode;\n else if (conclusion === 'success') job.exitCode = 0;\n jobs.push(job);\n }\n\n let conclusion: CiConclusion;\n if (jobs.length === 0) {\n conclusion = outcome.timedOut\n ? 'timed_out'\n : outcome.cancelled\n ? 'cancelled'\n : outcome.exitCode === 0\n ? 'success'\n : 'startup_failure';\n } else if (\n outcome.timedOut &&\n jobs.some((j) => j.conclusion === 'timed_out')\n ) {\n conclusion = 'timed_out';\n } else if (\n outcome.cancelled &&\n jobs.some((j) => j.conclusion === 'cancelled')\n ) {\n conclusion = 'cancelled';\n } else if (jobs.some((j) => j.conclusion === 'failure')) {\n conclusion = 'failure';\n } else if (jobs.some((j) => j.conclusion === 'timed_out')) {\n conclusion = 'timed_out';\n } else if (jobs.some((j) => j.conclusion === 'cancelled')) {\n conclusion = 'cancelled';\n } else {\n conclusion = 'success';\n }\n return { conclusion, jobs };\n}\n\n// ---------------------------------------------------------------------------\n// Event name + payload (pure)\n// ---------------------------------------------------------------------------\n\n/**\n * The act event for a trigger. A manual replay (NIP-C1 `o: manual`) runs a\n * workflow that need not declare `workflow_dispatch`, so it borrows the\n * first event the workflow DOES declare — otherwise act would plan no jobs.\n */\nexport function actEventName(\n trigger: CiTriggerContext,\n triggers: WorkflowTriggers\n): string {\n if (trigger.reason === 'push') return 'push';\n if (trigger.reason === 'pull_request') return 'pull_request';\n if (triggers.push) return 'push';\n if (triggers.pull_request) return 'pull_request';\n return 'workflow_dispatch';\n}\n\nfunction shortBranch(ref: string | undefined): string {\n if (!ref) return 'main';\n return ref.startsWith('refs/heads/') ? ref.slice('refs/heads/'.length) : ref;\n}\n\n/** The `github.event` payload act reads from `-e`. */\nexport function buildActEventPayload(\n trigger: CiTriggerContext\n): Record<string, unknown> {\n if (trigger.reason === 'pull_request' && trigger.pr) {\n const base = shortBranch(trigger.ref);\n return {\n action: 'synchronize',\n number: 0,\n ref: `refs/pull/0/merge`,\n pull_request: {\n number: 0,\n head: { sha: trigger.commit, ref: trigger.commit.slice(0, 12) },\n base: { ref: base },\n },\n };\n }\n return {\n ref: trigger.ref ?? 'refs/heads/main',\n before: '0'.repeat(40),\n after: trigger.commit,\n head_commit: { id: trigger.commit },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Zip extraction (central-directory driven: upload-artifact zips use data\n// descriptors, so local headers carry zero sizes) + artifact collection\n// ---------------------------------------------------------------------------\n\nconst EOCD_SIG = 0x06054b50;\nconst CDIR_SIG = 0x02014b50;\nconst LOCAL_SIG = 0x04034b50;\n\n/**\n * Extract a (non-zip64) zip into `destDir`, returning the extracted entry\n * names (forward-slash relative paths). Directory entries are skipped;\n * entries that would escape `destDir` throw.\n */\nexport function extractZip(zipPath: string, destDir: string): string[] {\n const buf = readFileSync(zipPath);\n // Find the End Of Central Directory record (scan back over the comment).\n let eocd = -1;\n for (\n let i = buf.length - 22;\n i >= Math.max(0, buf.length - 22 - 0xffff);\n i--\n ) {\n if (buf.readUInt32LE(i) === EOCD_SIG) {\n eocd = i;\n break;\n }\n }\n if (eocd < 0) throw new Error(`not a zip file: ${zipPath}`);\n const entryCount = buf.readUInt16LE(eocd + 10);\n let offset = buf.readUInt32LE(eocd + 16);\n if (offset === 0xffffffff)\n throw new Error(`zip64 archives are not supported: ${zipPath}`);\n\n const root = resolve(destDir);\n const names: string[] = [];\n for (let n = 0; n < entryCount; n++) {\n if (buf.readUInt32LE(offset) !== CDIR_SIG)\n throw new Error(`corrupt central directory: ${zipPath}`);\n const method = buf.readUInt16LE(offset + 10);\n const compSize = buf.readUInt32LE(offset + 20);\n const nameLen = buf.readUInt16LE(offset + 28);\n const extraLen = buf.readUInt16LE(offset + 30);\n const commentLen = buf.readUInt16LE(offset + 32);\n const localOffset = buf.readUInt32LE(offset + 42);\n const name = buf.toString('utf-8', offset + 46, offset + 46 + nameLen);\n offset += 46 + nameLen + extraLen + commentLen;\n\n if (name.endsWith('/')) continue;\n const target = resolve(root, name);\n if (target !== root && !target.startsWith(root + sep)) {\n throw new Error(\n `zip entry would escape the destination: ${JSON.stringify(name)}`\n );\n }\n if (buf.readUInt32LE(localOffset) !== LOCAL_SIG)\n throw new Error(`corrupt local header: ${zipPath}`);\n const localNameLen = buf.readUInt16LE(localOffset + 26);\n const localExtraLen = buf.readUInt16LE(localOffset + 28);\n const dataStart = localOffset + 30 + localNameLen + localExtraLen;\n const data = buf.subarray(dataStart, dataStart + compSize);\n let bytes: Buffer;\n if (method === 0) bytes = Buffer.from(data);\n else if (method === 8) bytes = inflateRawSync(data);\n else\n throw new Error(\n `unsupported zip compression method ${method} in ${zipPath}`\n );\n mkdirSync(dirname(target), { recursive: true });\n writeFileSync(target, bytes);\n names.push(name);\n }\n return names;\n}\n\nfunction walkFiles(dir: string): string[] {\n const out: string[] = [];\n for (const entry of readdirSync(dir, { withFileTypes: true }).sort((a, b) =>\n a.name.localeCompare(b.name)\n )) {\n const p = join(dir, entry.name);\n if (entry.isDirectory()) out.push(...walkFiles(p));\n else if (entry.isFile()) out.push(p);\n }\n return out;\n}\n\n/**\n * Turn act's artifact-server tree (`<dir>/<run>/<name>/…`) into one\n * {@link RunnerArtifact} per FILE. Zips are unpacked beside themselves into\n * `<name>/unpacked/`; anything else is listed as-is.\n */\nexport function collectArtifacts(artifactDir: string): RunnerArtifact[] {\n if (!existsSync(artifactDir)) return [];\n const out: RunnerArtifact[] = [];\n for (const run of readdirSync(artifactDir, { withFileTypes: true })) {\n if (!run.isDirectory()) continue;\n const runDir = join(artifactDir, run.name);\n for (const art of readdirSync(runDir, { withFileTypes: true }).sort(\n (a, b) => a.name.localeCompare(b.name)\n )) {\n if (!art.isDirectory()) continue;\n const artDir = join(runDir, art.name);\n const unpackedDir = join(artDir, 'unpacked');\n for (const file of walkFiles(artDir)) {\n if (file.startsWith(unpackedDir + sep)) continue;\n if (file.toLowerCase().endsWith('.zip')) {\n for (const name of extractZip(file, unpackedDir)) {\n out.push({\n path: join(unpackedDir, name),\n filename: name,\n name: art.name,\n });\n }\n } else {\n out.push({\n path: file,\n filename: relative(artDir, file).split(sep).join('/'),\n name: art.name,\n });\n }\n }\n }\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// The Runner\n// ---------------------------------------------------------------------------\n\n/** Shell out to `act` on the host Docker daemon. See the module header. */\nexport class ActRunner implements Runner {\n readonly family = 'act' as const;\n readonly selectors: string[];\n private readonly platforms: Record<string, string>;\n\n constructor(private readonly options: ActRunnerOptions = {}) {\n this.platforms = options.platforms ?? { ...DEFAULT_ACT_PLATFORMS };\n this.selectors = Object.keys(this.platforms);\n }\n\n /** The resolved act binary, or null when none is available. */\n binary(env: NodeJS.ProcessEnv = process.env): string | null {\n return resolveActBinary(env, this.options.actBin);\n }\n\n async run(request: RunnerRequest): Promise<RunnerRunResult> {\n const startedAt = Math.floor(Date.now() / 1000);\n const bin = this.binary();\n if (bin === null) {\n return startupFailure(\n startedAt,\n 'act binary not found (set RIG_ACT_BIN or install act)'\n );\n }\n\n const workflowText = readFileSync(\n join(request.checkoutDir, request.workflow.path)\n );\n const parsed = parseWorkflow(request.workflow.path, workflowText);\n if (parsed.parseError !== undefined) {\n return startupFailure(\n startedAt,\n `workflow parse error: ${parsed.parseError}`\n );\n }\n\n const scratch = await mkdtemp(join(tmpdir(), 'rig-act-'));\n const artifactDir = this.options.artifactDir ?? join(scratch, 'artifacts');\n mkdirSync(artifactDir, { recursive: true });\n const eventPath = join(scratch, 'event.json');\n writeFileSync(\n eventPath,\n JSON.stringify(buildActEventPayload(request.trigger))\n );\n\n const args = [\n actEventName(request.trigger, parsed.triggers),\n '-W',\n request.workflow.path,\n '-C',\n request.checkoutDir,\n '--json',\n '-e',\n eventPath,\n '--artifact-server-path',\n artifactDir,\n ];\n if (this.options.pull === false) args.push('--pull=false');\n for (const [label, image] of Object.entries(this.platforms)) {\n args.push('-P', `${label}=${image}`);\n }\n for (const name of Object.keys(request.secrets)) args.push('-s', name);\n for (const name of Object.keys(request.env ?? {})) args.push('--env', name);\n\n const env: NodeJS.ProcessEnv = {\n ...process.env,\n ...request.env,\n ...request.secrets,\n };\n\n const lines: ActLine[] = [];\n let timedOut = false;\n let cancelled = false;\n const startedAtMs = Date.now();\n const grace = this.options.killGraceMs ?? 10_000;\n\n const exitCode = await new Promise<number | null>((resolveExit) => {\n const child = spawn(bin, args, {\n cwd: request.checkoutDir,\n env,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n let killTimer: ReturnType<typeof setTimeout> | undefined;\n const kill = (): void => {\n child.kill('SIGTERM');\n killTimer = setTimeout(() => child.kill('SIGKILL'), grace);\n };\n const timeout = setTimeout(() => {\n timedOut = true;\n kill();\n }, request.timeoutMs);\n const onAbort = (): void => {\n cancelled = true;\n kill();\n };\n if (request.signal?.aborted) onAbort();\n else request.signal?.addEventListener('abort', onAbort, { once: true });\n\n const consume = (stream: NodeJS.ReadableStream): void => {\n let buffered = '';\n stream.setEncoding('utf-8');\n stream.on('data', (chunk: string) => {\n buffered += chunk;\n let nl: number;\n while ((nl = buffered.indexOf('\\n')) >= 0) {\n const line = buffered.slice(0, nl);\n buffered = buffered.slice(nl + 1);\n const parsedLine = parseActJsonLine(line);\n if (!parsedLine) {\n // Not an act JSON line — image pull failures, a refused Docker\n // daemon, or act crashing all look like this. Route it to the\n // runner channel instead of dropping it (ADR-0002) rather than\n // losing it before the streaming sink ever sees it.\n request.onLog?.(CI_RUNNER_CHANNEL_KEY, `${line}\\n`);\n continue;\n }\n lines.push(parsedLine);\n if (request.onLog && parsedLine.exitCode === undefined) {\n request.onLog(\n parsedLine.jobId,\n parsedLine.rawOutput || parsedLine.msg.endsWith('\\n')\n ? parsedLine.msg\n : `${parsedLine.msg}\\n`\n );\n }\n }\n });\n stream.on('end', () => {\n const parsedLine = parseActJsonLine(buffered);\n if (parsedLine) {\n lines.push(parsedLine);\n } else if (buffered !== '') {\n request.onLog?.(CI_RUNNER_CHANNEL_KEY, buffered);\n }\n });\n };\n consume(child.stdout);\n consume(child.stderr);\n\n child.on('error', () => resolveExit(null));\n child.on('close', (code) => {\n clearTimeout(timeout);\n if (killTimer) clearTimeout(killTimer);\n request.signal?.removeEventListener('abort', onAbort);\n resolveExit(code);\n });\n });\n\n if (timedOut || cancelled) {\n await this.removeOrphanedContainers(\n actContainerNamePrefix(parsed.name ?? basename(request.workflow.path)),\n startedAtMs,\n (note) => {\n this.options.warn?.(note);\n request.onLog?.(CI_RUNNER_CHANNEL_KEY, `${note}\\n`);\n }\n );\n }\n\n const summary = summarizeActRun(lines, { exitCode, timedOut, cancelled });\n const artifacts = collectArtifacts(artifactDir);\n const finishedAt = Math.floor(Date.now() / 1000);\n\n // act's artifact server does not record which job uploaded what; the\n // uploading job announces \"Artifact <name> has been successfully\n // uploaded\" in its log, so attribute by that line, else to the first job.\n const jobs: RunnerJobResult[] = summary.jobs.map((j) => ({\n jobId: j.jobId,\n name: j.name,\n conclusion: j.conclusion,\n ...(j.exitCode !== undefined ? { exitCode: j.exitCode } : {}),\n startedAt: j.startedAt || startedAt,\n finishedAt: j.finishedAt || finishedAt,\n log: j.log,\n artifacts: [],\n }));\n for (const artifact of artifacts) {\n const owner =\n jobs.find((j) =>\n j.log.includes(\n `Artifact ${artifact.name} has been successfully uploaded`\n )\n ) ?? jobs[0];\n owner?.artifacts.push(artifact);\n }\n\n // Keep the scratch dir when it holds the artifacts the caller will read.\n if (this.options.artifactDir !== undefined) {\n await rm(scratch, { recursive: true, force: true });\n }\n\n return { conclusion: summary.conclusion, jobs, startedAt, finishedAt };\n }\n\n /**\n * Remove the job containers a killed act left running (see the module\n * header): every container whose name starts with `prefix` and whose\n * Docker `Created` stamp is at or after this run's start (minus a little\n * slack). Never throws — a failed cleanup is reported through `note` and\n * the run result is still returned.\n */\n private async removeOrphanedContainers(\n prefix: string,\n startedAtMs: number,\n note: (line: string) => void\n ): Promise<void> {\n const docker = this.options.dockerBin ?? 'docker';\n const run = async (args: string[]): Promise<string> => {\n const { stdout } = await execFileAsync(docker, args, {\n encoding: 'utf-8',\n timeout: 30_000,\n });\n return stdout;\n };\n try {\n const ids = (await run(['ps', '-aq', '--filter', `name=^${prefix}`]))\n .split('\\n')\n .map((id) => id.trim())\n .filter((id) => id !== '');\n const ours: string[] = [];\n for (const id of ids) {\n const created = Date.parse(\n (await run(['inspect', '-f', '{{.Created}}', id])).trim()\n );\n if (\n Number.isNaN(created) ||\n created >= startedAtMs - CONTAINER_CREATED_SLACK_MS\n ) {\n ours.push(id);\n }\n }\n if (ours.length === 0) return;\n await run(['rm', '-f', ...ours]);\n note(\n `removed ${ours.length} job container(s) act left running after the kill (${prefix}*)`\n );\n } catch (err) {\n note(\n `could not remove act's leftover job containers (${prefix}*): ` +\n `${err instanceof Error ? err.message : String(err)} — remove them by hand`\n );\n }\n }\n}\n\nfunction startupFailure(startedAt: number, reason: string): RunnerRunResult {\n return {\n conclusion: 'startup_failure',\n startedAt,\n finishedAt: Math.floor(Date.now() / 1000),\n jobs: [\n {\n jobId: 'startup',\n conclusion: 'startup_failure',\n startedAt,\n finishedAt: Math.floor(Date.now() / 1000),\n log: `${reason}\\n`,\n artifacts: [],\n },\n ],\n };\n}\n","/**\n * The coordinator loop (#125): what `rig ci serve` runs.\n *\n * A Coordinator is an ordinary rig identity (its own seed, wallet, and\n * channel) that watches served repos on ONE relay and turns triggers into\n * runs, publishing NIP-C1 progress and results as paid writes from its own\n * channel. The relay is the only control plane: every input is a relay\n * event, every output is a relay event or a store upload.\n *\n * inputs kind:30617/30618 (owner's announcement + state → maintainers,\n * default branch, pushes), kind:1617/1618/1619 (pull requests and\n * their updates), and the coordinator inbox — kind:9843/9844\n * (Service Request / Stop), kind:9840 (Manual Trigger), kind:29846\n * (Repository Secret Update), all with `p` = this coordinator.\n * outputs kind:19843 Advertisement (renewed before it expires), and per\n * run the NIP-C1 sequence: 39842 `queued` → 39842 `in_progress`\n * (frozen provenance, job list) → per job a log upload + 9841 +\n * a renewed 39842 quoting it → 9842 → 39842 `concluded`. While\n * the runner runs, a 39841 live log tail (ADR-0002) is replaced\n * on a fixed cadence, and once more as the run concludes.\n *\n * Authorization (user story 12): a repo is served only while\n * `selectServiceRequests` finds an accepted, unstopped 9843 from the owner or\n * a declared maintainer — or from a pubkey the operator explicitly accepts\n * (`acceptedRequesters`, NIP-C1's operator policy; story 21: a contributor\n * serving a repo they do not maintain, at lower trust) — re-evaluated at\n * EVERY trigger and again at runner handoff (the frozen `service-request`\n * quote). A Manual Trigger needs its author to be a maintainer and no\n * standing request. Secrets (story 7) are\n * injected only when the trigger's author is a maintainer; a stranger's pull\n * request runs with an empty secret set.\n *\n * Money (story 15): before a run publishes anything the coordinator asks the\n * publisher for its fee rates (a failure means the channel is not usable)\n * and consults the injectable `canAfford` seam; a refused run is logged and\n * never half-published. Every relay write and store upload goes through the\n * ONE `Publisher` rig already has (story 28), serialized — the standalone\n * publisher is a sequential, nonce-guarded transport.\n *\n * Restart safety (story 13): ./state.ts persists a per-repo cursor (last\n * processed event, the ref map pushes are diffed against, processed ids) and\n * the secret inventory; ./relay-subscription.ts reconnects with backoff and\n * resumes from the cursor. The very first sight of a repo records its refs\n * WITHOUT running anything, so a fresh coordinator never storms through a\n * repository's existing history.\n *\n * Runner seam (story 22): the coordinator materializes the commit itself\n * (./materialize-commit.ts, rig's free read path) and hands a checkout to a\n * `Runner`; nothing here knows about act or Docker.\n *\n * Deliberate departures from ngit's reference coordinator, all noted in the\n * spec: logs and artifacts go to the TOON store and are referenced by\n * gateway URL; the advertisement carries `B = out-of-band`; the repo's own\n * relay doubles as the secret inbox; kind:1617 patches (what `rig pr\n * create` publishes) are pull-request triggers too — the base is materialized\n * and the patch applied with `git am`, so the run's commit is the resulting\n * HEAD, with the patch's declared tip kept as a second `c` tag so a `#c`\n * lookup by either id finds the run.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { readFile, rm } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { REPOSITORY_ANNOUNCEMENT_KIND } from '@toon-protocol/core/nip34';\nimport { contentTypeForPath } from '../mime.js';\nimport {\n REPOSITORY_STATE_KIND,\n authorizedStatusAuthors,\n} from '../nip34-events.js';\nimport type { UnsignedEvent } from '../nip34-events.js';\nimport { parseStateRefTags } from '../nip34-refs.js';\nimport type { FetchLike } from '../object-fetch.js';\nimport type { FeeRates, Publisher } from '../publisher.js';\nimport {\n defaultWebSocketFactory,\n fetchRemoteState,\n queryRelay,\n type NostrEvent,\n type NostrFilter,\n type WebSocketFactory,\n} from '../remote-state.js';\nimport { runGit } from '../materialize.js';\nimport {\n MaterializeError,\n materializeCommit,\n type MaterializeCommit,\n} from './materialize-commit.js';\nimport {\n CI_LIVE_LOG_TAIL_INTERVAL_MS,\n CI_MANUAL_TRIGGER_KIND,\n CI_RUNNER_CHANNEL_KEY,\n CI_SECRET_UPDATE_KIND,\n CI_SERVICE_REQUEST_KIND,\n CI_SERVICE_STOP_KIND,\n CI_WORKFLOW_PROGRESS_KIND,\n buildCiAdvertisement,\n buildCiJobResult,\n buildCiLiveLogTail,\n buildCiWorkflowProgress,\n buildCiWorkflowResult,\n liveLogTailBudget,\n parseCiManualTrigger,\n parseCiSecretUpdate,\n parseCiServiceControl,\n parseRepoAddress,\n repoAddress,\n selectServiceRequests,\n sliceLogTail,\n type CiConclusion,\n type CiJobQuote,\n type CiLiveLogTailChannel,\n type CiLiveLogTailJob,\n type CiPrContext,\n type CiProvenance,\n type CiTriggerContext,\n type CiTriggerReason,\n type CiWorkflowRef,\n type RepoAddress,\n type ServiceControl,\n} from './nip-c1-events.js';\nimport {\n subscribeRelay,\n realScheduler,\n type RelaySubscription,\n type Scheduler,\n} from './relay-subscription.js';\nimport type { Runner, RunnerRunResult } from './runner.js';\nimport {\n applySecretUpdate,\n containsSecretValue,\n decryptSecretUpdate,\n effectiveSecrets,\n generateSecretsKey,\n redactSecretValues,\n validateSecretUpdate,\n type SecretInventory,\n} from './secrets.js';\nimport {\n loadCoordinatorState,\n saveCursor,\n saveSecrets,\n type CoordinatorCursor,\n type InboxCursor,\n type RepoCursor,\n} from './state.js';\nimport {\n discoverWorkflows,\n matchesPullRequest,\n matchesPush,\n type DiscoveredWorkflow,\n} from './workflows.js';\n\n// ---------------------------------------------------------------------------\n// Options + handle\n// ---------------------------------------------------------------------------\n\nexport interface CoordinatorRepo {\n ownerPubkey: string;\n repoId: string;\n}\n\n/** What one run is expected to cost, for the affordability seam. */\nexport interface RunCostEstimate {\n /** Relay events the run will publish (progress, job results, result). */\n events: number;\n /** Store uploads (one log per job, plus artifacts). */\n uploads: number;\n rates: FeeRates;\n}\n\nexport type CanAfford = (estimate: RunCostEstimate) => Promise<boolean>;\n\n/** What {@link CoordinatorOptions.onRunConcluded} reports: a run whose result is on the relay. */\nexport interface ConcludedRun {\n runId: string;\n repoAddr: RepoAddress;\n trigger: CiTriggerContext;\n conclusion: CiConclusion;\n /** Event id of the run's Workflow Result (9842). */\n resultEventId: string;\n}\n\nexport interface CoordinatorOptions {\n /** Hex pubkey of the coordinator identity (the publisher signs as it). */\n coordinatorPubkey: string;\n publisher: Publisher;\n /** The ONE relay: subscriptions and paid publishes alike. */\n relayUrl: string;\n /** Gateway base URL for log/artifact links (`<gateway>/raw/<txId>`). */\n gatewayUrl: string;\n repos: CoordinatorRepo[];\n runner: Runner;\n stateDir: string;\n /** Where checkouts are materialized (one directory per trigger). */\n workdir: string;\n /** rig version, for the advertisement's `software` tag. */\n version: string;\n /** Concurrent runs (default 1). */\n concurrency?: number;\n /** Wall-clock budget per run in ms (default 30 min) → `timed_out`. */\n timeoutMs?: number;\n webSocketFactory?: WebSocketFactory;\n fetchFn?: FetchLike;\n resolveSha?: (sha: string, repo: string) => Promise<string | null>;\n gateways?: readonly string[];\n /** Unix-seconds clock (default: Date.now()/1000). */\n clock?: () => number;\n /** Timer seam (default: real timers). */\n scheduler?: Scheduler;\n log?: (line: string) => void;\n /** Materialization seam (default: the free read path). */\n materialize?: MaterializeCommit;\n /** Affordability seam (default: always true once fee rates are readable). */\n canAfford?: CanAfford;\n /**\n * Requester pubkeys (hex) whose Service Requests are accepted in addition\n * to the repo's owner and maintainers (NIP-C1 operator policy). They are\n * NOT maintainers: their Stops close only their own Requests, their pushes\n * and PRs get no secrets, and runs they cause carry lower trust.\n */\n acceptedRequesters?: Iterable<string>;\n /** Advertisement TTL in seconds (≤ 1800; default 1800, renewed at 5/6). */\n advertisementTtlSeconds?: number;\n /** Keep checkouts after their runs (default: delete). */\n keepCheckouts?: boolean;\n /**\n * Called once per run after its Workflow Result (9842) and final\n * `concluded` progress marker are published — including runs that never\n * reached the Runner (`startup_failure`, `cancelled`). Ignored or refused\n * triggers publish nothing and never reach it. `rig ci serve --once`\n * stops on the first call.\n */\n onRunConcluded?: (run: ConcludedRun) => void;\n}\n\nexport interface CoordinatorHandle {\n pubkey: string;\n /** The current NIP-44 recipient (`secrets-key`) pubkey. */\n readonly secretsKeyPubkey: string;\n /** Id of the current advertisement event, once published. */\n readonly advertisementId: string | null;\n /** Resolves after `stop()` has finished. */\n stopped: Promise<void>;\n stop(): Promise<void>;\n /** Resolves when no event is being handled and no run is queued or active. */\n idle(): Promise<void>;\n /** Which served repos currently have an accepted, unstopped Service Request. */\n serving(): { repoAddr: RepoAddress; serving: boolean }[];\n}\n\n// ---------------------------------------------------------------------------\n// Internal shapes\n// ---------------------------------------------------------------------------\n\ninterface RepoRuntime {\n addr: RepoAddress;\n ownerPubkey: string;\n repoId: string;\n authorized: Set<string>;\n /** Every 9843/9844 seen for this repo (by event id). */\n controls: Map<string, ServiceControl>;\n cursor: RepoCursor | undefined;\n secrets: SecretInventory;\n /** Known 1617/1618 events by id, so a 1619 update can find its PR. */\n prEvents: Map<string, NostrEvent>;\n defaultBranch: string | null;\n}\n\ninterface PendingTrigger {\n repo: RepoRuntime;\n reason: CiTriggerReason;\n /** Who caused the trigger — decides whether secrets are injected. */\n authorPubkey: string;\n /** The commit (or tag object) to materialize; a patch's base when `patch`. */\n commit: string;\n ref?: string;\n pr?: CiPrContext;\n patch?: { content: string; declaredTip?: string };\n manual?: {\n eventId: string;\n pubkey: string;\n workflow: CiWorkflowRef;\n tagObjectIds?: string[];\n };\n}\n\ninterface Checkout {\n dir: string;\n refs: number;\n}\n\ninterface QueuedRun {\n repo: RepoRuntime;\n trigger: CiTriggerContext;\n workflow: DiscoveredWorkflow;\n checkout: Checkout;\n secrets: Record<string, string>;\n manualProvenance?: CiProvenance;\n runId: string;\n queuedAt: number;\n /** Set while the run executes, so a superseding PR update can abort it. */\n abort?: AbortController;\n /** A later update to the same pull request replaced this run's tip. */\n superseded?: boolean;\n}\n\n/** Runs executed at once (`--concurrency`). */\nexport const DEFAULT_CONCURRENCY = 1;\nexport const DEFAULT_RUN_TIMEOUT_MS = 30 * 60 * 1000;\nexport const DEFAULT_ADVERTISEMENT_TTL = 30 * 60;\n/** Renew progress markers and the advertisement at this fraction of their TTL. */\nconst RENEW_FRACTION = 5 / 6;\n/** How much of a job log rides in the 9841 content. */\nexport const LOG_TAIL_BYTES = 4096;\n/**\n * The most the coordinator will ever upload for one job's log (ADR-0003).\n * `estimateRunCost`'s per-upload envelope is this same constant, so the\n * affordability estimate is an actual bound rather than a guess.\n */\nexport const LOG_UPLOAD_CAP_BYTES = 1024 * 1024; // 1 MiB\n/** Kept from the start of an over-cap log. */\nconst LOG_UPLOAD_HEAD_BYTES = 512 * 1024;\n/**\n * Reserved out of the nominal 512 KiB tail so the marker naming the omitted\n * count can never push the total over {@link LOG_UPLOAD_CAP_BYTES} — the\n * marker text is under 80 bytes even for a log in the gigabytes, so this\n * budget is never exhausted in practice.\n */\nconst LOG_UPLOAD_MARKER_BUDGET_BYTES = 256;\n/** Kept from the end of an over-cap log. */\nconst LOG_UPLOAD_TAIL_BYTES =\n 512 * 1024 - LOG_UPLOAD_MARKER_BUDGET_BYTES;\n/** Rotate the secrets key at least this often (NIP-C1: no later than 86,400 s). */\nconst SECRETS_KEY_MAX_AGE = 86_400;\n\nfunction nowSeconds(): number {\n return Math.floor(Date.now() / 1000);\n}\n\n/** The last {@link LOG_TAIL_BYTES} of a log, plus how many bytes were cut. */\nexport function logTail(log: string): { tail: string; omitted: number } {\n const bytes = Buffer.from(log, 'utf-8');\n if (bytes.byteLength <= LOG_TAIL_BYTES) return { tail: log, omitted: 0 };\n const cut = bytes.subarray(bytes.byteLength - LOG_TAIL_BYTES);\n return {\n tail: cut.toString('utf-8'),\n omitted: bytes.byteLength - LOG_TAIL_BYTES,\n };\n}\n\n/**\n * Bounds a (already-redacted) log to at most {@link LOG_UPLOAD_CAP_BYTES}\n * (ADR-0003). Under the cap, the log is returned whole and unchanged. Over\n * the cap, it uploads as its head + a marker naming the omitted byte count\n * + its tail; the tail yields a few bytes to the marker so the total never\n * exceeds the cap. Called AFTER redaction, so a secret is absent from the\n * result whether it fell in the kept head, the kept tail, or the omitted\n * middle.\n */\nexport function boundLogForUpload(log: string): Buffer {\n const bytes = Buffer.from(log, 'utf-8');\n if (bytes.byteLength <= LOG_UPLOAD_CAP_BYTES) return bytes;\n const head = bytes.subarray(0, LOG_UPLOAD_HEAD_BYTES);\n const tail = bytes.subarray(bytes.byteLength - LOG_UPLOAD_TAIL_BYTES);\n const omitted = bytes.byteLength - head.byteLength - tail.byteLength;\n const marker = Buffer.from(\n `\\n[... ${omitted} bytes omitted (log capped at ${LOG_UPLOAD_CAP_BYTES} bytes) ...]\\n`,\n 'utf-8'\n );\n return Buffer.concat([head, marker, tail]);\n}\n\n/** `<gateway>/raw/<txId>` — the TOON store's raw-bytes route. */\nexport function gatewayRawUrl(gatewayUrl: string, txId: string): string {\n return `${gatewayUrl.replace(/\\/+$/, '')}/raw/${txId}`;\n}\n\n/** Which refs moved between two ref maps (new or changed shas). */\nexport function movedRefs(\n before: Record<string, string>,\n after: Record<string, string>\n): { ref: string; sha: string }[] {\n const moved: { ref: string; sha: string }[] = [];\n for (const [ref, sha] of Object.entries(after)) {\n if (before[ref] !== sha) moved.push({ ref, sha });\n }\n return moved;\n}\n\nfunction tagValues(tags: string[][], name: string): string[] {\n return tags\n .filter((t) => t[0] === name)\n .map((t) => t[1] ?? '')\n .filter((v) => v !== '');\n}\n\n/** A promise-based mutex: `publishEvent`/`uploadBlob` are called one at a time. */\nclass Serial {\n private chain: Promise<unknown> = Promise.resolve();\n run<T>(fn: () => Promise<T>): Promise<T> {\n const next = this.chain.then(fn, fn);\n this.chain = next.catch(() => undefined);\n return next;\n }\n}\n\n// ---------------------------------------------------------------------------\n// startCoordinator\n// ---------------------------------------------------------------------------\n\nexport async function startCoordinator(\n opts: CoordinatorOptions\n): Promise<CoordinatorHandle> {\n const me = opts.coordinatorPubkey.toLowerCase();\n const { publisher, relayUrl, runner } = opts;\n const concurrency = Math.max(1, opts.concurrency ?? DEFAULT_CONCURRENCY);\n const timeoutMs = opts.timeoutMs ?? DEFAULT_RUN_TIMEOUT_MS;\n const clock = opts.clock ?? nowSeconds;\n const scheduler = opts.scheduler ?? realScheduler;\n const log = opts.log ?? (() => undefined);\n const materialize = opts.materialize ?? materializeCommit;\n const canAfford: CanAfford = opts.canAfford ?? (async () => true);\n const acceptedRequesters = new Set(\n [...(opts.acceptedRequesters ?? [])].map((p) => p.toLowerCase())\n );\n const adTtl = Math.min(\n opts.advertisementTtlSeconds ?? DEFAULT_ADVERTISEMENT_TTL,\n 1800\n );\n\n if (!publisher.uploadBlob) {\n throw new Error(\n 'the coordinator needs a publisher that can upload blobs (job logs and artifacts) — ' +\n 'the standalone publisher provides uploadBlob'\n );\n }\n const uploadBlob = publisher.uploadBlob.bind(publisher);\n if (opts.repos.length === 0)\n throw new Error('the coordinator needs at least one --repo to serve');\n\n const serial = new Serial();\n const publish = (event: UnsignedEvent): Promise<string> =>\n serial.run(\n async () => (await publisher.publishEvent(event, [relayUrl])).eventId\n );\n\n // ── State ────────────────────────────────────────────────────────────────\n const state = await loadCoordinatorState(opts.stateDir);\n const repos = new Map<RepoAddress, RepoRuntime>();\n for (const r of opts.repos) {\n const owner = r.ownerPubkey.toLowerCase();\n const addr = repoAddress(owner, r.repoId);\n repos.set(addr, {\n addr,\n ownerPubkey: owner,\n repoId: r.repoId,\n authorized: new Set([owner]),\n controls: new Map(),\n cursor: state.cursor[addr],\n secrets: state.secrets[addr] ?? {},\n prEvents: new Map(),\n defaultBranch: null,\n });\n }\n const inbox: InboxCursor = state.inbox;\n const inboxProcessed = new Set(inbox.processed);\n\n const persistCursor = async (): Promise<void> => {\n const cursor: CoordinatorCursor = {};\n for (const repo of repos.values())\n if (repo.cursor) cursor[repo.addr] = repo.cursor;\n inbox.processed = [...inboxProcessed];\n await saveCursor(opts.stateDir, cursor, inbox);\n };\n const persistSecrets = async (): Promise<void> => {\n const secrets: Record<string, SecretInventory> = {};\n for (const repo of repos.values()) secrets[repo.addr] = repo.secrets;\n await saveSecrets(opts.stateDir, secrets);\n };\n\n // ── Seed each repo from the relay (maintainers, default branch, refs) ────\n for (const repo of repos.values()) {\n const remote = await fetchRemoteState({\n relayUrls: [relayUrl],\n ownerPubkey: repo.ownerPubkey,\n repoId: repo.repoId,\n ...(opts.webSocketFactory\n ? { webSocketFactory: opts.webSocketFactory }\n : {}),\n ...(opts.resolveSha ? { resolveSha: opts.resolveSha } : {}),\n });\n if (remote.announceEvent) {\n repo.authorized = authorizedStatusAuthors(\n repo.ownerPubkey,\n remote.announceEvent.tags\n );\n }\n repo.defaultBranch = remote.headSymref;\n if (repo.cursor === undefined) {\n // First sight of this repo: remember where it stands, run nothing.\n repo.cursor = {\n lastCreatedAt: remote.refsEvent?.created_at ?? 0,\n lastEventId: remote.refsEvent?.id ?? '',\n refs: Object.fromEntries(remote.refs),\n processed: remote.refsEvent ? [remote.refsEvent.id] : [],\n };\n log(\n `[ci] ${repo.repoId}: recorded ${remote.refs.size} ref(s); watching for pushes`\n );\n }\n }\n await persistCursor();\n\n // ── Seed the Service Request / Stop history ─────────────────────────────\n // The live subscription replays it too, but a backlog push must not be\n // judged before the requests that authorize it have been read.\n const isServing = (repo: RepoRuntime): boolean =>\n selectServiceRequests([...repo.controls.values()], {\n coordinatorPubkey: me,\n repoAddr: repo.addr,\n authorized: repo.authorized,\n acceptedRequesters,\n }).active !== null;\n const handleControl = (ev: NostrEvent): void => {\n const control = parseCiServiceControl(ev);\n if (!control || control.coordinatorPubkey !== me) return;\n const repo = repos.get(control.repoAddr);\n if (!repo || repo.controls.has(control.eventId)) return;\n const before = isServing(repo);\n repo.controls.set(control.eventId, control);\n const after = isServing(repo);\n if (before !== after) {\n const who = repo.authorized.has(control.pubkey)\n ? 'maintainer'\n : 'operator-accepted requester';\n log(\n `[ci] ${repo.repoId}: ${after ? `serving (${who} Service Request accepted)` : 'service stopped'}`\n );\n } else if (\n control.kind === 'request' &&\n !repo.authorized.has(control.pubkey) &&\n !acceptedRequesters.has(control.pubkey.toLowerCase())\n ) {\n log(\n `[ci] ${repo.repoId}: Service Request from non-maintainer ${control.pubkey.slice(0, 8)} ignored`\n );\n }\n };\n const controlFilter: NostrFilter = {\n kinds: [CI_SERVICE_REQUEST_KIND, CI_SERVICE_STOP_KIND],\n '#p': [me],\n };\n try {\n const history = await queryRelay(\n relayUrl,\n controlFilter,\n 10_000,\n opts.webSocketFactory ?? defaultWebSocketFactory\n );\n for (const ev of history) handleControl(ev);\n } catch (err) {\n log(\n `[ci] could not read the Service Request history: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n\n // ── Advertisement + secrets key ──────────────────────────────────────────\n let secretsKey = generateSecretsKey();\n let secretsKeyBorn = clock();\n let advertisementId: string | null = null;\n /** Advertisements naming the CURRENT secrets key (id → [created_at, expiration)). */\n const liveAdvertisements = new Map<\n string,\n { createdAt: number; expiresAt: number }\n >();\n let adTimer: unknown = null;\n let stopping = false;\n\n const advertise = async (): Promise<void> => {\n if (stopping) return;\n const now = clock();\n if (now - secretsKeyBorn >= SECRETS_KEY_MAX_AGE) {\n secretsKey = generateSecretsKey();\n secretsKeyBorn = now;\n liveAdvertisements.clear();\n log('[ci] rotated the secrets key');\n }\n const expiresAt = now + adTtl;\n const event = buildCiAdvertisement(\n {\n version: opts.version,\n selectors: runner.selectors,\n admission: 'maintainer-request',\n execution: 'request-required',\n billing: 'out-of-band',\n secretsKey: { pubkey: secretsKey.pubkey, inboxRelays: [relayUrl] },\n expiresAt,\n },\n now\n );\n try {\n advertisementId = await publish(event);\n liveAdvertisements.set(advertisementId, { createdAt: now, expiresAt });\n } catch (err) {\n log(\n `[ci] advertisement publish failed: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n if (!stopping) {\n adTimer = scheduler.setTimeout(\n () => void advertise(),\n adTtl * RENEW_FRACTION * 1000\n );\n }\n };\n await advertise();\n\n // ── Run queue + concurrency slots ───────────────────────────────────────\n const queue: QueuedRun[] = [];\n const activeAborts = new Set<AbortController>();\n const activeRuns = new Set<QueuedRun>();\n let active = 0;\n let inflight = 0; // triggers being handled + runs active\n const idleWaiters: (() => void)[] = [];\n const checkIdle = (): void => {\n if (\n inflight === 0 &&\n queue.length === 0 &&\n eventQueue.length === 0 &&\n !draining\n ) {\n for (const w of idleWaiters.splice(0)) w();\n }\n };\n\n const releaseCheckout = async (checkout: Checkout): Promise<void> => {\n checkout.refs -= 1;\n if (checkout.refs <= 0 && !opts.keepCheckouts) {\n await rm(checkout.dir, { recursive: true, force: true }).catch(\n () => undefined\n );\n }\n };\n\n const progressAddress = (runId: string): string =>\n `${CI_WORKFLOW_PROGRESS_KIND}:${me}:${runId}`;\n\n const concludeWithoutJobs = async (\n run: QueuedRun,\n conclusion: CiConclusion,\n provenance: CiProvenance | undefined,\n startedAt?: number\n ): Promise<void> => {\n const base = {\n trigger: run.trigger,\n runId: run.runId,\n queuedAt: run.queuedAt,\n ...(startedAt !== undefined ? { startedAt } : {}),\n ...(provenance ? { provenance } : {}),\n jobs: [] as CiJobQuote[],\n };\n const now = clock();\n const resultEventId = await publish(\n buildCiWorkflowResult({ ...base, conclusion }, now)\n );\n await publish(\n buildCiWorkflowProgress(\n { ...base, status: 'concluded', conclusion, expiresAt: now + adTtl },\n now\n )\n );\n opts.onRunConcluded?.({\n runId: run.runId,\n repoAddr: run.repo.addr,\n trigger: run.trigger,\n conclusion,\n resultEventId,\n });\n };\n\n const executeRun = async (run: QueuedRun): Promise<void> => {\n const { repo, trigger, workflow } = run;\n const label = `${repo.repoId} ${workflow.path}@${trigger.commit.slice(0, 7)} [${run.runId.slice(0, 8)}]`;\n\n // Final handoff authorization: the frozen provenance quote.\n let provenance: CiProvenance | undefined = run.manualProvenance;\n if (!provenance) {\n const { active: request } = selectServiceRequests(\n [...repo.controls.values()],\n {\n coordinatorPubkey: me,\n repoAddr: repo.addr,\n authorized: repo.authorized,\n acceptedRequesters,\n }\n );\n if (!request) {\n log(`[ci] ${label}: service stopped before handoff — cancelled`);\n await concludeWithoutJobs(run, 'cancelled', undefined);\n return;\n }\n provenance = {\n kind: 'service-request',\n eventId: request.eventId,\n relayUrl,\n pubkey: request.pubkey,\n };\n }\n\n if (workflow.parseError !== undefined) {\n log(`[ci] ${label}: workflow parse error — startup_failure`);\n await concludeWithoutJobs(run, 'startup_failure', provenance, clock());\n return;\n }\n\n const startedAt = clock();\n const jobIds = workflow.jobs.map((j) => j.id);\n const quotes: CiJobQuote[] = [];\n const progress = (\n status: 'in_progress' | 'concluded',\n conclusion?: CiConclusion\n ) => {\n const now = clock();\n return buildCiWorkflowProgress(\n {\n trigger,\n runId: run.runId,\n status,\n ...(conclusion !== undefined ? { conclusion } : {}),\n inProgress:\n status === 'in_progress'\n ? jobIds.filter((id) => !quotes.some((q) => q.jobId === id))\n : [],\n queuedAt: run.queuedAt,\n startedAt,\n provenance: provenance as CiProvenance,\n jobs: quotes,\n expiresAt: now + adTtl,\n },\n now\n );\n };\n await publish(progress('in_progress'));\n log(`[ci] ${label}: in_progress (${jobIds.length} job(s))`);\n\n // Renew the in-progress marker before it expires on long runs.\n let renewTimer: unknown = null;\n const scheduleRenew = (): void => {\n renewTimer = scheduler.setTimeout(\n () => {\n void publish(progress('in_progress')).catch(() => undefined);\n scheduleRenew();\n },\n adTtl * RENEW_FRACTION * 1000\n );\n };\n scheduleRenew();\n\n // ── The live log tail (ADR-0002) ─────────────────────────────────────\n // The run stops being a black box: what the runner prints is accumulated\n // per job — plus the RUNNER CHANNEL, the distinguished non-job key for\n // output the runner attributes to no job — and republished on a fixed\n // cadence as ONE addressable event for the whole run, so a viewer\n // arriving four minutes into a five-minute job sees the current tail\n // immediately and the cost is a function of run duration alone.\n //\n // It is only ever a VIEW. The record is the job log uploaded below, so a\n // job drops out of the tail the moment its Job Result names that log:\n // what the tail carries is exactly the output that has no durable home\n // yet.\n //\n // The cadence runs on the same scheduler seam as every other timer here,\n // and it stops when the runner returns. Only `runner.run` is bounded by\n // `timeoutMs`; a cadence that outlived it (the per-job uploads below take\n // as long as they take) could publish more events than\n // `liveLogTailEventBudget(timeoutMs)`, which is the worst case the\n // affordability check paid for BEFORE the run started (#192).\n const secretValues = Object.values(run.secrets);\n const streamed = new Map<string, string>();\n const finishedJobs = new Set<string>();\n let tailClosed = false;\n const recordLog = (jobId: string, chunk: string): void => {\n if (tailClosed || chunk === '') return;\n streamed.set(jobId, (streamed.get(jobId) ?? '') + chunk);\n };\n const publishLiveLogTail = async (): Promise<void> => {\n // Redaction comes FIRST and slicing second, never the reverse: the\n // whole accumulated buffer is redacted exactly as the durable path\n // redacts it, and the tail is cut out of the result. That is what makes\n // a secret straddling a refresh boundary safe — those bytes were\n // already replaced before the slice was taken.\n const live: [string, string][] = [];\n for (const [key, text] of streamed) {\n if (key !== CI_RUNNER_CHANNEL_KEY && finishedJobs.has(key)) continue;\n live.push([key, redactSecretValues(text, secretValues)]);\n }\n // Every live entry — the unfinished jobs and the runner channel alike —\n // occupies bytes in the one event, so they share one budget.\n const budget = liveLogTailBudget(live.length);\n const jobs: CiLiveLogTailJob[] = [];\n let runnerChannel: CiLiveLogTailChannel | undefined;\n for (const [key, text] of live) {\n const sliced = sliceLogTail(text, budget);\n if (key === CI_RUNNER_CHANNEL_KEY) runnerChannel = sliced;\n else jobs.push({ job: key, ...sliced });\n }\n const now = clock();\n try {\n await publish(\n buildCiLiveLogTail(\n {\n trigger,\n runId: run.runId,\n jobs,\n ...(runnerChannel ? { runner: runnerChannel } : {}),\n expiresAt: now + adTtl,\n },\n now\n )\n );\n } catch (err) {\n // The view is best-effort; the record is not. A tail that cannot be\n // published must never cost the run its job results.\n log(\n `[ci] ${label}: live log tail not published: ${redactSecretValues(\n err instanceof Error ? err.message : String(err),\n secretValues\n )}`\n );\n }\n };\n let tailTimer: unknown = null;\n let tailPublished = false;\n const scheduleTail = (): void => {\n tailTimer = scheduler.setTimeout(() => {\n if (tailClosed) return;\n tailPublished = true;\n void publishLiveLogTail();\n scheduleTail();\n }, CI_LIVE_LOG_TAIL_INTERVAL_MS);\n };\n scheduleTail();\n\n const abort = new AbortController();\n activeAborts.add(abort);\n run.abort = abort;\n let timedOut = false;\n const timeoutHandle = scheduler.setTimeout(() => {\n timedOut = true;\n abort.abort();\n }, timeoutMs);\n\n let result: RunnerRunResult;\n try {\n result = await Promise.race([\n runner.run({\n checkoutDir: run.checkout.dir,\n workflow: { path: workflow.path, sha256: workflow.sha256 },\n trigger,\n secrets: run.secrets,\n timeoutMs,\n onLog: recordLog,\n signal: abort.signal,\n }),\n new Promise<RunnerRunResult>((resolve) => {\n abort.signal.addEventListener(\n 'abort',\n () => {\n // Give the runner a moment to report its own teardown; if it\n // does not, conclude on its behalf.\n scheduler.setTimeout(\n () =>\n resolve({\n conclusion: timedOut ? 'timed_out' : 'cancelled',\n jobs: [],\n startedAt,\n finishedAt: clock(),\n }),\n 1000\n );\n },\n { once: true }\n );\n }),\n ]);\n } catch (err) {\n log(\n `[ci] ${label}: runner failed: ${redactSecretValues(err instanceof Error ? err.message : String(err), Object.values(run.secrets))}`\n );\n result = {\n conclusion: 'startup_failure',\n jobs: [],\n startedAt,\n finishedAt: clock(),\n };\n } finally {\n scheduler.clearTimeout(timeoutHandle);\n scheduler.clearTimeout(renewTimer);\n // The cadence is over: from here the run's elapsed time is no longer\n // bounded by `timeoutMs`, and exactly one more tail — the closing\n // replacement below — is left in the budget. The SINK stays open: a\n // runner that outlived its abort is still entitled to say why (act's\n // container cleanup reaches the runner channel after the coordinator\n // has given up waiting), and that account belongs in the closing tail.\n scheduler.clearTimeout(tailTimer);\n activeAborts.delete(abort);\n }\n let conclusion = result.conclusion;\n if (timedOut) conclusion = 'timed_out';\n else if (run.superseded || (stopping && abort.signal.aborted))\n conclusion = 'cancelled';\n\n // Per job: upload the log (+ artifacts), publish 9841, renew 39842.\n // An injected value never leaves this process in the clear: not in the\n // uploaded log, not in the 9841 tail, not in an artifact, not in this\n // coordinator's own log (act masks its own output, but no Runner is\n // trusted to).\n for (const job of result.jobs) {\n const jobLog = redactSecretValues(job.log, secretValues);\n const { tail, omitted } = logTail(jobLog);\n const logReceipt = await serial.run(() =>\n uploadBlob({\n body: boundLogForUpload(jobLog),\n contentType: 'text/plain; charset=utf-8',\n repoId: repo.repoId,\n })\n );\n const artifacts: { url: string; filename: string; name: string }[] = [];\n for (const artifact of job.artifacts) {\n try {\n const body = await readFile(artifact.path);\n if (containsSecretValue(body, secretValues)) {\n log(\n `[ci] ${label}: artifact ${redactSecretValues(artifact.filename, secretValues)} not uploaded — it contains an injected secret`\n );\n continue;\n }\n const receipt = await serial.run(() =>\n uploadBlob({\n body,\n contentType: contentTypeForPath(artifact.filename),\n repoId: repo.repoId,\n })\n );\n artifacts.push({\n url: gatewayRawUrl(opts.gatewayUrl, receipt.txId),\n filename: artifact.filename,\n name: artifact.name,\n });\n } catch (err) {\n log(\n `[ci] ${label}: artifact ${redactSecretValues(`${artifact.filename} skipped: ${err instanceof Error ? err.message : String(err)}`, secretValues)}`\n );\n }\n }\n const runsOn = workflow.jobs.find((j) => j.id === job.jobId)?.runsOn;\n const jobConclusion: CiConclusion =\n timedOut && job.conclusion === 'failure' ? 'timed_out' : job.conclusion;\n const jobEventId = await publish(\n buildCiJobResult(\n {\n trigger,\n progressAddress: progressAddress(run.runId),\n relayUrl,\n jobId: job.jobId,\n ...(job.name !== undefined ? { name: job.name } : {}),\n conclusion: jobConclusion,\n logsUrl: gatewayRawUrl(opts.gatewayUrl, logReceipt.txId),\n logTail: tail,\n logOmittedBytes: omitted,\n artifacts,\n queuedAt: run.queuedAt,\n startedAt: job.startedAt,\n ...(job.exitCode !== undefined ? { exitCode: job.exitCode } : {}),\n ...(runsOn ? { runsOn } : {}),\n },\n clock()\n )\n );\n quotes.push({\n eventId: jobEventId,\n relayUrl,\n pubkey: me,\n jobId: job.jobId,\n });\n // This job's log now has a durable home, named by the Job Result just\n // published, so the job leaves the live tail (ADR-0002).\n finishedJobs.add(job.jobId);\n await publish(progress('in_progress'));\n }\n\n // The closing replacement (ADR-0002), and then nothing: the event is left\n // to expire under NIP-40, so a concluded run stops paying for refreshes\n // and a crashed coordinator's tail clears itself. Without this the last\n // thing a viewer saw live would be whatever the timer happened to catch,\n // which may be from before the error — a timed-out or crashed run\n // publishes no Job Result at all, so its jobs never dropped out and this\n // is where their closing output, and the runner channel's account of what\n // went wrong, reaches the relay.\n //\n // A run that never published a tail — one that concluded inside a single\n // cadence interval — has nothing on the relay to replace, and an empty\n // event costs a fee to tell a viewer nothing: the absence of a live tail\n // is a state every client already renders as a normal run.\n if (tailPublished) await publishLiveLogTail();\n tailClosed = true;\n\n const now = clock();\n const resultEventId = await publish(\n buildCiWorkflowResult(\n {\n trigger,\n runId: run.runId,\n conclusion,\n queuedAt: run.queuedAt,\n startedAt,\n provenance,\n jobs: quotes,\n },\n now\n )\n );\n await publish(progress('concluded', conclusion));\n log(`[ci] ${label}: ${conclusion}`);\n opts.onRunConcluded?.({\n runId: run.runId,\n repoAddr: repo.addr,\n trigger,\n conclusion,\n resultEventId,\n });\n };\n\n const pump = (): void => {\n while (active < concurrency && queue.length > 0 && !stopping) {\n const run = queue.shift() as QueuedRun;\n active += 1;\n inflight += 1;\n activeRuns.add(run);\n void executeRun(run)\n .catch((err) => {\n log(\n `[ci] run ${run.runId.slice(0, 8)} failed: ${err instanceof Error ? err.message : String(err)}`\n );\n })\n .finally(async () => {\n activeRuns.delete(run);\n active -= 1;\n inflight -= 1;\n await releaseCheckout(run.checkout);\n pump();\n checkIdle();\n });\n }\n };\n\n const enqueueRun = async (\n repo: RepoRuntime,\n trigger: CiTriggerContext,\n workflow: DiscoveredWorkflow,\n checkout: Checkout,\n secrets: Record<string, string>,\n manualProvenance?: CiProvenance\n ): Promise<void> => {\n // Money first (story 15): unreadable fee rates or an unaffordable run\n // means nothing is published for it.\n let rates: FeeRates;\n try {\n rates = await publisher.getFeeRates();\n } catch (err) {\n log(\n `[ci] refusing run of ${workflow.path} for ${repo.repoId}: cannot read fee rates ` +\n `(${err instanceof Error ? err.message : String(err)}) — is the wallet funded?`\n );\n return;\n }\n const jobs = Math.max(1, workflow.jobs.length);\n if (!(await canAfford({ events: 4 + 2 * jobs, uploads: jobs, rates }))) {\n log(\n `[ci] refusing run of ${workflow.path} for ${repo.repoId}: wallet cannot cover its writes`\n );\n return;\n }\n const run: QueuedRun = {\n repo,\n trigger,\n workflow,\n checkout,\n secrets,\n ...(manualProvenance ? { manualProvenance } : {}),\n runId: randomUUID(),\n queuedAt: clock(),\n };\n checkout.refs += 1;\n const position = active + queue.length + 1;\n const now = clock();\n await publish(\n buildCiWorkflowProgress(\n {\n trigger,\n runId: run.runId,\n status: 'queued',\n queue: Math.ceil(position / concurrency),\n queuedAt: run.queuedAt,\n ...(manualProvenance ? { provenance: manualProvenance } : {}),\n jobs: [],\n expiresAt: now + adTtl,\n },\n now\n )\n );\n queue.push(run);\n log(\n `[ci] queued ${workflow.path} for ${repo.repoId}@${trigger.commit.slice(0, 7)} [${run.runId.slice(0, 8)}]`\n );\n pump();\n };\n\n /**\n * A PR update supersedes the previous tip (#130): every run for the same\n * pull request that has not concluded is cancelled before the update's\n * own runs are queued. A still-queued run concludes `cancelled` from the\n * queue without reaching the Runner; an active one is aborted and its\n * Workflow Result + final progress marker say `cancelled`.\n */\n const supersedePrRuns = async (\n repo: RepoRuntime,\n prEventId: string,\n updateEventId: string\n ): Promise<void> => {\n const samePr = (run: QueuedRun): boolean =>\n run.repo === repo && run.trigger.pr?.prEventId === prEventId;\n const why = `superseded by PR update ${updateEventId.slice(0, 8)} — cancelled`;\n for (const run of queue.filter(samePr)) {\n queue.splice(queue.indexOf(run), 1);\n run.superseded = true;\n log(\n `[ci] ${repo.repoId} ${run.workflow.path}@${run.trigger.commit.slice(0, 7)} [${run.runId.slice(0, 8)}]: queued run ${why}`\n );\n await concludeWithoutJobs(run, 'cancelled', undefined);\n await releaseCheckout(run.checkout);\n }\n for (const run of activeRuns) {\n if (!samePr(run) || run.superseded) continue;\n run.superseded = true;\n log(\n `[ci] ${repo.repoId} ${run.workflow.path}@${run.trigger.commit.slice(0, 7)} [${run.runId.slice(0, 8)}]: ${why}`\n );\n run.abort?.abort();\n }\n };\n\n // ── Trigger handling: materialize → discover → enqueue ──────────────────\n let checkoutCounter = 0;\n\n const handleTrigger = async (t: PendingTrigger): Promise<void> => {\n const { repo } = t;\n inflight += 1;\n try {\n const dir = join(\n opts.workdir,\n `${repo.repoId}-${t.commit.slice(0, 7)}-${++checkoutCounter}`\n );\n let materialized;\n try {\n materialized = await materialize({\n ownerPubkey: repo.ownerPubkey,\n repoId: repo.repoId,\n relayUrls: [relayUrl],\n commit: t.commit,\n dir,\n ...(t.patch ? { patch: { content: t.patch.content } } : {}),\n ...(t.manual?.tagObjectIds && t.manual.tagObjectIds.length > 0\n ? { extraObjectIds: t.manual.tagObjectIds }\n : {}),\n ...(opts.webSocketFactory\n ? { webSocketFactory: opts.webSocketFactory }\n : {}),\n ...(opts.fetchFn ? { fetchFn: opts.fetchFn } : {}),\n ...(opts.resolveSha ? { resolveSha: opts.resolveSha } : {}),\n ...(opts.gateways ? { gateways: opts.gateways } : {}),\n });\n } catch (err) {\n const reason =\n err instanceof MaterializeError\n ? err.message\n : `materialize failed: ${err instanceof Error ? err.message : String(err)}`;\n log(`[ci] ${repo.repoId}@${t.commit.slice(0, 7)}: ${reason}`);\n if (t.manual) {\n // The workflow is known from the request, so the failure can be published.\n const trigger: CiTriggerContext = {\n repoAddr: repo.addr,\n commit: t.commit,\n workflow: t.manual.workflow,\n reason: 'manual',\n ...(t.ref !== undefined ? { ref: t.ref } : {}),\n ...(t.pr ? { pr: t.pr } : {}),\n };\n const checkout: Checkout = { dir, refs: 0 };\n const provenance: CiProvenance = {\n kind: 'manual-trigger',\n eventId: t.manual.eventId,\n relayUrl,\n pubkey: t.manual.pubkey,\n };\n const broken: DiscoveredWorkflow = {\n ...t.manual.workflow,\n triggers: {},\n jobs: [],\n parseError: reason,\n };\n await enqueueRun(repo, trigger, broken, checkout, {}, provenance);\n }\n return;\n }\n\n const checkout: Checkout = { dir: materialized.dir, refs: 0 };\n const commit = materialized.commit;\n\n // NIP-C1 Manual Trigger: \"A coordinator MUST resolve every supplied `c`\n // object and ignore the request unless all supplied object ids peel to\n // the same commit id.\" Extra `c` values are annotated tags; peel each\n // in the checkout and drop the request on any miss or mismatch.\n if (t.manual?.tagObjectIds && t.manual.tagObjectIds.length > 0) {\n const mismatch = await findUnpeelableTagId(\n materialized.dir,\n t.manual.tagObjectIds,\n commit\n );\n if (mismatch !== null) {\n log(\n `[ci] manual trigger ignored: c ${mismatch.slice(0, 7)} does not peel to ${commit.slice(0, 7)} (or is not in the repo)`\n );\n if (!opts.keepCheckouts)\n await rm(materialized.dir, { recursive: true, force: true }).catch(\n () => undefined\n );\n return;\n }\n }\n\n const workflows = await discoverWorkflows(materialized.dir);\n const tagObjectIds = [\n ...(t.manual?.tagObjectIds ?? []),\n ...(commit !== t.commit && !t.patch ? [t.commit] : []),\n ...(t.patch?.declaredTip && t.patch.declaredTip !== commit\n ? [t.patch.declaredTip]\n : []),\n ];\n const secretsAllowed = repo.authorized.has(t.authorPubkey.toLowerCase());\n const secrets = secretsAllowed ? effectiveSecrets(repo.secrets) : {};\n\n let selected: DiscoveredWorkflow[];\n let manualProvenance: CiProvenance | undefined;\n if (t.manual) {\n const wanted = t.manual.workflow;\n const wf = workflows.find((w) => w.path === wanted.path);\n if (!wf) {\n log(\n `[ci] manual trigger ignored: ${wanted.path} does not exist at ${commit.slice(0, 7)}`\n );\n selected = [];\n } else if (wf.sha256 !== wanted.sha256.toLowerCase()) {\n log(\n `[ci] manual trigger ignored: ${wanted.path} at ${commit.slice(0, 7)} has sha256 ${wf.sha256}, request named ${wanted.sha256}`\n );\n selected = [];\n } else {\n selected = [wf];\n }\n manualProvenance = {\n kind: 'manual-trigger',\n eventId: t.manual.eventId,\n relayUrl,\n pubkey: t.manual.pubkey,\n };\n } else if (t.reason === 'push') {\n selected = workflows.filter(\n (wf) =>\n wf.parseError !== undefined || matchesPush(wf, t.ref as string)\n );\n } else {\n const base = repo.defaultBranch ?? undefined;\n selected = workflows.filter(\n (wf) => wf.parseError !== undefined || matchesPullRequest(wf, base)\n );\n }\n\n if (selected.length === 0) {\n if (!opts.keepCheckouts)\n await rm(materialized.dir, { recursive: true, force: true }).catch(\n () => undefined\n );\n return;\n }\n for (const wf of selected) {\n // Only the served perspective is published as `a`. NIP-C1 lets a\n // Manual Trigger name other maintainers' announcements, but \"a\n // coordinator MUST resolve every `a` tag independently and MUST NOT\n // run a workflow for a repository merely because it was named\": rig\n // serves one perspective per repo and verifies only that one, so it\n // does not republish the others as if it had.\n const trigger: CiTriggerContext = {\n repoAddr: repo.addr,\n commit,\n ...(tagObjectIds.length > 0 ? { tagObjectIds } : {}),\n workflow: { path: wf.path, sha256: wf.sha256 },\n reason: t.reason,\n ...(t.pr ? { pr: t.pr } : t.ref !== undefined ? { ref: t.ref } : {}),\n };\n await enqueueRun(\n repo,\n trigger,\n wf,\n checkout,\n secrets,\n manualProvenance\n );\n }\n if (checkout.refs === 0 && !opts.keepCheckouts) {\n await rm(materialized.dir, { recursive: true, force: true }).catch(\n () => undefined\n );\n }\n } finally {\n inflight -= 1;\n checkIdle();\n }\n };\n\n // ── Event handling (serialized) ──────────────────────────────────────────\n const eventQueue: NostrEvent[] = [];\n let draining = false;\n\n const markRepoProcessed = (repo: RepoRuntime, ev: NostrEvent): void => {\n const cursor = repo.cursor as RepoCursor;\n cursor.processed.push(ev.id);\n if (ev.created_at > cursor.lastCreatedAt) {\n cursor.lastCreatedAt = ev.created_at;\n cursor.lastEventId = ev.id;\n }\n };\n const repoSeen = (repo: RepoRuntime, ev: NostrEvent): boolean =>\n (repo.cursor?.processed ?? []).includes(ev.id);\n\n const handleRepoState = async (\n repo: RepoRuntime,\n ev: NostrEvent\n ): Promise<void> => {\n const cursor = repo.cursor as RepoCursor;\n if (repoSeen(repo, ev) || ev.created_at < cursor.lastCreatedAt) return;\n // Both ref tag shapes, through the shared parser (`../nip34-refs.ts`), so\n // a coordinator serves repos whose state was written by ngit or any other\n // conformant NIP-34 client exactly as it serves rig's own (rig#156).\n const { refs: refMap, headSymref } = parseStateRefTags(ev.tags);\n const refs = Object.fromEntries(refMap);\n if (headSymref) repo.defaultBranch = headSymref;\n const moved = movedRefs(cursor.refs, refs);\n cursor.refs = refs;\n markRepoProcessed(repo, ev);\n await persistCursor();\n if (moved.length === 0) return;\n if (!isServing(repo)) {\n log(\n `[ci] ${repo.repoId}: push seen but no maintainer Service Request — not running`\n );\n return;\n }\n for (const { ref, sha } of moved) {\n if (!ref.startsWith('refs/heads/') && !ref.startsWith('refs/tags/'))\n continue;\n await handleTrigger({\n repo,\n reason: 'push',\n authorPubkey: ev.pubkey,\n commit: sha,\n ref,\n });\n }\n };\n\n const handlePullRequest = async (\n repo: RepoRuntime,\n ev: NostrEvent\n ): Promise<void> => {\n if (repoSeen(repo, ev)) return;\n if (ev.kind === 1617 || ev.kind === 1618) repo.prEvents.set(ev.id, ev);\n markRepoProcessed(repo, ev);\n await persistCursor();\n if (!isServing(repo)) {\n log(\n `[ci] ${repo.repoId}: pull request seen but no maintainer Service Request — not running`\n );\n return;\n }\n const author = ev.pubkey.toLowerCase();\n if (ev.kind === 1617) {\n const parents = tagValues(ev.tags, 'parent-commit');\n const commits = tagValues(ev.tags, 'commit');\n const base = parents[0];\n if (!base || ev.content.trim() === '') {\n log(\n `[ci] ${repo.repoId}: patch ${ev.id.slice(0, 8)} has no parent-commit/content — cannot run`\n );\n return;\n }\n const declaredTip = commits[commits.length - 1];\n await handleTrigger({\n repo,\n reason: 'pull_request',\n authorPubkey: author,\n commit: base,\n patch: { content: ev.content, ...(declaredTip ? { declaredTip } : {}) },\n pr: {\n prEventId: ev.id,\n prAuthor: author,\n prKind: 1617,\n sourceEventId: ev.id,\n sourceAuthor: author,\n sourceKind: 1617,\n },\n });\n return;\n }\n if (ev.kind === 1618) {\n const tip = tagValues(ev.tags, 'c')[0];\n if (!tip) {\n log(\n `[ci] ${repo.repoId}: pull request ${ev.id.slice(0, 8)} has no tip commit — cannot run`\n );\n return;\n }\n await handleTrigger({\n repo,\n reason: 'pull_request',\n authorPubkey: author,\n commit: tip,\n pr: {\n prEventId: ev.id,\n prAuthor: author,\n prKind: 1618,\n sourceEventId: ev.id,\n sourceAuthor: author,\n sourceKind: 1618,\n },\n });\n return;\n }\n // 1619: PR update — the PR it moves must be known.\n const prId = tagValues(ev.tags, 'E')[0];\n const pr = prId ? repo.prEvents.get(prId) : undefined;\n const tip = tagValues(ev.tags, 'c')[0];\n if (!pr || !tip) {\n log(\n `[ci] ${repo.repoId}: PR update ${ev.id.slice(0, 8)} references an unknown PR or no commit — ignored`\n );\n return;\n }\n await supersedePrRuns(repo, pr.id, ev.id);\n await handleTrigger({\n repo,\n reason: 'pull_request',\n authorPubkey: author,\n commit: tip,\n pr: {\n prEventId: pr.id,\n prAuthor: pr.pubkey.toLowerCase(),\n prKind: pr.kind === 1617 ? 1617 : 1618,\n sourceEventId: ev.id,\n sourceAuthor: author,\n sourceKind: 1619,\n },\n });\n };\n\n const handleManualTrigger = async (ev: NostrEvent): Promise<void> => {\n if (inboxProcessed.has(ev.id)) return;\n inboxProcessed.add(ev.id);\n inbox.lastCreatedAt = Math.max(inbox.lastCreatedAt ?? 0, ev.created_at);\n await persistCursor();\n const manual = parseCiManualTrigger(ev, me);\n if (!manual) {\n log(`[ci] malformed manual trigger ${ev.id.slice(0, 8)} ignored`);\n return;\n }\n const repo = repos.get(manual.trigger.repoAddr);\n if (!repo) {\n log(\n `[ci] manual trigger for unserved repo ${manual.trigger.repoAddr} ignored`\n );\n return;\n }\n if (!repo.authorized.has(manual.pubkey)) {\n log(\n `[ci] manual trigger from non-maintainer ${manual.pubkey.slice(0, 8)} ignored`\n );\n return;\n }\n const t = manual.trigger;\n await handleTrigger({\n repo,\n reason: 'manual',\n authorPubkey: manual.pubkey,\n commit: t.commit,\n ...(t.ref !== undefined ? { ref: t.ref } : {}),\n ...(t.pr ? { pr: t.pr } : {}),\n manual: {\n eventId: manual.eventId,\n pubkey: manual.pubkey,\n workflow: t.workflow,\n ...(t.tagObjectIds ? { tagObjectIds: t.tagObjectIds } : {}),\n },\n });\n };\n\n const handleSecretUpdate = async (ev: NostrEvent): Promise<void> => {\n if (inboxProcessed.has(ev.id)) return;\n inboxProcessed.add(ev.id);\n inbox.lastCreatedAt = Math.max(inbox.lastCreatedAt ?? 0, ev.created_at);\n await persistCursor();\n const update = parseCiSecretUpdate(ev);\n if (!update || update.coordinatorPubkey !== me) return;\n // NIP-C1: the `a` coordinate is the MAINTAINER's own repository perspective\n // (`30617:<maintainer>:<repo-id>`, signer == its pubkey). rig serves one\n // perspective per repo id, so the update lands on the served repo with\n // that id — provided the signer is one of its maintainers.\n const perspective = parseRepoAddress(update.repoAddr);\n const repo = perspective\n ? [...repos.values()].find((r) => r.repoId === perspective.repoId)\n : undefined;\n if (!repo) {\n log(`[ci] secret update for unserved repo ${update.repoAddr} ignored`);\n return;\n }\n if (!repo.authorized.has(update.pubkey)) {\n log(\n `[ci] ${repo.repoId}: secret update from non-maintainer ${update.pubkey.slice(0, 8)} ignored`\n );\n return;\n }\n const ad = liveAdvertisements.get(update.advertisementId);\n if (!ad || update.recipientPubkey !== secretsKey.pubkey) {\n log(\n `[ci] ${repo.repoId}: secret update ${ev.id.slice(0, 8)} addressed to a stale secrets key — re-send it`\n );\n return;\n }\n if (update.createdAt < ad.createdAt || update.createdAt >= ad.expiresAt) {\n log(\n `[ci] ${repo.repoId}: secret update ${ev.id.slice(0, 8)} outside its advertisement window — ignored`\n );\n return;\n }\n try {\n const plain = decryptSecretUpdate(\n update.ciphertext,\n secretsKey.secretKey,\n update.senderPubkey\n );\n validateSecretUpdate(plain, {\n pubkey: ev.pubkey,\n created_at: ev.created_at,\n });\n repo.secrets = applySecretUpdate(repo.secrets, plain, ev.id);\n await persistSecrets();\n const names = [...Object.keys(plain.set), ...plain.remove];\n log(\n `[ci] ${repo.repoId}: secrets updated by ${update.pubkey.slice(0, 8)} (${names.join(', ')})`\n );\n } catch (err) {\n log(\n `[ci] ${repo.repoId}: secret update ${ev.id.slice(0, 8)} rejected: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n };\n\n const handleEvent = async (ev: NostrEvent): Promise<void> => {\n switch (ev.kind) {\n case REPOSITORY_ANNOUNCEMENT_KIND: {\n const repoId = tagValues(ev.tags, 'd')[0];\n const repo = repoId\n ? repos.get(repoAddress(ev.pubkey, repoId))\n : undefined;\n if (repo && ev.pubkey.toLowerCase() === repo.ownerPubkey) {\n repo.authorized = authorizedStatusAuthors(repo.ownerPubkey, ev.tags);\n }\n return;\n }\n case REPOSITORY_STATE_KIND: {\n const repoId = tagValues(ev.tags, 'd')[0];\n const repo = repoId\n ? repos.get(repoAddress(ev.pubkey, repoId))\n : undefined;\n if (repo && ev.pubkey.toLowerCase() === repo.ownerPubkey)\n await handleRepoState(repo, ev);\n return;\n }\n case 1617:\n case 1618:\n case 1619: {\n for (const addr of tagValues(ev.tags, 'a')) {\n const repo = repos.get(addr);\n if (repo) {\n await handlePullRequest(repo, ev);\n return;\n }\n }\n return;\n }\n case CI_SERVICE_REQUEST_KIND:\n case CI_SERVICE_STOP_KIND:\n handleControl(ev);\n return;\n case CI_MANUAL_TRIGGER_KIND:\n await handleManualTrigger(ev);\n return;\n case CI_SECRET_UPDATE_KIND:\n await handleSecretUpdate(ev);\n return;\n default:\n return;\n }\n };\n\n const drain = async (): Promise<void> => {\n if (draining) return;\n draining = true;\n try {\n while (eventQueue.length > 0) {\n const ev = eventQueue.shift() as NostrEvent;\n try {\n await handleEvent(ev);\n } catch (err) {\n log(\n `[ci] event ${ev.id.slice(0, 8)} (kind ${ev.kind}) failed: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n } finally {\n draining = false;\n checkIdle();\n }\n };\n\n // ── Subscriptions ────────────────────────────────────────────────────────\n const filters: NostrFilter[] = [controlFilter];\n const sinceFor: ((() => number | undefined) | null)[] = [null];\n for (const repo of repos.values()) {\n filters.push({\n kinds: [REPOSITORY_ANNOUNCEMENT_KIND, REPOSITORY_STATE_KIND],\n authors: [repo.ownerPubkey],\n '#d': [repo.repoId],\n });\n sinceFor.push(null);\n filters.push({ kinds: [1617, 1618, 1619], '#a': [repo.addr] });\n sinceFor.push(() => repo.cursor?.lastCreatedAt);\n }\n filters.push({\n kinds: [CI_MANUAL_TRIGGER_KIND, CI_SECRET_UPDATE_KIND],\n '#p': [me],\n });\n sinceFor.push(() => inbox.lastCreatedAt);\n\n const subscription: RelaySubscription = subscribeRelay({\n url: relayUrl,\n filters,\n webSocketFactory: opts.webSocketFactory ?? defaultWebSocketFactory,\n scheduler,\n onEvent: (ev) => {\n eventQueue.push(ev);\n void drain();\n },\n onStatus: (status, detail) => {\n if (status === 'open') log(`[ci] connected to ${relayUrl}`);\n else if (status === 'reconnecting')\n log(`[ci] relay connection lost (${detail ?? ''}) — reconnecting`);\n },\n since: (index) => sinceFor[index]?.() ?? undefined,\n });\n\n // ── Stop ─────────────────────────────────────────────────────────────────\n let stopResolve: () => void = () => undefined;\n const stopped = new Promise<void>((resolve) => {\n stopResolve = resolve;\n });\n let stopPromise: Promise<void> | null = null;\n\n const stop = (): Promise<void> => {\n if (stopPromise) return stopPromise;\n stopPromise = (async () => {\n stopping = true;\n subscription.close();\n if (adTimer !== null) scheduler.clearTimeout(adTimer);\n const dropped = queue.splice(0);\n for (const run of dropped) {\n log(\n `[ci] dropping queued run ${run.runId.slice(0, 8)} (its queued marker expires on its own)`\n );\n await releaseCheckout(run.checkout);\n }\n for (const abort of activeAborts) abort.abort();\n await new Promise<void>((resolve) => {\n if (inflight === 0 && !draining) return resolve();\n idleWaiters.push(resolve);\n });\n await persistCursor();\n stopResolve();\n })();\n return stopPromise;\n };\n\n return {\n pubkey: me,\n get secretsKeyPubkey() {\n return secretsKey.pubkey;\n },\n get advertisementId() {\n return advertisementId;\n },\n stopped,\n stop,\n idle: () =>\n new Promise<void>((resolve) => {\n if (\n inflight === 0 &&\n queue.length === 0 &&\n eventQueue.length === 0 &&\n !draining\n )\n return resolve();\n idleWaiters.push(resolve);\n }),\n serving: () =>\n [...repos.values()].map((repo) => ({\n repoAddr: repo.addr,\n serving: isServing(repo),\n })),\n };\n}\n\n/**\n * Peel every `c` tag id a Manual Trigger supplied and return the first one\n * that is missing from the checkout or peels to a different commit than the\n * run's, or null when they all agree (NIP-C1 Manual Trigger rule).\n */\nasync function findUnpeelableTagId(\n checkoutDir: string,\n tagObjectIds: readonly string[],\n commit: string\n): Promise<string | null> {\n for (const id of tagObjectIds) {\n if (!/^[0-9a-f]{40}$/i.test(id)) return id;\n let peeled: string;\n try {\n peeled = (\n await runGit(checkoutDir, [\n 'rev-parse',\n '--verify',\n '--quiet',\n `${id}^{commit}`,\n ])\n ).trim();\n } catch {\n return id;\n }\n if (peeled.toLowerCase() !== commit.toLowerCase()) return id;\n }\n return null;\n}\n","/**\n * Materialize ONE commit into a working directory for a CI run (#125), using\n * rig's free read path — the coordinator, not the runner, owns repo\n * knowledge, so `act`'s local checkout handling never needs GitHub or any git\n * server (user story 23).\n *\n * Pipeline (the same engine `rig clone` uses, ../cli/clone.ts): relay state\n * (kind:30617/30618 → sha→txId hints) → download + SHA-1-verify the object\n * closure of the commit from Arweave gateways (../read-pipeline.ts) → write\n * the objects through git plumbing into a fresh repository\n * (../materialize.ts) → detached checkout of the commit.\n *\n * The kind:1617 patch path: `rig pr create` publishes patches whose content\n * is real `git format-patch` output against a `parent-commit`. For such a PR\n * the base commit is materialized and the patch applied with `git am`; the\n * resulting HEAD is the commit the run is for (its sha differs from the\n * contributor's local sha because the committer differs — the coordinator\n * publishes both, see ./coordinator.ts).\n *\n * Everything shells out with argument ARRAYS (never a shell); refnames never\n * reach git here at all (a detached checkout by full sha).\n */\n\nimport { spawn } from 'node:child_process';\nimport { mkdir } from 'node:fs/promises';\nimport type { FetchLike } from '../object-fetch.js';\nimport { runGit, writeGitObjects } from '../materialize.js';\nimport { collectRepoObjects, missingObjectsMessage } from '../read-pipeline.js';\nimport { fetchRemoteState, type WebSocketFactory } from '../remote-state.js';\n\nconst FULL_SHA_RE = /^[0-9a-f]{40}$/;\n\n/** The commit could not be obtained — the run concludes `startup_failure`. */\nexport class MaterializeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'MaterializeError';\n }\n}\n\nexport interface MaterializeCommitOptions {\n ownerPubkey: string;\n repoId: string;\n relayUrls: string[];\n /** The commit to check out (full sha). With `patch`, the patch's base. */\n commit: string;\n /** Destination directory (created; must not already be a repository). */\n dir: string;\n /** kind:1617 path: apply this format-patch text on top of `commit`. */\n patch?: { content: string };\n /**\n * Extra object ids to fetch alongside the commit's closure — the annotated\n * tag objects a Manual Trigger names as additional `c` values, so the\n * coordinator can peel them in the checkout. A missing one is NOT fatal\n * here (the caller decides what an unpeelable id means).\n */\n extraObjectIds?: readonly string[];\n webSocketFactory?: WebSocketFactory;\n fetchFn?: FetchLike;\n resolveSha?: (sha: string, repo: string) => Promise<string | null>;\n gateways?: readonly string[];\n /** Committer identity for `git am` (default: the coordinator's). */\n committer?: { name: string; email: string };\n}\n\nexport interface MaterializedCommit {\n dir: string;\n /** HEAD after checkout: `commit`, an annotated tag's peeled commit, or the `git am` result. */\n commit: string;\n /** Objects written into the checkout's object store. */\n objectsWritten: number;\n}\n\nexport type MaterializeCommit = (\n opts: MaterializeCommitOptions\n) => Promise<MaterializedCommit>;\n\n/** Run git with argument-array safety and a stdin payload. */\nfunction gitWithStdin(\n cwd: string,\n args: string[],\n stdin: string,\n env: NodeJS.ProcessEnv\n): Promise<string> {\n return new Promise((resolve, reject) => {\n const child = spawn('git', args, {\n cwd,\n env,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n let stdout = '';\n let stderr = '';\n child.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString('utf-8');\n });\n child.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString('utf-8');\n });\n child.on('error', (err) =>\n reject(new Error(`failed to spawn git: ${err.message}`))\n );\n child.on('close', (code) => {\n if (code !== 0) {\n reject(\n new Error(`git ${args[0]} failed (exit ${code}): ${stderr.trim()}`)\n );\n } else {\n resolve(stdout);\n }\n });\n child.stdin.on('error', () => {\n // 'close' surfaces the failure\n });\n child.stdin.write(stdin);\n child.stdin.end();\n });\n}\n\nexport const materializeCommit: MaterializeCommit = async (opts) => {\n if (!FULL_SHA_RE.test(opts.commit)) {\n throw new MaterializeError(\n `not a full commit sha: ${JSON.stringify(opts.commit)}`\n );\n }\n\n // ── Remote state (kind:30617 + 30618) — free ────────────────────────────\n const remote = await fetchRemoteState({\n relayUrls: opts.relayUrls,\n ownerPubkey: opts.ownerPubkey,\n repoId: opts.repoId,\n ...(opts.webSocketFactory\n ? { webSocketFactory: opts.webSocketFactory }\n : {}),\n ...(opts.resolveSha ? { resolveSha: opts.resolveSha } : {}),\n });\n if (!remote.announced && remote.refsEvent === null) {\n throw new MaterializeError(\n `repository ${opts.ownerPubkey.slice(0, 8)}…/${opts.repoId} is not on the relay`\n );\n }\n\n // ── Download + verify the commit's closure — free ───────────────────────\n const collected = await collectRepoObjects({\n tips: [opts.commit, ...(opts.extraObjectIds ?? [])],\n shaToTxId: remote.shaToTxId,\n resolveMissing: (shas) => remote.resolveMissing(shas),\n ...(opts.fetchFn ? { fetchFn: opts.fetchFn } : {}),\n ...(opts.gateways ? { gateways: opts.gateways } : {}),\n });\n const extra = new Set(\n (opts.extraObjectIds ?? []).map((id) => id.toLowerCase())\n );\n const fatal = collected.missing.filter(\n (m) => !extra.has(m.sha.toLowerCase())\n );\n if (fatal.length > 0) {\n throw new MaterializeError(\n missingObjectsMessage(\n fatal,\n `cannot materialize ${opts.commit.slice(0, 7)}`\n )\n );\n }\n\n // ── Fresh repository + detached checkout ────────────────────────────────\n await mkdir(opts.dir, { recursive: true });\n await runGit(opts.dir, ['init', '--quiet', '--initial-branch=main']);\n const objectsWritten = await writeGitObjects(\n opts.dir,\n collected.objects.values()\n );\n await runGit(opts.dir, ['checkout', '--quiet', '--detach', opts.commit]);\n\n if (opts.patch) {\n const committer = opts.committer ?? {\n name: 'rig ci',\n email: 'ci@rig.invalid',\n };\n const env: NodeJS.ProcessEnv = {\n ...process.env,\n GIT_COMMITTER_NAME: committer.name,\n GIT_COMMITTER_EMAIL: committer.email,\n };\n try {\n await gitWithStdin(opts.dir, ['am', '--quiet'], opts.patch.content, env);\n } catch (err) {\n throw new MaterializeError(\n `patch does not apply on ${opts.commit.slice(0, 7)}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n // HEAD is the peeled commit: `commit` itself, the annotated tag's target\n // when a tag object was requested, or the `git am` result.\n const head = (await runGit(opts.dir, ['rev-parse', 'HEAD'])).trim();\n\n return { dir: opts.dir, commit: head, objectsWritten };\n};\n","/**\n * A long-lived NIP-01 subscription (#125): the coordinator's ears.\n *\n * `../remote-state.ts`'s `queryRelay` is a one-shot REQ → EVENT* → EOSE →\n * CLOSE read, which is exactly right for every free read rig does — and\n * exactly wrong for a coordinator that must hear a push the moment it lands.\n * This module keeps ONE socket open per relay with one REQ per filter, keeps\n * delivering EVENTs after EOSE, and reconnects with exponential backoff when\n * the relay drops the connection. On every (re)connect the REQs are re-sent\n * with `since` taken from the caller's cursor getter, so a blip replays only\n * what was missed (user story 13).\n *\n * Built on the same `WebSocketFactory` seam as the rest of rig, so tests\n * drive it with an in-process scripted relay (./ci-testkit.ts) and the\n * reconnect timers go through an injectable scheduler (no real sleeps).\n * Relay payload decoding tolerates the same three encodings queryRelay does\n * (object / double-JSON / TOON); the helper is private there, so it is\n * mirrored here rather than exported from a module this slice does not own.\n */\n\nimport { decode as decodeToon } from '@toon-format/toon';\nimport type {\n NostrEvent,\n NostrFilter,\n WebSocketFactory,\n WebSocketLike,\n} from '../remote-state.js';\n\n/** Timer seam so the coordinator and its tests share one clock. */\nexport interface Scheduler {\n setTimeout(fn: () => void, ms: number): unknown;\n clearTimeout(handle: unknown): void;\n}\n\nexport const realScheduler: Scheduler = {\n setTimeout: (fn, ms) => setTimeout(fn, ms),\n clearTimeout: (handle) =>\n clearTimeout(handle as ReturnType<typeof setTimeout>),\n};\n\nexport type SubscriptionStatus = 'open' | 'closed' | 'reconnecting' | 'error';\n\nexport interface SubscribeRelayOptions {\n url: string;\n /** One REQ per filter; `onEvent` reports which one matched by index. */\n filters: NostrFilter[];\n webSocketFactory: WebSocketFactory;\n onEvent(event: NostrEvent, filterIndex: number): void;\n /** Called once per filter per (re)connect when the relay signals EOSE. */\n onEose?(filterIndex: number): void;\n onStatus?(status: SubscriptionStatus, detail?: string): void;\n /**\n * Cursor getter, evaluated at every (re)connect: when it returns a number\n * every filter is re-sent with that `since` (a filter's own `since` wins\n * when set). Return undefined for a full replay.\n */\n since?: (filterIndex: number) => number | undefined;\n backoff?: { initialMs?: number; maxMs?: number };\n scheduler?: Scheduler;\n}\n\nexport interface RelaySubscription {\n /** Stop for good: no reconnect, socket closed, no further callbacks. */\n close(): void;\n readonly connected: boolean;\n /** How many times the subscription has (re)connected so far. */\n readonly connections: number;\n}\n\nconst WS_OPEN = 1;\n\n/** Mirror of remote-state.ts's tolerant EVENT payload decoder. */\nexport function decodeRelayEvent(payload: unknown): NostrEvent | null {\n if (payload !== null && typeof payload === 'object')\n return payload as NostrEvent;\n if (typeof payload !== 'string') return null;\n try {\n const parsed: unknown = JSON.parse(payload);\n if (parsed !== null && typeof parsed === 'object')\n return parsed as NostrEvent;\n } catch {\n // Not JSON — fall through to TOON\n }\n try {\n return decodeToon(payload) as unknown as NostrEvent;\n } catch {\n return null;\n }\n}\n\n/** Open a persistent subscription. Never throws synchronously. */\nexport function subscribeRelay(opts: SubscribeRelayOptions): RelaySubscription {\n const scheduler = opts.scheduler ?? realScheduler;\n const initialBackoff = opts.backoff?.initialMs ?? 1000;\n const maxBackoff = opts.backoff?.maxMs ?? 60_000;\n const base = `ci-${Math.random().toString(36).slice(2, 8)}`;\n\n let ws: WebSocketLike | null = null;\n let closed = false;\n let connected = false;\n let connections = 0;\n let backoffMs = initialBackoff;\n let reconnectHandle: unknown = null;\n /** subId → filter index for the CURRENT socket generation. */\n let subIds = new Map<string, number>();\n\n const status = (s: SubscriptionStatus, detail?: string): void => {\n opts.onStatus?.(s, detail);\n };\n\n const scheduleReconnect = (detail: string): void => {\n if (closed || reconnectHandle !== null) return;\n connected = false;\n status('reconnecting', detail);\n const delay = backoffMs;\n backoffMs = Math.min(backoffMs * 2, maxBackoff);\n reconnectHandle = scheduler.setTimeout(() => {\n reconnectHandle = null;\n connect();\n }, delay);\n };\n\n const connect = (): void => {\n if (closed) return;\n const generation = ++connections;\n subIds = new Map();\n let socket: WebSocketLike;\n try {\n socket = opts.webSocketFactory(opts.url);\n } catch (err) {\n scheduleReconnect(\n `connect failed: ${err instanceof Error ? err.message : String(err)}`\n );\n return;\n }\n ws = socket;\n const mine = (): boolean => ws === socket && generation === connections;\n\n socket.addEventListener('open', () => {\n if (!mine() || closed) return;\n connected = true;\n backoffMs = initialBackoff;\n status('open');\n opts.filters.forEach((filter, index) => {\n const subId = `${base}-${generation}-${index}`;\n subIds.set(subId, index);\n const since = filter.since ?? opts.since?.(index);\n const sent: NostrFilter =\n since !== undefined ? { ...filter, since } : { ...filter };\n socket.send(JSON.stringify(['REQ', subId, sent]));\n });\n });\n\n socket.addEventListener('message', (msgEvent: { data?: unknown }) => {\n if (!mine() || closed) return;\n let msg: unknown[];\n try {\n msg = JSON.parse(String(msgEvent.data)) as unknown[];\n } catch {\n return;\n }\n if (!Array.isArray(msg) || msg.length < 2) return;\n const index = subIds.get(String(msg[1]));\n if (index === undefined) return;\n if (msg[0] === 'EVENT' && msg[2] !== undefined) {\n const event = decodeRelayEvent(msg[2]);\n if (event) opts.onEvent(event, index);\n } else if (msg[0] === 'EOSE') {\n opts.onEose?.(index);\n } else if (msg[0] === 'CLOSED') {\n // The relay refused/ended this REQ; treat like a dropped socket.\n scheduleReconnect(`relay closed subscription: ${String(msg[2] ?? '')}`);\n }\n });\n\n socket.addEventListener('error', (event: { message?: unknown }) => {\n if (!mine() || closed) return;\n const detail =\n typeof event === 'object' && event !== null && 'message' in event\n ? String(event.message)\n : 'unknown';\n status('error', detail);\n try {\n socket.close();\n } catch {\n // already closing\n }\n scheduleReconnect(`socket error: ${detail}`);\n });\n\n socket.addEventListener('close', () => {\n if (!mine() || closed) return;\n scheduleReconnect('socket closed');\n });\n };\n\n connect();\n\n return {\n close() {\n if (closed) return;\n closed = true;\n connected = false;\n if (reconnectHandle !== null) {\n scheduler.clearTimeout(reconnectHandle);\n reconnectHandle = null;\n }\n const socket = ws;\n ws = null;\n if (socket) {\n try {\n if (socket.readyState === WS_OPEN) {\n for (const subId of subIds.keys()) {\n socket.send(JSON.stringify(['CLOSE', subId]));\n }\n }\n socket.close();\n } catch {\n // ignore close errors\n }\n }\n status('closed');\n },\n get connected() {\n return connected;\n },\n get connections() {\n return connections;\n },\n };\n}\n","/**\n * Coordinator state on disk (#125): what `rig ci serve` must remember across\n * restarts so a relay blip or a process restart never loses or repeats a run.\n *\n * <stateDir>/cursor.json per served repo: the last processed event, the\n * ref map the last 30618 left behind (push\n * triggers are diffs against it), and a bounded\n * list of already-processed trigger ids; plus the\n * coordinator inbox's own cursor (9840 / 29846).\n * <stateDir>/secrets.json per served repo: the accepted secret inventory\n * (../ci/secrets.ts's SecretInventory).\n *\n * Secret VALUES are stored in plaintext here, under the coordinator's own\n * config directory — the #125 spec allows exactly that (\"keeps the decrypted\n * inventory in memory and on disk under its config directory\"). What is\n * NEVER persisted is the NIP-44 recipient key (the advertised `secrets-key`):\n * it lives only in the running process and is regenerated at every start, as\n * NIP-C1 requires, so an update encrypted to a previous process cannot be\n * opened by this one — maintainers re-send after a restart, the inventory\n * already accepted survives.\n *\n * Files are written atomically (temp file + rename) so a crash mid-write\n * leaves the previous state intact. `stateDir` defaults to\n * `<TOON_CLIENT_HOME|~/.toon-client>/rig-ci/<coordinator-pubkey>`.\n */\n\nimport { mkdir, readFile, rename, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport type { SecretInventory } from './secrets.js';\n\n/** Where one served repo's trigger processing stands. */\nexport interface RepoCursor {\n /** `created_at` of the newest trigger event processed for this repo. */\n lastCreatedAt: number;\n /** Id of that event (tie-breaker / audit). */\n lastEventId: string;\n /** Ref map (`refname → sha`) of the last kind:30618 seen — push diffs use it. */\n refs: Record<string, string>;\n /** Ids of trigger events already handled (bounded, newest last). */\n processed: string[];\n}\n\n/** The coordinator inbox (`#p` = coordinator) cursor: 9840 + 29846. */\nexport interface InboxCursor {\n lastCreatedAt?: number;\n processed: string[];\n}\n\n/** Repo address → its cursor. */\nexport type CoordinatorCursor = Record<string, RepoCursor>;\n\n/** Repo address → its accepted secret inventory. */\nexport type CoordinatorSecrets = Record<string, SecretInventory>;\n\nexport interface CoordinatorState {\n cursor: CoordinatorCursor;\n inbox: InboxCursor;\n secrets: CoordinatorSecrets;\n}\n\n/** Processed-id lists are capped at this many entries (newest kept). */\nexport const MAX_PROCESSED_IDS = 1000;\n\nexport function cursorPath(stateDir: string): string {\n return join(stateDir, 'cursor.json');\n}\n\nexport function secretsPath(stateDir: string): string {\n return join(stateDir, 'secrets.json');\n}\n\n/** `<TOON_CLIENT_HOME|~/.toon-client>/rig-ci/<coordinator-pubkey>` */\nexport function defaultCoordinatorStateDir(\n env: NodeJS.ProcessEnv,\n coordinatorPubkey: string\n): string {\n const home = env['TOON_CLIENT_HOME'] ?? join(homedir(), '.toon-client');\n return join(home, 'rig-ci', coordinatorPubkey.toLowerCase());\n}\n\n/** Keep the newest {@link MAX_PROCESSED_IDS} ids. */\nexport function boundProcessed(ids: string[]): string[] {\n return ids.length > MAX_PROCESSED_IDS\n ? ids.slice(ids.length - MAX_PROCESSED_IDS)\n : ids;\n}\n\ninterface CursorFile {\n repos: CoordinatorCursor;\n inbox: InboxCursor;\n}\n\nasync function readJson<T>(path: string): Promise<T | undefined> {\n let text: string;\n try {\n text = await readFile(path, 'utf-8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return undefined;\n throw err;\n }\n try {\n return JSON.parse(text) as T;\n } catch (err) {\n throw new Error(\n `coordinator state file ${path} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n}\n\nasync function writeJsonAtomic(path: string, value: unknown): Promise<void> {\n await mkdir(join(path, '..'), { recursive: true });\n const tmp = `${path}.tmp-${process.pid}-${Math.random().toString(36).slice(2, 8)}`;\n await writeFile(tmp, JSON.stringify(value, null, 2) + '\\n', 'utf-8');\n await rename(tmp, path);\n}\n\n/** Load both files; a missing file (or directory) is an empty state. */\nexport async function loadCoordinatorState(\n stateDir: string\n): Promise<CoordinatorState> {\n const cursorFile = await readJson<CursorFile>(cursorPath(stateDir));\n const secrets =\n (await readJson<CoordinatorSecrets>(secretsPath(stateDir))) ?? {};\n const cursor: CoordinatorCursor = {};\n for (const [addr, repo] of Object.entries(cursorFile?.repos ?? {})) {\n cursor[addr] = { ...repo, processed: boundProcessed(repo.processed ?? []) };\n }\n const inbox: InboxCursor = {\n ...(cursorFile?.inbox?.lastCreatedAt !== undefined\n ? { lastCreatedAt: cursorFile.inbox.lastCreatedAt }\n : {}),\n processed: boundProcessed(cursorFile?.inbox?.processed ?? []),\n };\n return { cursor, inbox, secrets };\n}\n\n/** Persist the repo cursors + inbox cursor (processed lists bounded). */\nexport async function saveCursor(\n stateDir: string,\n cursor: CoordinatorCursor,\n inbox: InboxCursor\n): Promise<void> {\n const repos: CoordinatorCursor = {};\n for (const [addr, repo] of Object.entries(cursor)) {\n repos[addr] = { ...repo, processed: boundProcessed(repo.processed) };\n }\n const file: CursorFile = {\n repos,\n inbox: { ...inbox, processed: boundProcessed(inbox.processed) },\n };\n await writeJsonAtomic(cursorPath(stateDir), file);\n}\n\n/** Persist the secret inventories. */\nexport async function saveSecrets(\n stateDir: string,\n secrets: CoordinatorSecrets\n): Promise<void> {\n await writeJsonAtomic(secretsPath(stateDir), secrets);\n}\n"],"mappings":"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","names":["join","conclusion","readFile","rm","join","spawn","resolve","spawn","mkdir","join","rm","conclusion","now","resolve","readFile","join","checkout","tip","best","resolve","join"]}