adr: align the ADR tooling with MADR 4 exactly (docs/decisions/, YAML frontmatter, full MADR template) #483
Labels
No labels
breaking
bug
documentation
enhancement
epic
good first issue
help wanted
refactoring
resolution/duplicate
resolution/invalid
resolution/wontfix
security
severity/critical
severity/high
severity/low
severity/medium
status/abandoned
status/blocked
status/needs-decision
status/needs-info
status/needs-verification
testing
upstream
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
sixfold-space/madtea#483
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
madt_adr/madtea adronly knows the house layout:docs/adr/, a# ADR NNNN - titleheading,- Status:/- Date:/- Tracking:bullet lines, and empty Context / Decision / Consequences sections (internal/service/adr/adr.go:422-434). The check action assumes the same layout.I am standardising every sixfold.space repo on MADR 4 (https://adr.github.io/madr/), and I want the tooling to produce and check MADR exactly, not a house dialect of it. Today
madt_adrrefuses a repo that has nodocs/adr/tree, so a MADR repo gets no numbering and no drift check.Decision
MADR 4 is the format. The tool follows the spec, not the other way round.
What changes
Layout
docs/decisions/(the MADR 4 default).docs/adr/stays supported for existing trees; detect which one the repo carries. A repo with both is an error.NNNN-kebab-title.md, four-digit zero-padded, monotonic, never reused. Unchanged.README.mdexists in the tree, skip when it does not, and makechecktolerate both.Template written by
adr new(the MADR 4 full template, verbatim section names):statusvalues are the MADR set:proposed | rejected | accepted | deprecated | superseded by ADR-NNNN.tracking=lands under## More Informationas a link line, not in the frontmatter, because MADR frontmatter has no tracking key.adr checkstatus, a missing or malformeddate, and asuperseded bythat names a record that does not exist.status: superseded by ...has no corresponding pointer back from the newer record (bidirectional supersedence).Numbering
offline=truefallback.Acceptance criteria
madtea adr new <slug>in a repo withdocs/decisions/writes a file that matches the MADR 4 full template above byte-for-byte apart from the filled title and date.madtea adr checkpasses on a clean MADR 4 tree and fails, naming the file, on each of: unknown status, missing date, danglingsuperseded by, one-way supersedence, duplicate number, broken link.docs/adr/trees in this repo and in cheshirecube-cloud-infra still passcheckunchanged.madt_help topic=adrand the tool description describe both layouts and name MADR 4 as the format.Out of scope
docs/adr/tree to MADR. Separate issue if I want it.Scope addition for
adr check: undecided records must be tracked by issues.statusis not one ofaccepted,rejected,deprecated, orsuperseded by ADR-NNNN.impacts: [repo, repo], the repos the decision constrains. Empty means the record's own repo only.## More Information, an open issue in the top-level repo and an open issue in every repo named inimpacts.checkfails when a link is missing, when a linked issue is closed, or when a repo inimpactshas no linked issue.checkrequires those issues closed and the closing comment to cite the ADR.checkalready runs in a repo with an origin, so it resolves issue state through the same client. A--offlinerun skips the issue checks and says so.adr.top-level-repoin git config), not a hardcoded name. Unset means the record's own repo.Acceptance criteria added: one test per failure mode above, plus one for the offline skip.
Implementation spec
This comment is the complete spec. Where it differs from the template sketch in the issue body, this comment wins. The earlier comment about undecided records is folded in here (section 6).
0. Source of truth
MADR 4.0.0, upstream
adr/madrat tag4.0.0. Three upstream files matter. Vendor them verbatim underinternal/service/adr/madr/with aLICENSEnote (MIT OR CC0-1.0, the upstream dual licence):template/adr-template-bare.md26f6598f70239f173239290beb7aa5fb71483f52adr newwrites this, with substitutions (section 3)template/adr-template.md3dea2440cae85e408bd281a1a5b14399f0d21b4aadr initcopies it as the explained templatetemplate/0000-use-markdown-architectural-decision-records.mdd7c7b10b63ffe4a71aa4aaf9458f45a9fcd0e412adr initwrites it as record 0000Verbatim
adr-template-bare.md, the exact bytesadr newstarts from:Upstream conventions I follow exactly: directory
docs/decisions/, filenameNNNN-title-with-dashes.md, status grammarproposed | rejected | accepted | deprecated | superseded by ADR-0123(identifier only, no link, per upstream 4.0.0-beta change #150),statusvalue quoted as a YAML string (upstream #91), list bullets are*.1. Layout detection
docs/decisions/exists: MADR mode. Sections 2 to 7 apply.docs/adr/exists anddocs/decisions/does not: legacy mode. Today's behaviour, unchanged.two ADR trees: docs/adr/ and docs/decisions/; keep one.newandcheckfail and point atadr init.^[0-9]{4}-[a-z0-9-]+\.md$. Any other file in the tree (README.md,adr-template.md,adr-template-bare.md) is ignored by numbering and by every check.2. New action
initmadt_adr action="init"/madtea adr init. Createsdocs/decisions/with four files. Refuses when the directory already exists.0000-use-markdown-architectural-decision-records.md: the upstream file, with this frontmatter prepended so it passescheck:adr-template.md: upstream, verbatim.adr-template-bare.md: upstream, verbatim.README.md:Args:
decision_makers(list, optional). Nothing else.3.
newin MADR modeArgs:
slug(required),title,tracking,impacts(list ofowner/repoor bare repo names),decision_makers(list, default gituser.name),offline. Status is alwaysproposedon creation; there is nostatusarg. A record is born undecided.Writes
NNNN-slug.mdasadr-template-bare.mdwith exactly these substitutions and nothing else:status:line becomesstatus: "proposed".date:line becomesdate: <YYYY-MM-DD, local date>.decision-makers:line becomesdecision-makers: [<names>](YAML flow list).impactsis given, one lineimpacts: [<repos>]is inserted afterinformed:. When absent, noimpactsline. This is the only key on top of MADR 4.0.0.# <!-- short title, ... -->becomes# <title>(titlearg, else title-cased slug).trackingis given, the empty body under## More InformationbecomesTracking: <tracking>.Numbering: unchanged. Next number from the origin default branch;
offline=truenumbers from the local tree.Index: when
README.mdexists and contains a## Indexheading with the table above, append a row| [NNNN](NNNN-slug.md) | <title> | proposed | <date> |. When there is no## Index, write no index row and say so in the result.Result text names the file, the number, and whether an index row was written.
4. Frontmatter grammar
YAML between the first line
---and the next---. Keys:statusproposed,rejected,accepted,deprecated,superseded by ADR-[0-9]{4}dateYYYY-MM-DD, a valid calendar datedecision-makersconsultedinformedimpactsowner/repoor bare repo name (house key)Unknown keys: warning, not a failure. MADR allows extra metadata.
Decided statuses:
rejected,accepted,deprecated,superseded by ADR-NNNN. Undecided:proposed. Anything else fails rule C3.5.
checkin MADR modePer record, in order. Each failure prints one line
<path>: <rule>: <message>. A clean tree printsOK: <n> records, <k> proposed records checked against the forge. Any failure exits non-zero.filename must be NNNN-kebab-title.mdmissing or unparseable YAML frontmatterstatuspresent and in the grammarstatus "<v>" is not one of proposed | rejected | accepted | deprecated | superseded by ADR-NNNNdatepresent and a validYYYY-MM-DDdate "<v>" is not a valid YYYY-MM-DD date# <title>and the title is not the template placeholder commentrecord has no title## Context and Problem Statement,## Considered Options,## Decision Outcomemissing mandatory section "<name>"number NNNN is also used by <other file>link "<target>" does not resolvesuperseded by ADR-NNNN: the target record exists, and the target's body contains a link to this record (two-way supersedence)superseded by ADR-NNNN but <target> does not link back<!-- option -->,<!-- decision driver -->,<!-- title of option -->,Chosen option: "", becausedecided record still contains template placeholder "<text>"README.mdhas## Index: every record has one row and every row resolves to a recordindex row missing for <file>/index row "<n>" has no recordLegacy mode keeps only today's checks (numbers unique, index bijection, links resolve).
6. Issue tracking for undecided records (C11)
An undecided record must be tracked by open issues.
## More Information:https://<forge-host>/<owner>/<repo>/issues/<n>,<owner>/<repo>#<n>, or#<n>for the record's own repo.impacts. Top-level repo comes from git configmadtea.adr.topLevelRepo(owner/repo). Unset means the record's own repo. A bare repo name inimpactsresolves to the same owner as the record's own repo.proposedrecord: every repo in the required set has at least one linked issue, and that issue is open. Failures:proposed record has no issue link for <owner/repo>;proposed record links <owner/repo>#<n> but it is closed.NNNN-slug. Failures:record is decided but <owner/repo>#<n> is still open;<owner/repo>#<n> closed without citing <NNNN-slug>.madt_*call uses for that checkout. A forge error is a failure:could not read <owner/repo>#<n>: <error>. It is not a silent pass.offline=true: skip C11 and printSKIPPED (offline): issue tracking not checked for <k> proposed records. The skip does not change the exit code.7. CLI parity
madtea adr init [--decision-makers a,b],madtea adr new <slug> [--title] [--tracking] [--impacts a,b] [--decision-makers a,b] [--offline],madtea adr check [--offline]. Flag names equal the MCP arg names (#444).8. Docs and help
docs/decisions/(MADR 4.0.0) ordocs/adr/(legacy) tree."madt_help topic=adr: document both modes, the frontmatter grammar, every check rule with its message, and the issue-tracking rule.docs/adr/tree (legacy mode; migrating this repo is out of scope).9. Tests
adr newoutput equals the vendoredadr-template-bare.mdwith only the section 3 substitutions applied. Byte comparison.adr initwrites the four files;0000equals the vendored upstream file plus the frontmatter.internal/service/adr/testdata/madr/, each failing on exactly that rule with the exact message.10. Acceptance criteria
This list replaces the one in the issue body.
madtea adr initcreates the four files in section 2 and refuses an existing directory.madtea adr new <slug>in MADR mode writes a file that is byte-identical to the vendored bare template apart from the section 3 substitutions.madtea adr checkpasses on a clean MADR tree and fails, naming the file, rule, and message, on every rule in sections 5 and 6.madtea adr check --offlineskips C11 with the stated line.docs/adr/tree and every other legacy tree passcheckunchanged.madt_help topic=adr, CLI help, and README updated.docs/adr/.11. Out of scope
docs/adr/tree to MADR.adr statusaction to move a record between statuses. Status is edited by hand;checkenforces the consequences.The tooling landed in #484:
adr init, MADR-modeadr new, and the MADR-modeadr check(frontmatter grammar, mandatory sections in order, two-way supersedence, and open-issue tracking for undecided records), withdocs/decisions/and legacydocs/adr/layout detection. Legacydocs/adr/trees keep the current checks.This repo still keeps its own decisions in the legacy
docs/adr/tree. The remaining work under this issue: convert that tree todocs/decisions/MADR 4.0.0 in one pass, so the repo uses MADR only, and add this change's own record there. It is one atomic pass, because the tool treats a repo with both trees as an error.