Merge pull request 'chore(governance): stamp nine merged ADRs Accepted + pin the invariant in smoke' (#114) from chore/adr-status-governance into main
Reviewed-on: #114
This commit is contained in:
commit
0a26787f72
12 changed files with 326 additions and 9 deletions
|
|
@ -67,6 +67,16 @@ TEST_SUITES: dict[str, tuple[str, ...]] = {
|
|||
# substantive override of the served surface. Cheap, and the axis is
|
||||
# invisible until it breaks silently.
|
||||
"tests/test_prior_surface_deduction_binding.py",
|
||||
# ADR governance. An ADR that governs a default-ON flag records a
|
||||
# decision already in force, so leaving it "Proposed" makes the
|
||||
# governance record assert something false about the running system —
|
||||
# which is what happened to ADR-0256 while deduction serving was live.
|
||||
# Filesystem + regex only, ~1.2s, and it is the record for a serving
|
||||
# path, so it belongs on the pre-push gate rather than under `full`.
|
||||
# test_adr_index.py rides along: it landed in #113 registered in NO
|
||||
# curated suite, which is the same silent-red shape it exists to catch.
|
||||
"tests/test_adr_status_governance.py",
|
||||
"tests/test_adr_index.py",
|
||||
),
|
||||
"runtime": (
|
||||
"tests/test_chat_runtime.py",
|
||||
|
|
@ -97,6 +107,11 @@ TEST_SUITES: dict[str, tuple[str, ...]] = {
|
|||
"tests/test_math_composition_ratification.py",
|
||||
"tests/test_teaching_coverage_cli.py",
|
||||
"tests/test_proposal_queue.py",
|
||||
# The ratification ceremony is the only path that turns a reviewed
|
||||
# proposal into a routable ratified chain — i.e. the one mechanism that
|
||||
# can move the curriculum-volume constraint. It landed in #113 in NO
|
||||
# curated suite, so its 14 tests ran only under `full`.
|
||||
"tests/test_ratification_ceremony.py",
|
||||
),
|
||||
"packs": (
|
||||
"tests/test_pack_draft_serve_boundary.py",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# ADR-0254: Grounded-Open Hedge Arm for the Shadow Coherence Gate
|
||||
|
||||
**Status:** Proposed (predicate pre-authorized by Joshua Shay, 2026-07-22 weekly-audit ruling T13 decision 2; ratify on PR #103 merge)
|
||||
**Status:** Accepted — ratified by Joshua Shay via the PR #103 merge (`da3447e9`, 2026-07-23), discharging this ADR's own ratify-on-merge predicate (pre-authorized by the 2026-07-22 weekly-audit ruling T13 decision 2)
|
||||
**Date:** 2026-07-23
|
||||
**Deciders:** Joshua Shay (ruling authority) + Claude (implementation)
|
||||
**Companion docs:** [`ADR-0036-safety-refusal-policy.md`](ADR-0036-safety-refusal-policy.md), [`ADR-0037-per-predicate-ethics-refusal.md`](ADR-0037-per-predicate-ethics-refusal.md), [`ADR-0038-hedge-injection.md`](ADR-0038-hedge-injection.md), [`ADR-0252-problem-solving-paradigm-consolidation.md`](ADR-0252-problem-solving-paradigm-consolidation.md)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# ADR-0256: Deduction Serving Governed by an Earned Reliability License
|
||||
|
||||
**Status:** Proposed (deduction-serve arc, Phase 3 — ratify-on-merge)
|
||||
**Status:** Accepted — ratified by Joshua Shay via the deduction-serve Phase 5 merge (`1a6ccaf9`, 2026-07-23), discharging this ADR's own ratify-on-merge predicate. The `deduction_serving_enabled` default was flipped False → True by a separate, explicit ratification (`5dd61803`, 2026-07-24)
|
||||
**Date:** 2026-07-23
|
||||
**Deciders:** Joshua Shay (ruling authority) + Claude (implementation)
|
||||
**Companion docs:** [`docs/research/deduction-serve-arc-phase0-baseline-2026-07-23.md`](../research/deduction-serve-arc-phase0-baseline-2026-07-23.md), [`docs/research/deduction-serve-arc-phase1-turn-spine-2026-07-23.md`](../research/deduction-serve-arc-phase1-turn-spine-2026-07-23.md), [`docs/research/deduction-serve-arc-phase2-eval-lane-2026-07-23.md`](../research/deduction-serve-arc-phase2-eval-lane-2026-07-23.md), [`docs/adr/ADR-0175-...`](.) (calibrated learning), [`docs/adr/ADR-0199-...`](.) (cross-domain arena), [`docs/adr/ADR-0206-...`](.) (response-governance bridge)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# ADR-0257 — English-clause argument band (Band v2-EN): opaque-atom propositional serving
|
||||
|
||||
- **Status:** Proposed
|
||||
- **Status:** Accepted — ratified by Joshua Shay via the PR #107 merge (`9405cf19`, 2026-07-23)
|
||||
- **Date:** 2026-07-23
|
||||
- **Relates to:** ADR-0256 (deduction-serve earned license), ADR-0201 (ROBDD
|
||||
keystone), ADR-0218 (proof-carrying substrate), ADR-0175/0199 (calibrated
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# ADR-0258 — Member-chain band (Band v3-MEM): singular membership + universal premises
|
||||
|
||||
- **Status:** Proposed
|
||||
- **Status:** Accepted — ratified by Joshua Shay via the PR #108 merge (`dd2245a7`, 2026-07-24)
|
||||
- **Date:** 2026-07-23
|
||||
- **Relates to:** ADR-0257 (Band v2-EN — this is its scope-out #1), ADR-0256
|
||||
(earned license), ADR-0201 (ROBDD keystone), ADR-0175/0199 (calibrated
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# ADR-0259 — Conditional-membership fusion band (Band v4-CM)
|
||||
|
||||
- **Status:** Proposed
|
||||
- **Status:** Accepted — ratified by Joshua Shay via the PR #109 merge (`5224b5e0`, 2026-07-24)
|
||||
- **Date:** 2026-07-23
|
||||
- **Relates to:** ADR-0258 (Band v3-MEM — this is its scope-out #1), ADR-0257
|
||||
(Band v2-EN — the connective grammar this band reuses), ADR-0256 (earned
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# ADR-0260 — Band v5-VP: verb-predicate arguments, decided
|
||||
|
||||
- **Status:** Proposed
|
||||
- **Status:** Accepted — ratified by Joshua Shay via the PR #111 merge (`2a82c8a3`, 2026-07-24)
|
||||
- **Date:** 2026-07-24
|
||||
- **Arc:** deduction-serve (ADR-0256 → 0257 → 0258 → 0259 → this)
|
||||
- **Governs:** `generate/proof_chain/verb.py`, the `en_verb_*` shape-bands,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# ADR-0261 — Band v6-EX: existential arguments, decided
|
||||
|
||||
- **Status:** Proposed
|
||||
- **Status:** Accepted — ratified by Joshua Shay via the `feat/existential-band` merge (`a8488e9c`, 2026-07-24)
|
||||
- **Date:** 2026-07-24
|
||||
- **Arc:** deduction-serve (ADR-0256 → 0257 → 0258 → 0259 → 0260 → this)
|
||||
- **Governs:** `generate/proof_chain/exist.py`, the `en_exist_*` shape-bands,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# ADR-0262 — Curriculum-grounded serving: exams answered from what was taught
|
||||
|
||||
- **Status:** Proposed
|
||||
- **Status:** Accepted — ratified by Joshua Shay via the `feat/curriculum-serve-physics` merge (`0ae54ebb`, 2026-07-24). Accepting this ADR does NOT enable `curriculum_serving_enabled`, which stays False pending ratified curriculum volume (§5.1)
|
||||
- **Date:** 2026-07-24
|
||||
- **Arc:** generalization Phase 2 (docs/plans/generalization-arc-2026-07-24.md §4)
|
||||
- **Governs:** `teaching/curriculum_premises.py`, `chat/curriculum_surface.py`,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# ADR-0263 — The ratified-ledger bridge
|
||||
|
||||
- **Status:** Proposed
|
||||
- **Status:** Accepted — ratified by Joshua Shay via the `feat/curriculum-serve-physics` merge (`0ae54ebb`, 2026-07-24). Amended 2026-07-25 with rule 5 (ledger-absence policy declared, not passed)
|
||||
- **Date:** 2026-07-24
|
||||
- **Arc:** generalization Phase 3.3 (docs/plans/generalization-arc-2026-07-24.md §2)
|
||||
- **Governs:** `core/ratified_ledger.py` and the three adapters over it —
|
||||
|
|
|
|||
126
docs/research/adr-status-governance-2026-07-25.md
Normal file
126
docs/research/adr-status-governance-2026-07-25.md
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
# ADR Status Governance — Nine Stamps and an Enforcement Pin
|
||||
|
||||
**Date:** 2026-07-25 · Follow-on to the assessment-verification arc (PR #113).
|
||||
Evidence + mechanism only. No flag was touched; no ADR's *content* was changed.
|
||||
|
||||
## 1. What was wrong
|
||||
|
||||
Nine ADRs were merged into `main` and left stamped `Proposed`:
|
||||
|
||||
| ADR | ratifying merge | why the stamp was contradictory |
|
||||
|---|---|---|
|
||||
| 0254 | PR #103 → `da3447e9` (2026-07-23) | its own status line said *"ratify on PR #103 merge"* |
|
||||
| 0256 | `1a6ccaf9` (2026-07-23) | its own status line said *"ratify-on-merge"*, **and** it governs `deduction_serving_enabled`, ratified `True` on 2026-07-24 |
|
||||
| 0257 | PR #107 → `9405cf19` | Band v2-EN, live in the serving cascade |
|
||||
| 0258 | PR #108 → `dd2245a7` | Band v3-MEM, live |
|
||||
| 0259 | PR #109 → `5224b5e0` | Band v4-CM, live |
|
||||
| 0260 | PR #111 → `2a82c8a3` | Band v5-VP, live |
|
||||
| 0261 | `a8488e9c` | Band v6-EX, live |
|
||||
| 0262 | `0ae54ebb` | curriculum-grounded serving (flag still OFF — see §3) |
|
||||
| 0263 | `0ae54ebb` | ratified-ledger bridge, consumed by three capabilities |
|
||||
|
||||
Every merge above was verified an ancestor of `main` before stamping
|
||||
(`git merge-base --is-ancestor`), and each ADR's ratifying merge was derived
|
||||
from the commit that *added* the file, not assumed.
|
||||
|
||||
ADR-0256 is the sharp case. It governs a flag that was ratified ON and serving
|
||||
live traffic, so the governance record asserted "this decision is not yet made"
|
||||
about a decision already in force. That is the same asymmetry the arc that just
|
||||
closed found in `workbench/api.py`: **the honest path degrades, the stale
|
||||
record lies.** An unregistered grounding label reported
|
||||
`epistemic_state_needed`; a hand-copied whitelist reported `none`. Here, an
|
||||
unwritten ADR would have been a visible gap; a `Proposed` one that is actually
|
||||
in force is a false statement.
|
||||
|
||||
## 2. What the stamps say
|
||||
|
||||
Status lines now record the *actual* ratifying act rather than inventing a
|
||||
ceremony: `Accepted — ratified by Joshua Shay via <the merge> (<sha>, <date>)`.
|
||||
Merge authority in this repo is Shay's alone (`AGENTS.md`: no merge automation;
|
||||
green PRs sit until explicit authorization), so the merge **is** the ratifying
|
||||
act and naming it is traceable rather than decorative.
|
||||
|
||||
## 3. Accepting an ADR is not flipping its flag
|
||||
|
||||
ADR-0262's stamp says so explicitly. `curriculum_serving_enabled` stays
|
||||
`False`; its blocker is ratified curriculum volume (§5.1), and eleven live
|
||||
bands remain 24×–73× short of the entailed-bucket floor — re-measured
|
||||
2026-07-25 against the live loader, unchanged from the Tier-S S6 figures
|
||||
(physics `causal` 7, `modal` 9, `systems_software` `sequence` 3,
|
||||
`mathematics_logic` `modal` 4). An accepted ADR records a decision about
|
||||
*mechanism*; a flipped flag records a decision about *earned evidence*. Two
|
||||
different rulings, and this document conflates neither.
|
||||
|
||||
## 4. The pin
|
||||
|
||||
`tests/test_adr_status_governance.py`, registered in the `smoke` suite (the
|
||||
pre-push gate). Two independent invariants, both mutation-checked — reverting
|
||||
ADR-0256 to `Proposed` fails each one separately:
|
||||
|
||||
1. **A default-ON flag is not governed by a `Proposed` ADR.** The
|
||||
flag → ADR mapping is **derived** by walking `core/config.py` for
|
||||
`<name>: bool = True` and reading the ADR references out of the preceding
|
||||
comment block. Deliberately not a hand-written table: a second copy of a
|
||||
closed set that falls behind the original in silence is the exact defect
|
||||
ADR-0256's arc fixed one file over.
|
||||
2. **A `ratify-on-merge` predicate cannot coexist with `Proposed`.** Self
|
||||
discharging and needs no bookkeeping: the file being present on `main` *is*
|
||||
the merge having happened, so the two states are contradictory by
|
||||
construction.
|
||||
|
||||
Plus `test_config_flag_parse_is_not_vacuous`, because a derivation that silently
|
||||
parses zero flags would make every other assertion pass on an empty set —
|
||||
silence reading as success.
|
||||
|
||||
Measured: **314 passed in 1.18s** standalone; 2 failed / 312 passed under
|
||||
mutation.
|
||||
|
||||
Current binding scope is exactly one flag/ADR pair
|
||||
(`deduction_serving_enabled` → 0256), because only one of the 32 runtime bool
|
||||
flags is both default-True and ADR-citing. Narrow today by construction, and it
|
||||
widens automatically the moment another earned path is ratified ON — which is
|
||||
the point.
|
||||
|
||||
## 5. Two orphaned test files, registered in passing
|
||||
|
||||
`tests/test_adr_index.py` (5 tests) and `tests/test_ratification_ceremony.py`
|
||||
(14) landed in PR #113 in **no** curated suite. `full` is the directory
|
||||
`tests/`, so they ran there and nowhere that gates. Nineteen tests outside every
|
||||
gate — including the ceremony, the one mechanism that can move the
|
||||
curriculum-volume constraint, and an index test whose whole purpose is catching
|
||||
staleness.
|
||||
|
||||
That is the **fifth** instance of this shape: a stale suite tuple (S5), a stale
|
||||
lane roster (#113), a masked lane pin (S2), an unregistered grounding label
|
||||
(#113), and now unregistered test files. Registered `test_adr_index.py` into
|
||||
`smoke` alongside the new pin, and `test_ratification_ceremony.py` into
|
||||
`teaching` beside `test_proposal_queue.py`.
|
||||
|
||||
## 6. Deliberately NOT fixed — recorded so it is not re-found
|
||||
|
||||
The ADR corpus is 312 files and its status vocabulary has real drift:
|
||||
|
||||
| status token | files |
|
||||
|---|---:|
|
||||
| `accepted` | 201 |
|
||||
| `proposed` | 67 |
|
||||
| *(status line unparseable)* | **27** |
|
||||
| `draft` | 4 |
|
||||
| `ratified` | 3 |
|
||||
| `accepted.` / `active` / `phase` | 2 each |
|
||||
| `implemented` / `empirical` / `superseded` | 1 each |
|
||||
|
||||
A closed-vocabulary assertion over all 312 would fail on ~35 pre-existing files
|
||||
and get muted or `xfail`ed, which is worse than no check — a muted gate reads as
|
||||
coverage. So the pin's scope is exactly the ADRs whose status is load-bearing
|
||||
*now*, and it fails loudly on any of *those* it cannot parse rather than
|
||||
skipping it.
|
||||
|
||||
**The open item:** normalize the 27 unparseable status lines and collapse
|
||||
`ratified`/`draft`/`active`/`implemented` into the intended lifecycle, then
|
||||
widen the pin to a closed vocabulary. Sizeable, mechanical, and a good
|
||||
delegation candidate — it is a docs sweep with a test as its acceptance
|
||||
criterion, not a design question.
|
||||
|
||||
Relates to [[project-generalization-arc]], `docs/adr/INDEX-by-domain.md`,
|
||||
`docs/handoff/BRIEF-CLOSE-assessment-verification-2026-07-25.md`.
|
||||
176
tests/test_adr_status_governance.py
Normal file
176
tests/test_adr_status_governance.py
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
"""An ADR that governs live behaviour must not still be stamped ``Proposed``.
|
||||
|
||||
Nine ADRs (0254, 0256-0263) sat at ``Proposed`` while merged into ``main``,
|
||||
two of them carrying an explicit ``ratify-on-merge`` predicate that the merge
|
||||
had already discharged, and one -- ADR-0256 -- governing
|
||||
``deduction_serving_enabled``, which was ratified ``True`` and serving live
|
||||
traffic. Nothing failed. The governance record and the running system
|
||||
disagreed for a day and the only reason it surfaced was a human reading the
|
||||
files.
|
||||
|
||||
This is the "pinned list goes stale" family that has now been caught four
|
||||
separate ways in this repo (a stale suite tuple, a stale lane roster, an
|
||||
unregistered grounding label, a masked lane pin). Every previous instance was
|
||||
fixed by making the divergence loud. This module does that for ADR status.
|
||||
|
||||
Deliberately NOT a repo-wide status-vocabulary check. The 312-file ADR corpus
|
||||
carries real drift -- 27 files whose status line this module's regex cannot
|
||||
parse, plus ``draft`` / ``ratified`` / ``active`` / ``accepted.`` variants.
|
||||
Normalizing that is a separate, larger job (recorded in
|
||||
``docs/research/adr-status-governance-2026-07-25.md``). Asserting a closed
|
||||
vocabulary here would fail on ~35 pre-existing files and get muted, which is
|
||||
worse than no check. So the scope is exactly the ADRs whose status is
|
||||
*load-bearing right now*, and the parser fails loudly on any of those it cannot
|
||||
read rather than skipping it.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
ADR_DIR = REPO_ROOT / "docs" / "adr"
|
||||
CONFIG_PATH = REPO_ROOT / "core" / "config.py"
|
||||
|
||||
#: ``**Status:** X`` / ``- **Status**: X`` / ``**Status**: **X**`` all appear in
|
||||
#: the corpus. Tolerant on the label, strict on capturing the remainder.
|
||||
_STATUS_RE = re.compile(
|
||||
r"^[ \t]*(?:[-*][ \t]*)?\*\*Status\*{0,2}[ \t]*:?[ \t]*\*{0,2}[ \t]*:?[ \t]*(?P<value>.+?)[ \t]*$",
|
||||
re.MULTILINE,
|
||||
)
|
||||
_BOOL_FIELD_RE = re.compile(r"^\s*(?P<name>\w+)\s*:\s*bool\s*=\s*(?P<default>True|False)\b")
|
||||
_ADR_REF_RE = re.compile(r"ADR-(\d{4})")
|
||||
_RATIFY_ON_MERGE_RE = re.compile(r"ratify[- ]on[- ]merge|ratify on PR", re.IGNORECASE)
|
||||
|
||||
|
||||
def _adr_paths() -> dict[str, Path]:
|
||||
"""Map a four-digit ADR number to its file.
|
||||
|
||||
Numbers are not unique in principle (``ADR-0131.3`` exists), so the key is
|
||||
the leading four digits and the first match wins deterministically by name.
|
||||
"""
|
||||
out: dict[str, Path] = {}
|
||||
for path in sorted(ADR_DIR.glob("ADR-*.md")):
|
||||
match = re.match(r"ADR-(\d{4})", path.name)
|
||||
if match:
|
||||
out.setdefault(match.group(1), path)
|
||||
return out
|
||||
|
||||
|
||||
def _status_of(path: Path) -> str | None:
|
||||
"""Return the first status value in ``path``, or ``None`` if unparseable."""
|
||||
match = _STATUS_RE.search(path.read_text(encoding="utf-8"))
|
||||
if match is None:
|
||||
return None
|
||||
return match.group("value").strip().strip("*").strip()
|
||||
|
||||
|
||||
def _is_proposed(status: str) -> bool:
|
||||
return status.lower().lstrip("*").startswith("proposed")
|
||||
|
||||
|
||||
def _flag_adr_citations() -> dict[str, tuple[str, frozenset[str]]]:
|
||||
"""Derive ``flag -> (default, cited ADR numbers)`` from ``core/config.py``.
|
||||
|
||||
Derived, never restated. A hand-copied mapping here would be the very
|
||||
defect ADR-0256's own arc fixed in ``workbench/api.py``: a second copy of a
|
||||
closed set that falls behind the original in silence. A flag's comment
|
||||
block citing ``ADR-NNNN`` is read as a claim that the ADR governs the flag.
|
||||
"""
|
||||
lines = CONFIG_PATH.read_text(encoding="utf-8").splitlines()
|
||||
out: dict[str, tuple[str, frozenset[str]]] = {}
|
||||
for index, line in enumerate(lines):
|
||||
field = _BOOL_FIELD_RE.match(line)
|
||||
if field is None:
|
||||
continue
|
||||
comment: list[str] = []
|
||||
cursor = index - 1
|
||||
while cursor >= 0:
|
||||
stripped = lines[cursor].strip()
|
||||
if stripped.startswith("#"):
|
||||
comment.append(stripped)
|
||||
elif not stripped and not comment:
|
||||
pass # blank lines before the block start are skipped
|
||||
else:
|
||||
break
|
||||
cursor -= 1
|
||||
cited = frozenset(_ADR_REF_RE.findall("\n".join(comment)))
|
||||
out[field.group("name")] = (field.group("default"), cited)
|
||||
return out
|
||||
|
||||
|
||||
def test_config_flag_parse_is_not_vacuous() -> None:
|
||||
"""Guard the derivation itself.
|
||||
|
||||
If ``core/config.py`` is reformatted such that the field regex stops
|
||||
matching, every downstream assertion would pass on an empty set and this
|
||||
module would go quietly blind -- silence reading as success, which is the
|
||||
failure mode it exists to prevent.
|
||||
"""
|
||||
flags = _flag_adr_citations()
|
||||
assert len(flags) >= 25, f"expected the runtime config's bool flags; parsed only {len(flags)}"
|
||||
assert any(
|
||||
default == "True" for default, _ in flags.values()
|
||||
), "no default-True flag parsed — the field regex has drifted from core/config.py"
|
||||
assert any(
|
||||
cited for _, cited in flags.values()
|
||||
), "no flag cites an ADR — the comment-block walker has drifted"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("flag", "adr"),
|
||||
sorted(
|
||||
(flag, adr)
|
||||
for flag, (default, cited) in _flag_adr_citations().items()
|
||||
if default == "True"
|
||||
for adr in cited
|
||||
),
|
||||
)
|
||||
def test_default_on_flag_is_not_governed_by_a_proposed_adr(flag: str, adr: str) -> None:
|
||||
"""A flag that is ON by default is serving live traffic.
|
||||
|
||||
The ADR that governs it therefore records a decision already in force.
|
||||
Leaving it ``Proposed`` makes the governance record assert something false
|
||||
about the running system -- exactly the asymmetry ADR-0256's arc found in
|
||||
``workbench/api.py``, where the honest path degraded and the stale copy
|
||||
lied.
|
||||
|
||||
If this fails because a flag's comment cites an ADR only as background
|
||||
rather than as its governing decision, move the citation into prose outside
|
||||
the flag's comment block. Do not weaken the assertion.
|
||||
"""
|
||||
paths = _adr_paths()
|
||||
assert adr in paths, f"{flag} cites ADR-{adr}, which does not exist in docs/adr/"
|
||||
|
||||
status = _status_of(paths[adr])
|
||||
assert status is not None, (
|
||||
f"ADR-{adr} governs default-on flag {flag!r} but its status line is unparseable; "
|
||||
"a load-bearing ADR must declare a readable status"
|
||||
)
|
||||
assert not _is_proposed(status), (
|
||||
f"{flag} defaults to True (live serving) but ADR-{adr} is still {status!r}. "
|
||||
"Either stamp the ADR with the decision that put the flag on, or turn the flag off."
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("adr_path", sorted(ADR_DIR.glob("ADR-*.md")), ids=lambda p: p.name)
|
||||
def test_ratify_on_merge_predicate_is_discharged(adr_path: Path) -> None:
|
||||
"""An ADR whose status says "ratify on merge" cannot be merged AND Proposed.
|
||||
|
||||
ADR-0254 (``ratify on PR #103 merge``) and ADR-0256 (``ratify-on-merge``)
|
||||
both sat ``Proposed`` after the merges that were supposed to ratify them.
|
||||
The predicate is self-discharging: the file being present on ``main`` IS
|
||||
the merge having happened, so the two states are contradictory by
|
||||
construction and need no external bookkeeping to detect.
|
||||
"""
|
||||
status = _status_of(adr_path)
|
||||
if status is None or not _is_proposed(status):
|
||||
return
|
||||
assert not _RATIFY_ON_MERGE_RE.search(status), (
|
||||
f"{adr_path.name} is on main and still {status!r}. Its own status line makes the "
|
||||
"merge the ratifying act, so the merge already discharged it — stamp it Accepted "
|
||||
"and record which merge did so."
|
||||
)
|
||||
Loading…
Reference in a new issue