Skip to content

docs(adr): migrate planning/decisions to native ADRs in docs/adr (29 → 26) - #433

Merged
lesnik512 merged 2 commits into
mainfrom
docs/adr-migration
Aug 30, 2026
Merged

docs(adr): migrate planning/decisions to native ADRs in docs/adr (29 → 26)#433
lesnik512 merged 2 commits into
mainfrom
docs/adr-migration

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Closes #430.

Why

29 decision records sat in planning/decisions/ behind a vendored validator and a
local deviation from an external planning convention. Three problems had
accumulated:

  • Not native. The engineering skills expect ADRs at docs/adr/ with sequential
    NNNN-slug.md names. Dated slugs, custom frontmatter and a fixed four-section
    body are understood by nothing outside this repo.
  • Rotted in specific ways. One record was factually false against main. One
    answered a question closed twice over. Three leaned on evidence deleted with
    architecture/ in docs: drop architecture/; invariants are tests, contracts are decisions #424. One was arithmetic over two neighbours.
  • Not lean. 1843 lines, much of it prose restating what the code says.
    planning/README.md already named this as owed work: "nothing yet prunes a
    record once its call is settled".

Design

29 records become 26 ADRs at docs/adr/NNNN-slug.md, numbered chronologically by
the original's date, slug kept minus the date prefix, summary: frontmatter
dropped (its only consumer was the deleted renderer).

Compression rule. Keep the decision in one sentence, every measured quantity,
the specific reason each alternative was rejected, and the revisit trigger. Cut
prose restating the code, background re-derived from a linked doc, and narrative
transitions. Three representative records were compressed first and signed off
before the other 23. Result: 1843 → 794 lines. Nine records carry benchmark deltas
and keep their numbers verbatim; the two -inherent records compress lightly,
because their reasoning is the payload.

Two drops. signatureitem-opacity-superseded had no measurement and its
question closed twice over — the candidate's substance shipped as wiring.py's
find_dep_provider and absent_disposition in #308, which the record itself
states. Its one durable claim, that types_parser must not import
ProvidersRegistry, is visible in that module's import block and stated in
CLAUDE.md. Accepted cost: nothing enforces the rule now.
contextprovider-resolver-inline-declined was verified false against main — its
Decision sentence says the resolver "keeps delegating to the bound
ContextProvider.resolve", a method #425 deleted. The concern behind the decline
is structurally impossible now that ProviderScopeFrozenError raises on the
mutation it feared.

One merge. d1-d4-derived-inherent states outright that it has no independent
revisit trigger; it is arithmetic over its two neighbours. Its D1 half lands in
0020, its D4 line-count argument in 0021. Those two stay separate: each carries
a distinct per-integration table and a distinct trigger (a framework gaining a
lifecycle hook, versus a per-parameter provider hook).

scope-map-inline-declined and resolve-provider-not-a-seam are deliberately
not merged despite arguing one question. Both are measured, container.py cites
the latter by path, and the latter explicitly states it does not supersede the
former — merging would flatten two opposite answers into one.

Three re-anchors, all citing evidence deleted with architecture/ in #424.
0011 re-anchors the registry-symmetry argument on CLAUDE.md's registries entry;
0015 strikes the "concurrency.md was kept" claim; 0003 strikes a claimed
enter_scope/CreateScope docs mapping that grep confirms never existed (adding
that vocabulary row is out of scope).

MkDocs. not_in_nav: | /adr/. ADRs build and their links are validated, but
they stay out of the site menu. exclude_docs was rejected: an excluded file's
outgoing links get no validation at all. Residual accepted cost — the 26 pages get
real site URLs and enter sitemap.xml, acceptable since the repo is public and the
records are already GitHub-indexable.

Links. Sibling ADRs link as bare NNNN-slug.md; links formerly reaching
../../docs/ become ../. No ADR links to a path outside docs/
modern_di/... and tests/... stay inline code.

Teardown. planning/index.py, _templates/decision.md, _templates/deferred.md
and .convention-version are deleted, along with the index and check-planning
recipes and the validator line in lint-ci. planning/README.md is rewritten
around what remains. links.py, releases/, scripts/ and _templates/release.md
stay. tests/test_invariant_census.py keeps the INVARIANT: shape check and loses
the two citation checks, with every helper and constant only they used.

Non-goals

  • Re-deciding anything. 0011's own trigger has arguably fired, since the
    framing it rested on was abandoned. Re-anchoring its prose is in scope;
    re-litigating the call is not.
  • planning/deferred/. It stays as-is; Migrate deferred items to GitHub Issues and retire the deferred concept #431 migrates it to Issues and retires
    the concept. Its two links into decisions/ are repointed here so check-links
    stays green.
  • New user docs. The enter_scope vocabulary row is not written.
  • An INVARIANT: test for the types_parser layering rule. Considered and
    rejected: it cuts against removing the census's citation checks in the same PR.
  • pyproject.toml's SLF001 comment. It names a planning/deferred/ file that
    still exists, so the reference resolves; Migrate deferred items to GitHub Issues and retire the deferred concept #431 repoints it.

One deviation from the issue's stated non-goals: planning/scripts/perf-readability-audit.workflow.mjs
drove its agents at planning/decisions/, so this PR repoints it at docs/adr/
rather than ship a harness aimed at a deleted directory.

Verification

No new test seams — a good test here checks that links resolve and the build is
clean, not an ADR's content.

  • just check-links — the primary gate, and exactly what this migration risks:
    every relative link and heading anchor in the repo, including trees MkDocs never
    sees. OK.
  • just docs-build (mkdocs build --strict) — covers the 26 newly built pages.
    Clean, no warnings; all 26 present in site/adr/.
  • just test-ci512 passed, 100% line coverage. The census trim is the only
    Python change; the coverage risk was dead helpers, and none remain.
  • just lint-ciclean, with the deleted validator line removed in the same
    commit.

Three published Releases whose bodies linked into planning/decisions/ were
re-pushed from their updated in-repo notes: 2.29.0 and 3.4.0 repoint at 0013,
3.3.0 gets an absolute permalink to 0026 (its relative link was already broken
in a Release body), and 3.2.0's inline-code mention becomes a link to 0025.
Neither dropped record is linked from a Release.


https://claude.ai/code/session_01FBic7HFGR6sdRfSW3cGEv2

Move the 29 decision records to docs/adr/NNNN-slug.md, dropping two,
merging one, re-anchoring three whose evidence was deleted with
architecture/, and compressing the rest (1843 -> 794 lines). Delete the
planning tooling the old format required: index.py, the decision and
deferred templates, .convention-version, and the index/check-planning
recipes.

ADRs are built by MkDocs but kept out of the nav with not_in_nav, so
relative .md links between them resolve on GitHub and on the site.

Claude-Session: https://claude.ai/code/session_01FBic7HFGR6sdRfSW3cGEv2
Review found three numbers cut that the compression rule says to keep:
0015's peer baseline (292 ns warm hit against dishka ~245, wireup ~98,
that-depends ~85, dependency-injector ~61) and the inconsistent dishka
parenthetical it left behind, and 0016's RLock range plus the
_next_deeper figure the remaining cost is measured against. Repoint the
perf-readability audit harness at docs/adr/, and trim rationale out of
the census docstring.

Claude-Session: https://claude.ai/code/session_01FBic7HFGR6sdRfSW3cGEv2

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Details
Benchmark suite Current: f22d035 Previous: fa77633 Ratio
benchmarks/test_guard_by_type.py::test_g16_resolve_by_type 3261886.812979408 iter/sec (stddev: 1.3918226581085048e-8) 3942022.934212653 iter/sec (stddev: 2.309896453466658e-8) 1.21
benchmarks/test_guard_by_type.py::test_g17_resolve_by_type_large_registry 3321822.774497667 iter/sec (stddev: 9.207945296853416e-9) 4112858.8201955766 iter/sec (stddev: 1.886225647098389e-8) 1.24
benchmarks/test_guard_cold.py::test_g8_cold_first_resolve 24680.113639914918 iter/sec (stddev: 0.000002920347977768684) 24739.894068814585 iter/sec (stddev: 0.0000038121083324387136) 1.00
benchmarks/test_guard_cold.py::test_g8b_cold_first_resolve_cached 18940.995016473902 iter/sec (stddev: 0.0000023793833158448368) 18764.294714550128 iter/sec (stddev: 0.000003916044858254767) 0.99
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[1] 451.1773176789072 iter/sec (stddev: 0.0000752061327658595) 592.5861318844157 iter/sec (stddev: 0.00006296074278938853) 1.31
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[2] 420.7768496163303 iter/sec (stddev: 0.0000946635753964873) 519.5251569691909 iter/sec (stddev: 0.00021040745177117575) 1.23
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[4] 399.5290310698681 iter/sec (stddev: 0.00005296048510436462) 414.4855560888166 iter/sec (stddev: 0.0002448265343448415) 1.04
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[1] 2404.796992850763 iter/sec (stddev: 0.000024752181869674966) 2329.574674603042 iter/sec (stddev: 0.0000290802385199261) 0.97
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[2] 1803.3398394831652 iter/sec (stddev: 0.0001720133562202577) 1723.1921312835866 iter/sec (stddev: 0.00022062647213172504) 0.96
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[4] 1270.9805795230345 iter/sec (stddev: 0.000050555589927191454) 1165.8087528642104 iter/sec (stddev: 0.00005994027387913142) 0.92
benchmarks/test_guard_lifecycle.py::test_g6_build_child_container 781195.575690802 iter/sec (stddev: 4.1209975655412064e-8) 804962.4648029776 iter/sec (stddev: 6.02847416669573e-8) 1.03
benchmarks/test_guard_lifecycle.py::test_g6b_build_child_container_auto_scope 715973.1222974593 iter/sec (stddev: 3.8639161457844086e-8) 765504.631275264 iter/sec (stddev: 5.6068887888367584e-8) 1.07
benchmarks/test_guard_lifecycle.py::test_g7_request_lifecycle_batch 2525.186780444069 iter/sec (stddev: 0.000009106347758657456) 2440.6134899406984 iter/sec (stddev: 0.00001651544678920396) 0.97
benchmarks/test_guard_lifecycle.py::test_g7c_event_loop_floor_control 72993.22821921961 iter/sec (stddev: 8.677039709060992e-7) 77281.7756172458 iter/sec (stddev: 0.0000012988863747743572) 1.06
benchmarks/test_guard_lifecycle.py::test_g13_teardown_at_scale 50759.23813555624 iter/sec (stddev: 0.0000013177735223810996) 48944.95290380992 iter/sec (stddev: 0.0000016124198528796291) 0.96
benchmarks/test_guard_resolve.py::test_g1_transient_resolve 2176983.299815358 iter/sec (stddev: 2.3152409209855863e-8) 2664231.736467139 iter/sec (stddev: 3.28428551600116e-8) 1.22
benchmarks/test_guard_resolve.py::test_g2_cached_resolve 3655493.8325392148 iter/sec (stddev: 4.747250128479804e-9) 4879073.147842481 iter/sec (stddev: 1.060605836657873e-8) 1.33
benchmarks/test_guard_resolve.py::test_g3_deep_chain 826515.4931256069 iter/sec (stddev: 3.402227905552501e-8) 985461.6080776677 iter/sec (stddev: 5.084532450165497e-8) 1.19
benchmarks/test_guard_resolve.py::test_g4_wide_resolve 461254.6976485525 iter/sec (stddev: 7.147293863538686e-8) 489556.6430625755 iter/sec (stddev: 9.67413862880382e-8) 1.06
benchmarks/test_guard_resolve.py::test_g5_cross_scope 1798344.3632518398 iter/sec (stddev: 5.2629156238679125e-8) 2068208.0538772766 iter/sec (stddev: 3.100820300387718e-8) 1.15
benchmarks/test_guard_resolve.py::test_g9_context_resolve 942042.7731219333 iter/sec (stddev: 2.8784809725539843e-8) 1033386.7361755 iter/sec (stddev: 4.2539899561253e-8) 1.10
benchmarks/test_guard_resolve.py::test_g12_override_active_resolve 577845.1789213789 iter/sec (stddev: 5.601839133982075e-8) 668808.5259711052 iter/sec (stddev: 7.036121748714989e-8) 1.16
benchmarks/test_guard_resolve.py::test_g18_alias_hop 2483019.252091839 iter/sec (stddev: 8.72030257067565e-9) 3112637.770022544 iter/sec (stddev: 2.3720897620340684e-8) 1.25
benchmarks/test_guard_validate.py::test_g10_validate_deep_chain 29323.420713842526 iter/sec (stddev: 0.0000035613303755741256) 27672.02299243562 iter/sec (stddev: 0.000002642271864823158) 0.94
benchmarks/test_guard_validate.py::test_g11_validate_wide 17892.254382846197 iter/sec (stddev: 0.0000025111698978075787) 16725.92308110814 iter/sec (stddev: 0.000004104413783651893) 0.93

This comment was automatically generated by workflow using github-action-benchmark.

@lesnik512
lesnik512 merged commit fa0c1f2 into main Aug 30, 2026
9 checks passed
@lesnik512
lesnik512 deleted the docs/adr-migration branch August 30, 2026 15:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate planning/decisions/ to native ADRs in docs/adr/ (29 → 26)

1 participant