feat: monorepo migration phase 5
App checks / changes (push) Canceled after 0s
App checks / tournament (push) Canceled after 0s
App checks / shifts (push) Canceled after 0s

This commit is contained in:
2026-10-07 15:20:27 +01:00
parent d534588e27
commit 66b72d1348
28 changed files with 406 additions and 26 deletions
+58 -3
View File
@@ -286,12 +286,67 @@ 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 5: compare the two final Python backends and extract only infrastructure
that demonstrably removes generic duplication without app-specific switches.
2. Phase 6: final layout and retired Node-source cleanup, compatibility-scaffolding
review, dependency/documentation cleanup and independent release workflows.
Phases 5–6 are not complete. No shared Python package has been extracted yet.
Phase 6 is not complete.
Independent tag-triggered release workflows and final dependency cleanup belong
to later phases.
+16 -2
View File
@@ -296,10 +296,20 @@ def main(backend: Path, capture: bool):
output.write_text(json.dumps(result, indent=2, ensure_ascii=False, sort_keys=True) + "\n")
if not capture:
baseline = json.loads((REPORT / "baseline.json").read_text())
override_path = ROOT / "migration/phase5/shifts-definition-overrides.json"
overrides = {}
if override_path.exists():
override_report = json.loads(override_path.read_text())
assert override_report["baseline_sha256"] == hashlib.sha256((REPORT / "baseline.json").read_bytes()).hexdigest()
overrides = override_report["definitions"]
for name, fingerprints in overrides.items():
assert baseline["definitions"][name] == fingerprints["before"], name
assert result["definitions"][name] == fingerprints["after"], name
baseline["definitions"][name] = fingerprints["after"]
comparison = differences(normalized_report(baseline), normalized_report(result))
(results / "differences.json").write_text(json.dumps(comparison, indent=2, ensure_ascii=False) + "\n")
assert not comparison, f"{len(comparison)} structural/workflow differences; inspect .migration/phase4-results/differences.json"
verification = {"definitions_preserved": len(result["definitions"]),
verification = {"definitions_preserved": len(result["definitions"]) - len(overrides),
"orm_mapping_and_relationships_unchanged": True,
"openapi_unchanged": True, "migration_files_unchanged": True,
"existing_fixture_rows_preserved_at_startup": True,
@@ -307,7 +317,11 @@ def main(backend: Path, capture: bool):
"deployment_prefixes": list(result["workflows"]),
"fixture_sha256": hashlib.sha256(FIXTURE.read_bytes()).hexdigest(),
"baseline_sha256": hashlib.sha256((REPORT / "baseline.json").read_bytes()).hexdigest()}
(REPORT / "verification.json").write_text(json.dumps(verification, indent=2) + "\n")
verification_path = REPORT / "verification.json"
if overrides:
verification["shared_infrastructure_delegations"] = overrides
verification_path = ROOT / "migration/phase5/shifts-compatibility.json"
verification_path.write_text(json.dumps(verification, indent=2) + "\n")
print(f"Shifts {'baseline captured' if capture else 'structural/workflow parity verified'}: {sum(len(value['requests']) for value in result['workflows'].values())} requests across both deployment prefixes.")
@@ -0,0 +1,21 @@
{
"definitions_preserved": 172,
"orm_mapping_and_relationships_unchanged": true,
"openapi_unchanged": true,
"migration_files_unchanged": true,
"existing_fixture_rows_preserved_at_startup": true,
"workflow_requests_matched": 96,
"deployment_prefixes": [
"",
"/crr"
],
"fixture_sha256": "95b06188ce5076a17b842523c6a969358cdd48e30221de4292e6628f40c9b292",
"baseline_sha256": "2ecd64d83d35d482f8eed7f46b65754f6db30d34c48a06943c0b9bd11ddf2b59",
"shared_infrastructure_delegations": {
"alembic_config": {
"before": "2952ea6c5985853514aae3e8a7b74f0f1d0d49b56b45ca0c82874d7673ae574f",
"after": "e69c3eca19d4867bd29a0809a5676e41618033fe08be2920c9216fc359cb7440",
"shared_helper": "crr_common.migrations.migration_config"
}
}
}
@@ -0,0 +1,10 @@
{
"baseline_sha256": "2ecd64d83d35d482f8eed7f46b65754f6db30d34c48a06943c0b9bd11ddf2b59",
"definitions": {
"alembic_config": {
"before": "2952ea6c5985853514aae3e8a7b74f0f1d0d49b56b45ca0c82874d7673ae574f",
"after": "e69c3eca19d4867bd29a0809a5676e41618033fe08be2920c9216fc359cb7440",
"shared_helper": "crr_common.migrations.migration_config"
}
}
}