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.jsonandbaseline/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.jsonandtournament-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
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 workingsrc/{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:
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-brandwith one canonical Tournament logo (2324×2051) and favicon, copied byte-for-byte from the baseline. Shared CSS defines the red#ed1c24palette, 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-brandalias and public asset directory; shared files also work in Vite development under/jogo/. - Shifts mounts
/brand, generates prefixed asset URLs through Jinjaurl_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-tournamentPython 3.13 workspace member, version 1.0.0, with FastAPI, SQLAlchemy 2, Alembic and Pydantic Settings. - Modeled the nine existing SQLite tables with typed
Mappedcolumns. 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/runsoriginals/.
| 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__.pyexports the established class names; forward relationship typing usesTYPE_CHECKINGwhile 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 intorouters/dependencies.py. - Replaced
web.pywith 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-testand 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
- Verify the new CI jobs on Node 22/Python 3.13 after publishing the changes.
- 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.