chore: migrate off the planning/ convention - #37
Merged
Conversation
planning/ held 21 files: the portable convention README, five templates, five
change files, one decision record, an empty deferred.md, index.py, and three
release notes. Nothing is lost. Git history is the record for the change files;
the prose in them that outlives a PR has moved — the decision record and one
rescued rejected alternative are now ADRs, the enforceable claims are now tests,
the vocabulary is CONTEXT.md. planning/releases/{0.4.3,0.5.0,0.5.1}.md were each
diffed against `gh release view <tag>` and are identical bar a trailing newline
GitHub adds, so the published Releases are the record.
architecture/ held eight pages: a README, a glossary, and six capability pages.
The capability prose is mechanism a reader gets from db_retry/ directly and is
deleted; glossary.md seeded CONTEXT.md.
Decisions
- planning/decisions/2026-06-27-retry-default-stays-env-configured.md becomes
docs/adr/0001-retry-default-stays-env-configured.md, frontmatter stripped.
- Rescued from the non-goals of
planning/changes/2026-06-26.01-retriable-error-seam.md:
docs/adr/0002-retriable-taxonomy-is-asyncpg-classes.md — the taxonomy stays a
tuple of asyncpg classes rather than SQLSTATE data. Its only home was
architecture/retriable.md, which this commit deletes.
Invariants
Five enforceable claims became tests whose name is the claim. Each was verified
by breaking it in db_retry/, confirming red, restoring, confirming green, and
confirming a benign narrowing stays green:
- test_a_statement_of_unknown_outcome_is_never_retriable — red when
RETRIABLE_ASYNCPG_ERRORS widens to asyncpg.TransactionRollbackError; green when
a sibling (DeadlockDetectedError) is added instead, and when
PostgresConnectionError is narrowed to a subclass.
- test_the_default_attempt_count_is_re_read_on_every_call — red when
settings.get_retries_number() is hoisted into the decorator body.
- test_a_retry_never_re_runs_the_body_immediately — red under wait_none(); green
when the curve is retuned (initial, exp_base, jitter).
- test_use_replica_overrides_a_target_session_attrs_already_in_the_dsn — red when
the dict union in build_db_dsn is flipped; green under an equivalent
{**query, ...} rewrite.
- test_host_and_port_stay_paired_through_the_shuffle — 0/12 runs passed both when
host and port are shuffled independently and when failover derives from a
second shuffle; 12/12 green when the shuffle is dropped entirely. Six hosts
with distinct ports, so an accidental in-step shuffle cannot mask a mis-pairing
more than once in 720 runs.
The pairing assertion moved out of test_build_connection_plan_multihost, which
gave both hosts port 5432 — a swap there is unobservable.
architecture/retriable.md gave StatementCompletionUnknownError as SQLSTATE 40002.
It is 40003; 40002 is TransactionIntegrityConstraintViolationError. The invariant
test carries the correct code, and the same wrong class name in
tests/test_retry.py's parametrize comment is corrected.
Glossary audit
The single glossary entry, "Retriable error", rejected two synonyms.
"recoverable error" appears nowhere outside the glossary itself and is dropped.
"transient error" appears twice, both times in README.md and both user-visible
strings; both are reworded to "retriable", which is what earns the entry its
listing. Two terms are added on the subtlety clause: "Attempt" (retries=N is
stop_after_attempt(N), so retries=1 never retries) and "Primary host" (a position
in the two-stage connect, not a PostgreSQL replication role).
Release policy
.github/workflows/release.yml no longer reads planning/releases/<tag>.md. This
retires the curated-release-notes policy: a Release body is now always GitHub's
generated notes, edited after the fact with `gh release edit --notes-file` when a
release wants prose. The file is now identical to modern-di's, which dropped the
same gate in 1ea74ee (modern-python/modern-di#449).
justfile drops the index and check-planning recipes and the planning/index.py
line from lint-ci; .github/workflows/_checks.yml gains the offline lychee links
job that replaces it. Two unscheduled items from the change files' non-goals were
written up as draft issues outside the repo and not opened.
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
db-retryoff 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.planning/ held 21 files: the portable convention README, five templates, five
change files, one decision record, an empty deferred.md, index.py, and three
release notes. Nothing is lost. Git history is the record for the change files;
the prose in them that outlives a PR has moved — the decision record and one
rescued rejected alternative are now ADRs, the enforceable claims are now tests,
the vocabulary is CONTEXT.md. planning/releases/{0.4.3,0.5.0,0.5.1}.md were each
diffed against
gh release view <tag>and are identical bar a trailing newlineGitHub adds, so the published Releases are the record.
architecture/ held eight pages: a README, a glossary, and six capability pages.
The capability prose is mechanism a reader gets from db_retry/ directly and is
deleted; glossary.md seeded CONTEXT.md.
Decisions
docs/adr/0001-retry-default-stays-env-configured.md, frontmatter stripped.
planning/changes/2026-06-26.01-retriable-error-seam.md:
docs/adr/0002-retriable-taxonomy-is-asyncpg-classes.md — the taxonomy stays a
tuple of asyncpg classes rather than SQLSTATE data. Its only home was
architecture/retriable.md, which this commit deletes.
Invariants
Five enforceable claims became tests whose name is the claim. Each was verified
by breaking it in db_retry/, confirming red, restoring, confirming green, and
confirming a benign narrowing stays green:
RETRIABLE_ASYNCPG_ERRORS widens to asyncpg.TransactionRollbackError; green when
a sibling (DeadlockDetectedError) is added instead, and when
PostgresConnectionError is narrowed to a subclass.
settings.get_retries_number() is hoisted into the decorator body.
when the curve is retuned (initial, exp_base, jitter).
the dict union in build_db_dsn is flipped; green under an equivalent
{**query, ...} rewrite.
host and port are shuffled independently and when failover derives from a
second shuffle; 12/12 green when the shuffle is dropped entirely. Six hosts
with distinct ports, so an accidental in-step shuffle cannot mask a mis-pairing
more than once in 720 runs.
The pairing assertion moved out of test_build_connection_plan_multihost, which
gave both hosts port 5432 — a swap there is unobservable.
architecture/retriable.md gave StatementCompletionUnknownError as SQLSTATE 40002.
It is 40003; 40002 is TransactionIntegrityConstraintViolationError. The invariant
test carries the correct code, and the same wrong class name in
tests/test_retry.py's parametrize comment is corrected.
Glossary audit
The single glossary entry, "Retriable error", rejected two synonyms.
"recoverable error" appears nowhere outside the glossary itself and is dropped.
"transient error" appears twice, both times in README.md and both user-visible
strings; both are reworded to "retriable", which is what earns the entry its
listing. Two terms are added on the subtlety clause: "Attempt" (retries=N is
stop_after_attempt(N), so retries=1 never retries) and "Primary host" (a position
in the two-stage connect, not a PostgreSQL replication role).
Release policy
.github/workflows/release.yml no longer reads planning/releases/.md. This
retires the curated-release-notes policy: a Release body is now always GitHub's
generated notes, edited after the fact with
gh release edit --notes-filewhen arelease wants prose. The file is now identical to modern-di's, which dropped the
same gate in 1ea74ee (modern-python/modern-di#449).
justfile drops the index and check-planning recipes and the planning/index.py
line from lint-ci; .github/workflows/_checks.yml gains the offline lychee links
job that replaces it. Two unscheduled items from the change files' non-goals were
written up as draft issues outside the repo and not opened.
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.