Repository navigation
Tech debt map (Oct 2026): tracking issue #493
Copy link
Copy link
Closed
31 / 3131 of 31 issues completedLabels
enhancementNew feature or requestNew feature or request
Description
Tracking issue for a tech-debt audit of main at ec46dc4. Each finding is filed as a sub-issue.
Summary
The code itself is well kept. There are no TODOs, ruff and ty pass, the middleware order is pinned by tests, and SQLModel is used consistently. The debt sits in three places:
- Silent failure paths where failing loudly matters most: pre-app config, audit capture, the permission source cache.
- Plugin modules acting as framework.
settings,usersandbackground_tasksare imported by the framework or by other modules in ways the SM009 check can't see. - Prose that has drifted from the system: CLAUDE.md, the migration graph, and the two scaffolders.
Baseline: make test-py → 4470 passed, 11 skipped, 1 xfailed in 24m12s. Every lint check passes once make gen-pages has run.
Tier 1: correctness and security (fix first)
- Tenancy: Core outer join leaves the tenant-scoped right side unfiltered (cross-tenant counts) #506 Tenancy: a Core outer join leaves the tenant-scoped right side unfiltered (cross-tenant counts)
- Tenancy fails open when the pre-app host-settings read hits a transient DB error #494 Tenancy fails open when the pre-app settings read hits a transient DB error
- Session cookie is never marked Secure #495 Session cookie is never marked Secure
- One invalid stored host setting crashes boot on every worker #497 One invalid stored host setting crashes boot on every worker
- Audit capture is a process-global; writes made in Celery tasks are never audited #498 Writes made in Celery tasks are never audited
- Build-time HostSettings fields (multi_tenant, i18n_*, tenant_header) not marked requires_restart #499 Build-time HostSettings fields are not marked requires_restart
- tenants: invitation emails are never sent (InvitationCreated has no subscriber) #496 Tenant invitation emails are never sent
Tier 2: structural (write a short design note before coding)
- Design: the
settingsplugin module has become de facto framework; make the dependency explicit #500 Thesettingsplugin has become de facto framework - Design: a user-directory protocol so modules stop importing
users.models(Keycloak installs break) #501 A user-directory protocol so modules stop importingusers.models - Design: put the event outbox behind a framework transport protocol instead of inside background_tasks #502 Put the event outbox behind a framework transport protocol
- Migration graph has 2 roots / 4 heads and wrong branch labels; CLAUDE.md says it is one linear chain #503 Migration graph: 2 roots, 4 heads, wrong branch labels
- Frontend: shared typed API client generated from the OpenAPI export (replace ~34 hand-rolled fetch calls) #504 Typed API client generated from the OpenAPI export
- Frontend: Inertia page props are hand-typed and force-cast (44
as unknown as) #505 Typed Inertia page props (remove 44as unknown ascasts)
Tier 3: hygiene and process
-
make lintand CI are not equivalent (fresh-clone tsc failure, local-only checks, no Postgres suite / doctor in CI) #508 Makemake lintand CI run the same checks - Python suite takes ~24 min serially; no parallelism, and per-test app setup costs up to ~10 s #524 The Python suite takes about 24 minutes serially
- ~143 mypy-style
# type: ignore[code]comments do nothing under ty; global ty rule disables are broader than documented #507 mypy-styletype: ignorecomments are inert under ty, and the global rule disables are too broad - Two module scaffolders produce different modules (scripts/new_module.py vs smpy CLI templates) #509 Two module scaffolders that disagree
- Release: no test gate, inconsistent package versions, CHANGELOG never cut from [Unreleased] #510 Release: no test gate, inconsistent versions, CHANGELOG never cut
- ~88 MB of unreferenced binaries tracked in git (dataset/, qa-shots/) #511 About 88 MB of unreferenced binaries tracked in git
- Tests: replace fixed sleeps with a polling
wait_forhelper #512 Fixed sleeps in tests - Framework pagination helper (PageParams + paginate) to replace per-module copies with inconsistent validation #513 Framework pagination helper
- JS test gaps: api_tokens and feature_flags pages have no tests #514 JS test gaps (api_tokens, feature_flags)
- Compat shims carry no removal version; add an enforced REMOVE_IN and retire them in a batch #515 Compat shims have no removal version
- Route guard monkeypatches FastAPI private internals with an unbounded
fastapi>=0.115pin #516 Route guard depends on FastAPI private internals - PermissionRegistry caches a failed runtime source as empty until invalidated #517 A failed permission source is cached as empty
- 403 page parses the permission name back out of the error message string #518 The 403 page parses the permission name out of a string
- Docs drift: CLAUDE.md inaccuracies, stale paths, 63 historical plan docs mixed into user docs, docs site never built in CI #519 Docs drift; the docs site is never built in CI
- Hand-maintained module lists in pyproject.toml drift (ty extra-paths already misses framework/cli and framework/inertia) #520 Hand-maintained module lists in pyproject.toml
- Frontend deps: host re-declares packages/ui's dependency set; two headless UI libraries; loose Tailwind floor #521 Duplicated frontend dependencies and two headless UI libraries
- Planned splits for files at the 300-line cap and oversized modules (before the next feature forces them) #522 Planned splits for files at the 300-line cap and oversized modules
- Small framework helpers for repeated boilerplate: AuditLink.for_model, independent_session #523 Small helpers:
AuditLink.for_model,independent_session
Suggested order
- Tier 1, starting with the small ones: Session cookie is never marked Secure #495, Build-time HostSettings fields (multi_tenant, i18n_*, tenant_header) not marked requires_restart #499, One invalid stored host setting crashes boot on every worker #497, PermissionRegistry caches a failed runtime source as empty until invalidated #517.
- tenants: invitation emails are never sent (InvitationCreated has no subscriber) #496 (the mailer).
- The design note for Design: the
settingsplugin module has become de facto framework; make the dependency explicit #500, which unblocks most of Tier 2. make lintand CI are not equivalent (fresh-clone tsc failure, local-only checks, no Postgres suite / doctor in CI) #508 and Python suite takes ~24 min serially; no parallelism, and per-test app setup costs up to ~10 s #524, so the CI gate can be trusted and the suite is fast enough to run locally before the larger refactors land.
Reviewed and not counted as debt
- Middleware ordering. It is pinned by
test_middleware_order.py. - Broad
exceptblocks around the menu, CSP and health providers. They are correct isolation boundaries and they log. - Dialect branches in migrations. The Postgres round-trip covers them.
- Explicit commits in outbox claim/dispatch. They are lease logic on separate sessions.
- The mid-loop commit in
users/admin/bulk_invite.py:136. It is deliberate and the comment explains why. A per-address SAVEPOINT would be cleaner but isn't worth tracking. - Committed i18n generated files. They are covered by a drift test.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request
Activity
settingsplugin module has become de facto framework; make the dependency explicit #500users.models(Keycloak installs break) #501as unknown as) #505# type: ignore[code]comments do nothing under ty; global ty rule disables are broader than documented #5074 remaining items
wait_forhelper #512fastapi>=0.115pin #516antosubash commentedon Oct 22, 2026
All 31 sub-issues of this tech-debt map are closed. The last ones, #504 and #522, landed in #579.
https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4