Skip to content

Commit 68cadb2

Browse files
hyperpolymathclaude
andcommitted
docs(adr): ADR-004 README single-source derivation (author .adoc, derive .md per declared consumer)
Design-first per owner ratification 2026-07-07: principle accepted conditional on standards-design review before any workflow code. Proposes the descriptiles publishing block, the gated readme-derive reusable workflow, guardrails, and the boj-server + profile-repo pilot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 6208833 commit 68cadb2

1 file changed

Lines changed: 191 additions & 0 deletions

File tree

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
// SPDX-License-Identifier: CC-BY-SA-4.0
2+
// (MPL-2.0 is automatic legal fallback until PMPL is formally recognised)
3+
// Author: Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
4+
= ADR-004: README Single-Source Derivation — Author AsciiDoc, Derive Markdown per Declared Consumer
5+
:toc:
6+
:sectnums:
7+
:icons: font
8+
Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
9+
v0.1.0, 2026-07-07
10+
11+
== Status
12+
13+
**Proposed** — design under owner review. The principle was ratified
14+
2026-07-07 with the condition "standards-design first": no workflow code
15+
is to be built until this ADR is accepted.
16+
17+
== Context
18+
19+
The estate default is `README.adoc` (~215 repos). AsciiDoc is the
20+
canonical authoring format for its richer semantics (toc, includes,
21+
callouts, source highlighting).
22+
23+
Exactly two repos genuinely need a *Markdown* README, for
24+
external-platform rendering reasons, not stylistic preference:
25+
26+
* **boj-server** — listed on Glama, the MCP-server directory. Glama
27+
renders Markdown only; an `.adoc` README displays as raw markup.
28+
* **hyperpolymath/hyperpolymath** — the GitHub profile repo. GitHub
29+
special-cases `README.md` for profile rendering and shows a
30+
persistent notice until one exists.
31+
32+
This narrow two-repo exception lived as prose lore. On 2026-06-25 an
33+
agent over-generalised it into an estate-wide `README.adoc` → `README.md`
34+
conversion (~33 repos, identical commit message "renders on
35+
Glama/profile/community-health"). All were reverted on 2026-06-29 by
36+
restoring each repo's pre-conversion `.adoc` from origin history.
37+
38+
Root cause: **format policy expressed as prose is executable by
39+
judgment, and judgment over-generalises.** The exception list also
40+
cannot grow safely — every new Markdown-only consumer (another
41+
directory, a package registry) would extend the lore and raise the
42+
same risk again.
43+
44+
A related precedent: the Item-11 `.md`/`.adoc` dedupe work established
45+
that format conversion is only safe behind a *vocabulary-preservation
46+
gate* (a fail-closed check that the output retains ≥98% of the input's
47+
content tokens), implemented in `md2adoc.py` and backed by a static
48+
pandoc 3.9.
49+
50+
== Decision (proposed)
51+
52+
=== Principle
53+
54+
Author every README once, in AsciiDoc. Where a repo has a
55+
Markdown-only consumer, CI *derives* `README.md` from `README.adoc`.
56+
Derived files are build artefacts: never hand-edited, never
57+
hand-converted, always regenerable.
58+
59+
Uniform in authoring, flexible in publishing. The two current
60+
exceptions stop being exceptions and become configuration.
61+
62+
=== Consumer declaration (descriptiles `publishing` block)
63+
64+
A repo that needs a derived README declares it in its descriptiles —
65+
proposed home: a `(publishing ...)` block in
66+
`.machine_readable/descriptiles/ECOSYSTEM.a2ml` (ecosystem = who
67+
consumes this repo, so the semantics fit; a standalone
68+
`PUBLISHING.a2ml` artefact is the flagged alternative if the owner
69+
prefers to keep ECOSYSTEM untouched).
70+
71+
Sketch:
72+
73+
[source,scheme]
74+
----
75+
(publishing
76+
(readme
77+
(canonical "README.adoc")
78+
(derived
79+
((format . gfm)
80+
(path . "README.md")
81+
(consumer . glama)
82+
(reason . "Glama MCP directory renders Markdown only")))))
83+
----
84+
85+
Rules:
86+
87+
* **No declaration ⇒ no derived README, ever.** Absence is a
88+
machine-readable "do not convert".
89+
* Every derived entry carries `consumer` and `reason` — the *why*
90+
travels with the config, so a future sweep agent reads intent
91+
instead of guessing it.
92+
* Any estate sweep touching README format MUST read this block first
93+
and skip undeclared repos. This is the anti-runaway guard.
94+
95+
=== Derivation pipeline (reusable workflow, `standards`)
96+
97+
One reusable workflow, `readme-derive.yml`, called by opted-in repos
98+
(same pattern as the other `-reusable` workflows here):
99+
100+
1. **Resolve** — `asciidoctor-reducer` flattens includes so the source
101+
is self-contained.
102+
2. **Convert** — `asciidoctor -b docbook` → `pandoc -f docbook -t gfm`
103+
(the reliable route; direct asciidoc→gfm converters lose structure).
104+
3. **Gate** — vocabulary-preservation check, fail-closed: the derived
105+
`.md` must retain ≥98% of the `.adoc`'s lowercased ≥4-char content
106+
tokens (badge/attribute syntax excluded). Same design as
107+
`md2adoc.py`, inverted direction. A failing gate fails the job; it
108+
cannot silently lose content.
109+
4. **Stamp** — prepend to the derived file:
110+
+
111+
[source,markdown]
112+
----
113+
<!-- GENERATED from README.adoc — do not edit. Regenerated by
114+
standards/.github/workflows/readme-derive.yml -->
115+
----
116+
+
117+
plus the repo's SPDX header.
118+
5. **Freshness** — on PRs and pushes, fail if `README.md` is stale
119+
relative to `README.adoc` (regenerate-and-diff). Default mode is
120+
*fail-and-tell* rather than auto-commit, keeping the workflow free
121+
of push credentials; an `auto_commit` input can be added later if
122+
the friction warrants it (owner call).
123+
124+
=== Guardrails
125+
126+
* **rhodibot broadening**: where a canonical `README.adoc` exists and
127+
the repo's descriptiles declare *no* Markdown consumer, an existing
128+
`README.md` is deletable cruft (this auto-heals any future runaway).
129+
Current rhodibot only removes <5-line stubs.
130+
* The two-repo exception lore in agent memory is superseded by the
131+
declaration mechanism once the pilot lands.
132+
133+
=== Rollout
134+
135+
[cols="1,4,2"]
136+
|===
137+
|Phase |Scope |Gate
138+
139+
|A
140+
|This ADR (design only, no code)
141+
|Owner accepts ADR
142+
143+
|B
144+
|Build `readme-derive.yml` + gate script in `standards`; define the
145+
`publishing` block in the descriptiles docs
146+
|ADR accepted
147+
148+
|C
149+
|Pilot: flip **boj-server** and **hyperpolymath/hyperpolymath** to
150+
authored `.adoc` + derived `.md` (ratified 2026-07-07 — both flip)
151+
|Phase B green; derived output visually verified on Glama and the
152+
profile page before the hand-authored `.md` is replaced
153+
154+
|D
155+
|Opt-in for any future Markdown consumer; rhodibot broadening
156+
|Pilot proves out
157+
|===
158+
159+
== Consequences
160+
161+
* One authoring format estate-wide; no repo ever has two hand-edited
162+
READMEs drifting apart.
163+
* Markdown consumers are served without demoting the canonical format.
164+
* The exception list becomes config: adding a consumer is a one-block
165+
descriptiles change plus a caller workflow, not new lore.
166+
* Conversion runs only in deterministic CI behind a fail-closed gate —
167+
no judgment calls left for an agent to over-generalise.
168+
* Cost: pandoc/asciidoctor in one more reusable workflow; a freshness
169+
check that fails PRs which edit `README.adoc` without regenerating
170+
(mitigated by fail-and-tell output including the regeneration
171+
command).
172+
173+
== Alternatives considered
174+
175+
* **Keep hand-maintained `.md` in the exception repos** (status quo):
176+
drift risk between the two files, and the lore-based rule already
177+
caused one estate-wide runaway.
178+
* **Convert canonical format to Markdown where consumed**: rejected —
179+
this is exactly the June runaway, and it demotes the authoring
180+
format estate-wide to serve a per-platform rendering gap.
181+
* **Wait for consumers to render AsciiDoc**: GitHub already renders
182+
`.adoc`; the gap is external (Glama et al.). A bug report to Glama
183+
is being filed in parallel, but the estate cannot depend on
184+
third-party fix timelines.
185+
186+
== Open items (owner veto points)
187+
188+
1. `publishing` block in `ECOSYSTEM.a2ml` vs standalone
189+
`PUBLISHING.a2ml`.
190+
2. Gate threshold (98% proposed, matching md2adoc precedent).
191+
3. Freshness mode: fail-and-tell (proposed) vs auto-commit.

0 commit comments

Comments
 (0)