updated spec status
All checks were successful
Build, test and publish / verify (push) Successful in 6m5s
All checks were successful
Build, test and publish / verify (push) Successful in 6m5s
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
# S18 — Scheduling dance groups across a show
|
||||
|
||||
Status: IMPLEMENTED
|
||||
|
||||
As a dance school manager,
|
||||
I want OR-Tools to schedule groups and order performances using participation,
|
||||
changing-time and variety rules,
|
||||
so that the complete show is manageable for dancers and enjoyable for audiences.
|
||||
|
||||
Refinement confirmed by the maintainer on 2026-11-03. This specification records
|
||||
the agreed design. Implementation and validation are complete and ready for review.
|
||||
See [validation evidence](../../docs/s18-validation.md) and
|
||||
[the applied scheduling rules](../../show-scheduling-rules.md).
|
||||
|
||||
## Show scope and configuration
|
||||
|
||||
- A show consists of dayparts and can span multiple dates, typically two days.
|
||||
All counts below apply across the entire show, not separately per date.
|
||||
- Each show has one configurable integer `preferredDayParts`, between 1 and the number of
|
||||
dayparts inclusive. Its default is 2, or 1 for a one-daypart show.
|
||||
- This replaces the existing configurable appearance counts per group type.
|
||||
- A show without dayparts can be stored but cannot be scheduled.
|
||||
- If reducing the number of dayparts makes `preferredDayParts` invalid, require the user to
|
||||
correct it before scheduling again; do not silently change it.
|
||||
- Groups without dancers are excluded from scheduling and from all performance
|
||||
counts and showcase-position requirements.
|
||||
|
||||
## Required group appearances
|
||||
|
||||
- Each nonempty REGULAR, SELECTION and PRE_EDUCATION group performs in exactly
|
||||
`preferredDayParts` distinct dayparts, once within each selected daypart.
|
||||
- Each nonempty PRE_SCHOOLER group performs exactly once in the complete show,
|
||||
regardless of `preferredDayParts`. At most two such groups are expected. They can be assigned
|
||||
freely to dayparts; no additional placement restriction applies.
|
||||
- Each nonempty SHOW_CASE group performs exactly once in every daypart.
|
||||
- With at least two showcase groups, each daypart starts with one showcase and
|
||||
ends with another. Additional showcases can appear anywhere between them.
|
||||
Opening and closing groups may differ between dayparts.
|
||||
- With exactly one showcase group, it appears either first or last in each
|
||||
daypart; the planner chooses. It does not perform twice in that daypart.
|
||||
- With no showcase groups, there is no showcase-position requirement.
|
||||
|
||||
## Dancer participation and coaches
|
||||
|
||||
- A dancer without a showcase membership performs in at most `preferredDayParts` distinct
|
||||
dayparts across the complete show. This is a hard constraint.
|
||||
- A showcase participant necessarily performs in all dayparts. Their other
|
||||
group performances may span more than `preferredDayParts` dayparts, but this is undesirable.
|
||||
- For each non-coach showcase participant, count the distinct dayparts in which
|
||||
they perform outside showcases. Their excess is `max(0, count - preferredDayParts)`.
|
||||
Minimize the sum of these excesses as the first optimization priority.
|
||||
- A person dancing in several groups must use the same dancer registration in
|
||||
every group, including when accompanying preschoolers. Up to five group
|
||||
memberships per dancer are expected.
|
||||
- Explicitly link a coach registration to the corresponding dancer registration;
|
||||
never infer identity from matching names. Support maintaining this link.
|
||||
- Coaches are available in all dayparts and are excluded from both the hard
|
||||
dancer limit and the excess penalty, including coaches in showcases.
|
||||
- Being assigned as a group's coach does not automatically mean performing in
|
||||
that group. Actual performances are determined by dancer membership only.
|
||||
Coach availability never increases a group's required appearances.
|
||||
- Dancing coaches remain subject to changing-time rules and contribute to the
|
||||
group's average age just like other dancing members.
|
||||
|
||||
## Changing time and feasibility
|
||||
|
||||
- Between any two performances involving the same dancer within a daypart,
|
||||
there must be at least two intervening performances not involving that dancer.
|
||||
- This is mandatory for everyone, including coaches and showcase participants.
|
||||
- The rule restarts at each daypart boundary. Assume enough changing time
|
||||
between dayparts, including on the same date.
|
||||
- All hard constraints apply together, even for unusual membership combinations
|
||||
such as a person dancing in both preschool and ordinary groups. Do not relax
|
||||
a rule to obtain a result.
|
||||
|
||||
## Balance and optimization priorities
|
||||
|
||||
- Count all scheduled performances, including preschool and showcase groups.
|
||||
- The difference between the largest and smallest daypart performance counts
|
||||
must not exceed two. This is a hard constraint.
|
||||
- Prefer a difference of zero. Preschool groups are an expected cause of
|
||||
differences but have no special exemption or exclusive right to cause them.
|
||||
- Optimize valid schedules in this strict priority order:
|
||||
1. Minimize the total excess for non-coach showcase participants defined above.
|
||||
2. Minimize the difference between largest and smallest daypart counts.
|
||||
3. Improve style and age variety between consecutive performances within
|
||||
each daypart.
|
||||
- A lower priority must not worsen a higher priority. Showcases participate in
|
||||
variety scoring as well.
|
||||
|
||||
## Variety scoring
|
||||
|
||||
- For adjacent groups, the style penalty is the number of shared styles divided
|
||||
by the number of distinct styles in their combined style sets. No shared
|
||||
styles gives zero penalty; two empty style sets also give zero penalty.
|
||||
- Compute dancer ages on the first date of the show. All dancers in a group,
|
||||
including dancing coaches, contribute equally to its average age.
|
||||
- The age penalty decreases linearly from 1 for equal average ages to 0 for a
|
||||
difference of five years; differences greater than five years also score 0.
|
||||
Equivalently: `max(0, 1 - abs(meanAgeA - meanAgeB) / 5)`.
|
||||
- Style and age penalties have equal weight. Minimize their combined penalties
|
||||
over adjacent performances. Empty groups have already been excluded.
|
||||
|
||||
## Solver execution and failure
|
||||
|
||||
- Retain a maximum solver runtime of thirty minutes.
|
||||
- Accept a feasible result without proof of optimality, provided every hard
|
||||
constraint holds. Distinguish feasibility from proven optimality.
|
||||
- If no valid result is found, preserve the existing assignments and order.
|
||||
- Explain whether infeasibility was proven or the time limit expired without
|
||||
a valid result; do not report a timeout as proof of infeasibility.
|
||||
- Retain existing protections against concurrent scheduling and applying stale
|
||||
results after relevant data changes.
|
||||
|
||||
## Manual editing and schedule lifecycle
|
||||
|
||||
- Add UI and API support for moving performances between dayparts and changing
|
||||
their order. Partial or temporarily invalid assignments can be saved as drafts.
|
||||
- Distinguish draft and final schedules explicitly. Display concrete violations
|
||||
for invalid drafts; do not present them as valid final schedules.
|
||||
- Add an explicit finalization action that checks all hard rules and permits
|
||||
finalization only when they all pass. Optimization preferences do not block it.
|
||||
- Automatically generated valid schedules also start as drafts for manager review.
|
||||
- Manual changes to a final schedule return it to draft. Changes to relevant
|
||||
dancer memberships, coach identity links or show settings likewise invalidate
|
||||
final status when they affect scheduling rules.
|
||||
- Treat existing schedules as drafts when introducing this lifecycle.
|
||||
- JSON import preserves assignments and order but makes imported schedules
|
||||
drafts and validates them against the new rules.
|
||||
- JSON export remains available for drafts and finals and preserves status.
|
||||
Import still produces a draft even when the exported status was final.
|
||||
- Include the new configuration and coach identity information in persistence
|
||||
and school JSON round trips so scheduling semantics survive saving and import.
|
||||
|
||||
## Documentation and validation
|
||||
|
||||
- Provide `show-scheduling-rules.md` describing the rules applied by OR-Tools in
|
||||
plain English, including hard constraints, exceptions, optimization priorities,
|
||||
scoring, cross-date scope and failure behavior.
|
||||
- Validate automatic results and finalization using the same agreed rules.
|
||||
- Verification must cover ordinary, preschool and showcase appearances; coach
|
||||
identity and exemptions; overlapping memberships; changing gaps; balance;
|
||||
optimization priority and scoring; multi-date shows; empty groups; invalid
|
||||
configuration; infeasible and timed-out runs; manual drafts/finalization; and
|
||||
persistence/import/export of the new information.
|
||||
188
specs/implemented/S6_deployment-gitea-action-to-production.md
Normal file
188
specs/implemented/S6_deployment-gitea-action-to-production.md
Normal file
@@ -0,0 +1,188 @@
|
||||
# S6 — Manual versioned production deployment through Gitea
|
||||
|
||||
Status: IMPLEMENTED
|
||||
|
||||
Agreed after the grill-me interview; shared understanding confirmed.
|
||||
|
||||
## User story
|
||||
|
||||
As a developer, I want to manually release a verified main-branch build from
|
||||
Gitea to the Synology, choosing a semantic version increment, so that I control
|
||||
when the launching partner receives a new production version and can recover
|
||||
the previous working version when necessary.
|
||||
|
||||
This replaces the original automatic-deployment story. Build, test and image
|
||||
publication remain automatic; production deployment requires a deliberate action.
|
||||
|
||||
## Existing foundation and scope
|
||||
|
||||
- Extend the implemented S4 pipeline, which verifies builds and publishes matching
|
||||
frontend/backend images with immutable full commit SHA tags for successful main
|
||||
builds. Deploy those existing images without rebuilding them.
|
||||
- Target one production Docker Compose stack on the Synology (`linux/amd64`).
|
||||
The existing Gitea runner triggers deployment over SSH.
|
||||
- Keep one active backend. A brief restart interruption is acceptable, including
|
||||
interruption of a scheduling calculation; the user may restart the calculation.
|
||||
- Production is private to the LAN or existing VPN. A configurable Synology
|
||||
hostname/IP and port provide access without an application login.
|
||||
- Publish only the frontend port. Keep the backend reachable within the Compose
|
||||
network; route browser API requests through the frontend proxy.
|
||||
- Actual server addresses, ports, paths and account configuration must be
|
||||
established and documented during implementation, without committing secrets.
|
||||
|
||||
## Manual release interface
|
||||
|
||||
Provide a separate **Deploy production** workflow using Gitea's native manual
|
||||
workflow trigger (`workflow_dispatch`). Its Run workflow popup offers:
|
||||
|
||||
- **New release** or **Retry release** mode.
|
||||
- For a new release: a release-type choice of **patch**, **minor** or **major**.
|
||||
- For a retry: the existing release version tag.
|
||||
|
||||
Provide a manual rollback action for the previous successful production release.
|
||||
The native Gitea button need not be renamed or embedded into an existing build
|
||||
run. Gitea 1.25.4 supports the required modal and choice inputs.
|
||||
|
||||
### New release eligibility and ordering
|
||||
|
||||
- Run the release workflow from main. Branch builds cannot deploy production.
|
||||
- Pin the current main SHA at workflow start. Require successful build checks and
|
||||
image publication for that exact SHA; missing or failed verification leaves
|
||||
production unchanged. Both images must be retrievable before replacement.
|
||||
- Recheck main immediately before changing production. If it has advanced, abort
|
||||
without changing production. Once replacement starts, finish deployment or
|
||||
recovery using the pinned SHA; later commits require another manual release.
|
||||
- Serialize deployment, recovery, rollback, version allocation and tag creation.
|
||||
Prevent stale runs from replacing newer successful production releases.
|
||||
- Reject a new release for an already release-tagged commit and direct the
|
||||
operator to retry or rollback instead. Repeated clicks must not create extra
|
||||
versions or deploy out of order.
|
||||
|
||||
## Semantic versioning and Git tags
|
||||
|
||||
- Use one shared application version for frontend and backend, represented by
|
||||
an immutable annotated Git tag `vMAJOR.MINOR.PATCH` on the verified SHA.
|
||||
- The first release is `v1.0.0`. Inspect remote tags before initialization and
|
||||
fail visibly on conflicting existing history instead of overwriting it.
|
||||
- Subsequent releases increment the highest existing release version tag,
|
||||
including tags belonging to failed deployments. A rollback does not lower the
|
||||
version baseline. Patch increments PATCH; minor increments MINOR and resets
|
||||
PATCH; major increments MAJOR and resets MINOR and PATCH.
|
||||
- For example, from `v1.2.3`, the choices produce `v1.2.4`, `v1.3.0` or `v2.0.0`.
|
||||
- After checking eligibility and image availability, create the tag before
|
||||
changing production. This establishes tag-writing access before replacement.
|
||||
Never move a release tag or reuse its version for different code.
|
||||
- Retain a tag if deployment subsequently fails or aborts. Record the outcome so
|
||||
a tag is not mistaken for proof of successful production deployment.
|
||||
- A retry reuses the existing tag and exact SHA without allocating a version.
|
||||
Require successful build verification and matching available images. Retrying
|
||||
an older release must not supersede a newer successful release. Retrying an
|
||||
existing release is an explicit exception to the current-main requirement.
|
||||
- Rollback restores an existing release and creates no new version. It is an
|
||||
explicit exception to the prohibition on replacing newer code with older code.
|
||||
- Continue using SHA-tagged container images; the Git release tag and deployment
|
||||
record map the semantic version to that SHA and image pair.
|
||||
- Maven/npm version fields and automated changelog generation are outside S6.
|
||||
Do not introduce a version-bump commit and rebuild into the release path.
|
||||
|
||||
## Deployment checks and recovery
|
||||
|
||||
- Retrieve the candidate images and validate prerequisites before replacing the
|
||||
running release. Frontend and backend must use the same selected commit SHA.
|
||||
- Within five minutes of starting the replacement, require all of:
|
||||
- Backend readiness succeeds.
|
||||
- The frontend page is served.
|
||||
- A read-only API request succeeds through the frontend proxy.
|
||||
- Both running containers match the selected SHA/image pair.
|
||||
- Checks must not create or modify user data. Log the selected SHA/version and
|
||||
final outcome.
|
||||
- A failed candidate automatically restores the previous successful image pair
|
||||
and checks its health. The workflow remains failed even if recovery succeeds.
|
||||
- Retain references to the current and previous successful releases and ensure
|
||||
their images remain available. A manual rollback targets that previous release;
|
||||
fail before modifying production if its required images cannot be retrieved.
|
||||
- If the first deployment fails with no previous release, or recovery itself
|
||||
fails, stop with a clear failure report and manual recovery instructions.
|
||||
Preserve diagnostics; do not enter an endless deployment/recovery loop.
|
||||
- Record successful production release state separately from allocated Git tags,
|
||||
so failed attempts do not become the rollback target.
|
||||
|
||||
## Data policy and S16/S17 boundaries
|
||||
|
||||
S16 is **not a prerequisite** for S6. Until S16 is implemented, application state
|
||||
is held in memory and may be lost during deployment, rollback or recovery. The
|
||||
launching-partner phase explicitly accepts this limitation. Restoring old images
|
||||
does not restore lost data.
|
||||
|
||||
- State the temporary data-loss limitation in the workflow description and
|
||||
deployment runbook. The operator is responsible for informing the launching
|
||||
partner; do not add a further approval step to each deployment.
|
||||
- After S16 is implemented, deployments and rollbacks must preserve saved data.
|
||||
Keep persistent storage outside replaceable containers and do not delete it
|
||||
during deployment or recovery.
|
||||
- After S16, ordinary release and rollback paths require database compatibility
|
||||
with the previous application version. Incompatible migrations require a
|
||||
separately planned maintenance procedure; image rollback cannot undo them.
|
||||
- Database backup and restore remain separate S17 work. S6 does not promise
|
||||
recovery of data lost before persistence or through storage loss.
|
||||
|
||||
## Credentials and operational documentation
|
||||
|
||||
- Use a dedicated Synology deployment account with SSH key authentication and a
|
||||
pinned server host key. Document the narrowly required deployment permissions.
|
||||
- Use a separate registry credential with pull access for production and a
|
||||
credential with the required repository permission for release-tag creation.
|
||||
- Store deployment secrets in Gitea Actions secrets, provision server-side
|
||||
credentials securely where needed, and keep secret values out of Git and logs.
|
||||
- Document initial provisioning, configuration, release/retry/rollback operation,
|
||||
credential setup, health checks, release records and manual recovery.
|
||||
- Retain deployment diagnostics for fourteen days, including failed deployment
|
||||
and recovery outcomes, without exposing credentials.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [x] Successful main builds still automatically verify and publish matching SHA
|
||||
images; publishing does not automatically change production.
|
||||
- [x] Gitea's manual workflow form provides new/retry mode and the appropriate
|
||||
release-type or existing-tag inputs; invalid combinations fail clearly.
|
||||
- [x] The first release and subsequent patch/minor/major releases allocate the
|
||||
agreed versions and immutable annotated remote Git tags on verified SHAs.
|
||||
- [x] Duplicate new releases, tag conflicts, concurrent attempts, stale main
|
||||
snapshots and older pending deployments cannot overwrite release identity
|
||||
or replace a newer successful release.
|
||||
- [x] An unverified release or missing image is rejected before production changes;
|
||||
branch builds cannot deploy production.
|
||||
- [x] A real Synology deployment serves the frontend and proxied API over the
|
||||
private endpoint, with both containers running the selected image pair.
|
||||
- [x] Readiness, frontend, proxied read-only API and image-identity checks establish
|
||||
success within five minutes or trigger recovery.
|
||||
- [x] A deliberately failed release restores the previous successful version,
|
||||
while the workflow reports failure. Failed first deployment and failed
|
||||
recovery produce actionable diagnostics and manual recovery instructions.
|
||||
- [x] A failed release can be retried with its existing tag/SHA without allocating
|
||||
another version or superseding a newer successful release.
|
||||
- [x] Manual rollback demonstrably restores the previous successful release with
|
||||
no new version, and required retained images are available.
|
||||
- [x] The temporary pre-S16 data-loss policy is visible in workflow documentation
|
||||
and the runbook. Once S16 applies, demonstrate saved data surviving both
|
||||
deployment and rollback, including the migration compatibility requirement.
|
||||
The latter is conditional S16 acceptance and is not applicable yet.
|
||||
- [x] Server setup and operational recovery documentation are usable; diagnostics
|
||||
and release outcomes are retained for fourteen days without secrets.
|
||||
|
||||
## Implementation and evidence
|
||||
|
||||
This specification records the confirmed design. The evidence combines automated
|
||||
behavior tests, real Gitea production release/guard runs and isolated real NAS
|
||||
fault tests. Their distinct scopes and the virtual-tag boundary of the isolated
|
||||
tests are documented in `docs/s6-validation.md`. Configuration alone does not
|
||||
complete S6.
|
||||
|
||||
Implementation PR #12 is merged. Production `v1.0.0` runs at
|
||||
`http://192.168.2.28:8088` following a successful main build and real manual Gitea
|
||||
deployment. Native duplicate/branch guards and downloadable artifacts were
|
||||
verified. Twelve isolated real NAS checks cover recovery, retry, distinct-version
|
||||
rollback, failure guards and manual recovery; they use separate state/containers
|
||||
and virtual test tags, without changing production. The test resources have been
|
||||
removed. See `docs/s6-validation.md` for run links, digests, evidence boundaries
|
||||
and the conditional S16 data-persistence acceptance. S6 is ready for review.
|
||||
Reference in New Issue
Block a user