chore: adopt the modern-di convention; drop planning/ and architecture/ - #159
Merged
Conversation
The spec for a change is now its PR body. .github/PULL_REQUEST_TEMPLATE.md carries the shape and is reviewed with the diff, so there is no change file to write and no lane to pick. Two things outlive the PR and have exactly one home each: a rejected alternative becomes an ADR under docs/adr/, and unscheduled work becomes a GitHub issue. architecture/ was 12 capability pages restating what faststream_outbox/ already says. Its invariants turned out to be already executable: 32 existing tests now carry an INVARIANT: docstring -- the claim on line 1, what breaks it in the second paragraph -- and only one test is genuinely new. Engine ownership was asserted in CLAUDE.md and pinned by nothing, so a broker that disposed the caller's engine would have passed the whole suite. tests/test_invariant_census.py guards the scheme: every INVARIANT: docstring must state what breaks it, and every path::test_name citation must resolve to a real test. Code comments cite tests, not pages. planning/ took nothing forward but its five decision records, which became docs/adr/0001-0005. planning/lint-suppressions.md was a rationale register with no home under the admission check; its recurring half -- everything downstream of BrokerUsecase's invariance on its config type -- is now ADR-0006, and the rest is derivable from the suppression sites. The audits were all closed (PRs #61-#95). The one genuinely open deferred item, the OutboxRouter routers forwarding gap, is now #158. CONTEXT.md is the glossary docs/agents/domain.md had been promising: 16 terms, each listed only because there is a synonym to reject. Curated release notes go with the planning tree -- release.yml falls back to GitHub's generated notes, rendered from the squashed PR titles since the previous tag. Closes #158 is deliberately not written here: that issue records work not being done in this PR.
Benchmark gate✅ gate passed
Gated (fails the build): |
lesnik512
commented
Sep 5, 2026
…ites Review pass on the six ADRs the migration produced. Four are gone. The doc-code-fence record declined a CI feature that was never built, with a revisit trigger amounting to "if it starts hurting". The conn-union record's contract already lives in AbstractOutboxClient's docstring. The free-threading pair was one decision split across two files, and the second mostly restated docs/introduction/installation.md, which already carries everything a user needs -- they merge into one record holding only the declined options. The BrokerUsecase-invariance record is replaced by a one-line comment at each of the four suppression sites, which is where the cause belongs; the two invalid-method-override suppressions get their own note, since the outbox's added publish kwargs are a deliberate divergence rather than the same variance rule. The census keeps the INVARIANT: docstring-shape check and drops the citation check. It guarded a single citation, and its scan covered only .py files, so the one citation that mattered -- CLAUDE.md's -- was the one it missed.
A revisit trigger guesses at the condition that would reopen a decision, and a guess that never fires is noise on every read. The reasoning that settled the call is the durable part and it stays; docs/agents/domain.md now says to argue against that reasoning rather than to check whether a predicted trigger fired. CLAUDE.md is 124 -> 106 lines, cut by running the admission check over itself. Gone: the architecture summary (CONTEXT.md's opening paragraph already says it), the _scheduling/_time bullet (readable from two stdlib-only leaves), the Outcome sum-type description (readable from message.py -- only the DLQFailureReason public contract survives), the no-state-column restatement, and the parts of Code Style that restate pyproject.toml: ruff's select/ignore, the coverage threshold, and PLC0415, which select = ["ALL"] already enforces. What is left of the import rule is the part the linter cannot say -- that a noqa is the wrong fix.
The free-threading ADR's trigger is scheduled work with an external dependency, not a prediction: DISABLE_SQLALCHEMY_CEXT_RUNTIME=1 comes out of CI and the docs when SQLAlchemy's Cython extensions declare Py_MOD_GIL_NOT_USED. Verified still required on CPython 3.14.7t against SQLAlchemy 2.0.52 -- importing sqlalchemy re-enables the GIL, and the variable is what keeps it off.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Two documentation trees carried the cost of every change and neither paid it back.
architecture/was 12 capability pages, 612 lines, restating whatfaststream_outbox/alreadysays. The promotion rule — "update the matching capability file in the same PR" — asked did you
update the page? and never should this paragraph exist?, so pages only ever grew.
planning/was 63 files: 34 change files, 13 curated release notes, 6 closed audit reports, 5decision records, 5 templates, and a validator wired into
lint-ci. Of that, only some of thedecision records and one deferred item were still load-bearing. Every audit was fully closed (PRs
#61–#95). The change files duplicated what the PRs they described already recorded.
CONTEXT.mddid not exist, thoughdocs/agents/domain.mdhad been pointing at it as "not yetcreated" for months, so the glossary rule it stated was unenforceable.
Design
Adopt the convention
modern-diarrived at over PRs #424–#450.The spec for a change is its PR body.
.github/PULL_REQUEST_TEMPLATE.mdcarries why / design /non-goals / verification and is reviewed with the diff. No change file, no lane. A trivial PR
deletes the template and ships a conventional-commit title.
Two things outlive the PR, one home each: a rejected alternative becomes an ADR in
docs/adr/(
NNNN-slug.md, with a revisit trigger); unscheduled work becomes a GitHub issue.CLAUDE.mdnowopens with the admission check that decides where a fact goes at all — derivable from the package →
don't write it; enforceable → a test; a user needs it →
docs/; otherwise it does not get written.Mechanism moves into the tests that already pinned it. The invariants turned out to be
executable already: 32 existing tests now carry an
INVARIANT:docstring — the claim on line 1,what breaks it in the second paragraph — spanning all 11 capabilities the old
CLAUDE.mdtablelisted. This was marking, not writing.
tests/test_invariant_census.pyguards the shape: anINVARIANT:docstring without a "what breaks it" paragraph fails.One test is genuinely new. Engine ownership — "the caller owns the
AsyncEngine; the broker neverdisposes it" — was asserted in
CLAUDE.mdand pinned by nothing, and every other test constructs adedicated engine, so a broker that disposed it would have passed the whole suite.
The ADR corpus is two records, not five. The migration first carried all five
planning/decisions/forward; review cut it back. The doc-code-fence record declined a CI feature that was never built.
The
conn-union record's contract already lives inAbstractOutboxClient's docstring, which iswhere a contract belongs. The two free-threading records were one decision split in two, and the
second largely restated
docs/introduction/installation.md— they merge into one holding only thedeclined options. What survives is the metrics-recorder record (trimmed) and free-threading.
The
BrokerUsecase-invariance rationale thatplanning/lint-suppressions.mdcarried is now aone-line comment at each of the four suppression sites, plus a separate note on the two
invalid-method-overridesuppressions, which are a deliberate signature divergence rather than thesame variance rule.
CONTEXT.mdis written: 16 terms, each listed only because there is a synonym to reject.Curated release notes go with the planning tree.
release.ymldrops the mandatory-notes gate andfalls back to GitHub's generated notes, rendered from the squashed PR titles since the previous tag
— which is what makes the conventional-commit title on a trivial PR carry its weight.
Non-goals
faststream_outbox/edits are five comments atexisting suppression sites; no logic moves.
docs_slug/docs/troubleshooting/scheme. That rests on exceptionsowning every message, which this package does not do.
CLAUDE.mdtoAGENTS.md. Worth doing, but org-wide rather than here alone —tracked separately.
.githubrepo. That repo is still on convention 2.0.0,so inheriting its template would contradict this one. Tracked separately.
OutboxRouterroutersgap. It was the only genuinely open item inplanning/deferred.mdand is now OutboxRouter does not forwardroutersto the inner broker #158 — it needs a design call on lifespan-start ordering andAsyncAPI composition, not a kwarg passthrough.
Verification
just test→ 612 passed, 100.00% coverage (the gate).just lint-ciclean —ruff format,ruff check --no-fix,ty.just docs-buildclean under--strict, withdocs/adr/built so itsoutgoing links are validated and
not_in_navkeeping it out of the site menu.A repo-wide sweep for
planning/,architecture/,check-planning,just index,deferred.md,and
lint-suppressionsreturns one hit: the sentence indocs/agents/domain.mdthat says neitherdirectory exists any more.
mkdocs --strictpassing after the deletion is the completeness prooffor the docs side — those references resolve while the directories exist and only break once they
are gone.