S12 - Async feedback for scheduling
All checks were successful
Build, test and publish / verify (push) Successful in 7m25s

This commit is contained in:
2026-11-03 23:18:44 +01:00
parent f1785ed174
commit c241392132
34 changed files with 1310 additions and 79 deletions

79
specs/S12-validation.md Normal file
View File

@@ -0,0 +1,79 @@
# S12 implementation validation
## Delivered behavior
Scheduling starts through a short POST returning 202 and a run identifier.
The backend reserves and snapshots inputs before submitting background work;
completion applies a validated result or records a distinct unsuccessful outcome.
Per-show exclusion remains in the shared school service, with different shows
allowed to calculate concurrently. Status publication and subscription snapshots
share a lock so connecting cannot miss a transition between snapshot and subscription.
The solver publishes optimization phases and first feasible-solution discovery
through a callback. Intermediate assignments remain private. Final summaries
calculate the same attendance, spread and adjacency penalties as the solver.
SSE provides a current snapshot on connection and status changes, with separate
15-second heartbeats. The nginx proxy disables buffering. The frontend keeps one
application-wide connection, explicitly retries connection errors (including
permanent EventSource failures caused by proxy responses), and reconciles state
without event replay. Run identifiers deduplicate completion notifications.
## Validation
- Maven verify: 60 backend tests passed, plus 19 standards-checker regression tests.
- Backend line coverage: 1349/1387, or 97.26%, above the required 80%.
- Frontend: 29 unit tests, production build and ESLint.
- Playwright: three real REST/browser scenarios, including scheduling completion,
result summaries, a completion snackbar, reloading to recover the latest result
and suppression of old notifications after reload.
- HTTP SSE integration verifies initial snapshot, pushed completion and a new
connection recovering the completed run.
- Background tests cover duplicate exclusion, parallel shows, stale-result
rejection, preserved assignments, failure outcomes and retry after failure.
- Frontend tests cover missed completion after disconnect, reconnection, heartbeat
handling, deduplication and completion arriving before the POST response.
- Real solver tests cover phase/discovery callbacks, distinct infeasible/no-solution
outcomes and objective-summary definitions including coach exemptions.
## Manual standards assessment
No waivers or weakened checks were introduced. The architecture tests pass.
New records contain immutable values; the SSE snapshot defensively copies its
list. The ten-component SchedulingRun record groups run identity, timestamps,
progress and outcome in one immutable snapshot, avoiding mutable task state in
API responses. Solver observers use the existing application-owned port and
framework-independent domain types; OR-Tools remains encapsulated in solver.
Retained review signals:
- SchedulingRuns fan-out (25): task orchestration uses JDK executor/collection
types, the scheduling port and immutable input/progress/result/status models.
Its injected dependencies remain two; splitting these short lifecycle methods
would distribute the shared publication and run-slot ordering.
- SchedulingEventsResource fan-out (16): SSE/Mutiny transport, JSON envelope and
service subscription types are confined to the REST adapter.
- OrToolsSchedulingEngine fan-out (21), solve cognitive complexity (11) and
length (40 code lines): the ordered three-stage loop retains deadline and
earlier-feasible-result fallback behavior while publishing phase/discovery.
requireSolution cyclomatic complexity (8) retains explicit mappings for stable
failure outcomes and their human-readable explanations.
- Integration/background test fan-out and the background lifecycle test's
34-line method reflect real HTTP/solver fixtures and lifecycle assertions.
Existing REST-test length signals remain below blocking limits after adapting
them to await background completion.
## Persistence follow-up in S16
The maintainer explicitly transferred durable scheduling status and
backend-restart recovery to S16 and approved completion of S12 with that scope
adjustment. S12 is IN_REVIEW in `in_review/S12_support-async-feedback-for-scheduling`;
implementation is ready for review and final acceptance is pending.
The application currently stores schools, saved assignments and the latest
scheduling status in memory. S16 now explicitly requires PostgreSQL storage of
accepted runs and their latest status, atomic durable result/status commits,
stable identifiers in recovered API/SSE snapshots and startup conversion of
previously running calculations to Interrupted without automatic resumption.
Restart/crash recovery tests belong to S16; the S12 validation above covers
navigation, browser reload and connection recovery within one backend lifetime.

View File

@@ -1,6 +1,6 @@
# S12 — Asynchronous feedback for scheduling
Status: REFINED
Status: IN_REVIEW
Agreed after the grill-me interview; shared understanding confirmed.
@@ -38,9 +38,9 @@ and what the outcome is, including when I leave and return to the show page.
- For a successful result, show a concise summary with concrete results for
attendance, spread and variety, using understandable labels.
- Clearly distinguish invalid input, infeasibility, solver failure, time limit
without a valid solution, rejection because inputs changed, and interruption
by a backend restart. Explain why no result was applied and that existing
assignments were preserved.
without a valid solution and rejection because inputs changed. Explain why
no result was applied and that existing assignments were preserved.
Backend-restart interruption recovery belongs to S16.
## Applying results
@@ -85,16 +85,17 @@ and what the outcome is, including when I leave and return to the show page.
## Persistence and backend restarts
- Retain the latest scheduling status and outcome across backend restarts,
aligned with the PostgreSQL persistence specified in
`S16_store-state-in-persistent-storage`.
- Calculations interrupted by a backend restart become recognizably Interrupted
rather than remaining Running indefinitely. Do not resume them automatically;
allow the user to start again.
- Previously saved results survive restart. Volatile in-memory status alone
does not satisfy S12; its implementation must integrate with S16 storage.
- Target the single active backend instance defined by S16. Distributed
execution and scheduling on another PC remain in S13.
The maintainer explicitly transferred durable scheduling status and recovery
following backend restarts to [S16](../refined/S16_store-state-in-persistent-storage)
when completing S12. The latest status and outcome in S12 survive navigation,
page reloads and SSE reconnection while the same backend process remains active.
Schools, assignments and scheduling status currently remain in memory.
S16 owns PostgreSQL storage of the latest run, atomic persistence of saved
assignments and completed status, and startup recovery of previously running
calculations as Interrupted without automatic resumption. Previously saved
results must survive restart under S16. These are not completion criteria for S12.
Distributed execution and scheduling on another PC remain in S13.
## Out of scope
@@ -106,34 +107,38 @@ and what the outcome is, including when I leave and return to the show page.
## Acceptance criteria
- [ ] Starting Schedule Groups returns without waiting for solver completion;
- [x] Starting Schedule Groups returns without waiting for solver completion;
leaving or reloading the page does not interrupt the calculation.
- [ ] Opening a show in another screen recovers its current backend status;
- [x] Opening a show in another screen recovers its current backend status;
duplicate runs for the same show are prevented in frontend and backend,
while different shows can run concurrently.
- [ ] Running status shows elapsed time, phase and discovery of a valid solution
- [x] Running status shows elapsed time, phase and discovery of a valid solution
during a phase, without a fabricated completion percentage or ETA.
- [ ] SSE delivers status changes promptly through the deployed proxy. Initial
- [x] SSE delivers status changes promptly through the deployed proxy. Initial
connection and reconnection reconcile the current state, and loss of
connectivity visibly marks status as potentially outdated.
- [ ] Successful runs automatically apply their result after the existing start
- [x] Successful runs automatically apply their result after the existing start
confirmation, retain stale-input protection and distinguish proven optimal
results from valid results at the time limit.
- [ ] The final display includes finish time and concrete attendance, spread and
- [x] The final display includes finish time and concrete attendance, spread and
variety results when successful; unsuccessful outcomes are distinguishable
and explain that previous assignments were preserved.
- [ ] An application-wide snackbar reports completion or failure with the show
- [x] An application-wide snackbar reports completion or failure with the show
name and View show action, including after navigation to another page.
- [ ] Connection recovery notifies once for a missed completion; phase changes,
- [x] Connection recovery notifies once for a missed completion; phase changes,
repeated reconnection and reopening do not replay old snackbars.
- [ ] The latest status and outcome survive navigation, reload and backend
restart through persistent storage integrated with S16.
- [ ] After backend restart, interrupted calculations are marked Interrupted,
do not resume automatically and can be started again; saved results remain.
- [x] The latest status and outcome survive navigation, page reload and SSE
reconnection within the lifetime of the running backend. Durable storage
and recovery after backend restart are explicitly transferred to S16.
## Implementation handoff
## Review handoff
This specification records the agreed design and behavior. Backend and frontend
changes, persistence integration, proxy configuration and validation are
subsequent implementation work. S12 replaces S9's synchronous request behavior
while retaining the existing scheduling and result-application protections.
Background execution, solver progress callbacks, SSE, frontend status recovery,
result summaries, proxy streaming configuration and application-wide completion
notifications are implemented and verified. S12 replaces S9's synchronous request
behavior while retaining scheduling and result-application protections.
See [S12 validation](../S12-validation.md) for verification and retained standards
signals. Durable storage and backend-restart recovery are tracked in S16 and do
not block review of this specification under the agreed scope adjustment.
Implementation is ready for review; final acceptance is pending.

View File

@@ -69,6 +69,34 @@ JSON remains the import/export format, rather than the primary storage model.
interrupted, rather than remain indefinitely marked as running. They do not
resume automatically. Previously saved results remain available.
## Persistent scheduling status (transferred from S12)
The maintainer explicitly moved durable scheduling status and backend-restart
recovery from S12 to S16 when completing S12. S12 supplies background execution,
status snapshots, SSE and notifications within the lifetime of the backend.
- Persist the latest calculation per show in PostgreSQL, including its run
identifier, school/show identity, start and finish timestamps, state, latest
phase, whether a valid solution was found, outcome explanation and final
attendance/spread/variety summary when available. A run history is not required.
- Persist acceptance of a run before acknowledging its start. After restart or
crash, an accepted calculation must remain identifiable even if it never
produced a result.
- Commit successful assignment replacement and its completed run status
atomically. Publish success through the status API and SSE only after the
transaction commits. A failed commit must not leave a successful status with
missing assignments, or replaced assignments with an outdated running status.
- On startup, mark calculations left Running by the previous backend instance
as Interrupted before serving their recovered status. Explain that no new
result was applied, retain previously saved assignments and allow a new run.
Do not resume calculations automatically or retain their old run-slot guards.
- Recover the latest status and outcome through S12's existing status endpoint
and initial/reconnection SSE snapshots. Preserve stable run identifiers so
reconnecting clients can deduplicate notifications; reopening the application
must not replay old completion snackbars.
- Verify recovery after both graceful restart and abrupt process termination,
including a calculation running at termination and a completed saved result.
## Database failures and schema changes
- Refuse mutations while the database is unavailable and provide a clear failure
@@ -103,8 +131,14 @@ for now. S16 does not promise recovery after loss of the database or its volume.
another: the stale edit receives a clear conflict response, and the user
can reload and retry.
- [ ] Scheduling results based on outdated input revisions are rejected.
- [ ] Interrupted calculations are recognizably interrupted after restart;
previously saved results remain available.
- [ ] The latest accepted scheduling run, its stable identifier, timestamps,
progress, outcome and objective summary survive normal restart and crash
and are returned through the status API and SSE snapshots.
- [ ] Successful assignment replacement and completed run status commit
atomically; success is published only after durable commit.
- [ ] Calculations left Running become Interrupted before recovered status is
served; previous assignments remain, no automatic resumption occurs and a
new run can start. Reopening does not replay old completion snackbars.
- [ ] Database unavailability prevents successful mutation acknowledgements;
startup connection and migration failures are clearly reported.
- [ ] An empty database can be initialized through Flyway and populated through