Files
jfmcarreira 66b72d1348
App checks / changes (push) Canceled after 0s
App checks / tournament (push) Canceled after 0s
App checks / shifts (push) Canceled after 0s
feat: monorepo migration phase 5
2026-10-07 15:20:27 +01:00

353 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Migration progress
Verified on 2026-10-07. Follow `MONOREPO_MIGRATION_PLAN.md`; its historical source
labels resolve to `originals/tournment-manager-web/` and `originals/crr-shifts/`.
## Phase 0 — behaviour baseline: complete
Both untouched source snapshots were copied into disposable `.migration/`
directories before dependency installation, tests and builds. SHA-256 manifests
confirm that source files match the snapshots after the checks and app copies.
No commands installed dependencies or generated outputs inside `originals/`.
| Baseline check | Result |
| --- | --- |
| Tournament `npm test` | 55 passed, 8 files |
| Tournament `npm run typecheck` | Passed |
| Tournament `npm run build` | Vue and server builds passed |
| Shifts pytest, Python 3.13.13 | 14 passed |
| Tournament SQLite integrity / foreign keys | Passed |
| Desktop/mobile screenshots | 14 captured, including print cards |
Tournament was checked locally using Node 26.0.0/npm 11.12.1, satisfying the
legacy `>=22` engine constraint. Node 22 remains the CI/container target; its CI
checks have not run yet. The legacy package has no `lint` script. Shifts' legacy
requirements resolve the same versions as the new root lockfile's runtime/test
dependencies. Existing upstream/runtime deprecation warnings remain visible.
### Preserved evidence
- `baseline/tournament.json` and `baseline/shifts.json`: source checksums,
executed commands, exit codes and individual passing test names.
- `baseline/artifacts.json`: fixture and screenshot checksums.
- `fixtures/tournament.sqlite`: database created by the actual legacy Node
application, containing all nine existing tables and no Alembic stamp.
- `fixtures/tournament-schema.json` and `tournament-rows.json`: exact legacy
schema/index definitions and persisted contents for adoption checks.
- `fixtures/tournament-state.json`: expected public state, including standings,
final-stage results, champion and third-place match.
- `fixtures/tournament-api.json`: observable requests/statuses/bodies for a
synthetic workflow, authentication failures, duplicate-number conflict and 404.
- `fixtures/tournament-routes.json`: explicit API method/path inventory. This is
an inventory, not a substitute for the legacy integration suite.
- `fixtures/tournament-cookie.json`: cookie attributes with its token removed.
- `screenshots/`: desktop (1440×1000), mobile (390×844), login/results/admin pages
and Tournament match cards in print media, with a capture manifest.
Fixtures contain synthetic Portuguese names and public test-only credentials;
they contain no production data. Timestamps in the final Tournament database
are fixed; the browser clock is fixed for screenshots. Shifts demo schedules
use the server's capture month, October 2026. Raw execution logs and test-runner
reports remain available locally under ignored `migration/results/`.
### Reproduce
```sh
make baseline-test
make baseline-fixtures
make baseline-screenshots
make baseline-preserve
```
These commands require Node 22+, Python 3.13, uv, and Playwright's Chromium
download for screenshot capture. `baseline-test` replaces disposable staging
copies. Fixture/screenshot capture intentionally refreshes migration artefacts.
It starts isolated servers on localhost ports 18080 and 18081 and shuts them
down afterwards. Run screenshot commands serially. Repeated captures in a
different month/browser version may need a reviewed visual baseline update.
This machine's `uv` is installed through the Python 3.14.6 pyenv environment,
while `.python-version` selects 3.13. Commands here used
`PYENV_VERSION=3.14.6 make shifts-test` (and that environment for baseline tooling)
to resolve the uv executable; uv still used Python 3.13.13 for Shifts. Other
machines with uv available independently do not need that override.
## Phase 1 — monorepo paths: local exit verified; remote CI pending
- Tournament source/lockfile copied into `apps/tournament/`, retaining its
working `src/{client,server,shared}` layout during this phase.
- Shifts app/templates/static files, tests and unchanged migration history copied
into `apps/shifts/backend/`; domain/architecture reference docs copied too.
- Root uv workspace, Python 3.13 constraint, single lockfile and independent
Shifts project metadata added. Tournament enters the workspace in Phase 3.
- Root namespaced Makefile commands, Python configuration, app instructions and
development documentation added.
- Independent root-context Dockerfiles and Compose services/volumes added.
Optional per-app environment files let either service be configured separately.
- Path-filtered CI tests/builds only affected apps; shared branding/Python/root
tooling changes trigger both as applicable. No production or CI job runs from
`originals/`.
| Migrated check | Result |
| --- | --- |
| `make tournament-test` | 55 tests and both typechecks passed |
| `make tournament-build` | Vue and server builds passed |
| `make shifts-test` | 14 passed with locked Python 3.13 dependencies |
| Live app rendering from `apps/` | Both applications served successfully |
| `make migration-visual-check` | All 14 captures byte-for-byte match legacy |
| `docker compose config --quiet` | Passed; both independent services listed |
| Docker image builds | Both passed: Tournament Node 22, Shifts Python 3.13 |
| `make images-smoke-test` | Both containers started; JSON/HTML routes passed |
| GitHub Actions | Workflow added; remote execution not verified |
`tools/migrate_app_sources.py` was used for the one-time source copy. It refuses
existing destinations rather than overwriting application work. Do not rerun it
against the populated apps. Requirements files stay in the migrated Shifts copy
until the lockfile workflow passes CI, as required by the plan.
Docker was initially stopped; Docker Desktop was started and both independent
image builds then passed. The smoke check used temporary containers, random
localhost ports, synthetic credentials and tmpfs databases; containers were
removed after checking Tournament `/api/public/state` and `/results`, and Shifts
`/health` and `/login`. No production database or persistent volume was used.
Repeat image checks after building both images:
```sh
docker build -f apps/tournament/Dockerfile -t crr-tournament .
make shifts-build
make images-smoke-test
```
## Phase 2 — shared CRR brand: locally verified complete
- Created `packages/crr-brand` with one canonical Tournament logo (2324×2051)
and favicon, copied byte-for-byte from the baseline. Shared CSS defines the
red `#ed1c24` palette, neutral/control/error colors, font stack, radii, shadows
and spacing tokens, plus common base, button, panel and error-alert rules.
- Both apps load tokens/base/components before app styles. Existing variable
names now alias shared tokens. Responsive layouts, typography hierarchy,
control padding, specialist badges and print-card dimensions stay app-local.
- Tournament imports the package through Vite's `@crr-brand` alias and public
asset directory; shared files also work in Vite development under `/jogo/`.
- Shifts mounts `/brand`, generates prefixed asset URLs through Jinja `url_for`,
and uses the canonical logo in PDF exports. Its login logo retains the old
display aspect ratio to avoid a subpixel form-layout shift from the source
image's slightly different dimensions.
- Removed the two app-local logo files and Tournament's local favicon after
successful visual verification. Original snapshots remain unchanged.
- Both root-context images consume the package; the existing brand path filter
triggers both application CI jobs.
| Check | Result |
| --- | --- |
| Tournament tests/typechecks | 55 passed; both typechecks passed |
| Tournament Vue/server build | Passed |
| Shifts tests, including PDF exports and brand resources | 16 passed |
| Shifts brand assets under `/` and `/crr` | CSS, logo and favicon passed |
| Tournament Vite development under `/jogo/` | Page, favicon, entrypoint, shared CSS and logo passed |
| `make branding-visual-check` | All 14 captures preserve measured legacy element rectangles exactly |
| Tournament print cards | Both desktop/mobile captures match Phase 0 byte-for-byte |
| `make migration-visual-check` | All 14 captures match the new Phase 2 reference byte-for-byte |
| Both Docker builds | Passed |
| `make images-smoke-test` | Both containers serve HTML/JSON and referenced CSS/JS/PNG resources |
Visual review confirmed the intended palette/control-focus/panel-corner changes
and sharper canonical Shifts logo, with no layout regressions in the captured
desktop/mobile pages. `phase2/screenshots/` preserves the 14 updated PNGs,
capture manifest and measured rectangles. `phase2/verification.json` records
the geometry/print results, element counts and screenshot checksums. Phase 0
screenshots and compatibility fixtures are retained unchanged.
`make branding-visual-check` compares geometry against new captures of the
isolated legacy copies, allowing the deliberate color/asset changes. Future
`make migration-visual-check` runs use `phase2/screenshots/` as their exact
reference. Both require the Phase 0 staging/Playwright setup described above.
Container smoke checks emulate the existing Tournament proxy's `/jogo` prefix
stripping when fetching its built asset URLs. Remote CI remains unverified.
## Phase 3 — Tournament FastAPI replacement: locally verified complete
- Added the independent `crr-tournament` Python 3.13 workspace member, version
1.0.0, with FastAPI, SQLAlchemy 2, Alembic and Pydantic Settings.
- Modeled the nine existing SQLite tables with typed `Mapped` columns. The frozen
Alembic baseline creates the exact Node schema and defaults. Existing databases
are validated before stamping, including column types/nullability, defaults,
primary/unique keys, checks, indexes, foreign keys, autoincrement and group
collation. Validation/stamping/upgrades use an explicit SQLite transaction;
failed upgrades roll back schema creation. Foreign keys/WAL/5000 ms timeout
apply to every connection.
- Ported calendar generation, standard/total-points standings and head-to-head
tie breakers, brackets/byes/third-place advancement, seeding and state assembly.
- Added domain-grouped request/response schemas and separate auth/public/admin
routers. CamelCase payloads, status/error shapes, Portuguese messages, HTTP
methods and confirmation rules are preserved. Request validation also covers
JavaScript-style ID coercion, name trimming and UTF-16 length limits.
- Reproduced the Node HMAC-SHA256/base64url session format and cookie semantics;
verified a token produced by the actual Node implementation. Preserved the
five-failure/15-minute login window and successful-login reset. Synchronization
preserves serial numbering and atomic login limiting under ASGI concurrency.
- Ported notification-only SSE with retry 3000, 25-second heartbeat, exact
state-change frames and subscriber cleanup. FastAPI serves the existing Vue
build, including the legacy sub-path proxy deployment behavior.
- Switched development and production runtime to Uvicorn/Python. The image builds
Vue with Node 22 and preserves UID/GID 1000 for existing SQLite volumes. Removed
Fastify, better-sqlite3, Zod, tsx and backend-only type packages/scripts from
the frontend dependency graph after compatibility checks passed.
- Updated Makefile/CI to check both Python and frontend code. Preserved all 44
legacy HTTP/service assertions under `contracts/` and execute them unchanged
using migration-only Python transports. This compatibility command uses owned
contracts and app tooling; CI never reads/builds/runs `originals/`.
| Check | Result |
| --- | --- |
| Backend pytest | 28 passed |
| Preserved legacy assertions against Python | 44 passed: 19 HTTP/SSE, 25 deterministic services |
| Frontend/shared tests | 13 passed |
| Vue typecheck and build | Passed |
| Fresh database schema/defaults vs Node | Exact normalized SQLite DDL match |
| Python startup with populated Node fixture | All nine table contents and public state preserved |
| Incompatible schemas / migration failure | Refused without stamping; failed DDL rolled back |
| Token compatibility / cookies / limiter | Node-produced token and HTTPS/proxy/window/reset/concurrency checks passed |
| `make migration-visual-check` | All 14 screenshots match Phase 2 byte-for-byte |
| Both Docker builds | Passed |
| Container smoke checks | Fresh Tournament, adopted Node fixture and Shifts passed; referenced resources served |
| Shifts regression suite | 16 passed |
`phase3/compatibility.json` preserves passing legacy test names and contract
checksums; raw test reports remain in `.migration/port-compat/`. Database tests
copy the Node fixture before adoption; container adoption checks likewise use a
read-only fixture mount copied into tmpfs. No production database was used.
The TypeScript server source/config files are retired reference material pending
Phase 6 cleanup; production images contain only the Python backend and built
Vue assets. The Vue source directory move is also deferred to that cleanup.
Compatibility transport scaffolding remains until the corresponding cleanup
exit criteria are verified. Remote GitHub Actions execution is still unverified.
On this machine, the pyenv override described above also applies to Tournament
Makefile commands; uv still executes the applications on Python 3.13.
## Phase 4 — Shifts structural refactor: locally verified complete
- Moved bootstrap, scheduling, calendar-feed, notification and PDF modules into
`apps/shifts/backend/app/services/`, updating CLI, startup and test imports.
- Split persistence into `models/{user,team,schedule,assignment,swap,notification}.py`.
`models/__init__.py` exports the established class names; forward relationship
typing uses `TYPE_CHECKING` while SQLAlchemy registers all classes normally.
- Split password hashing/verification and CSRF-token generation into `security/`.
Session/current-user/admin checks, CSRF validation, flash/context, redirects and
navigation helpers moved into `routers/dependencies.py`.
- Replaced `web.py` with the planned auth/account/calendar/exports/dashboard/swaps
routers and admin teams/users/schedules/assignments/notifications routers.
Supporting schedule/swap/assignment/user helpers have an acyclic dependency
graph. All 48 route declarations and their forms/dependencies moved intact.
- Updated architecture/contributor/development documentation and wired the new
compatibility check into `make shifts-test` and path-aware CI.
Before changing application code, captured `phase4/baseline.json` and produced
`fixtures/shifts.sqlite` through the existing Shifts code. The fixture contains
synthetic admin/member users, a multi-team login, rotation membership, and
generated/manual/swapped assignments. It contains no production data.
The one-time structural generator relocated definitions verbatim and selected
explicit imports, refusing existing destinations. Verification compares **173
top-level function/class AST fingerprints**, ORM columns/defaults/constraints/
indexes/relationships, full OpenAPI and existing migration-file checksums against
the baseline. It copies the populated fixture before each run and confirms that
startup preserves every existing row.
| Check | Result |
| --- | --- |
| Shifts pytest | 16 passed |
| `make shifts-compat-test` | 96 request/response and resulting-data comparisons matched across `/` and `/crr` |
| ORM/schema and relationship comparison | Identical |
| Route/form/OpenAPI contract | Identical; 48 declarations retained |
| Existing migration history and function/class ASTs | Identical |
| Populated Shifts SQLite fixture on startup | All existing rows preserved |
| `make migration-visual-check` | All 14 captures match Phase 2 byte-for-byte, including print cards |
| Shifts image build | Passed after starting Docker Desktop |
| Container smoke checks | Shifts and both Tournament fresh/adopted-fixture checks passed |
| Tournament regression checks | 28 Python tests, 13 frontend/shared tests, 44 legacy assertions and Vue typecheck passed |
Workflows cover authentication/CSRF/authorization, calendar feeds, PDF exports,
swap creation/acceptance/approval/reversal, administrative CRUD, rotation edits,
assignment changes, password editing and logout. Swap operations exchange both
assignments; explicit generation retains manual/swapped rows. Timestamps, CSRF
nonces and new password salts are normalized only in comparison artefacts;
application behavior is unchanged. The verifier fixes its runtime calendar date
to the capture date so CI can reproduce default-month/navigation HTML later.
`phase4/structure.json` records router ownership and relocated function hashes;
`phase4/verification.json` records successful checks and fixture/baseline hashes.
Raw reports and difference diagnostics stay under `.migration/phase4-results/`.
`make shifts-test` includes this check; `make shifts-compat-test` runs it alone.
Do not rerun the one-time generator or overwrite the baseline. The original
snapshots and branding/compatibility baselines remain intact. Remote CI is still
unverified.
## Phase 5 — proven Python infrastructure: locally verified complete
Compared both final backends and extracted two concrete common operations into
the internal `crr-python` uv member (`src/crr_common`):
- `create_sync_engine`: synchronous SQLAlchemy construction with SQLite request-
worker threading support. It recognizes SQLAlchemy URL/dialect objects, honors
explicit connection options and forwards engine/pool options. Each app still
controls its connection pragmas, filesystem setup and session factories.
- `migration_config`: Alembic ini/script/database wiring, with absolute paths,
interpolation-safe URLs and an optional caller-owned connection. Baseline
validation, stamping/upgrades, transactions and history stay app-specific.
This removes duplicated setup without app-name switches or domain concepts.
No models, schemas, auth primitives, error messages or business rules moved into
the package. Both applications declare an explicit workspace dependency and
resolve it through the root lockfile. Local commands use the workspace install;
both independent production images install a non-editable wheel in `/opt/venv`.
Added `make python-common-test`, included it in root `make test` and both affected
app CI jobs. Shared-package paths already trigger both jobs. Docker contexts now
include the package before their frozen uv install step.
| Check | Result |
| --- | --- |
| Shared package tests | 7 passed: cross-thread SQLite, memory/pool forwarding, consumer-owned pragmas, non-SQLite/explicit options, paths/URL escaping and connection ownership |
| Tournament backend | 28 passed |
| Tournament frontend/shared + Vue typecheck/build | 13 passed; typecheck and build passed |
| Legacy Tournament assertions on Python | 44 passed |
| Shifts pytest | 16 passed |
| Shifts workflow/schema/ORM/OpenAPI compatibility | 96 requests matched across both prefixes; mappings, data and histories preserved |
| `make migration-visual-check` | All 14 screenshots match Phase 2 byte-for-byte |
| Independent Docker builds | Both passed |
| Shared-package imports in both images | Verified `/opt/venv/lib/python3.13/site-packages/crr_common` |
| Container startup/resource/adoption smoke checks | Fresh Tournament, populated Node fixture and Shifts passed |
The Phase 4 baseline remains unchanged. `phase5/shifts-definition-overrides.json`
records only the intentional `alembic_config` delegation, with exact before/after
AST fingerprints and the baseline checksum. The verifier still guards the other
172 definitions and all route/form/ORM/data/history expectations; its latest
evidence is in `phase5/shifts-compatibility.json`. Unknown changes continue to
fail rather than being normalized away. `tools/record_shared_infrastructure.py`
was the one-time manifest generator and refuses to overwrite recorded evidence.
Remote GitHub Actions execution is still unverified. The apps retain independent
versions, images, databases and auth models; `crr-python` has an internal 0.1.0
version and is released through its consuming applications.
Historical-source checksum revalidation is blocked: `originals/` is currently
empty, and both referenced legacy directories are missing. `make baseline-preserve`
therefore cannot verify their checksums. Phase 5 did not modify these directories;
its app/package tests use saved fixtures and owned compatibility contracts, all
of which remain present and pass. Snapshot retention/restoration must be resolved
before declaring the full migration acceptance criteria complete.
## Next work
1. Verify the new CI jobs on Node 22/Python 3.13 after publishing the changes.
2. Phase 6: final layout and retired Node-source cleanup, compatibility-scaffolding
review, dependency/documentation cleanup and independent release workflows.
Phase 6 is not complete.
Independent tag-triggered release workflows and final dependency cleanup belong
to later phases.