Skip to content

chore: migrate off the planning/ convention - #65

Merged
lesnik512 merged 1 commit into
mainfrom
chore/migrate-off-planning
Sep 6, 2026
Merged

chore: migrate off the planning/ convention#65
lesnik512 merged 1 commit into
mainfrom
chore/migrate-off-planning

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Migrates faststream-concurrent-aiokafka off the planning/ convention and onto PR-body-as-spec, with CONTEXT.md owning the vocabulary and docs/adr/ holding rejected alternatives. Part of modern-python/.github#67.

Replaces planning/ and architecture/ with CONTEXT.md, docs/adr/, and
invariant tests, matching the layout rolled out in modern-python/.github#50 and
across modern-di. Tracking issue: modern-python/.github#67.

Nothing was lost with the deleted directories:

  • planning/releases/*.md are the published GitHub Release bodies. Checked all
    seven against gh release view: 0.6.2-0.6.5 are byte-identical but for a
    trailing newline, 0.6.0 differs only in a link to a pre-rename spec path. The
    Releases are the record. 0.6.1 is the one exception and is drafted as an issue
    rather than fixed here: its published body was hand-condensed at publish time
    and ends with a "Full notes" link to blob/main/planning/releases/0.6.1.md,
    which 404s once this lands. The content survives at the 0.6.1 tag; the fix is
    a one-off Release edit, not a repo change.
  • planning/changes/* are design and plan documents for shipped work. Git
    history is the record; the two rejected alternatives in them that outlive their
    PR were rescued (below).
  • planning/deferred.md held one open item, now drafted as an issue.
  • planning/_templates/, planning/index.py, planning/.convention-version,
    planning/audits/, planning/retros/ are convention machinery with nothing
    left to serve.
  • architecture/README.md describes the promotion rule the convention imposed.
    The five capability pages are prose about mechanism that the modules state more
    precisely — this package's source is unusually densely commented, and every
    claim about dispatch, routing, prefix extraction, watermarks and the direct-ack
    guards was already pinned by a named test. The two claims that were not are now
    tests.

Decisions. The three records become docs/adr/0001-0003, frontmatter stripped
and revisit triggers kept: batch-subscribers-unsupported (0001),
health-delegation-chain-not-deepened (0002), control-signals-not-honoured (0003).
Two more are rescued from change files:

  • docs/adr/0004 records that refusing a non-MANUAL subscriber was built and
    rejected — it breaks the broker-level registration contract by turning a
    legitimate mixed-subscriber app into a hard error on its ACK subscribers.
    From the guard-direct-ack change file. 0001 now scopes its refusal against it.
  • docs/adr/0005 records that a run()-owning async CommitLoop was rejected
    twice: the wait-tasks carry no invariant, and moving them would force every
    scheduler test back onto the event loop. From the pending-commits and
    commit-scheduler change files.

Invariants. Two architecture claims were enforceable and unpinned:

  • tests/test_rebalance.py::test_the_rebalance_flush_default_stays_under_aiokafkas_max_poll_interval
    reads aiokafka's own max_poll_interval_ms default rather than hardcoding
    300 s, so it fires on a dependency bump as well as on our own default. Verified
    red at the boundary (flush default raised to 300.0) and red on a simulated
    upstream drop to 5 s; still green when the flush default is tightened to 5.0 and
    when the unrelated shutdown timeout is raised to 45.0.
  • tests/test_commit_scheduler.py::test_the_commit_scheduler_reads_no_clock_and_touches_no_asyncio
    pins the seam ADR-0005 defends. Verified red three ways — import time plus a
    time.monotonic() call, a from asyncio import Event collaborator, and an
    async def on an existing method — and still green when a new synchronous
    method and a typing import are added. Both modules restored byte-identically
    and the suite re-run: 182 passed, 100% coverage.

CONTEXT.md is authored from scratch; there was no architecture/glossary.md.
FastStream and aiokafka own most of the vocabulary and it says so rather than
redefining broker, subscriber, consumer group, partition or offset. Six
terms are local. The _Avoid_ audit cut three of the four candidate entries
because the rejected synonym appears nowhere: no "skip" is used for a
pass-through, no "ignore" for a route, and "ready" never names the whole pending
list. A fourth was reversed — "handler" for the user's subscriber function is not
a straggler but the dominant usage, including inside the user-visible
_DIRECT_ACK_REASONS strings, so the entry now disambiguates instead of
legislating. The one surviving _Avoid_, "in-flight handler" for a user task, is
proven and its six stragglers are fixed: four README lines, a test docstring and a
test assertion message. None is a user-visible program string.

One source edit was forced. middleware.py's nack guard message carries a
GitHub URL to planning/decisions/2026-07-28-control-signals-not-honoured.md,
deliberately absolute so a PyPI user without a checkout can open it. That is a
user-visible string and it now points at docs/adr/0003-.... README's two links
to the same record were repointed with it.

AGENTS.md gains Workflow and Where-a-fact-goes, loses the planning lanes and the
architecture promotion rule, and sheds the tooling notes the justfile already
states and the invariant list the tests already enforce. justfile and lint-ci drop
index / check-planning; the offline lychee gate lands in _checks.yml
(modern-python/.github#66). context7.json drops its planning excludeFolders
entry. docs/agents/domain.md is deliberately not written: origin/main has no
docs/ and AGENTS.md had no "Agent skills" section, so authoring it alone would
be a half-rollout of a separate convention with nothing to link it from.

The link gate went from 4 errors to 0. Three lived only in planning/ and are
fixed by deleting it; the fourth was a dangling docs/adr/0004 link left by an
interrupted earlier attempt, fixed by writing the record it wanted.

Beyond the recipe, and the reason this is chore: and not docs: release.yml read
planning/releases/<tag>.md as both a mandatory gate for stable tags and the
Release body source, so deleting planning/ would have failed every future stable
release. It now matches modern-di/.github/workflows/release.yml verbatim
(generated notes, prerelease flag only), which dropped the same gate in 1ea74ee,
modern-python/modern-di#449. This retires the mandatory-curated-notes policy for
this repo; a release wanting prose is edited after the fact.

Deferred work is drafted, unopened, in
DRAFT-ISSUES-faststream-concurrent-aiokafka.md one directory up: the commit_all
flush-urgency loss on a transient-error re-queue, the partition-scoped rebalance
flush, and the 0.6.1 Release link.

Verification

  • just lint-ci — clean (eof-fixer, ruff format, ruff check, ty).
  • Test suite green at the repo's 100% coverage gate.
  • Offline link gate (lychee --offline --no-progress '**/*.md') — 0 errors.
  • No planning/, architecture/, check-planning, check-links or convention-version reference remains.

Note

release.yml previously read planning/releases/<tag>.md, both as a hard gate for stable tags and as the Release body source; deleting planning/ without changing it would have broken the next stable release. It now uses GitHub's generated notes, matching modern-di post-modern-python/modern-di#449. This retires the mandatory-curated-notes policy.

Replaces `planning/` and `architecture/` with `CONTEXT.md`, `docs/adr/`, and
invariant tests, matching the layout rolled out in modern-python/.github#50 and
across modern-di. Tracking issue: modern-python/.github#67.

Nothing was lost with the deleted directories:

- `planning/releases/*.md` are the published GitHub Release bodies. Checked all
  seven against `gh release view`: 0.6.2-0.6.5 are byte-identical but for a
  trailing newline, 0.6.0 differs only in a link to a pre-rename spec path. The
  Releases are the record. 0.6.1 is the one exception and is drafted as an issue
  rather than fixed here: its published body was hand-condensed at publish time
  and ends with a "Full notes" link to `blob/main/planning/releases/0.6.1.md`,
  which 404s once this lands. The content survives at the `0.6.1` tag; the fix is
  a one-off Release edit, not a repo change.
- `planning/changes/*` are design and plan documents for shipped work. Git
  history is the record; the two rejected alternatives in them that outlive their
  PR were rescued (below).
- `planning/deferred.md` held one open item, now drafted as an issue.
- `planning/_templates/`, `planning/index.py`, `planning/.convention-version`,
  `planning/audits/`, `planning/retros/` are convention machinery with nothing
  left to serve.
- `architecture/README.md` describes the promotion rule the convention imposed.
  The five capability pages are prose about mechanism that the modules state more
  precisely — this package's source is unusually densely commented, and every
  claim about dispatch, routing, prefix extraction, watermarks and the direct-ack
  guards was already pinned by a named test. The two claims that were not are now
  tests.

Decisions. The three records become `docs/adr/0001-0003`, frontmatter stripped
and revisit triggers kept: batch-subscribers-unsupported (0001),
health-delegation-chain-not-deepened (0002), control-signals-not-honoured (0003).
Two more are rescued from change files:

- `docs/adr/0004` records that refusing a non-`MANUAL` subscriber was built and
  rejected — it breaks the broker-level registration contract by turning a
  legitimate mixed-subscriber app into a hard error on its `ACK` subscribers.
  From the guard-direct-ack change file. 0001 now scopes its refusal against it.
- `docs/adr/0005` records that a `run()`-owning async `CommitLoop` was rejected
  twice: the wait-tasks carry no invariant, and moving them would force every
  scheduler test back onto the event loop. From the pending-commits and
  commit-scheduler change files.

Invariants. Two architecture claims were enforceable and unpinned:

- `tests/test_rebalance.py::test_the_rebalance_flush_default_stays_under_aiokafkas_max_poll_interval`
  reads aiokafka's own `max_poll_interval_ms` default rather than hardcoding
  300 s, so it fires on a dependency bump as well as on our own default. Verified
  red at the boundary (flush default raised to 300.0) and red on a simulated
  upstream drop to 5 s; still green when the flush default is tightened to 5.0 and
  when the unrelated shutdown timeout is raised to 45.0.
- `tests/test_commit_scheduler.py::test_the_commit_scheduler_reads_no_clock_and_touches_no_asyncio`
  pins the seam ADR-0005 defends. Verified red three ways — `import time` plus a
  `time.monotonic()` call, a `from asyncio import Event` collaborator, and an
  `async def` on an existing method — and still green when a new synchronous
  method and a `typing` import are added. Both modules restored byte-identically
  and the suite re-run: 182 passed, 100% coverage.

CONTEXT.md is authored from scratch; there was no `architecture/glossary.md`.
FastStream and aiokafka own most of the vocabulary and it says so rather than
redefining `broker`, `subscriber`, `consumer group`, `partition` or `offset`. Six
terms are local. The `_Avoid_` audit cut three of the four candidate entries
because the rejected synonym appears nowhere: no "skip" is used for a
pass-through, no "ignore" for a route, and "ready" never names the whole pending
list. A fourth was reversed — "handler" for the user's subscriber function is not
a straggler but the dominant usage, including inside the user-visible
`_DIRECT_ACK_REASONS` strings, so the entry now disambiguates instead of
legislating. The one surviving `_Avoid_`, "in-flight handler" for a user task, is
proven and its six stragglers are fixed: four README lines, a test docstring and a
test assertion message. None is a user-visible program string.

One source edit was forced. `middleware.py`'s `nack` guard message carries a
GitHub URL to `planning/decisions/2026-07-28-control-signals-not-honoured.md`,
deliberately absolute so a PyPI user without a checkout can open it. That is a
user-visible string and it now points at `docs/adr/0003-...`. README's two links
to the same record were repointed with it.

AGENTS.md gains Workflow and Where-a-fact-goes, loses the planning lanes and the
architecture promotion rule, and sheds the tooling notes the justfile already
states and the invariant list the tests already enforce. justfile and lint-ci drop
`index` / `check-planning`; the offline lychee gate lands in `_checks.yml`
(modern-python/.github#66). context7.json drops its `planning` excludeFolders
entry. `docs/agents/domain.md` is deliberately not written: `origin/main` has no
`docs/` and AGENTS.md had no "Agent skills" section, so authoring it alone would
be a half-rollout of a separate convention with nothing to link it from.

The link gate went from 4 errors to 0. Three lived only in `planning/` and are
fixed by deleting it; the fourth was a dangling `docs/adr/0004` link left by an
interrupted earlier attempt, fixed by writing the record it wanted.

Beyond the recipe, and the reason this is chore: and not docs: release.yml read
`planning/releases/<tag>.md` as both a mandatory gate for stable tags and the
Release body source, so deleting `planning/` would have failed every future stable
release. It now matches modern-di/.github/workflows/release.yml verbatim
(generated notes, prerelease flag only), which dropped the same gate in 1ea74ee,
modern-python/modern-di#449. This retires the mandatory-curated-notes policy for
this repo; a release wanting prose is edited after the fact.

Deferred work is drafted, unopened, in
DRAFT-ISSUES-faststream-concurrent-aiokafka.md one directory up: the commit_all
flush-urgency loss on a transient-error re-queue, the partition-scoped rebalance
flush, and the 0.6.1 Release link.
@lesnik512
lesnik512 merged commit 42fde5d into main Sep 6, 2026
6 checks passed
@lesnik512
lesnik512 deleted the chore/migrate-off-planning branch September 6, 2026 19:14
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