Skip to content

Adopt modern-di's lean planning convention - #6

Merged
lesnik512 merged 11 commits into
mainfrom
planning-convention-lean
Aug 22, 2026
Merged

Adopt modern-di's lean planning convention#6
lesnik512 merged 11 commits into
mainfrom
planning-convention-lean

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Adopts modern-di's lean planning convention: the spec for a change becomes the
PR body, planning/changes/ and architecture/ are deleted, deferred.md
becomes an indexed directory, and a Markdown link checker joins the lint gate.

This body is the first written under the convention it introduces — there is no
change file, by design.

Why

A capability page had gone silently wrong. architecture/chats.md described
chat_type as sa.Enum(native_enum=False, create_constraint=True), "not a
native Postgres enum type", and argued that a native enum "would need
alembic-postgresql-enum ... a dependency not worth buying to store two
values." #4 reversed exactly that. The convention's promotion rule says the
implementing PR hand-edits the affected page; it did not happen, nothing
enforced it, and the page stayed wrong. A second stale claim turned up during
review: architecture/messages.md cited a # pragma: no cover that exists
nowhere in app/.

The pages were largely a second telling of planning/decisions/. Twelve
decision files already owned the load-bearing calls while the pages re-narrated
them — auth.md's 401-vs-PermissionDeniedError, anonymous prefixes and
cookie-secure flag against domain-error-vocabulary,
anonymous-doc-and-metrics-paths and explicit-cookie-secure-flag; chats.md's
upsert and 403-vs-404 against upsert-via-duplicate-key-recovery and
mutation-requires-membership. The dependence ran one way: git grep architecture/ -- planning/decisions/ returned nothing. The decision files never
needed the pages.

The change file and the PR body were already duplicates. Every one of #1#5
wrote a substantial body and linked a change file that largely restated it.

Design

Every fact gets one owner. app/ for anything readable from the module; a named
test for an enforceable claim; planning/decisions/ for a rejected alternative;
planning/deferred/ for real work not scheduled. Prose about mechanism has no
home — there is no file to add a paragraph to. planning/README.md carries the
admission check that decides where a given fact belongs.

An enforceable claim is now a test whose name is the claim, with a docstring
opening INVARIANT: and a second paragraph naming what breaks it. Adopted as a
convention and applied to the claims rehomed here; the existing 109 tests are
not retrofitted and there is no census test.

Deleting the pages was preceded by an audit, not a git rm. Every fact in
the five pages was classified against the admission check, and the ones with no
other home got one: the cookie-vs-bearer rationale and the login-401 reasoning
became decision records, the service_debug credential-leak hazard
(echo/echo_pool log password_hash on every registration) became a one-line
comment on the setting, the race-simulation and malformed-subject claims became
INVARIANT: docstrings, and glossary.md's seven terms became a ## Vocabulary
section in CLAUDE.md. Facts derivable from app/ were dropped deliberately.

Six deviations from upstream convention 2.2.0 are recorded in
planning/README.md; five match modern-di's practice. The sixth is local: this
repo has no lint-ci recipe, so links.py runs as a step in the workflow's
lint job.

Non-goals

  • No mkdocs site. Considered and dropped with the capability pages — with
    nothing to publish, the source-link problem (relative links escaping
    docs_dir fail mkdocs --strict, so source pointers become unchecked
    absolute URLs) disappears with them.
  • No planning/releases/. Not a published package, no tags.
  • No tests/test_invariant_census.py. Real machinery for a repo this size.
  • No retrofit of the existing suite to the INVARIANT: shape.
  • No application logic changes. The only source edits are two comments.

Verification

  • just test — 109 passed, 100% coverage. just test-migrations — 4 passed.
    No application logic changed, so green confirms the coverage-omit edit and
    nothing else.
  • just lint — clean.
  • just check-planningplanning: OK, now validating deferred/ +
    decisions/ including the revisit-trigger gate on all 10 deferred items.
  • just check-linkslinks: OK. The checker was verified to actually fail on
    a missing target and on a link escaping the repo, so the pass means something.
  • just index — 10 deferred, 13 decisions, no ## Changes section.

Known consequence, accepted

#1#5 have merged bodies containing repo-relative links into
planning/changes/*.md, which GitHub resolves against the default branch. Those
links 404 once this merges. The rationale itself survives in the PR bodies.
readme.md now points a reader at #1 for the bootstrap design, so that is the
first place a newcomer will encounter it.

Location is status now: presence in decisions/ means accepted, and
superseded_by is the only state worth recording. Also repoints the
dangling planning/deferred.md reference in
2026-08-21-mutation-requires-membership.md at the deferred item that
now owns that content, orphaned by the deferred.md -> deferred/ split.
Restore the login-401 reasoning to the domain-error decision, replace the
coverage-omit enumeration with its criterion, and link the sibling deferred
item by name. Six smaller fixes: one-line `check-links` doc comment, the full
host-recipe list, an author-supplied test count in the PR template, the
`deferred/` row in the admission table, both halves of the `service_debug`
hazard, and a pointer to upstream's APPLY.md in deviation 5.
@lesnik512
lesnik512 merged commit f1cc9a5 into main Aug 22, 2026
3 checks passed
@lesnik512
lesnik512 deleted the planning-convention-lean branch August 22, 2026 09:30
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