README
¶
Architecture Decision Records
This directory records the significant architectural decisions made on Atlas, using the MADR-influenced format described in template.md.
An ADR captures a decision, the context that forced it, the options considered, and the consequences accepted. ADRs are immutable once accepted: if a decision changes, a new ADR supersedes the old one rather than editing it.
A number belongs to exactly one decision, and it is assigned when the record lands on main — never on a branch. go test ./docs/adr enforces unique, gapless numbers, keeps the index below in step with the directory, and checks that every ADR-NNNN citation anywhere in the repository still resolves. The index is a table of contents: one row per record, carrying that record's own # ADR-NNNN: heading, so what the decision says belongs in the record, not in the cell.
Writing a record
Do not pick a number. A record in flight carries none:
- Copy
template.mdtodocs/adr/draft-<slug>.md— a kebab-case slug, no number. - Keep the heading as
# ADR-DRAFT: Your title, and fill in the record. - Add no row to the index below.
- Cite it as
ADR-draft-<slug>from code comments and docs, or linkdraft-<slug>.md.
When the PR merges, a workflow on main runs make adr-number. That renames the
file to NNNN-<slug>.md, rewrites the heading, appends the index row, and rewrites
every citation of the draft to the number it just got. Nothing for you to remember;
you can also run make adr-number by hand on main.
Why the ceremony: the number used to be taken when the record was written, which is the earliest possible moment and the one with the least information. Two open branches both saw the same "next free" number and both took it — that is how 0090, 0103 and 0105 came to be shared by unrelated ADRs (the later record of each pair now lives at 0139, 0140 and 0141). Once a test caught the collision, the cost became a renumber on every merge instead: one record walked 0164 → 0169 across six of them without a word of its content changing. Assigning the number where the question has one answer removes both. The full argument is in the record on merge-time numbering.
A number, once assigned, is never reassigned — that is what makes (ADR-0168) in a
comment safe to write.
When the record rests on a question you could not answer
Some decisions are right while something we do not know stays unknown. ADR-0292 is the case that prompted this rule: it renders a MIMWAL table's cells by position because the meaning of the columns is established by no reference anyone could find, and it would be re-argued the day one turns up. Written like any other record, that reads a year later as settled — nothing in it separates "we decided this" from "we decided this for now, on a gap in what we know".
So say both, in the front matter:
- **Open question:** MIMWAL's grid column semantics are not established from its own
source. The positional cells rest on that gap.
- **Question checked:** 2026-09
The two come as a pair, and go test ./docs/adr insists on it: a question with no
date cannot go stale, and a date with no question says a thing was checked without
saying what. The question may wrap onto indented lines. The date is the month
somebody last looked at the question, not the month the record was written, and
the guard fails once it has stood for a year — the same interval, for the same
reason, as the Worker Type setup steps of ADR-0289.
The reasoning behind the pair — including why it states a month somebody looked rather than a deadline somebody set — is in ADR-0293.
When the check fires, the fix is never to bump the date. Go and look: if the question now has an answer, delete both lines and check whether the decision that rested on the gap still holds — that is the case this exists for. If it is still open, write down what you learned and date the month you looked.
Reading the older records: "connector"
Records written before ADR-0203 say connector where the rest of the documentation now says Worker Type (an execution capability), Worker (one configured target and identity of that type) or Worker Instance (a process leasing its jobs) — the word meant all three depending on where it stood, which is why it was replaced.
Those records are not rewritten: an ADR is immutable once accepted, and
ADR-0203 says so explicitly — older connector wording is reconciled through links,
not by editing history. So read ADR-0036, ADR-0041,
ADR-0067, ADR-0154/0166/0172/0173/0201 and
their neighbours in their own vocabulary; ADR-0203 carries the mapping. The
persisted contracts kept the old spelling too, on purpose: the connector/ package
paths, the connector="…" BPMN attribute, atlas worker --connector, the
ATLAS_*_CONNECTORS variables and the /api/v1/connectors routes.
Index
| ADR | Title | Status | Implementation |
|---|---|---|---|
| 0001 | Event sourcing and log-structured state | Accepted | Landed |
| 0002 | Single-writer partition model | Accepted | Landed |
| 0003 | Pebble as embedded state store | Accepted | Landed |
| 0004 | Compile BPMN to an integer-indexed graph | Accepted | Landed |
| 0005 | Group commit and fsync strategy | Accepted | Landed |
| 0006 | Partition routing and cross-partition communication | Accepted | Partial |
| 0007 | Job worker protocol | Accepted | Landed |
| 0008 | FEEL expression compilation strategy | Accepted | Landed |
| 0009 | Record serialization format | Accepted | Landed |
| 0010 | Go as implementation language, no CGO | Accepted | Landed |
| 0011 | Single-binary distribution with an embedded web viewer and editor | Accepted | Landed |
| 0012 | A buildless, self-contained web UI app shell | Accepted | Landed |
| 0013 | Embed the bpmn-js modeler as a vendored asset | Accepted | Landed |
| 0014 | DMN business rule tasks via the temis engine | Accepted | Landed |
| 0015 | Reuse the external FEEL engine behind an expr boundary |
Accepted | Landed |
| 0016 | Model Context Protocol server as a stdio adapter over the HTTP API | Accepted | Landed |
| 0017 | Retain finished process instances in a history index | Accepted | Landed |
| 0018 | Test-driven development as the default workflow | Accepted (amended) | Landed |
| 0019 | Durable deployments via an on-disk sidecar store | Accepted | Landed |
| 0020 | Message events and correlation | Accepted | Landed |
| 0021 | Diagram drafts, separate from deployments | Accepted | Landed |
| 0022 | Retain a per-element token-visit history for the Operations overlay | Accepted | Landed |
| 0023 | Collaborations and pools as multi-process deployments | Accepted | Landed |
| 0024 | Parallel gateway join synchronization | Accepted | Landed |
| 0025 | Extend the hand-written properties panel instead of vendoring bpmn-js-properties-panel | Proposed (amended) | Partial |
| 0026 | A Problems panel with validation targeted at an engine version | Accepted | Landed |
| 0027 | Element templates for pre-configured, reusable elements | Proposed | Partial |
| 0028 | User tasks, forms, and the Tasks app | Accepted | Landed |
| 0029 | Public process start via a published form link | Accepted | Landed |
| 0030 | Play mode — ephemeral in-Modeler process simulation | Accepted | Landed |
| 0031 | Diagram version history in the Modeler | Proposed | Not started |
| 0032 | In-Modeler AI copilot over the MCP/HTTP surface | Proposed | Partial |
| 0033 | Inclusive gateway join synchronization | Accepted | Landed |
| 0034 | Projects as containers for heterogeneous artifacts | Accepted | Landed |
| 0035 | Message start events and the processInstanceKey built-in | Accepted | Landed |
| 0036 | A clio connector — server-registered event-store integration | Accepted | Landed |
| 0037 | Structured JSON variables | Accepted | Landed |
| 0038 | Collaboration message-flow replay | Accepted | Landed |
| 0039 | Input/output variable mappings for business rule tasks | Accepted | Landed |
| 0040 | Boundary events — timer and message, interrupting and non-interrupting | Accepted | Landed |
| 0041 | Connector management and the secret store | Accepted | Partial |
| 0042 | User-task runtime assignment and claim/unclaim | Accepted | Landed |
| 0043 | An OpenAPI spec and an embedded API explorer for the HTTP API | Accepted | Landed |
| 0044 | User management and the authentication boundary | Accepted | Landed |
| 0045 | Binding user-task assignment to real identities | Accepted | Landed |
| 0046 | Single-process step-by-step replay | Accepted | Landed |
| 0047 | Polyglot script tasks (PowerShell, …) via job workers | Accepted | Landed |
| 0048 | Per-step variable snapshots in the single-process replay | Accepted | Landed |
| 0049 | Internal service authentication for the in-process MCP adapter | Accepted | Landed |
| 0050 | Central DMN decisions via a temis decision connector | Accepted | Landed |
| 0051 | Timer start events (duration, date, cycle) | Accepted | Landed |
| 0052 | Message end events | Accepted | Landed |
| 0053 | First-class data objects — typed, event-sourced state, and lineage | Accepted | Landed |
| 0054 | Date and cycle timers for catch and boundary events | Accepted | Landed |
| 0055 | FEEL-expression timer schedules for catch and boundary events | Accepted | Landed |
| 0056 | FEEL cycles, and FEEL on timer start events | Accepted | Landed |
| 0057 | First-class FEEL temporals for timer schedules | Accepted | Landed |
| 0058 | Data output associations — write a value and transition a data object's state | Accepted | Landed |
| 0059 | Data input associations — read a data object into an activity | Accepted | Landed |
| 0060 | Field-level data object writes — set one member of a structured object | Accepted | Landed |
| 0061 | Incident model — job-failure incidents, raise, resolve, resume | Accepted | Landed |
| 0062 | An embedded DMN editor (dmn-js) | Accepted | Landed |
| 0063 | DMN decision binding (latest vs deployment) | Accepted | Landed |
| 0064 | Timer FEEL-failure incidents — park and raise instead of firing immediately | Accepted | Landed |
| 0065 | Multi-token process replay and causal token lineage | Accepted | Landed |
| 0066 | Durable decision-evaluation records for debugging | Accepted | Landed |
| 0067 | A service-task connector catalog, and REST with a model-authored endpoint | Accepted | Landed |
| 0068 | Task input/output variable mappings with activity-local scopes | Accepted | Landed |
| 0069 | An engine-internal encrypted secret vault (ADR-0041 option A3) | Accepted | Landed |
| 0070 | The secret vault is on by default, with a generated key | Accepted | Landed |
| 0071 | Sharing scopes — private and shared access boundaries for design-time work | Accepted | Landed |
| 0072 | Multiple DMN models per process deployment | Accepted | Landed |
| 0073 | A principals directory for member and assignee pickers | Accepted | Landed |
| 0074 | Embedded subprocesses (scope lifecycle via child counters) | Accepted | Landed |
| 0075 | A clio inbound event bridge — at-least-once ingestion with engine-side idempotent delivery | Accepted | Landed |
| 0076 | Call activities (single-partition) | Accepted | Landed |
| 0077 | Multi-instance activities (parallel and sequential) | Accepted (amended) | Landed |
| 0078 | Design-view token simulation — a client-side control-flow walkthrough | Accepted | Landed |
| 0079 | An outbound mail connector (SMTP first) | Accepted (amended) | Landed |
| 0080 | Sublinear runtime views via maintained aggregate counters | Accepted | Landed |
| 0081 | A community marketplace for connectors, service tasks, and script tasks | Proposed | Partial |
| 0082 | Event subprocesses (message- and timer-triggered, interrupting and non-interrupting) | Accepted | Landed |
| 0083 | An O(1) instances summary — per-definition finished-count and last-activity counters | Accepted | Landed |
| 0084 | CSV batch validation — upload a file, validate every row against business rules, correct the failures | Accepted | Landed |
| 0085 | Process-instance TTL — self-cleaning via the due-timer index | Accepted | Landed |
| 0086 | Gateway conditions resolve over the scope chain | Accepted | Landed |
| 0087 | In-process CSV ingestion — upload in a user task, parse in the process | Accepted | Landed |
| 0088 | Signal events (broadcast throw/catch) | Accepted | Landed |
| 0089 | Error events (scoped propagation to the nearest handler) | Accepted (amended) | Landed |
| 0090 | Bulk-terminate running instances — an explicit selection and a filtered scope | Accepted | Landed |
| 0091 | User-task scheduling — priority and due date | Accepted | Landed |
| 0092 | One-click clio credential provisioning | Accepted | Landed |
| 0093 | Native Gmail and Microsoft Graph mail providers | Accepted | Landed |
| 0094 | Singleton message start — at most one live instance per correlation key | Accepted | Landed |
| 0095 | External variable modification on a running instance | Accepted | Landed |
| 0096 | Token simulation — event triggers, inclusive gateways, and an auto-decide mode | Accepted | Landed |
| 0097 | Token simulation — message starts, event-subprocess triggers, and multi-instance | Accepted | Landed |
| 0098 | Audit trail for external variable modifications | Accepted | Landed |
| 0099 | An ArchiMate 3.2 enterprise-architecture view | Accepted | Landed |
| 0100 | Token simulation — configurable multi-instance count, modelled cardinality wins | Accepted | Landed |
| 0101 | Token simulation — a thrown message/signal delivers to a waiting catch | Accepted | Landed |
| 0102 | Receive tasks | Accepted | Landed |
| 0103 | Compensation and compensation handlers | Accepted | Landed |
| 0104 | Token simulation — entering embedded subprocesses | Accepted | Landed |
| 0105 | Per-server call-activity target overrides | Accepted | Landed |
| 0106 | A BMC Remedy connector — server-registered ITSM entry creation | Accepted (amended) | Landed |
| 0107 | Backup and restore — a one-file download of the design-time data directory | Accepted | Landed |
| 0108 | BPMN transactions (cancel end event, cancel boundary, transactional compensation) | Accepted | Landed |
| 0109 | Whole-instance snapshot — a full backup that includes running instances | Accepted (amended) | Landed |
| 0110 | Event-based gateways (deferred choice) | Accepted | Landed |
| 0111 | Completing the incident model — retry backoff and timer-FEEL failure incidents | Accepted | Landed |
| 0112 | Send tasks | Accepted | Landed |
| 0113 | Org-wide UI brand theme | Accepted | Landed |
| 0114 | OpenSearch event exporter — a WAL-tailing sink, off the hot path | Accepted | Landed |
| 0115 | History retention — an export-gated, age-based hard delete of finished instances | Accepted | Landed |
| 0116 | Terminate end events | Accepted | Landed |
| 0117 | An AI agent task — an LLM agent as a managed connector on the job path | Accepted | Partial |
| 0118 | A web-scraping connector — model-authored URL + CSS selector extraction | Accepted | Landed |
| 0119 | Deactivating a deployed process | Accepted | Landed |
| 0120 | Mockup (engine-simulated) service tasks | Accepted | Landed |
| 0121 | BPMN lanes | Accepted (Layer A) | Landed |
| 0122 | A protected system project and bootstrap-deployed platform processes | Accepted | Landed |
| 0123 | A sanctioned automated user-provisioning path for system processes | Accepted (amended) | Landed |
| 0124 | Server-side BPMN diagram auto-layout in Go | Accepted | Landed |
| 0125 | Escalation events (non-interrupting, propagating throw/catch) | Accepted | Landed |
| 0126 | Self-service registration link on the login screen | Accepted | Landed |
| 0127 | A layered layout pipeline and executable layout invariants | Accepted (amended) | Landed |
| 0128 | Process applications — the project, elevated into a deployable, versioned, portable unit | Accepted | Partial |
| 0129 | Remote deployment targets — publish an application to another Atlas server | Accepted | Landed |
| 0130 | Deprecating a process version — a drain state distinct from pausing | Proposed | Not started |
| 0131 | Engine recovery checkpoints and WAL compaction | Accepted | Landed |
| 0132 | Link events (intra-scope goto — a compile-time synthetic flow) | Accepted | Landed |
| 0133 | Standard loop activities (the ↻ marker) | Accepted (amended) | Landed |
| 0134 | Git-backed applications — a repository as an application's source of truth | Accepted | Partial |
| 0135 | Retries as a property of every job-backed task | Accepted | Landed |
| 0136 | Terminated tokens in the step-by-step replay | Accepted | Landed |
| 0137 | Conditional events (data-triggered catch/boundary) | Accepted | Landed |
| 0138 | Ad-hoc subprocesses (on-demand, unordered contained activities) | Accepted | Landed |
| 0139 | A first-class "CSV to JSON" connector kind with model-authored layout | Accepted (amended) | Landed |
| 0140 | Live collaborative modeling sessions — real-time co-editing of drafts by people and AI agents | Accepted | Landed |
| 0141 | A SharePoint connector (create list item, via Microsoft Graph) | Accepted | Landed |
| 0142 | Operational metrics over a Prometheus endpoint | Accepted | Landed |
| 0143 | Process documentation export | Accepted | Landed |
| 0144 | Per-definition history TTL — retention the model declares | Accepted | Landed |
| 0145 | A Developer View for code-bearing fields | Accepted | Landed |
| 0146 | History expiry as a due-date index — retention that scales with what is due | Accepted | Landed |
| 0147 | Splitting the api Server object, without weakening the single writer | Accepted (amended) | Landed |
| 0148 | Org-wide brand logo | Accepted | Landed |
| 0149 | A bounded outbound-call budget for every connector | Accepted (amended) | Landed |
| 0150 | A preview mail provider, and incidents on the live diagram | Accepted (amended) | Landed |
| 0151 | Incidents beyond the live diagram — the replay, the lists, and one shared action | Accepted | Landed |
| 0152 | OAuth2 client-credentials for the REST connector | Accepted | Landed |
| 0153 | SCIM 2.0 provisioning connector | Accepted | Landed |
| 0154 | Generic LDAP connector | Accepted (amended) | Landed |
| 0155 | The Secrets panel says what a value has to be | Accepted | Landed |
| 0156 | In-process vs. out-of-process service tasks — where a step's work runs, and what we recommend | Superseded by ADR-0164 | Superseded |
| 0157 | Every side-effecting task on a worker process — atlas worker, optional supervision, and a Workers console |
Accepted | Landed |
| 0158 | A connector reference that explains itself — and an incident you can actually resolve | Accepted | Landed |
| 0159 | Auditable manual task completion | Accepted | Landed |
| 0160 | Fix the connector from the incident | Accepted | Landed |
| 0161 | What an element was handed, on the diagram | Accepted | Landed |
| 0162 | Process instance migration | Accepted | Landed |
| 0163 | Deleting a connector deployed models still reference — and keeping a table inside its card | Accepted | Landed |
| 0164 | No in-process service tasks — the core loop must never be able to get stuck | Accepted | Landed |
| 0165 | SOAP / Web Services (WSDL) connector | Accepted | Landed |
| 0166 | Active Directory connector | Accepted (amended) | Landed |
| 0167 | A released connector ships in the marketplace | Accepted | Partial |
| 0168 | Moving a connector onto a worker — where the task detail travels, and where the credential lives | Accepted | Landed |
| 0169 | A form on the incident — repairing an instance with named fields instead of raw JSON | Accepted | Landed |
| 0170 | ADR numbers are assigned at merge, not on a branch | Accepted (amended) | Landed |
| 0171 | A directory-file connector — LDIF and DSML | Accepted | Landed |
| 0172 | A Microsoft Entra ID connector | Accepted (amended) | Landed |
| 0173 | Three SQL connectors, and the first kinds born on a worker | Accepted | Landed |
| 0174 | A connector task's input mappings are its outbound payload | Accepted | Landed |
| 0175 | Replicated partition cells for horizontal scale-out | Proposed | Not started |
| 0176 | Standards boundary and the Atlas runtime contract | Accepted | Landed |
| 0177 | Reload skips the deploy-time validation gate | Accepted | Landed |
| 0178 | Responsibility metadata — RACI on the element, with R derived from the assignment | Proposed | Not started |
| 0179 | A worker's job history lives in clio, not in Atlas | Accepted | Landed |
| 0180 | Groups as scope members | Accepted | Landed |
| 0181 | Mock mode for the Active Directory connector | Accepted | Landed |
| 0182 | Active Directory runs on a worker by default | Accepted | Landed |
| 0183 | The Modeler asks the server where an authored kind runs | Accepted | Landed |
| 0184 | Grant audit log | Accepted (amended) | Landed |
| 0185 | Live group membership | Accepted | Landed |
| 0186 | Embedding a public start form cross-origin (scoped CORS) | Accepted | Landed |
| 0187 | Database change events — captured in the database, read on a worker, deduplicated in the engine | Proposed | Not started |
| 0188 | A database is a Console entry, not a start parameter — and a worker is never a thing you create | Accepted | Landed |
| 0189 | Panorama architecture modeling and live operational overlays | Accepted (amended) | Landed |
| 0190 | Add explicit RSS and Atom extraction to the web-scraping connector | Accepted | Landed |
| 0191 | TLS 1.3 in the binary — an optional listener with operator-supplied certificates | Accepted | Landed |
| 0192 | BMC Remedy runs on a worker by default | Accepted | Landed |
| 0193 | The Active Directory mockup switch belongs in the Console | Accepted | Landed |
| 0194 | API tokens — a credential a machine can actually be given | Accepted | Landed |
| 0195 | Requiring a login is the default | Accepted | Landed |
| 0196 | The MCP transport is authenticated, and acts as its caller | Accepted | Landed |
| 0197 | A throttle on the login, and a security audit trail | Accepted | Landed |
| 0198 | The Prometheus exposition moves behind the boundary | Accepted | Landed |
| 0199 | Every mounted route declares its access class | Accepted | Landed |
| 0200 | Atlas as an OAuth resource server, so a hosted MCP client can connect | Accepted | Landed |
| 0201 | Atlas Jira connector | Accepted | Landed |
| 0202 | Atlas holds the AD mockup's starting entries | Accepted | Landed |
| 0203 | Worker execution model and integration terminology | Accepted | Landed |
| 0204 | Hosted apps — user HTML/JS served from an isolated origin | Proposed | Not started |
| 0205 | Who owns a connector, and who may use the events it brings in | Accepted | Landed |
| 0206 | Active Directory is a connector you configure, not one you write into a model | Accepted | Landed |
| 0207 | Package Worker Types as signed external runtime artifacts | Accepted | Partial |
| 0208 | Worker Type package contract, trust, and distribution | Accepted | Partial |
| 0209 | Roles per endpoint group | Accepted | Landed |
| 0210 | Federated authentication | Accepted | Landed |
| 0211 | Panorama's derived landscape mesh and notation projections | Accepted (amended) | Landed |
| 0212 | The element-template applier, and what a template binding means in Atlas | Proposed | Not started |
| 0213 | The mock Active Directory is visible in the Console | Accepted | Landed |
| 0214 | Jira as an inbound event source — a polled issue watch, deduplicated per issue | Accepted | Landed |
| 0215 | The Modeler Playground — batch simulation and analysis of a draft | Accepted | Landed |
| 0216 | Mockups are one view, not one per kind | Accepted | Not started |
| 0217 | A mock REST API served from an OpenAPI document | Accepted (amended) | Landed |
| 0218 | Jira runs on a worker by default | Accepted | Landed |
| 0219 | Variable write attribution | Accepted | Landed |
| 0220 | The Console may dial a database, and the engine still links no driver | Accepted | Landed |
| 0221 | A database task runs against seeded answers, not against a SQL engine | Accepted (amended) | Landed |
| 0222 | An artifact's id is its identity — renaming moves it, collisions are refused | Accepted | Landed |
| 0223 | Jira account lookup | Accepted | Landed |
| 0224 | A mockup run is visible, and it carries what the process bound | Accepted | Landed |
| 0225 | An inbound watch has an hourly budget | Accepted | Landed |
| 0226 | A start event is a trigger, and the one that fires is the one that starts | Accepted | Landed |
| 0227 | A Jira read is bounded and moves forward | Accepted | Landed |
| 0228 | User presence in the Console | Accepted | Landed |
| 0229 | The Modeler's editor bar carries two acts and a menu | Accepted | Landed |
| 0230 | The process information model — UML classes above BPMN's data objects | Accepted | Landed |
| 0231 | Structured HTML extraction, richer feed entries, and a fetch that survives the real web | Accepted | Landed |
| 0232 | Importing a UML class diagram — reading what somebody else drew | Accepted | Landed |
| 0233 | Finish ADR-0164 — in-process connector work becomes a finite list, then nothing | Accepted | Landed |
| 0234 | Google Sheets and Drive as inbound event sources — a polled row watch and a polled folder watch | Accepted | Landed |
| 0235 | Google Sheets as a Worker Type — a spreadsheet is a process data source | Accepted | Landed |
| 0236 | A non-interrupting message or signal boundary event stays armed | Accepted | Landed |
| 0237 | The class canvas on diagram-js | Accepted (amended) | Landed |
| 0238 | A reverse index from call activity to child instance | Accepted | Landed |
| 0239 | Read-only queries run off the run loop, on a consistent view | Accepted | Landed |
| 0240 | Variables stays on the Modeler's bar, as a pressed button with a shortcut | Accepted | Landed |
| 0241 | Finding an instance — a key lookup and a per-definition index | Accepted | Landed |
| 0242 | One route table describes the shell | Proposed | Not started |
| 0243 | The views are built from shared parts | Proposed | Not started |
| 0244 | Searchable variables — a declared value index | Accepted | Landed |
| 0245 | The call activity's "+" is the way into the process it calls | Accepted | Landed |
| 0246 | The Tasks app descends into a called process instead of navigating to it | Accepted | Landed |
| 0247 | An instance that is gone is still findable | Accepted | Landed |
| 0248 | A search term is literal, and widening is asked for | Accepted | Landed |
| 0249 | Cancelled tokens on the runtime overlay, and a deferred choice drawn once | Accepted | Landed |
| 0250 | Documentation prose is Markdown, rendered by one closed renderer | Accepted | Landed |
| 0251 | Adjusting a deployed definition's diagram without redeploying it | Accepted (amended) | Landed |
| 0252 | Runtime badges hang outside the shape, clear of its caption | Accepted | Landed |
| 0253 | Agent tool calls drive ad-hoc activation — the toolbox is the model | Accepted | Landed |
| 0254 | An agent round on a worker — the toolbox travels out, the tool calls travel back | Accepted | Landed |
| 0255 | An agent model is a Console Worker — the one field that is not a secret | Accepted (amended) | Landed |
| 0256 | The model is authored, the provider is configured — and one call is a task | Accepted | Landed |
| 0257 | What an agent may read is authored, the way its reach already is | Accepted | Landed |
| 0258 | Discord as a Worker Type — a process speaks in the channel the team already reads | Accepted | Landed |
| 0259 | The data object lifecycle — what the BPMN data state resolves against | Accepted | Landed |
| 0260 | A form is generated at design time, by the Worker an operator already configured | Accepted | Landed |
| 0261 | The diagram is the query — filtering instances by the element they sit on | Accepted | Landed |
| 0262 | Discord as an inbound event source — a channel is a log, and a snowflake is its sequence | Accepted (amended) | Landed |
| 0263 | The brand palette reaches the form runtime | Accepted | Landed |
| 0264 | A row watch's idempotency mark is its own, and the cursor is why that needs no migration | Accepted | Landed |
| 0265 | Signing in does not wait for the run loop | Accepted | Landed |
| 0266 | The runtime counts leave the run loop, and take the write paths with them | Accepted | Landed |
| 0267 | The console speaks German first, through a catalogue rather than a rewrite | Accepted | Landed |
| 0268 | Task folders are saved filters, stored as rules and generated into FEEL | Accepted | Landed |
| 0269 | The engine stays namespace-blind, and the deploy says so | Accepted | Landed |
| 0270 | A poll costs a page, not a backlog | Accepted | Landed |
| 0271 | A batch persists the work it still owes | Accepted | Landed |
| 0272 | One token may not hold the writer forever | Accepted | Landed |
| 0273 | A gateway that cannot route parks, it does not complete | Accepted | Landed |
| 0274 | The in-process runner claims what it works | Accepted | Landed |
| 0275 | Reading a running instance is an object question | Accepted | Landed |
| 0276 | A loop's size is checked before it is built | Accepted | Landed |
| 0277 | A join synchronizes within its own execution scope | Accepted | Landed |
| 0278 | Filing a deployment into a project is a write on that project | Accepted | Landed |
| 0279 | Topology is compiled, including the join's ancestors | Accepted | Landed |
| 0280 | Recovery proves its prefix or refuses to start | Accepted | Landed |
| 0281 | A role change takes effect on the next request, not the next login | Accepted | Landed |
| 0282 | One inventory of what is on disk, and what a backup owes it | Accepted | Landed |
| 0283 | Only the end of the active segment may be torn | Accepted | Landed |
| 0284 | A cancellation sees the children created in its own batch | Accepted (amended) | Landed |
| 0285 | A batch is one framed unit in the log | Accepted | Landed |
| 0286 | A list carries its own search | Accepted | Landed |
| 0287 | Create the worker from the incident, and run the deploy preflight on every deploy path | Accepted | Landed |
| 0288 | An agent's commit is authored by the person who asked for it | Deprecated | Not started |
| 0289 | A Worker Type carries its own setup, in the panel where it is chosen | Accepted | Landed |
| 0290 | A join counts tokens per incoming flow | Accepted | Landed |
| 0291 | One place names every resource budget, and one way sets them | Accepted | Landed |
| 0292 | A MIM import hands over its rows as data, and counts them as work | Accepted | Landed |
| 0293 | A record that rests on an open question says so, and the question expires | Accepted | Landed |
| 0294 | A variable is a record, a collection is a loop's harvest — two budgets | Accepted | Landed |
| 0295 | A migration re-indexes what its target declares | Accepted | Landed |
| 0296 | A loop records the element it produced, not the collection so far | Accepted (amended) | Landed |
| 0297 | Confine the internal worker token to the worker protocol | Accepted | Landed |
| 0298 | A record has two states — whether the decision holds, and whether it is built | Accepted | Landed |
| 0299 | What earns a Worker Type — admission criteria for a new kind | Proposed | Not started |
| 0300 | An OpenAPI document as element templates | Accepted | Landed |
| 0301 | Derive the information model from the processes that use it | Accepted | Landed |
| 0302 | The repository stops rewriting who authored an agent's commit | Accepted | Landed |
| 0303 | Isolate general-purpose script tasks inside a restrictive OS sandbox | Accepted | Landed |
| 0304 | A capability record says when somebody last confirmed it | Accepted | Landed |
| 0305 | Business capabilities and value streams as design-time records | Accepted | Partial |
| 0306 | A lifecycle may take its states from an «enumeration» | Accepted | Landed |
| 0307 | The milestone event compiles | Accepted | Landed |
| 0308 | Panorama binds the capability register | Accepted | Landed |
| 0309 | Measuring a capability | Accepted | Landed |
| 0310 | Read the difference between what is built and what is planned | Accepted | Landed |
| 0311 | The approver decides on a page of their own, in the customer's colours | Accepted | Landed |
| 0312 | Catalogue, order, inventory — three models, not one | Accepted | Partial |
| 0313 | The portal follows the browser's language; the console still does not | Accepted | Partial |
| 0314 | Personal data in the portal — a reference by default, a destroyable key for the rest | Accepted (amended) | Partial |
| 0315 | Who runs the portal — a role for the operation, a catalogue for the object | Accepted | Partial |
| 0316 | A theme belongs to a catalogue, the sign-in screen belongs to the operator | Accepted | Landed |
| 0317 | Acting on a user task is an object question | Accepted | Landed |
| 0318 | A user task's assignment may be an expression | Accepted | Landed |
| 0319 | Durable, versioned decision deployments — and latest resolved when a process is deployed |
Accepted | Landed |
| 0320 | The decision editor is a page, not an overlay | Accepted | Landed |
| 0321 | A decision has a draft, and Save stops writing the model | Accepted | Landed |
| 0322 | One decision can be deployed on its own, from the editor | Accepted | Landed |
| 0323 | Two people can edit one decision together, and the lock is the decision | Accepted | Landed |
| 0324 | A decision is published as its own document | Accepted | Landed |
| 0325 | A DMN model's diagram is completed on read, and can be re-laid on request | Accepted | Landed |
| 0326 | A decision can be tried against sample inputs before anything is deployed | Accepted | Landed |
| 0327 | A deployed decision satisfies a latest-bound business rule task | Accepted | Landed |
| 0328 | The process document shows the decision a business rule task runs | Accepted | Landed |
| 0329 | A decision deployment is not deletable, and what has to be true before it is | Accepted | Landed |
| 0330 | A model with no reference stays findable | Accepted | Landed |
| 0331 | Deleting a DMN reference says what it would break | Accepted | Landed |
| 0332 | Accounts and groups are pulled from Entra by delta query, and the first run writes nothing | Accepted | Landed |
| 0333 | The inventory is taken before it is enforced, and what it records says Atlas did not grant it | Accepted | Landed |
| 0334 | Reconciliation reads absence as a finding, and only inside a scope somebody promised was complete | Accepted | Landed |
| 0335 | Starting an instance returns its key | Accepted | Partial |
| 0336 | Cleaning up the decision store | Accepted | Landed |
| 0337 | An incident flood is read by cause and resolved in bulk | Accepted | Landed |
| 0338 | Where a business object is used | Accepted | Landed |
| 0339 | The definition key space never goes backwards | Accepted | Landed |
| 0340 | An outage stops at the worker, not at every token | Accepted (amended) | Landed |
| 0341 | Recertification asks whether a right is still justified, and refuses to let silence answer | Accepted | Landed |
| 0342 | A conflict is a fact about a pair, and no rule can say which half is wrong | Accepted | Landed |
| 0343 | A reminder has to be able to ask about somebody else, and a person must not | Accepted | Landed |
| 0344 | A right may carry an end, and an end that has passed is a debt rather than a fact | Accepted | Landed |
| 0345 | The job-type index space never goes backwards | Accepted | Landed |
| 0346 | The remedy must not destroy the evidence, so a hold that ends leaves a row | Accepted | Landed |
| 0347 | A product may narrow its catalogue's audience, because a person only ever sees one catalogue | Accepted | Landed |
| 0348 | A favourite is a bookmark, so it stores an id and resolves nothing | Accepted | Landed |
| 0349 | Whose name an order may carry is a question Atlas must ask, and a hierarchy it must not invent | Accepted | Landed |
| 0350 | A write arrow may set several members at once | Accepted | Landed |
| 0351 | An «enumeration» says which values a member may take | Accepted | Landed |
| 0352 | Draw a relationship from the class it starts at | Accepted | Landed |
| 0353 | The catalogue answers forwards, and the person who maintains a service asks backwards | Accepted | Landed |
| 0354 | An approval list is a work list, so it starts where nobody has looked | Accepted | Landed |
| 0355 | A catalogue is searched over words it carries, in the browser, and a hit says where it lives | Accepted | Landed |
| 0356 | The recipient is picked from the directory Atlas already publishes, and a name nobody holds is the caller's mistake | Accepted | Landed |
| 0357 | A portable backup does not overwrite another installation's identity | Accepted | Landed |
| 0358 | A product names one Atlas form, and the order line carries the answers | Accepted | Landed |
| 0359 | A position can be withdrawn on its own and its details corrected; what is held is never changed in place | Accepted | Landed |
| 0360 | A category is a heading a product writes on itself, not a thing the catalogue owns | Accepted (amended) | Landed |
| 0361 | A price is a sentence the catalogue writes, frozen like a rule and computed by nothing | Accepted | Landed |
| 0362 | One decision covers a request; the engine still completes one task per line | Accepted | Landed |
| 0363 | The drawing says which members are used | Accepted | Landed |
| 0364 | The verdict is served, not duplicated | Accepted | Landed |
| 0365 | A number an operations view states is a counter or a walk, never the length of a page | Accepted | Landed |
| 0366 | The live diagram counts every parked token, not the ones a bounded scan reached | Accepted | Landed |
| 0367 | A directory photo arrives the way every other directory fact does | Accepted | Landed |
| 0368 | A picture of a person belongs to the account, in two provenances and one place | Accepted | Landed |
| 0369 | Addressing and the envelope for a message that leaves the node | Accepted | Not started |
| 0370 | A published message waits for its subscriber | Accepted (amended) | Partial |
| 0371 | A participant names an interface; the server says which node | Proposed | Not started |
| 0372 | Delivering a message to another node is a worker's job | Accepted | Not started |
| 0373 | A process publishes an interface, not its model | Accepted | Not started |
| 0374 | The white box is a cached contract, not a live look inside | Proposed | Not started |
| 0375 | The feed generator is Go, so the Go checks stop needing Node | Superseded by ADR-0444 | Superseded |
| 0376 | A product manager maintains the catalogue over MCP | Accepted | Landed |
| 0377 | With nobody to be, the portal shows a catalogue and refuses an order | Accepted | Landed |
| 0378 | A capped listing answers with a page, not with an array | Accepted | Landed |
| 0379 | A DMN document's own namespace decides which DMN version reads and writes it | Accepted | Landed |
| 0380 | A commitment is checked against what the process actually does | Proposed | Not started |
| 0381 | A problem is an aggregation over incidents, not a BPMN element | Proposed | Not started |
| 0382 | Whole-store reads leave the writer, and the store is sized for what it holds | Accepted | Landed |
| 0383 | A position's level is read from the graph once, and a root with no parts is not a bundle | Accepted (amended) | Landed |
| 0384 | A position is a product and the shape of it, and it says so in its own key | Accepted | Landed |
| 0385 | A decision is addressed by both of its names | Accepted | Landed |
| 0386 | A decision result keeps the type the model declares for it | Accepted | Landed |
| 0387 | The catalogue keeps its own model, and answers to TMF620 at the boundary | Proposed | Not started |
| 0388 | A call that can only be null is refused at deploy | Accepted (amended) | Landed |
| 0389 | Forked instance migration | Accepted | Landed |
| 0390 | The orderer is told where their position stands, from a route of their own | Accepted | Landed |
| 0391 | A product is shown, and its picture is not part of what was promised | Accepted | Landed |
| 0392 | The same refusal at every door that already refuses | Accepted | Landed |
| 0393 | A rule the compiler gains later is a gate on deploy, whatever stage it lives in | Accepted | Landed |
| 0394 | An approval is read and decided where the work already is | Accepted | Landed |
| 0395 | A product's structure is assembled per product, not related pairwise | Accepted | Landed |
| 0396 | The service catalogue is drawn on the starmap, as derived fact | Accepted (amended) | Landed |
| 0397 | The orderable window is enforced when an order is placed | Accepted | Landed |
| 0398 | A business rule task can call a decision service | Accepted | Landed |
| 0399 | The service portal carries its own sign-in | Accepted | Landed |
| 0400 | An edge that was taken is not an edge that was declared | Accepted | Partial |
| 0401 | What a node is called when there is more than one log | Accepted | Partial |
| 0402 | One estate, several nodes — a starmap stitched from subgraphs | Accepted (amended) | Landed |
| 0403 | The estate graph is derived from the log, never the log from the graph | Accepted | Landed |
| 0404 | The whole graph can be walked, in a projection with a stated scope | Accepted | Partial |
| 0405 | A decision is shown, not described | Accepted | Landed |
| 0406 | A decision service is drawn, and drawn around its members | Accepted | Landed |
| 0407 | Requirements as design-time records, attached by a declared edge | Proposed | Not started |
| 0408 | The portal's order rows carry one link into a process, not three | Accepted | Landed |
| 0409 | The archive substrate waits, its schema does not | Accepted | Landed |
| 0410 | A peer credential carries the reach a membership cannot give it | Accepted | Landed |
| 0411 | The shipped system processes call Atlas as REST connector tasks, told their own address | Accepted | Landed |
| 0412 | A catalogue heading is a key that groups and a wording per language that shows | Accepted (amended) | Landed |
| 0413 | A language tag is checked where it is written, and nowhere else | Accepted (amended) | Landed |
| 0414 | A missing translation is reported, not refused | Accepted | Landed |
| 0415 | The portal offers the languages its catalogue is kept in | Accepted | Landed |
| 0416 | The shop shows an order's open tasks, and the order records the instances that work it | Accepted | Landed |
| 0417 | The shop and Tasks are one column wide on a narrow screen | Accepted | Landed |
| 0418 | A withdrawn ordered right goes back through its order | Accepted | Landed |
| 0419 | A declared type is honoured at the decision boundary | Accepted | Landed |
| 0420 | One graph model, several projections, and a successor view built beside the starmap | Proposed | Not started |
| 0421 | A task is listed to whoever it was addressed to | Accepted | Landed |
| 0422 | Who decided is recorded, on the task, the order and the right | Accepted | Landed |
| 0423 | A business rule task follows the newest decision when it runs, or the version it names | Accepted | Landed |
| 0424 | A Playground rule can demand the cases it speaks about | Proposed | Not started |
| 0425 | A product's lifecycle is one process, and each operation is a message start it is triggered at | Accepted | Landed |
| 0426 | An untriggered create never seeds several start events | Accepted | Landed |
| 0427 | Converting a product to a lifecycle process keeps the old processes alive until nothing needs them | Accepted | Landed |
| 0428 | A product lifecycle may run as one instance per position, and its later operations are delivered to that instance | Accepted (amended) | Landed |
| 0429 | A product declares its actions, each a command whose outcome is a fact published beyond Atlas | Accepted (amended) | Partial |
| 0430 | The event feed has its own role and its own token scope | Accepted | Landed |
| 0431 | A system process announces its facts as signals | Accepted | Landed |
| 0432 | The event feed is narrowed by the catalogue that maintains the product | Accepted | Landed |
| 0433 | The event feed is pushed to a CloudEvents endpoint | Accepted | Landed |
| 0434 | The catalogue can be switched off, and switching it off removes a surface, not a semantics | Accepted | Landed |
| 0435 | Atlas keeps one catalogue of the events it emits | Accepted | Partial |
| 0436 | A catalogue is imported as one document | Accepted | Landed |
| 0437 | A package is imported from the command line | Accepted | Landed |
| 0438 | The mail Worker reads its mailbox — an inbound watch, mailbox operations, and who may use them | Accepted | Landed |
| 0439 | On Windows, a supervised worker ends with the server, however the server ends | Accepted | Landed |
| 0440 | A worker runs the jobs of one type concurrently, up to the places it has free | Accepted (amended) | Landed |
| 0441 | A position's answers reach its processes | Accepted | Landed |
| 0442 | An S3 Worker Type — a process puts a file down, finds it again, and hands it out | Accepted | Landed |
| 0443 | Publishing warns about answers in the clear | Accepted | Landed |
| 0444 | The Console shows the CHANGELOG as release notes, read while the server runs | Accepted | Landed |
| 0445 | A FEEL expression is written in a conversation, and checked by the engine before anybody reads it | Accepted | Landed |
| 0446 | Atlas shows the messages its deployed models send and receive | Proposed | Not started |
| 0447 | Atlas shows the signals its deployed models throw and receive | Proposed | Not started |
| 0448 | Trusted proxies — the client's address behind a load balancer, and the health check that is not news | Accepted | Landed |
| 0449 | A wizard is a subprocess one filler walks through | Accepted | Landed |
| 0450 | A scope drains only when no token is in transit into it | Accepted | Landed |
| 0451 | A catalogue is exported as the document it is imported as | Accepted | Landed |
| 0452 | The Modeler says which version it shows, and Deploy saves the draft | Accepted | Landed |
| 0453 | An application holds its values per installation, and a constant binds to one by name | Accepted (amended) | Landed |
| 0454 | Process constants are seeded with every instance and written by nothing else | Accepted (amended) | Landed |
| 0455 | A start variable's default is what an instance starts with when its start did not supply it | Accepted | Landed |
| 0456 | Constants the installation holds are read under the namespace that holds them, and the installation holds its own | Accepted | Landed |
| 0457 | The Modeler shows a model's source and applies a correction to it | Accepted | Landed |
The two states of a record
A record answers two questions, and they are not the same question. Status says
whether the decision holds. Implementation says whether it is built. go test ./docs/adr checks both against their word lists and against each other, and the index
above carries both.
Status
- Proposed — under discussion
- Accepted — decided and in effect
- Superseded by ADR-XXXX — replaced by a later decision
- Deprecated — no longer relevant
A parenthetical after the word is fine and common: Accepted (amended 2026-08-17: the marker runs on every activity kind). A different word is not — the guard rejects it.
Implementation
- Not started — none of the chosen option is in the tree
- Partial — part of what the record decided is built and part is not
- Landed — the chosen option is in production code, with tests
- Superseded — a later record replaced the decision; nothing here left to measure
This field carries no parenthetical, because the index is rendered from it. What is built and what is not belongs in the record, where a reader is.
Partial is the word that needs care. An extension the record itself defers is not
what makes a record partial — ADR-0154 decided an LDAP connector and listed a delta
cookie as a follow-up, and that record is Landed. A piece of the decision that is
missing is what makes it partial — ADR-0027 decided that an author selects and applies
an element template, and only the store behind it exists.
Why they are two fields
They were one until 2026-09. Status was asked to carry both, readers used it for the
more useful question, and the two meanings drifted apart the only way they could: 115
records whose code had shipped, with tests, still read Proposed, because nothing in
the process ever went back to change the line. An audit
said so in August and named 28 of them; two weeks later not one had moved. That is a
missing mechanism, not a missing afternoon.
So there is a rule with a test behind it: Implementation: Landed requires Status: Accepted. Nobody merges code against a decision that has not been made, so a record
with landed code is a record whose decision was taken — whatever the front matter
still says. The reconciliation that applied this to all 295 records, and the evidence
behind each classification, is in
the status reconciliation.
Documentation
¶
Overview ¶
Package adr carries the ADR directory's own conventions as code: the parser for a decision record, the guard tests in adr_test.go that keep the directory and its index honest, and the merge-time numbering below.
The problem it solves is structural. A number used to be taken when a record was written, on a branch — so two branches that each wrote a record each took the same "next free" number, and whichever merged second had to renumber: rename the file, fix the heading, move the index row, and chase every citation. Numbers 0090, 0103 and 0105 were each shared by two unrelated decisions that way before there was a test; afterwards the test caught it, but the renumbering churn stayed, and one record walked 0164 → 0165 → 0166 → 0167 → 0168 → 0169 across six merges without a word of its content changing.
So a record in flight carries no number at all. It lives at draft-<slug>.md, heads with `# ADR-DRAFT: title`, and is cited as ADR-draft-<slug>. Two branches writing records touch different files and no shared line of the index, so they cannot collide. AssignNumbers gives every draft its number once it is on main — where "the next free number" is finally a question with one answer — and rewrites the file name, the heading, the index row and every citation in one pass.
A number, once assigned, is never reassigned. That is what makes a citation safe: ADR-0168 in a comment means today what it will mean in a year.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BaseStatus ¶ added in v0.7.0
BaseStatus strips a status's parenthetical. The index deliberately abbreviates "Accepted (amended 2026-08-17: …)" to "Accepted (amended)", so only the status itself — Proposed, Accepted, Superseded by ADR-NNNN, Deprecated — is comparable.
Types ¶
type Assignment ¶
type Assignment struct {
Slug string
Num int
From string // draft-<slug>.md
To string // NNNN-<slug>.md
Title string
Status string
Implementation string
}
Assignment is one draft becoming a numbered record.
func AssignNumbers ¶
func AssignNumbers(root string) ([]Assignment, error)
AssignNumbers gives every draft under root/docs/adr the next free number and makes the whole repository agree about it: the file is renamed, its heading rewritten, its row appended to the index, and every `ADR-draft-<slug>` citation and `draft-<slug>.md` link in the tree rewritten to the number it just got.
It is meant to run on main, where "the next free number" has one answer — see the package comment. With no drafts present it changes nothing, which is the common case for the workflow that runs it on every push.
type Record ¶
type Record struct {
Num int // 0 while the record is a draft
Name string // file name within docs/adr
Slug string // the file name's kebab-case part, stable across numbering
Title string // from the `# ADR-NNNN: title` heading
Status string // from the `- **Status:** ...` line
// Implementation is the second half of a record's state, from the
// `- **Implementation:** ...` line. Status answers "does this decision hold";
// Implementation answers "is it built". One field carried both for 295 records
// and could answer neither: a decision whose code had shipped still read
// `Proposed`, because nothing in the process ever went back to change it.
// ADR-0298 says why they are separate rather than merged.
Implementation string
// OpenQuestion and QuestionChecked are the optional pair a record carries when
// its reasoning rests on something nobody could answer: what the question is,
// and the YYYY-MM month somebody last looked at it. They come as a pair or not
// at all — a question with no date cannot go stale and would quietly read as
// settled, a date with no question says a thing was checked without saying what
// — and a guard test fails once a date has stood for a year.
OpenQuestion string
QuestionChecked string
}
Record is one decision record on disk. A record still in flight has Num 0 and lives at draft-<slug>.md; a record that has landed carries its number in both the file name and the heading, and the guard tests insist the two agree.
func LoadRecords ¶
LoadRecords reads every record in dir — numbered and draft alike — and reports every malformed one in a single joined error, so a run names all the problems rather than the first. Records that parsed well enough to identify are returned even when the error is non-nil, which is what lets the guard tests check the index against the directory while still reporting a record's own defects.
func (Record) QuestionAge ¶ added in v0.6.0
QuestionAge reports how long ago the record's open question was last looked at, relative to now, and whether there was a month to read at all. A negative age is a date in the future — a typo, and the one that would make a freshness check quieter rather than louder, so the guard can fail on it rather than wait it out.
The month is taken at its first day: a check stated as "2026-09" is treated as having happened on 1 September, which ages it slightly faster than it happened. That is the safe direction for a freshness rule.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
adrnum
command
Command adrnum assigns a number to every architecture decision record that is still in flight, and makes the repository agree about it.
|
Command adrnum assigns a number to every architecture decision record that is still in flight, and makes the repository agree about it. |