Decisions

Every ADR governing the fleet's choices.

What we picked, what we ruled out, and the reasoning that survives across sessions. Sourced from dark-factory/docs/decisions/; sorted newest first.

179
ADR-179 — Factory and fleet are disjoint concerns; fleet-events is renamed to factory-events

Through 2026 H1 the fleet's vocabulary has used fleet and factory somewhat interchangeably:

178
ADR-178 — Library / CLI / UI three-layer pattern with infra-agnostic providers

Gyrum's operational logic today is scattered. Provisioning a runner involves:

177
ADR-177: single canonical owner per external integration

Two concrete divergences surfaced on 2026-05-09 made the lack of a named principle expensive:

176
ADR-176: Fixer fire-event verification principle

Every gyrum-fixup fire emits structured evidence at fire time — not just "did the gate stop complaining?" — so fixer correctness is queryable, auditable, and…

175
ADR-175: Trust-boundary placement as fleet-wide principle

Every SaaS app the fleet runs has exactly one trust boundary — the BFF — and internal services trust the network behind it. New services default to no public…

174
ADR-174 — Verb-led IA synthesis: nine verb-routes over a preserved noun-route catalogue

ai-frontend has 95 noun-routes (/operate, /infra, /projects, /platform, /me, /ops, /team, /incidents, /release-status, …) shaped around organisational silos…

173
ADR-173: Player Queue as Unattended-Execution Substrate; Relationship to Warp

Same gating shape as ADRs 171 + 172. Reclassify to Accepted when warp#1762 Phase 1.1 (type:deploy_request schema) ships, demonstrating the typed-ticket-on-Warp…

172
ADR-172: Submitter ↔ Worker Pipeline Contract

The companion JSON Schema lives at [docs/specs/pipeline-contract-v0.md](../specs/pipeline-contract-v0.md).

171
ADR-171: Player + Pipeline + Blocks as the Unattended Execution Model

This ADR locks the design now while the substrate paper is fresh, but the architecture it commits to is gated on prior phases shipping evidence:

170
ADR-170: Worker Threat Model and Security Principles

The fleet's unattended work runs as Claude Code sessions on the operator's laptop. The substrate paper (docs/proposals/unattended-pipeline-execution-substrate.m…

169
ADR-169: Auto-swarm dispatch + auto-merge gating — the queue is the dispatcher; the verification spec is the merge gate

Per [warp#1783](https://warp.gyrum.ai/items/1783) the self-healing EPIC has no dispatcher: the auto-filed warp ticket rides the existing agent-executor queue,…

168
ADR-168: Auto-diagnosis library shape — YAML rules, regression fixtures, curation policy

The auto-diagnosis library is a directory of versioned YAML rules under gyrum-knowledge-base/auto-diagnosis/<shape>.yaml. Each rule has six required fields…

167
ADR-167: Structured failure event schema — the wire format the self-healing fleet observes

Every gyrum-* tool, ansible wrapper, and CI hook that can fail emits a structured JSON event when it fails. The event has six required fields (tool, context,…

166
ADR-166: ai-research splits along an executor-isolation boundary; the queue is the seam

ai-research today is one binary holding every fleet secret (SSH, Hetzner, GitHub PATs). The operator wants to put it on the internet but the secret surface is…

165
ADR-165: GHCR retention policy + tag taxonomy — 3-layer auto-prune defense

Operator audit 2026-05-07 surfaced 4,455 PRIVATE container versions across the three highest-volume repos on GHCR (GitHub Container Registry, the OCI image…

164
ADR-164: Ticket category axis — work / warning / approval-needed / incident, orthogonal to type

The current warp ticket model has type (bug | feature | chore | docs | refactor | security | renewal | audit | epic | principle | campaign) which answers WHAT…

163
ADR-163: Self-hosted-only fleet CI — runs-on contract + audited override + 1-week soft-warn rollover

Phase 0 of EPIC [warp#1721](https://warp.gyrum.ai/items/1721). Operator (jon, 2026-05-07 ~01:30Z) named the structural shape: "we're using our own runners.…

162
ADR-162: Stacked-PR contract — branch naming + base resolution + rebase semantics + audit shape

Phase 0 of EPIC [warp#1686](https://warp.gyrum.ai/items/1686) ("Stacked PRs — branch of a branch"). Today every gyrum-* tool assumes PRs target main. This…

161
ADR-161: Auto-deploy fires on `workflow_run` completion of `release.yml`, not on push to `main`

The problem this ADR closes is upstream of the receiver, not in it. Push lands on main before release.yml has run; :latest does not yet point at the new SHA.…

160
ADR-160: gyrum-fleet — Go library + CLI for fleet ops (supersedes bash-script shape)

Phase 0 of EPIC [warp#1189](https://warp.gyrum.ai/items/1189) ("direct ssh/sudo access forbidden; all access via scripts"). The EPIC is correct in structural…

159
ADR-159: Fleet host-state-as-truth — hosts.yaml schema + bidirectional CI gate

Sub-ticket H0.1 of EPIC [warp#1582](https://warp.gyrum.ai/items/1582) (fleet host-state-as-source-of-truth). Sister to [ADR-153](./153-host-state-declarative-sc…

158
ADR-158: gyrum SaaS shell — shell-vs-page boundary, sub-package shape, dogfood adoption order

Phase 0 of EPIC warp#1646 (fleet SaaS chrome). The EPIC's structural lift only works if the shell-vs-page boundary is locked BEFORE Phase 1 extraction; without…

157
ADR-157: sitemap gate semantics + auto-update vs manual-curation

Sub-ticket S0.3 of EPIC warp#1551 (fleet sitemap-as-source-of-truth) — the third and last of the Phase 0 ADRs. ADR-155 (S0.1, shipped 2026-05-06 PR#664) names…

156
ADR-156: sitemap UX shape + role/auth handling

Sub-ticket S0.2 of EPIC warp#1551 (fleet sitemap-as-source-of-truth). Phase 0 ships three sister ADRs that lock contracts before Phase 1 substrate ships: S0.1…

155
ADR-155: sitemap.json schema + per-stack discovery contracts

Sub-ticket S0.1 of EPIC warp#1551 (fleet sitemap-as-source-of-truth). Phase 0 ships three sister ADRs that lock contracts before Phase 1 substrate ships: S0.1…

154
ADR-154: SaaS inter-service link graph (templates declare contracts; factory composes + validates)

Sub-ticket of EPIC warp#1592 Phase 0. Phase 0 ships three sister ADRs that lock the SaaS template library's contracts before any Phase 1 template ships: T0.1…

153
ADR-153: Declarative host-state schema — role-manifests, observed-state, drift-item, idempotency contract

Operator (jon, 2026-05-06) named the architectural shape behind warp#1581: four hosts (gyrum-ci-runner, warp-01, dark-factory-db, agent-worker-01) appeared on…

152
ADR-152: Rate-limit graceful degradation — wrapper-side detection plus server-side claim safety net

Phase 0 of EPIC [warp#1568](https://warp.gyrum.ai/items/1568) (EFF-8: sub-agent rate-limit graceful degradation). Tonight's session (2026-05-06) surfaced a…

151
ADR-151: Persona verdict-line directive + body-parser semantics for gyrum-review-pr

gyrum-review-pr runs the 3-persona AI review panel (Priya / Marcus / Lin) against an open PR. Each persona is implemented as an LLM that produces a review BODY…

150
ADR-150: Cloudflare token narrowing for ACME DNS-01 (Path A now, EAB later)

Today (post-r1+r3, 2026-04-26), every fleet host that terminates TLS via acme.sh --dns dns_cf carries a Cloudflare API token on disk with Zone:DNS:Edit…

149
ADR-149: Cardinality guard panics at metric registration time

[ADR-005](./005-cardinality-labels-vs-fields.md) sets the library rule: an attribute becomes a Prometheus label only if its value set is bounded by…

148
ADR-148: Bootstrap stages are independently re-runnable, not transactional

Server bootstrap scripts historically came in two shapes:

147
ADR-147: The `=== STAGE: ===` marker is the stable interface between procedural scripts and the Platform UI

infrastructure/deploy.sh has been emitting === STAGE: <Name> === lines since the Platform UI's Deploy tab shipped — the pipeline stepper in…

146
ADR-146: warp public-identity architecture (route ownership, smart-redirect, cookie+Bearer coexistence)

Warp today is a tools-only kanban: the home page (/) renders the board, /login renders a 350-line three-path login form, and every other route (/items/:id,…

145
ADR-145: PR title format with ticket ID prefix

On 2026-05-05 the fleet merged 18 PRs against this and sister repos. Every PR cited its driving Warp ticket in the body (Closes warp#NNN), and…

144
ADR-144: Agent API key shape (gyrum-go APIKey primitive)

The fleet currently has one production bearer-token implementation — warp's internal/auth package (see refs section). It mints wrp_<32-hex-chars> tokens,…

143
ADR-143: Fleet RBAC matrix (Role × Permission)

The fleet today gates HTTP endpoints with a coarse four-value scope string — admin / user / agent / readonly — checked at route-mount time via…

142
ADR-142: gyrum-go/pkg/auth audit + adoption shape

gyrum-go/pkg/auth was authored as the fleet's intended shared auth library (Argon2id + JWT + httpOnly cookies + RBAC port shapes), but no repo currently uses…

141
ADR-141: Warp mobile-first auth UX — magic-link primary, threat model, session shape

[warp#1442](https://warp.gyrum.ai/items/1442) (EPIC: warp public identity — mobile-first home page + login + account) opens warp to phone access, which the…

140
ADR-140: Standards-doc voting format (warp#1392 Phase 0)

The fleet's standards docs are static. ADR-117 declared the module-guidelines tier (dark-factory/guidelines/<module-type>@v<n>.md) as the canonical home for…

139
ADR-139: Folder and component layout standards

The fleet ships a visual canon (warp#865 is mid-rollout) but no structural canon. Folder and component layout — where source files live, where tests live,…

138
ADR-138: Activity-gated heartbeat replaces process-alive heartbeat

Phase 0 of EPIC [warp#1318](https://warp.gyrum.ai/items/1318) (activity-gated heartbeat). Today's session surfaced two failure modes that the current heartbeat…

137
ADR-137 — Project-manifest `db:` block: per-project Postgres as the default, declared via discriminated union

As of 2026-05-05 the gyrum fleet runs 22 production projects against one Postgres at 10.0.0.3:5432, all using the role darkfactory, which has GRANTs on every…

136
ADR-136: Runner-cache contract for CI workflows + agent-execution pool

Phase B of the self-hosted runner migration ([warp#1363](https://warp.gyrum.ai/items/1363), PR#228) flipped warp's CI to self-hosted ARM64 runners. The…

135
ADR-133 — Product-to-repos mapping (`products.yaml`)

A "product" in the gyrum fleet is the user-facing thing — warp, ai-research, distill, devtools, dark-factory itself. A user thinks "I use warp"; they do not…

134
ADR-134: Runtime-base image as unit of isolation for CI + agent-execution runners

The current self-hosted runner pool (gyrum-ci-runner-1, gyrum-ci-runner-2 — both Hetzner CAX Ampere ARM64 hosts) leans on the host as the unit of isolation.…

133
ADR-133: Changelog entry schema and PR-body block format

Sub-ticket of EPIC [warp#1358](https://warp.gyrum.ai/items/1358) — per-project changelog + /changes pages + PR title-format ticket-ID contract. Today the fleet…

132
ADR-132: Test-corpus-driven language migration

The fleet is about to begin a non-trivial cross-language migration: warp#1263 EPIC ports the devtools bash CLIs to Go binaries. Naive line-by-line porting of…

131
ADR-131: Claim affinity, completion-conferred and time-bounded soft-preference routing for context-warm agents

Every fresh gyrum-start-work rebuilds the same context the just-shipped sub-agent already had. Reading the ADR cluster, sibling tickets, recent merges, area…

127
ADR-127: Docs as typed playbooks

Durable engineering content lives in git ([ADR-126](./126-durable-engineering-content-in-git.md)). The substrate is settled. The shape of the content within…

126
ADR-126: Durable engineering content lives in git, not in ephemeral substrates

Multiple times this session (and many sessions before) operators and agents have written durable engineering content — design rationales, conventions,…

125
ADR-125 — Four product-surface substrates: Heed, Brief, Ramp, Vie

The factory has accumulated rich SIGNAL — the agent-reports journal (warp#246), structural-check verdicts, persona-review outcomes, the curator's blocking…

124
ADR-124: PRINCIPLE — pipeline tooling itself must be block-composition, not monolithic Go

The fleet has shipped two compatible-but-distinct shapes for "what a pipeline step looks like". The library shape — gyrum-pipelines — is block-composition:…

123
ADR-123: Templates as generator, AI as gap-filler (build pipeline shift)

Five gy-queue MVP build attempts — cloud_brave_amber, light_grain_stone, haven_willow_wind, heath_noble_ridge, pond_leaf_falcon, cliff_quartz_north — burned…

122
ADR-122 — Safe-cutover pattern: cert pre-acquire + read-only window

Today's warp-01 → warp-02 cutover failed twice: cert acquisition raced with DNS propagation (Caddy http-01 challenge couldn't validate because Let's Encrypt's…

118
ADR-118 — Per-block validation: every action proves itself

Today the gate stack catches code-level defects (build, test, coverage, complexity, gosec, staticcheck) but does NOT catch the "module ran clean, reality is…

117
ADR-117: Module guidelines

Today the agent brief is the only contract for module-typed work (DB changes, playbook authoring, API endpoints, UI components, ansible roles, agent briefs…

116
ADR-116: gyrum-knowledge-base RAG (retrieval-augmented generation) service

Today every sub-agent brief has to embed the relevant ADRs, principles, memory rules, and CLAUDE.md sections verbatim, because the agent has no canonical place…

115
ADR-115: Principle-aware PR reviewers

The 2026-04-26 PR cycle produced two --admin overrides on the same wrong-premise pattern: Marcus blocked devtools#126 citing the "docs/shared/ propagation…

114
ADR-114: Capability-based provisioning

migrate-service-to-fresh-host.yaml and provision-host.yaml hardcode cx22 as the destination Hetzner server type. The 2026-04-26 dry-run on GAF surfaced "Server…

113
ADR-113: Principles tier above initiative — fundamentals layer constraining the four-tier taxonomy

The board today carries a three-tier taxonomy ([warp #299](https://warp.gyrum.ai/items/299)): initiative > epic > ticket. What is missing is a layer above…

112
ADR-112: Executor isolation — split web-facing factory from private executor

The factory today runs on localhost:9400: local-only, no auth, fine for development. As the runtime moves from rendering dashboards to executing privileged…

111
ADR-111: gyrum-ai-broker as the single model-routing layer

The recent fan-out of type: claude steps ([warp #334](https://warp.gyrum.ai/items/334)), structural checks for those steps ([warp #335](https://warp.gyrum.ai/it…

110
ADR-110: Observe-and-file is the dominant pattern

ADR-109 introduced two shapes for runtime-driven Claude calls: type: claude (Claude is the step) and observer: (Claude watches a step). Both are real, but they…

109
ADR-109: `type: claude` step and `observer:` field

ADR-107 named the principle ("gates as playbook primitives") and migrated four open gate tickets ([#313](https://warp.gyrum.ai/items/313) dry-run,…

108
ADR-108: Sub-agent fires as first-class playbook fires

Today the factory runs two parallel execution worlds and only one of them is visible.

107
ADR-107: Gates as playbook primitives

> Numbering note. Slots 105 and 106 are claimed by in-flight ADR > tickets ([warp #317](https://warp.gyrum.ai/items/317) e2e gate; > [warp…

106
ADR-106: Generic migrate-service-to-fresh-host playbook + manifest-driven wizard

The warp-01 → warp-02 cutover that triggered this work is the third host migration the fleet has run (after db-shared-01 → db-buzzy-01 via…

104
ADR-104: Dry-run-on-history adaptive gate

> Numbering note. The originating brief and warp epic > (warp #313) named this "ADR-100". Slot 100 was already filled by > 100-factory-growth-boundaries.md…

103
ADR-103: PM stays in warp; the factory is the operations cockpit

ADR-097 (accepted earlier today) committed to absorbing the operator-facing PM kanban into the factory at gyrum.ai/pm/* and shrinking warp.gyrum.ai to API +…

102
ADR-102: Agent fleet — bootstrap a fresh box into a warp-claiming worker in one script

The fleet has had warp as its system-of-record AI-worker inbox since ADR-077 and has had structured PM, epics, and a claim-returns-context-bundle protocol…

101
ADR-101: Host inventory and roles — every host is a manifest, roles are additive

The fleet today has one production host (warp-01, the Hetzner CX22 carrying warp + distill + hello-world) and a queue of host work (Buzzy box, dark-factory-db,…

100
ADR-100: Factory growth boundaries — monolith UI, federated backends, audience-driven splits

The factory at gyrum-labs/ai-frontend is the operator's everything-pane: today, 2026-04-25, it has 77 routes, 1,772 tests, and is growing roughly five routes…

099
ADR-099: Agent briefs go through PR review before they're acted on

2026-04-25 produced three consecutive sub-agent brief failures inside one session. SWARM-AR was briefed to "implement ADR-095 backups" with a playbook + UI…

098
ADR-098: Epics with structured requirements, agent-driven decomposition, live progress rollup

SWARM-AA landed this evening and gave warp items three structural fields they were missing — parent_id (the link from a child item up to the work it belongs…

097
ADR-097: Project management lives in the factory; warp is the system of record

> Superseded note (2026-04-25, same day, ~6 hours later). ADR-103 > reverses the operator-UI cut below: the operator-facing PM kanban > stays at warp.gyrum.ai…

096
ADR-096: Warp as fleet PM via one-way GitHub-issue mirror

Tonight there are roughly forty-seven open issues across gyrum-labs/* repos that the operator works in directly through the GitHub UI, and roughly forty-seven…

095
ADR-095: Backup pipeline — daily pg_dump to Hetzner Object Storage with retention

ADR-092 §"Data persistence" landed three persistence shapes for project databases (ephemeral, durable, off-host) and then explicitly punted on backups:…

094
ADR-094: docker-compose.yml bundled inside the project's GHCR image

Today's first end-to-end exercise of the new ADR-092 deploy pipeline hit "needs source on disk" pain twice in one evening. The deploy-project ansible role's…

093
ADR-093: Manifest-driven project tools — auto-rendered admin affordances per project

While bringing up warp.gyrum.ai end-to-end through the new ADR-092 pipeline, the operator hit a recurring shape problem: every gyrum project that exposes an…

092
ADR-092: Host/Project separation — two-images-per-project, host owns edge

Tonight's first end-to-end deploy of warp to warp.gyrum.ai failed for hours and required a manual quick-path because the existing pipeline conflated two things…

091
ADR-091: Rename product from Weft to Crank

ADR-071 picked "Weft" as the product name for Gyrum's SaaS factory two days ago. The semantic intent — that the Gyrum factory has its own user-facing name…

090
ADR-090: AI is the scaffold; pipelines are the runtime

The Gyrum platform is built on a two-phase contract. Phase 1 (today): AI agents help author, debug, and extend playbooks — the human talks to Claude, Claude…

089
ADR-089: Host security telemetry, response, and forensics lifecycle

Every gyrum-managed host runs a security telemetry stack — auditd for filesystem and syscall events, Falco for eBPF container events, AIDE for binary…

088
ADR-088: Observability lifecycle — register-on-provision, silence-before-destroy

Every gyrum-managed host registers with the observability stack as a final step of its provision pipeline and de-registers as the first step of its destroy…

087
ADR-087: Cloud-init for host bootstrap instead of post-provision SSH harden

Every cloud VM provisioned by a gyrum pipeline carries a cloud-init user-data payload that handles package install, ops-user creation, firewall rules, and…

086
ADR-086: Structural rule graduation — from prose to check to default

Any recurring operational rule in the Gyrum platform passes through three layers on its way to being load-bearing — prose in a prompt or doc, a structural…

085
ADR-085: Pipeline blocks as typed I/O ports

Every playbook step declares its inputs and its outputs as named, typed ports. Wiring between steps is explicit — a downstream input names an upstream output…

084
ADR-084: Outbound-call chokepoint — shared toolkit for external invocations

Every side-effecting call to an external system — gh, git, ssh, curl, ansible, docker, psql, gcloud — flows through a shared toolkit script in devtools/ before…

083
ADR-083: Rule promotion engine — auto-hoisting prose rules to devtools

> Numbering note. The user brief named this "ADR-082". Slot 082 was > filled earlier today by 082-experiment-experiments-url-split.md > (landed via PR #345…

082
ADR-082: /experiment vs /experiments — accept the URL split, make it visible

ADR-081 §10 left one open question on the Top-10 trickle's critical path: the /experiment (singular, Experiment mode-home) vs /experiments (plural, fleet…

081
ADR-081: UI shell consolidation grammar — scopes, card shape, empty-state prose

The Platform UI (ai-frontend) has reached ~70 shipped routes across the five modes (Home / Ship / Operate / Infra / Experiment), the persona homes (/me, /ops,…

080
ADR-080: ADR-078 Phase 2c amendment — warp as the durable trigger queue

ADR-078 ships trigger-driven orchestration in four phases; the Phase 2b MVP (cron-only, direct in-process invocation) is single-replica and best-effort-on-miss…

079
ADR-079: Dark-factory as the operational-approval substrate

> Supersession note. This ADR supersedes ADR-077 §3 — the > ~/.gyrum/proposals/, ~/.gyrum/approvals/, ~/.gyrum/actions/ > journals and the paired gyrum-propose…

078
ADR-078: Trigger-driven playbook orchestration

We extend the ADR-068 playbook runtime with a trigger layer — an additive triggers: section in the playbook YAML that declares how a playbook gets fired…

076
ADR-076: Project-to-host deployment binding (Layer 1)

Every project's YAML grows a deployments: block that binds each of its repos to a concrete origin: a host alias resolved through gyrum-catalog, a TCP port, a…

075
ADR-075: Fleet-E2E as a cross-product branch-testing service

We promote distill-gyrum-ai/e2e-integration/ from "distill's own end-to-end suite" to Fleet-E2E — the fleet's cross-product branch-testing service, a single…

074
ADR-074: CI/CD plane separation

We separate the Gyrum compute surface into three planes — Dev, CI, Ops — and forbid any shared filesystem, shared credentials, or shared runtime between them.…

073
ADR-073: Preview environments per feature branch

Every feature branch opened as a pull request on any repo inside a project (ADR-059) spins up its own ephemeral, full-stack preview environment at a private,…

072
ADR-072: Infrastructure as playbooks

Every infrastructure mutation Gyrum performs — DNS records, Cloudflare Tunnel routes, Cloudflare Access applications and policies, Caddy site configuration,…

071
ADR-071: Product name — Weft

> Superseded in part by [ADR-091](./091-rename-weft-to-crank.md). §1 (product name = Weft) is replaced — the product is now Crank. Every other section of this…

070
ADR-070: Playbook UI contract

We ship a single Svelte component — <PlaybookRunner> — as the canonical frontend binding to the ADR-068 playbook runtime. The component accepts a playbook id…

069
ADR-069: Playbook security model

We gate the playbook runtime (ADR-068) with a six-part security model: (1) per-playbook authorisation via allowed_personas: + allowed_users: front-matter,…

068
ADR-068: Playbook runtime architecture

We build an embedded, Postgres-backed playbook runtime inside ai-research that executes playbooks (ADR-067) as first-class server-side work. The runtime…

067
ADR-067: Playbooks as a unified primitive

A Playbook is a unified primitive — one schema, one front-matter, one catalogue — with a kind: discriminator that selects kind-specific extension fields; every…

066
ADR-066: Microvisualisations — sparklines + timeline strips

The Platform UI is converging on two recurring visualisation shapes that are neither full charts nor single scalars. They appear in KPI tiles, alert rows,…

065
ADR-065: Platform UI adopts persona-aware routes + a niceties framework

ADR-054/058 gave the Platform UI a five-mode lens (Home / Ship / Operate / Infra / Experiment). ADR-059 added a second orthogonal axis — project. Both answer…

064
ADR-064: gyrum-catalog library + hexagonal architecture

ADR-060 introduced capture-layer experiments (exp-315 Hetzner audit, exp-316 GitHub repo audit, planned exp-317 Prometheus drift, exp-318 Caddy routes, exp-319…

063
ADR-063: Tenant scoping + portability

The gyrum platform is operated as a single-tenant system today: one operator, one set of projects, one set of infrastructure, one set of secrets, one fleet…

062
ADR-062: Design system + component strategy (Svelte vs Web Components)

The Platform UI (ai-frontend, SvelteKit + Svelte 5 runes) has grown a set of recurring visual primitives — KPI tiles, status pills, mode switchers, side rails,…

061
ADR-061: Extract pipelines + projects to standalone libraries

Two pieces of generic infrastructure currently live inside application repositories:

060
ADR-060: Catalog-driven infrastructure (capture + compose)

Two engines already live in gyrum-labs/ai-research:

059
ADR-059: Platform UI adopts project-first information architecture

ADR-054 and ADR-058 landed the five-mode shell — Home / Ship / Operate / Infra / Experiment — as the Platform UI's primary navigation. Modes are verbs: "what…

058
ADR-058: Platform UI adopts five modes — Home / Ship / Operate / Infra / Experiment

ADR-054 (shipped earlier on 2026-04-22) split the Platform UI's single-page mode dispatcher into three dedicated routes — /ship, /operate, /experiment — and…

057
ADR-057: Codify the Prometheus scrape config in git, drift-check with a reconcile script

On 2026-04-22 the fleet observability rollout added 17 per-product scrape jobs to Prometheus one at a time, as each service was wired through…

056
ADR-056: Caddy per-product routing — guard against backend-port collision; codify only placeholders

On 2026-04-22 the Platform UI's Operate-mode screen flagged research.gyrum.ai and guardian.gyrum.ai as reporting identical 11.36 % error rate and 21.7 ms p95 —…

055
ADR-055: Pin every observability-stack container to an exact major.minor.patch

On 2026-04-22 a security-follow-up probe of the observability stack on dark-factory-ops (178.104.63.31) found the live and the bootstrap compose files in three…

054
ADR-054: Three mode-homes become dedicated routes — `/ship`, `/operate`, `/experiment`

ai-frontend ADR-003 (2026-04 draft in ai-frontend/docs/decisions/003-three-modes-platform-ui.md) ruled that the Platform UI's three modes — Ship / Operate /…

053
ADR-053: Frontend observability — `/client-log` endpoint + slim browser SDK

[ADR-051](051-observability-by-default-in-build-pipeline.md) wired observability into every backend emitted by the build-full pipeline: pkg/observ import,…

052
052 — Operate-mode data sources proxied server-side, not direct-browser

The Platform UI's Operate mode ([ai-frontend §5](https://github.com/gyrum-labs/ai-frontend/blob/main/docs/platform-ux-redesign-proposal.md#5-operate-mode))…

051
ADR-051: Observability-by-default in the `build-full` pipeline

build-full (the 73-step pipeline at ai-research-dev/cmd/server/pipeline/) generates brand-new SaaS products from briefs. Every product shipped through this…

050
ADR-050: Bearer-auth product /metrics endpoints via a gyrum-go helper

Note on ADR number: drafted as ADR-049 in parallel with the alertmanager bearer-auth work. A parallel agent merged an unrelated ADR-049: Alertmanager…

049
049 — Bearer auth at Caddy for alertmanager.gyrum.ai

Issue #268 was filed describing alertmanager.gyrum.ai and ntfy.gyrum.ai as "publicly exposed with no auth", severity HIGH.

048
048 — Codify ops-VPS Caddy site configs in server-bootstrap

The ops VPS (dark-factory-ops) runs Caddy as the public ingress for the four observability surfaces: grafana.gyrum.ai, alertmanager.gyrum.ai, ntfy.gyrum.ai,…

047
ADR-047: Folder-per-product Grafana convention + UID-pinned datasources

The 2026-04-22 manual end-to-end wire-up of distill into the observability stack (dark-factory issues #266–#273) surfaced three related failure modes in the…

046
ADR-046: Tier-1 discipline dashboard pack (db / auth / billing / jobs / email)

The Tier-0 pack ([ADR-013](013-templates-in-dark-factory.md), [ADR-014](014-stable-uid-as-idempotency-key.md)) answers five versions of one question: is this…

045
ADR-045: `gyrum-observ-integrate` CLI supersedes the Phase-1 manual checklist

The original [observability playbook](../observability-playbook.md) was a Phase-1-shaped checklist: nine numbered steps an operator copy-pasted into a service…

044
ADR-044: Email logging records shape; body content is never written to a log sink

pkg/email ships a LogSender for dev/CI so operators can see that a send "happened" without wiring a real provider. The same instinct — "dump everything so we…

043
ADR-043: Stdlib `net/smtp` is the default concrete Sender; native SDKs are opt-in

ADR-042 commits us to a Sender abstraction with multiple concrete implementations. The question this ADR answers is which concrete sender ships first — and by…

042
ADR-042: `email.Sender` is the abstraction; provider SDKs slot in behind it

Transactional email (password resets, receipts, verification codes, invites) is a Tier-1 gap on the [platform gap audit](../platform-gap-audit.md). Every…

041
ADR-041: The "Verify observability" deploy stage is non-blocking

deploy.sh gained a Verify observability stage ([ADR-040](./040-grafana-provisioning-at-deploy-time-not-ci-time.md)) that probes Loki for recent log lines and…

040
ADR-040: Grafana dashboard provisioning runs at deploy time, not CI time

Phase 3 of the observability rollout ([ADR-013](013-templates-in-dark-factory.md), [ADR-014](014-stable-uid-as-idempotency-key.md)) produces per-service…

039
ADR-039: Audit sink chains events with SHA-256 prev_hash; chain boundary is the process

pkg/audit/sink (ADR-038) needs a tamper-evidence property: after the stream leaves the process, an auditor should be able to detect insertion, deletion, or…

038
ADR-038: Tamper-evident audit stream is a separate sink, sibling to the app audit logger

pkg/audit already exists: a structured, asynchronous audit logger backed by a Store interface, designed for services that need a high-throughput record of who…

037
ADR-037: `SafeFetch` denies private ranges by default; opt-out per call

Distill's YouTube handler takes a user-supplied URL and dereferences it server-side. So do (or will) summarisers, feed fetchers, OEmbed integrations, and any…

036
ADR-036: CSRF protection uses the double-submit cookie pattern

Every gyrum service with cookie-based authentication needs CSRF protection on state-changing endpoints. The two mainstream patterns are:

035
ADR-035: Cross-cutting security helpers live in `pkg/security`, not `pkg/middleware`

The security tranche (threat model, standards, playbook, runbook, tripwires, alerting) identified three library-level controls every service needs to integrate:

034
ADR-034: Legacy `log.Printf` sites retrofit to `slog.Default`, not an injected logger

pkg/observ standardised gyrum Go services on log/slog, JSON output, the service/version/commit/release_track ambient tags, and the layered redactor. A handful…

033
ADR-033: DB wrapper — per-query via `WrapDSN`, stats-only via `WrapDB`

Phase 2c of the observability rollout adds a database/sql wrapper to [gyrum-go/pkg/observ](https://github.com/gyrum-labs/gyrum-go/tree/main/pkg/observ). The…

032
ADR-032: `app_info` gauge as the service-identity signal

Prometheus metrics answer "how is the service behaving", but several operational questions are about the service itself: is it reachable right now, what…

031
ADR-031: The observability stack runs on the same VPS as the apps it observes

[ADR-001](001-self-hosted-observability-stack.md) committed us to self-hosting Loki + Prometheus + Alloy + Grafana (+ Tempo in Phase 6). It did not answer…

030
ADR-030: Metrics as a sub-package of `pkg/observ`, not part of it

Phase 1 of the observability rollout shipped pkg/observ as the foundation for every gyrum Go service: build identity, JSON slog logger, probe primitive,…

029
ADR-029: Security standards enforcement — CI checks vs review discipline

[security-standards.md](../security-standards.md) lists ~15 rules every service must follow. Some can be enforced mechanically (dependency pins, SHA-pinned…

028
ADR-028: Credential rotation cadence — quarterly minimum, immediate on breach

Credentials — HMAC session secrets, API keys, DB passwords, SSH keys, OAuth client secrets — do not stop being valuable to an attacker on any schedule. The…

027
ADR-027: Honeytoken strategy — where, how, what triggers the alert

Honeytokens are fake credentials and fake resources planted in a system where legitimate users would never touch them. Any touch is, by construction, an…

026
ADR-026: Security defence-in-depth — layered controls, no single point of protection

The threats enumerated in [security-threat-model.md](../security-threat-model.md) span a wide surface: auth, authz, input validation, output encoding,…

025
ADR-025: SLO-based burn-rate alerts — multi-window multi-burn, not threshold

User-visible contracts (latency, availability, success rate) are the concerns we most want to alert on correctly. The two failure modes of alerting on them:

024
ADR-024: Runbook required per alert — no runbook, no ship

An alert without a runbook is a notification that wakes someone up with a problem they don't know how to solve. Real incidents — and years of other teams'…

023
ADR-023: Alerts as code, not UI — provisioning API only, UI edits overwritten

Grafana lets alert rules be created and edited from the UI. That path is attractive: click-to-save, immediate effect, no YAML syntax. But a rule edited in the…

022
ADR-022: Grafana Alerting over Prometheus Alertmanager

[ADR-001](001-self-hosted-observability-stack.md) put Grafana + Loki + Prometheus on a self-hosted VPS. Phase 5 needs alerting: a rule evaluator, a router, and…

021
ADR-021: Reserved

This slot is intentionally empty. ADR-020 and ADR-022 both exist; no ADR-021 was ever written.

020
ADR-020: Shared infrastructure vs per-product isolation

The factory runs one Postgres instance (dark-factory-postgres) that every product-needing-SQL shares. [platform-gap-audit §5.5](../platform-gap-audit.md#55-sing…

019
ADR-019: Deploy order in multi-repo products

Multi-repo products (frontend + backend + worker, typically also migrations) introduce a coordination problem single-repo products do not have: in what order…

018
ADR-018: `.gyrum/product.yml` as the product registry

The factory has ~30+ repos under gyrum-labs/ (see [platform-gap-audit §1.2](../platform-gap-audit.md#12-current-shape)). Today, "what is a product?" is…

017
ADR-017: Platform scope and sequencing

gyrum is a SaaS factory (see [platform-gap-audit.md §1](../platform-gap-audit.md)). It ships product experiments at a cadence of several live products and many…

016
ADR-016: HTTP access middleware — shape, route template, panic composition

ADR-002 chose log/slog and ADR-003 chose JSON-line output; the Phase 1 library in [pkg/observ](https://github.com/gyrum-labs/gyrum-go/tree/main/pkg/observ)…

015
ADR-015: Separate per-service (Tier-0) and fleet-wide (Tier-1) dashboards

On-call triage and platform health are different questions asked by different people at different times.

014
ADR-014: Stable UIDs as the dashboard idempotency key

Grafana identifies a dashboard by its uid — a short stable slug that, together with the organisation, uniquely names the dashboard. A POST to Grafana's…

013
ADR-013: Grafana dashboard templates live in `dark-factory`, not `gyrum-go`

The Phase 1 observability library lives in github.com/gyrum-labs/gyrum-go/pkg/observ ([ADR-008](008-single-observ-library-vs-per-service-convention.md)). A…

012
ADR-012: Maximum 12 panels per dashboard

[ADR-011](011-one-question-per-dashboard.md) establishes that every dashboard answers one question. That rule is about purpose. Purpose alone does not prevent…

011
ADR-011: One question per dashboard

Dashboards grow. A dashboard that started as "is this service healthy?" acquires a panel for the one time someone debugged a memory leak, then a panel for the…

010
ADR-010: Dashboards as raw JSON, not jsonnet / grafonnet / Terraform

Phase 3 of the observability rollout ([observability-architecture.md](../observability-architecture.md#whats-not-here-yet)) wires deploy-stage dashboard…

009
ADR-009: `Probe(ctx, label)` — a two-line primitive for end-to-end log-pipeline self-test

The log pipeline has six serial stages (see [observability-architecture.md#data-flow--logs](../observability-architecture.md#data-flow--logs)): app → stderr →…

008
ADR-008: One shared `observ` library, not per-service slog setup

Every Go service in the factory needs the same things: JSON log lines on stderr, build-identity fields on every line, redaction, a probe primitive, a…

007
ADR-007: Layered redaction — keys, value patterns, message scrub (Phase 1 and 1.5)

Every log line leaves the process with the possibility of embedding a secret. The failure mode is catastrophic and one-way: once a credential has reached Loki,…

006
ADR-006: Seven canonical error categories, by convention not by type

When a service logs an error, the Phase 5 alerting story needs to group errors into a small, named set of categories so that SLO burn-rate alerts can target…

005
ADR-005: Bounded labels, unbounded fields — cardinality discipline

Loki indexes by labels. Every distinct combination of label values creates a new stream; chunks are allocated per stream. High-cardinality labels (user IDs,…

004
ADR-004: Ship `release_track` as a first-class bounded label from day one

Canary deployments are a Phase 3+ feature: split traffic between two replicas of the same service, compare error rate and latency between them, promote the new…

003
ADR-003: JSON-per-line on stderr, not logfmt or protobuf

Once we committed to slog (ADR-002) and a Loki-backed stack (ADR-001), the on-wire line format still had a choice. slog ships both JSONHandler and TextHandler;…

002
ADR-002: Go stdlib `log/slog` as the library's base, not zap or zerolog

The observability library needs a structured logging core. Every Go service in the factory will import it, so the choice propagates everywhere and is expensive…

001
ADR-001: Self-hosted observability stack (Loki + Prometheus + Alloy + Grafana + Tempo)

Phase 1 of the observability rollout ships a structured-logging library (github.com/gyrum-labs/gyrum-go/pkg/observ — see [gyrum-go…