Skip to content

refactor(db): split db/schedules.py into a 10-mixin package (#1481) - #1693

Merged
vybe merged 2 commits into
devfrom
AndriiPasternak31/issue-1481
Jul 19, 2026
Merged

vybe merged 2 commits into
devfrom
AndriiPasternak31/issue-1481

Conversation

@AndriiPasternak31

Copy link
Copy Markdown
Contributor

Fixes #1481

Summary

Pure mechanical split of the 4,139-line src/backend/db/schedules.py into a 10-mixin db/schedules/ package composed into ScheduleOperations — the sanctioned db/agent_settings/ shape (Invariant #2). The facade import path from db.schedules import ScheduleOperations is preserved by the package re-export, so database.py and db/__init__.py are unchanged. Zero behavior change, zero schema change, zero WHERE-clause edits, zero signature edits.

Mixins: ScheduleCommonMixin / Crud / Webhooks / Executions / Queue / Cleanup / Analytics / Stats / GitConfig / Retention. No import edges between mixin files — every cross-slice reference resolves via the composed class's MRO (self.<method>()); the only intra-package import is the bare _norm_ts module-global from _common.py.

Proof (mechanical-move invariance)

  • 103/103 function bodies AST-byte-identical vs pre-rebase dev (symbol-set diff: 103 functions, none added / dropped / renamed; AST source-segment body diff: all 103 bodies byte-identical).
  • 20 status-CAS writers (refactor: status-as-projection — schedule_executions.status never read as authoritative for "is running" #1082) re-discovered by a package-wide AST scan — the writer-inventory tripwire now rglob-scans the whole db/schedules/ package (was one hardcoded file) with an in-function assert sites non-empty guard so the CAS-precondition check can never pass vacuously; found == _EXPECTED_UPDATE_SITES both ways.
  • 3 fat signatures untouched (create_task_execution / update_execution_status / create_schedule_execution) — byte-identical, reserved for refactor(db): reduce database.py facade signature width — param objects for execution/chat writers #1482.
  • Only allowlisted text deviations: relative-import depth (.→..), the getLogger("db.schedules") literal pinned in the 3 logging modules, and the _PERCENTILE_ROWSET_CAP monkeypatch doc-comment retargeted to db.schedules.analytics.

Lockstep test edits (the only non-move work): writer-discovery rglob rework + db.schedules.* sys.modules eviction widening (conftest + test_agent_analytics.py) + _PERCENTILE_ROWSET_CAP monkeypatch retarget (module-identity trap).

Verification

  • Crux test tests/unit/test_schedule_status_observability.py: 19 passed, 1 skipped; the writer inventory found exactly _EXPECTED_UPDATE_SITES (20 update sites) with no missing / stale entries.
  • Lockstep-edited test_agent_analytics.py + test_schedule_analytics.py: 25 passed.
  • Import smoke: from db.schedules import ScheduleOperations resolves cleanly (95 methods).
  • verify-local --skip-agent: PASS including import-smoke, proving the db/schedules/ package ships in the prod image (docker/backend/Dockerfile COPYs db/ wholesale). 1 red = pre-existing test_1474 on dev, proven identical on base (not introduced by this PR).

Follow-ups (for Andrii)

  • docs/memory/feature-flows/scheduling.md refresh — path/line refs to db/schedules.py were already stale pre-PR; ~30 flow docs cite the physical path (the import path is unchanged).
  • F841 dead FAILED_STATES in the analytics slice (analytics-dedup).
  • Stale db/schedules.py prose comments referencing the old single-file layout.

Sequencing: #1482 (facade param-objects) rebases on this.

🤖 Generated with Claude Code

AndriiPasternak31 and others added 2 commits July 18, 2026 17:48
#1481)

Pure mechanical move of the 4,139-line src/backend/db/schedules.py into a
ten-mixin db/schedules/ package composed into ScheduleOperations — the
sanctioned db/agent_settings/ shape (Invariant #2). Zero behavior change,
zero schema change (0 DDL in the file, Invariant #3 untouched), zero WHERE
clause edits, zero signature edits.

- __init__.py composes ScheduleOperations from ScheduleCommonMixin / Crud /
  Webhooks / Executions / Queue / Cleanup / Analytics / Stats / GitConfig /
  Retention and re-exports the class + _norm_ts + _TRIGGER_BUCKETS so the
  facade import `from db.schedules import ScheduleOperations` is unchanged
  (both prod sites: database.py, db/__init__.py). No import edges between
  mixin files; cross-slice refs resolve via the composed class's MRO.
- find_soft_deleted_schedules_past_retention moves to retention.py so its
  caller, count_soft_deleted_schedules_past_retention, and the shared
  _soft_deleted_schedules_predicate module-global are co-located (a bare-name
  module-global does not resolve via MRO — would NameError on the #834 purge).
- The three fat signatures (create_task_execution / update_execution_status /
  create_schedule_execution) are byte-identical (reserved for #1482).
- The 20 #1082 status-CAS writers and all pull/backlog/lease seams (#1081/#1550,
  BACKLOG-001) moved verbatim; every WHERE precondition preserved.
- Only allowlisted text deviations: relative-import depth (.->..), logger name
  pinned to the literal getLogger("db.schedules") in the 3 logging modules, and
  the _PERCENTILE_ROWSET_CAP monkeypatch doc-comment retargeted to
  db.schedules.analytics.

Lockstep test edits forced by the move (the only non-move work):
- test_schedule_status_observability.py: writer discovery now rglob-scans the
  db/schedules/ package (was one hardcoded file) with an in-function
  `assert sites` non-empty tripwire so the CAS-precondition guard can never pass
  vacuously; found == _EXPECTED_UPDATE_SITES (20) both ways.
- test_agent_analytics.py / test_schedule_analytics.py: _PERCENTILE_ROWSET_CAP
  monkeypatch retargeted to db.schedules.analytics (module-identity trap).
- conftest.py + test_agent_analytics.py: sys.modules eviction/baseline widened
  to db.schedules.* children (prefix match, future-sub-split-proof).

Proof: symbol-set diff (103 functions, none added/dropped/renamed) + AST
source-segment body diff (all 103 bodies byte-identical) vs origin/dev.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…exemplar (#1481)

Records ScheduleOperations' composition from ten concern-scoped mixins in
db/schedules/__init__.py alongside the existing db/agent_settings/ exemplar,
noting the preserved facade import path and the MRO-not-imports cross-slice
contract. Pure doc delta for the #1481 mechanical split; no behavior change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AndriiPasternak31 AndriiPasternak31 self-assigned this Jul 18, 2026
@AndriiPasternak31
AndriiPasternak31 marked this pull request as ready for review July 18, 2026 22:08
@AndriiPasternak31
AndriiPasternak31 requested review from dolho and vybe July 18, 2026 22:08
@github-actions

Copy link
Copy Markdown

⚠️ Nightly unit-suite check skipped — merge conflict against dev.

Resolve by running git merge dev locally and pushing the result. The next nightly run will re-test once the conflict is gone.

@vybe vybe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validated via /validate-pr: mechanical split with facade re-export preserved, 103/103 AST-byte-identical bodies, writer-inventory tripwire now rglob-scans the package (no vacuous pass), all checks green incl. regression-diff. No schema change, no packaging impact (db/ COPYed wholesale, prod-image-smoke green). Approving.

@vybe
vybe merged commit 4a65536 into dev Jul 19, 2026
22 checks passed
vybe pushed a commit that referenced this pull request Jul 19, 2026
- db/schedules.py deleted by the #1693 package split: re-apply the two
  #1296 bucket lines (reminder → Reminders) to db/schedules/analytics.py
- retarget test_reminder_bucket_is_reminders import to db.schedules.analytics
  (package __init__ re-exports the facade surface only — #1693 lockstep pattern)
- compose files: keep both #1632 OPERATOR_QUEUE_* and #1296 REMINDER_* blocks
- architecture.md: keep #1296 Cleanup row + #1632 Operator Queue Sync row
- feature-flows.md: keep both rows
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.

2 participants