chore: migrate off the planning/ convention - #176
Merged
Conversation
Replaces the two-axis planning/ + architecture/ convention with PR-body-as-spec, CONTEXT.md for vocabulary, and docs/adr/ for rejected alternatives, per modern-python/.github#67. - planning/ deleted in full, 51 files. changes/ held 21 design records whose content is the diff each describes, and git history is that record; audits/ and retros/ recorded two audit arcs that shipped complete (3 criticals, 5 design issues, 7 refactors, 8 test gaps, 10 low-priority items across #89-#103 and #107; all 26 findings of the second audit across #108-#110), so nothing there is outstanding; specs/ and plans/ held one org-wide README standardization whose lite-bootstrap half is in README.md today. releases/ held ten notes: 1.4.0 and 1.3.0 diffed byte-identical against `gh release view` apart from a trailing newline GitHub adds, and 1.1.0 differs only where the local copy was edited after publication to chase a planning/ path rename — the Releases are the record either way. - deferred.md's three items plus two never-scheduled findings from the second audit's scope appendix are drafted as issue bodies in ../DRAFT-ISSUES-lite-bootstrap.md, each carrying its revisit trigger. Not opened on GitHub. - planning/decisions/ held four records; the previous, interrupted attempt at this migration had carried only two of them across. All four are now ADRs, frontmatter stripped: keep-per-instrument-axis is 0002, teardown-marker limits is 0003 (its "Update (1.4.0)" section kept as prose), orjson-opt-in is 0005, otlp-http-exporter-shape is 0006. None of the four declared a supersedes or superseded_by relationship, so none was lost in the strip. Four more ADRs rescue rejections that were buried in change files: 0001 (FastMCP teardown via a Provider lifespan rather than app._lifespan), 0004 (trace-exclusion stays one method), 0007 (the zero-dependency core was attempted and failed on pydantic's TypedDict rule under Python < 3.12), 0008 (the structlog->Sentry seam keeps its startswith heuristic rather than gaining a marker key). All eight keep a revisit trigger. - architecture/ carried five prose pages. Three claims in them were enforceable and are now INVARIANT tests; the rest was mechanism readable from lite_bootstrap/ and is deleted, not relocated. test_importing_lite_bootstrap_needs_only_the_stdlib_and_typing_extensions (new tests/test_core_import_surface.py) walks every module's top-level AST and fails on a third-party import outside an import_checker or TYPE_CHECKING guard — the invariant that broke four times and the one this library actually controls in the free-threading story. Verified: an unguarded `import orjson` in logging_factory.py fails it, and adding a new *guarded* optional import plus a new stdlib import leaves it green. test_every_framework_config_post_init_cascade_reaches_base_config (new tests/test_config_cascade.py) spies on BaseConfig's terminator while constructing each framework config. Verified against the exact bug that shipped once: dropping CorsConfig's super().__post_init__() call fails it for FastAPIConfig and LitestarConfig, and giving SwaggerConfig a new __post_init__ that does call super() leaves it green. test_only_the_silent_skip_path_lands_in_skipped_instruments (tests/test_free_bootstrap.py) pins the asymmetry CONTEXT.md defines "skipped" by. Verified: appending the dependency-missing case to skipped_instruments fails it, while adding another config-skipped instrument to FreeBootstrapper — the list legitimately growing — leaves it green. The free-threading page was judged to hold no enforceable claim of its own: its support matrix is a statement about upstream wheel availability, and its single-threaded-init section is a design stance, not a testable one. Its two live blockers are drafted as issues instead. - CONTEXT.md was written by the interrupted attempt and audited rather than trusted. Its "Skipped" entry claimed both skip paths appear in build_summary(); a dependency skip appears in neither list, and the entry is corrected — the same claim the new invariant test now holds. All five _Avoid_ entries were audited against real usage and all five earn their listing: "instrumentation" and "integration" appear only in their sanctioned senses (opentelemetry-instrumentation-* packages and the middleware named after them; docs/integrations/ and Sentry's own Integration objects), with one straggler — docs/introduction/quickstart.md said "production instrumentation" for what are mostly not instrumentation at all, now "production observability". That is a user-visible string on the docs site. - README.md's extras paragraph linked architecture/free-threading.md and claimed no mandatory runtime dependencies. Both are fixed: the free-threading facts a user needs are inlined, and core's one dependency is named. That paragraph is user-visible on PyPI, and the old claim is what ADR-0007 exists to correct. - AGENTS.md gains Workflow and Where-a-fact-goes and loses the planning lanes, the architecture/ promotion rule, and the capability index. The admission check applied to itself cut eight invariant bullets and four convention bullets that restated a docstring, a comment already at the site, pyproject.toml or the justfile — the frozen/non-frozen split Python enforces on its own, the slots-and-bare-super comment that sits three lines above the code it explains, the from_dict/from_object asymmetry both docstrings state. What survives is the three rules invisible in the code that obeys them, and the ty suppression spellings the justfile does not give. - mkdocs.yml excludes /adr/ from the published site, matching modern-di: an internal decision record is not user documentation, and mkdocs --strict fails on a docs/ file absent from nav. - justfile and lint-ci drop index and check-planning (there was no check-links recipe); the offline lychee gate in _checks.yml replaces them (modern-python/.github#66). Locally it went from 8 errors to 0 over 57 unique links — every one of the eight lived inside planning/, and deleting it is what made them green. Beyond the recipe, and the reason this is chore: and not docs: release.yml read the Release body from planning/releases/<tag>.md and hard-required one for every stable tag, so deleting planning/ would have failed every future stable release. It is now byte-identical to modern-di's post-migration workflow apart from the PyPI project name, using GitHub's generated notes; modern-di dropped the same gate in 1ea74ee (modern-python/modern-di#449). This retires the mandatory-curated-notes policy; a release wanting prose is edited after the fact with `gh release edit`. Verification: just lint-ci clean, just test 242 passed at 100% coverage, just docs-build strict clean, lychee --offline 0 errors, and no remaining reference to planning/, architecture/, check-planning, check-links or convention-version anywhere in the tree.
The sibling commit on this branch deletes planning/, so the exclusion now names a directory that does not exist. Dropping the key rather than leaving an empty list: Context7's schema (https://context7.com/schema/context7.json) declares no required properties and gives excludeFolders a default of [], so an absent key and an empty list are equivalent. Absent matches the repos that never carried the line.
21 tasks
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.
Migrates
lite-bootstrapoff theplanning/convention and onto PR-body-as-spec, withCONTEXT.mdowning the vocabulary anddocs/adr/holding rejected alternatives. Part of modern-python/.github#67.This branch also carries
chore(context7): drop the stale planning/ exclusion— thecontext7.jsonentry namingplanning/only becomes dead once this migration deletes the directory, so the two land together.Replaces the two-axis planning/ + architecture/ convention with PR-body-as-spec,
CONTEXT.md for vocabulary, and docs/adr/ for rejected alternatives, per
modern-python/.github#67.
content is the diff each describes, and git history is that record; audits/
and retros/ recorded two audit arcs that shipped complete (3 criticals, 5
design issues, 7 refactors, 8 test gaps, 10 low-priority items across fix: honor root_path in offline-docs redoc handler #89-refactor: rename OpentelemetryConfig → OpenTelemetryConfig; FreeBootstrapperConfig → FreeConfig #103
and refactor: replace InstrumentNotReadyWarning with is_configured classmethod + summary log #107; all 26 findings of the second audit across fix: lifecycle & teardown correctness (PR1 of bug-audit-v2) #108-chore: hygiene + CI gate (PR3 of bug-audit-v2) #110), so nothing
there is outstanding; specs/ and plans/ held one org-wide README
standardization whose lite-bootstrap half is in README.md today. releases/
held ten notes: 1.4.0 and 1.3.0 diffed byte-identical against
gh release viewapart from a trailing newline GitHub adds, and 1.1.0 differs only wherethe local copy was edited after publication to chase a planning/ path rename —
the Releases are the record either way.
audit's scope appendix are drafted as issue bodies in
../DRAFT-ISSUES-lite-bootstrap.md, each carrying its revisit trigger. Not
opened on GitHub.
this migration had carried only two of them across. All four are now ADRs,
frontmatter stripped: keep-per-instrument-axis is 0002, teardown-marker limits
is 0003 (its "Update (1.4.0)" section kept as prose), orjson-opt-in is 0005,
otlp-http-exporter-shape is 0006. None of the four declared a supersedes or
superseded_by relationship, so none was lost in the strip. Four more ADRs
rescue rejections that were buried in change files: 0001 (FastMCP teardown via
a Provider lifespan rather than app._lifespan), 0004 (trace-exclusion stays one
method), 0007 (the zero-dependency core was attempted and failed on pydantic's
TypedDict rule under Python < 3.12), 0008 (the structlog->Sentry seam keeps its
startswith heuristic rather than gaining a marker key). All eight keep a
revisit trigger.
and are now INVARIANT tests; the rest was mechanism readable from
lite_bootstrap/ and is deleted, not relocated.
test_importing_lite_bootstrap_needs_only_the_stdlib_and_typing_extensions
(new tests/test_core_import_surface.py) walks every module's top-level AST and
fails on a third-party import outside an import_checker or TYPE_CHECKING
guard — the invariant that broke four times and the one this library actually
controls in the free-threading story. Verified: an unguarded
import orjsonin logging_factory.py fails it, and adding a new guarded optional import plus
a new stdlib import leaves it green.
test_every_framework_config_post_init_cascade_reaches_base_config (new
tests/test_config_cascade.py) spies on BaseConfig's terminator while
constructing each framework config. Verified against the exact bug that
shipped once: dropping CorsConfig's super().post_init() call fails it for
FastAPIConfig and LitestarConfig, and giving SwaggerConfig a new post_init
that does call super() leaves it green.
test_only_the_silent_skip_path_lands_in_skipped_instruments
(tests/test_free_bootstrap.py) pins the asymmetry CONTEXT.md defines "skipped"
by. Verified: appending the dependency-missing case to skipped_instruments
fails it, while adding another config-skipped instrument to FreeBootstrapper —
the list legitimately growing — leaves it green.
The free-threading page was judged to hold no enforceable claim of its own: its
support matrix is a statement about upstream wheel availability, and its
single-threaded-init section is a design stance, not a testable one. Its two
live blockers are drafted as issues instead.
trusted. Its "Skipped" entry claimed both skip paths appear in
build_summary(); a dependency skip appears in neither list, and the entry is
corrected — the same claim the new invariant test now holds. All five Avoid
entries were audited against real usage and all five earn their listing:
"instrumentation" and "integration" appear only in their sanctioned senses
(opentelemetry-instrumentation-* packages and the middleware named after them;
docs/integrations/ and Sentry's own Integration objects), with one straggler —
docs/introduction/quickstart.md said "production instrumentation" for what are
mostly not instrumentation at all, now "production observability". That is a
user-visible string on the docs site.
no mandatory runtime dependencies. Both are fixed: the free-threading facts a
user needs are inlined, and core's one dependency is named. That paragraph is
user-visible on PyPI, and the old claim is what ADR-0007 exists to correct.
the architecture/ promotion rule, and the capability index. The admission check
applied to itself cut eight invariant bullets and four convention bullets that
restated a docstring, a comment already at the site, pyproject.toml or the
justfile — the frozen/non-frozen split Python enforces on its own, the
slots-and-bare-super comment that sits three lines above the code it explains,
the from_dict/from_object asymmetry both docstrings state. What survives is the
three rules invisible in the code that obeys them, and the ty suppression
spellings the justfile does not give.
internal decision record is not user documentation, and mkdocs --strict fails
on a docs/ file absent from nav.
recipe); the offline lychee gate in _checks.yml replaces them
(Add the offline link gate to the 18 green repos, and unpublish the ADRs in the two that have them .github#66). Locally it went from 8 errors to 0 over 57 unique
links — every one of the eight lived inside planning/, and deleting it is what
made them green.
Beyond the recipe, and the reason this is chore: and not docs: release.yml read
the Release body from planning/releases/.md and hard-required one for every
stable tag, so deleting planning/ would have failed every future stable release.
It is now byte-identical to modern-di's post-migration workflow apart from the
PyPI project name, using GitHub's generated notes; modern-di dropped the same
gate in 1ea74ee (modern-python/modern-di#449). This retires the
mandatory-curated-notes policy; a release wanting prose is edited after the fact
with
gh release edit.Verification: just lint-ci clean, just test 242 passed at 100% coverage, just
docs-build strict clean, lychee --offline 0 errors, and no remaining reference to
planning/, architecture/, check-planning, check-links or convention-version
anywhere in the tree.
The sibling commit on this branch deletes planning/, so the exclusion now
names a directory that does not exist.
Dropping the key rather than leaving an empty list: Context7's schema
(https://context7.com/schema/context7.json) declares no required properties
and gives excludeFolders a default of [], so an absent key and an empty list
are equivalent. Absent matches the repos that never carried the line.
Verification
just lint-ci— clean (eof-fixer, ruff format, ruff check, ty).lychee --offline --no-progress '**/*.md') — 0 errors.planning/,architecture/,check-planning,check-linksorconvention-versionreference remains.Note
release.ymlpreviously readplanning/releases/<tag>.md, both as a hard gate for stable tags and as the Release body source; deletingplanning/without changing it would have broken the next stable release. It now uses GitHub's generated notes, matchingmodern-dipost-modern-python/modern-di#449. This retires the mandatory-curated-notes policy.