Skip to content

docs: drop architecture/; invariants are tests, contracts are decisions - #424

Merged
lesnik512 merged 18 commits into
mainfrom
docs/drop-architecture-dir
Aug 13, 2026
Merged

docs: drop architecture/; invariants are tests, contracts are decisions#424
lesnik512 merged 18 commits into
mainfrom
docs/drop-architecture-dir

Conversation

@lesnik512

@lesnik512 lesnik512 commented Aug 13, 2026

Copy link
Copy Markdown
Member

Why

architecture/ was 1,470 lines across 11 pages that restated what modern_di/ already says, and the restatement is what rots.

It had been cut to invariants twice and regrew both times. b2404c4 (#282, 2026-07-07) removed 433 lines against 110 insertions across six pages, with the subject "trim architecture/ to charter — invariants only". Twenty-two days later 047b6ea (#395, 2026-07-29) removed another 292 against 147 insertions across four pages. Between them, 6b980b2 (#322) repaired a page that had gone stale against the code. By the time this branch started, resolution.md was back to 175 lines carrying full prose on entry points, wiring-plan buckets and thread safety — all derivable.

Promotion discipline was never the failure. 48 of the 67 commits touching modern_di/ since mid-June also touched architecture/ (72%, measured at ed9b00d). The checklist in .github/PULL_REQUEST_TEMPLATE.md asked "did you update the page?" and never "should this paragraph exist?" — so every PR added and none subtracted. A page that exists accumulates.

The churn confirms which half rots. resolution.md +617/−442 over 23 commits on a 175-line file; containers.md +451/−253; validation.md +474/−290. Against that, glossary.md is +93/−4 over 2 commits. Pages that restate mechanism churn; pages that fix vocabulary do not.

Design

Every fact now routes to exactly one of four homes:

Home Holds
modern_di/ anything readable from the module — the default
a named test an invariant: must stay true, and a change could silently break it
planning/decisions/ a rejected alternative, with the reasoning that would otherwise be re-litigated
docs/ anything a user needs

The admission check, now in planning/README.md and the PR template:

Can an agent get this by reading modern_di/? → don't write it.
Would a wrong change here fail a test? → it belongs in the test, not in prose.
Does a user need it? → docs/.
Otherwise it does not get written.

And the rule that was missing: prose about mechanism has no home. There is no file to add a paragraph to. The absence of the directory is the mechanism. The two failed thinnings are cited in planning/README.md so the next person to propose a capability page has the evidence in front of them.

The invariants were already executable. The plan assumed ~13 new tests were needed; an audit found nearly all of them already existed and were already well-named — test_arity_rung_front_guards_the_override, the five test_can_call_positionally_rejects_*, test_validate_is_free_when_already_validated, test_scope_algebra_answers_deeper_members_for_any_int_enum, and more. The work became marking, not writing: 31 tests now carry an INVARIANT: docstring, and only four tests are genuinely new (one-graph, compile-resolver-fails-loudly, scope.py import purity, union-member origin degradation).

An INVARIANT: docstring is line 1 INVARIANT: <claim>., blank line, then a paragraph naming what breaks it — where the anti-refactor warnings now live:

def test_resolve_costs_exactly_one_resolver_frame_per_node() -> None:
    """INVARIANT: resolving one node costs exactly one Python frame -- its own compiled resolver.

    Extracting the override guard, the scope hop, the kwargs build or the creator call into a
    shared helper costs one frame *per resolved node* and moves the slope from 2 to 3.
    """

That paragraph states what breaks the invariant, not necessarily a regression that one test alone would catch — the unit of truth is the invariant plus the suite. The accepted cost is recorded in planning/README.md: a reader cannot tell from one docstring whether that test or a sibling catches a given regression.

tests/test_invariant_census.py guards the scheme, on the existing test_docs_slug_census.py precedent: every test_* name cited in a comment or docstring under modern_di/ and tests/ must resolve to a real test; every INVARIANT: docstring must have a "what breaks it" paragraph; and every tests/….py / planning/….md path cited from code, plus every test name cited from CLAUDE.md and planning/decisions|deferred/, must resolve.

Code comments cite tests, not pages. The ~16 comments reading see architecture/performance.md now read see test_resolve_costs_exactly_one_resolver_frame_per_node. A pointer to a test is verifiable; a pointer to prose is not.

Vocabulary moved to CLAUDE.md, which is auto-loaded where architecture/README.md never was. A term earns a place when it has a synonym to reject, or a meaning subtle enough that code and docs must agree on it.

Where each page went

Page Restatement (deleted) Residue
resolution.md entry points, wiring buckets, thread safety 8 invariants → tests
validation.md what-validate-checks, exception table _validated memoizes; graph-walked-is-graph-resolved
containers.md ctor/registry tables, close/open mechanics no closed-check on parent; warns at least once
providers.md Group/Factory/CacheSettings tables rebinding an in-use provider — dropped, see non-goals
performance.md measurement runbook (already in benchmarks/README.md) frame budget, arity ladder, no-cell-on-warm-path
scopes.md resolution rule, find_container steps scope.py imports only enum
concurrency.md lock model phase contract → tests; Beta-not-Stable → docs/
integration-kit.md API description per-adapter boundary — dropped, see non-goals
testing-and-overrides.md override API; modern-di-pytest (already in docs/) override scope short-circuit → inline comment, tests/providers/test_factory.py:185
glossary.md 10 terms → CLAUDE.md
README.md the boundary + one-owner rules → planning/README.md

Non-goals

  • Not a code change. No behaviour in modern_di/ moves; only comments change there. The four new tests assert what already shipped.

  • Not deleting docs/. The architecture/docs/ altitude split ends because one side of it ends.

  • Not reshaping decisions/ or deferred/. Frontmatter, naming and index.py are unchanged. deferred/2026-07-29-upstream-lean-convention.md is updated — its central finding rested on architecture/ promotion working — but deliberately not resolved; its revisit trigger stands.

  • Not adding any planning/decisions/ record. Six were written during this work — transient teardown order, rebinding an in-use provider, override values not being type-checked, the integration-kit per-adapter boundary, the free-threaded Beta support level, and a record of this change itself — and all six were then dropped as not worth their weight. The free-threaded contract survives at user altitude in docs/introduction/design-decisions.md; the rest now live only in the code and its tests, or nowhere. planning/decisions/ is unchanged by this branch apart from four repointed links.

  • Not migrating the 13 sibling integration repos. docs/integrations/writing-integrations.md now tells adapter authors to keep invariants in named tests rather than a prose truth home; each repo decides its own layout on its own schedule. Nothing here tells them what to do with the architecture/ directories they have today.

  • Not editing published GitHub Release bodies. The committed releases/*.md links are repointed from blob/main/ to blob/<version>/, verified to resolve against the tree as it stood; the live Release pages keep their original text.

  • The census is name and path integrity, not truth. It cannot check that a comment cites the right test, nor that a rationale paragraph is true. Semantic drift is caught by the invariant test failing.

Verification

  • just test-ci509 passed, 100.00% line coverage.
  • just lint-ci — ruff, ty, planning/index.py --check, planning/links.py: all clean.
  • just check-linkslinks: OK. This is the completeness proof: those links resolve while architecture/ exists and only break once it is gone, so a green run after the deletion means every reference was rewritten or dropped.
  • just docs-buildmkdocs build --strict, clean.
  • grep -rn "architecture/" returns only tag-pinned release URLs (verified with git cat-file -e 2.28.0:architecture/integration-kit.md and 2.30.0:architecture/concurrency.md), past-tense historical prose in planning/, and planning/links.py's module docstring — deliberately untouched, since that file is vendored into consumer repos and its docstring describes the general convention.

No performance claim; nothing on the resolve path changes.

Size: 17 commits, 49 files, +651 / −1,630 (net −979). The 1,470-line figure is the gross size of the deleted directory.

Deliberate deviations from the original plan

  • planning/links.py untouched — vendored into consumer repos; editing it widens the fork for no gain.
  • No new docs/introduction/concurrency.md and no mkdocs.yml change — design-decisions.md §2 already owned thread safety.
  • Four test_container.py::test_*_never_validates tests marked that the plan's map had missed; without them architecture/validation.md:9 would have survived nowhere.
  • The census was widened beyond the plan, twice, after review found citation forms it could not guard.

Adds INVARIANT: docstrings to the tests that already pin the override,
navigation, positional-path, live-context, validation, scope-algebra and
container-lifecycle claims formerly stated in architecture/. Converts
leading # comments that carried the same rationale into the docstring;
comments explaining a test's own scope or limitations are left in place.
… their real tests

Two INVARIANT docstrings overclaimed what their test proves: the override
guard test implied it exercises an otherwise-unwireable factory (it doesn't
- test_unwireable_factory_override_short_circuits does), and the scope-hop
test implied it catches a skipped-navigation regression (its own retained
comment already said it doesn't - two sibling tests do). Reword both to
state only what each test proves and cite the test that proves the rest.
…roves

The "never gates whether validation may run" clause wasn't exercised by
this test (it only shows a repeat validate() skips the re-walk) and read
as contradicting is_validated(): return, which does gate the re-walk.
Keep only the memoization claim here and attribute the never-auto-
triggers half to the four test_container.py tests that actually prove it.
architecture/validation.md states that validate() is the only thing that
walks the graph - construction, open(), add_providers and resolve() never
do. That invariant was missing from the original map and would otherwise
survive nowhere once the page is deleted. The four tests already enforce
it; mark each with its own INVARIANT docstring naming only the entry
point that test exercises.
Replaces every architecture/*.md pointer in modern_di/ and tests/ with the
name of the test that actually enforces the claim, or a self-contained
sentence where no test covers it. Widens test_invariant_census.py to scan
docstrings as well as comments, and both modern_di/ and tests/, so the new
citations (and the ~7 hand-verified ones from Task 2) are guarded against
rename rot.
Applies the fix wave from the branch's final whole-branch review: gives
the union-member-origin-degradation contract a home (INVARIANT docstring
+ docs correction), fixes two planning/decisions cross-references that
misroute readers, restores the glossary charter's subtle-meaning clause
in CLAUDE.md, closes three citation-path gaps in the invariant census
(module paths, planning-record paths, and Markdown-cited test names),
names decisions/ and INVARIANT: docstrings as accumulation surfaces that
need their own subtraction habit, and applies four minor cleanups
(deduplicated docstrings, a redundant clause, "since removed" phrasing,
and the positional-fast-path measurement rationale).
…nvariant

planning/README.md understated test_invariant_census.py after the final fix
wave widened its citation scope beyond modern_di/ comments; the sentence now
names all four sources it checks.

The union-degradation contract lived as an INVARIANT docstring on
test_signature_item_parser, a 12-row parametrized table whose name is not the
claim -- editing rows :30/:32 could silently orphan it. Extracted a dedicated
test_union_member_degrades_to_bare_origin asserting the claim directly and
moved the docstring there; the two table rows stay, since the table's job is
general parser regression coverage, not hosting this one invariant. Updated
the mechanism comment in types_parser.py to cite the new test.
Keeps only the free-threaded Beta record, which is cited from
tests/test_free_threading.py and guarded by the census.

Fixes the three links that would have dangled, and corrects the two
thinning-commit figures in planning/README.md, which still labelled
gross deletions as net cuts (433 deletions against 110 insertions
across six pages; 292 against 147 across four).
The Beta support level and the thread-safety boundary it stated now live
at user altitude in docs/introduction/design-decisions.md; the four
references repoint there.

planning/decisions/ gains nothing from this branch.
…heck

Nothing on this branch exercised it, and decisions/ took no records.
decisions/ now holds rejected alternatives only, stated the same way in
the four-homes table, the admission check, and the PR template.
@lesnik512
lesnik512 merged commit 911d7ea into main Aug 13, 2026
9 checks passed
@lesnik512
lesnik512 deleted the docs/drop-architecture-dir branch August 13, 2026 18:48
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.

1 participant