feat: plan orchestration (T-E) — snapshot, engine/validator run, publish/supersede #82

Merged
bart merged 5 commits from feature/domain-planning into main 2026-09-11 13:35:09 +00:00
Collaborator

Description

A planner can now trigger a planning run, and the system carries it all the way through to a checked, publishable draft. Triggering a run captures exactly what the crews, jobs and presence data looked like at that moment, hands it to the real planning engine and the real validator, and stores the result as a new plan version with its proposed assignments, any jobs it couldn't place (and why), and any issues the validator flagged. Publishing that version supersedes only the earlier published plan on the dates it actually replaces — other dates the older plan covered keep running unchanged — and a published assignment is never edited in place; a later change always shows up as a new assignment in a new version, so the plan's history stays exact. Confirming a plan also moves its jobs from open to planned, and moves them back to open if a later plan drops them without replacing them. Planners can also ask, for any single job, which crew/time combinations could currently take it, computed live rather than pre-stored.

Closes #20
Row: docs/12-implementation-plan.md PH2 / T-E

Type of Change

  • New feature
  • Bug fix
  • Refactor / cleanup (no behaviour change)
  • Documentation
  • Chore / build / CI
  • Breaking change

Breaking Changes

None.

Test Plan

Tests green (unit tests against fakes for the state machines and the publish/supersede logic; integration tests against a real Postgres for the snapshot build, the full solve pipeline through the real engine and validator, and the publish/supersede scenario the ticket specifically asked for — publishing a second version for a date supersedes only the first version's coverage of that date, and its assignment on an untouched date is provably unmutated). The two existing test files that previously asserted "not implemented yet" for these five endpoints were updated to match the real behaviour; their role-gating checks are untouched.

Checklist

  • Conventional commit(s), one logical change per commit
  • Tests added/updated and passing
  • docs/ updated for any behavioural change (ADR added if a prior decision was reversed)
  • No hard deletes; no direct status updates outside polaris.domain
  • No business logic in routers/components
  • Migration included if the schema changed (one per PR max, reversible downgrade)
  • No new dependency without a reason in the commit body

Additional Context

The ticket brief assumed the engine and validator were still stubbed; both merged before this work started, so this calls the real polaris.engine/polaris.validator directly — no stub anywhere, and no shape mismatch turned up between what they produce and what this ticket needed.

A couple of judgment calls, both recorded in docs/04-lifecycles.md: (1) until triage (PH3.2) exists, publishing a version treats every proposed assignment on it as accepted rather than requiring review = accepted first; (2) a solve task's stored payload carries one extra key (solver_run_id) beyond the frozen contract shape, which is what lets GET /tasks/{id} report a solve task's result without a reverse lookup — task.payload is a plain JSON column, so this doesn't touch the sealed contracts package.

Publish and discard as HTTP endpoints stay PH3.4's 501 stubs; this PR ships the domain service and its supersede semantics underneath them, exercised directly by integration tests, not through those endpoints.

🤖 Generated with Claude Code

https://claude.ai/code/session_013YioTVKBPoE6thZqbnTtnM

## Description A planner can now trigger a planning run, and the system carries it all the way through to a checked, publishable draft. Triggering a run captures exactly what the crews, jobs and presence data looked like at that moment, hands it to the real planning engine and the real validator, and stores the result as a new plan version with its proposed assignments, any jobs it couldn't place (and why), and any issues the validator flagged. Publishing that version supersedes only the earlier published plan on the dates it actually replaces — other dates the older plan covered keep running unchanged — and a published assignment is never edited in place; a later change always shows up as a new assignment in a new version, so the plan's history stays exact. Confirming a plan also moves its jobs from open to planned, and moves them back to open if a later plan drops them without replacing them. Planners can also ask, for any single job, which crew/time combinations could currently take it, computed live rather than pre-stored. ## Related Issues Closes #20 Row: `docs/12-implementation-plan.md` PH2 / T-E ## Type of Change - [x] New feature - [ ] Bug fix - [ ] Refactor / cleanup (no behaviour change) - [ ] Documentation - [ ] Chore / build / CI - [ ] Breaking change ## Breaking Changes None. ## Test Plan Tests green (unit tests against fakes for the state machines and the publish/supersede logic; integration tests against a real Postgres for the snapshot build, the full solve pipeline through the real engine and validator, and the publish/supersede scenario the ticket specifically asked for — publishing a second version for a date supersedes only the first version's coverage of that date, and its assignment on an untouched date is provably unmutated). The two existing test files that previously asserted "not implemented yet" for these five endpoints were updated to match the real behaviour; their role-gating checks are untouched. ## Checklist - [x] Conventional commit(s), one logical change per commit - [x] Tests added/updated and passing - [x] `docs/` updated for any behavioural change (ADR added if a prior decision was reversed) - [x] No hard deletes; no direct status updates outside `polaris.domain` - [x] No business logic in routers/components - [x] Migration included if the schema changed (one per PR max, reversible downgrade) - [x] No new dependency without a reason in the commit body ## Additional Context The ticket brief assumed the engine and validator were still stubbed; both merged before this work started, so this calls the real `polaris.engine`/`polaris.validator` directly — no stub anywhere, and no shape mismatch turned up between what they produce and what this ticket needed. A couple of judgment calls, both recorded in `docs/04-lifecycles.md`: (1) until triage (PH3.2) exists, publishing a version treats every proposed assignment on it as accepted rather than requiring `review = accepted` first; (2) a `solve` task's stored payload carries one extra key (`solver_run_id`) beyond the frozen contract shape, which is what lets `GET /tasks/{id}` report a solve task's result without a reverse lookup — `task.payload` is a plain JSON column, so this doesn't touch the sealed contracts package. Publish and discard as HTTP endpoints stay PH3.4's `501` stubs; this PR ships the domain service and its supersede semantics underneath them, exercised directly by integration tests, not through those endpoints. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_013YioTVKBPoE6thZqbnTtnM
Adds the domain layer a planning run needs end to end: a snapshot builder
that assembles a PlanningSnapshot from live master/planning data, a result
writer that turns the engine's PlanResult and the validator's findings into
plan_version/assignment/unassigned_job/validation_finding rows, exhaustive
solver_run and plan_version state machines (plus the narrower assignment and
job edges a publish drives), and the publish/supersede transaction that
activates a version's dates, supersedes only the previously-published
version(s) for those exact dates, and flips job status open<->planned.

The worker's solve and validate task handlers wire this to the already-merged
real engine (T-C, #18) and validator (T-D, #19) directly -- no stub engine or
validator anywhere, per the corrected ticket brief.

Refs #20 (T-E)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013YioTVKBPoE6thZqbnTtnM
Replaces the PH1.7 501 stubs for these five endpoints with real handlers.
POST /plans/runs is the one mutation here, going through
domain.planning.runs.start_run under the same X-Reason convention every
other mutating endpoint uses; the GET endpoints are plain read projections
straight off the ORM, same pattern crud_router's own list/get routes use.
GET /jobs/{id}/opportunities reuses the real engine's own opportunity
generation rather than re-deriving that logic at the API layer.

Refs #20 (T-E)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013YioTVKBPoE6thZqbnTtnM
plans_locks_assignments_stub_test.py and jobs_crews_shifts_stub_test.py
asserted 501 for every endpoint from docs/09-api.md, including the five this
ticket just implemented for real. Role-gating (403 for a wrong role) is
unchanged and stays covered here since it never touches the database; the
"allowed role, real response" coverage for those five paths moves to
tests/integration/planning/test_routers.py, which needs a real session the
way these deliberately DB-free stub tests don't.

Refs #20 (T-E)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013YioTVKBPoE6thZqbnTtnM
Records the implementation notes docs/04-lifecycles.md's plan-version rules
didn't already pin down: the validated->draft diagram edge is a new child
row, not a same-row rollback; an old published version only flips to
superseded once none of its dates are active; solver_run/plan_version/
assignment write their own audit rows since they aren't Auditable; publish
treats every proposed assignment as accepted until PH3.2's triage exists;
and a solve task's payload carries an extra solver_run_id key so
GET /tasks/{id} can report its result without a reverse lookup.

Refs #20 (T-E)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013YioTVKBPoE6thZqbnTtnM
# Conflicts:
#	backend/tests/unit/api/routers/jobs_crews_shifts_stub_test.py
bart referenced this pull request from a commit 2026-09-11 13:34:43 +00:00
bart merged commit 11db7e7e0e into main 2026-09-11 13:35:09 +00:00
bart deleted branch feature/domain-planning 2026-09-11 13:35:09 +00:00
Sign in to join this conversation.
No description provided.