feat(maven): enforce maven_files version↔️ package consistency via composite FK
What this MR does
Closes #188 (closed).
maven_files carries two independent ancestor FKs:
maven_package_id(NOT NULL) →maven_packages(id, namespace_id)maven_version_id(nullable) →maven_versions(id, namespace_id)
Neither referenced the other, so a version-level file's maven_package_id could disagree with the package its maven_version actually belongs to — both single-column FKs pass independently and the inconsistent row lands. Not a live bug today (the intended FindOrCreatePackage → FindOrCreateVersion(pkg.ID) → Upsert*File(pkg.ID, ver.ID) flow builds the ids consistently), but unguarded for a future Steps 6–9 handler.
The fix
A new migration 20260618150000_add_maven_files_version_package_composite_fk.sql:
- Adds a unique index on
maven_versions (id, maven_package_id, namespace_id)to serve as the composite FK's referenced key ((id, namespace_id)is already the PK, so addingmaven_package_idstays unique). - Drops the standalone
fk_maven_files_maven_version_id_maven_versions. - Adds
fk_maven_files_version_belongs_to_packageon(maven_version_id, maven_package_id, namespace_id) → maven_versions (id, maven_package_id, namespace_id).
Under Postgres MATCH SIMPLE (the default), the composite FK is skipped whenever any referenced column is NULL:
- Package-level files (
maven_version_id IS NULL): composite FK not enforced; the kept standalonemaven_package_idFK still guarantees the package exists. - Version-level files (both
NOT NULL): composite FK enforced → forces the file'smaven_package_idto equal the version's package.
DB-enforced, zero application cost, and keeps ADR-007's denormalized package→files scan path intact.
Migration mechanics
Follows the 20260612130200_add_blob_storage_attachments_blob_fk.sql precedent: maven_files is HASH-partitioned, so the FK is added as a single validating ADD CONSTRAINT (Postgres forbids NOT VALID FKs on partitioned tables, SQLSTATE 42809). Safe and trivially fast here because the table is empty in the greenfield window. The squawk-ignore-file header silences mirror that precedent.
Tests
Adds four cases to internal/datastore/migrations/maven_schema_integration_test.go:
TestMavenConstraints_FilesCompositeVersionPackageFKShape— the 3-column composite FK exists and the old 2-column version-only FK is gone.TestMavenConstraints_FilesRejectVersionUnderDifferentPackage— a file claiming package A while pointing at a version under package B is rejected with SQLSTATE 23503.TestMavenConstraints_FilesAcceptConsistentVersionAndPackage— the consistent version-level flow still inserts.TestMavenConstraints_FilesPackageLevelMetadataUnaffectedByCompositeFK—maven_version_id IS NULLmetadata files still insert (MATCH SIMPLE skips the composite FK).
The migration token list is extended so the existing NO TRANSACTION / one-statement-per-drop mechanics checks cover the new file.
Out of scope / follow-ups
structure.sqlregeneration is left to CI'sdb:structure-check(mise run db:dump-structure); per project convention the dump is a generated artifact and is not hand-edited.- jet regen: index/constraint only, no model-column change — a no-op for the generated types, so no
internal/datastore/jet/**changes. - ADR-007 update documenting the composite FK is synced via the handbook repo, not edited in-tree.
Related: !564 (merged), S10 Maven hosted #21 (closed).
Database Review Evidence
Migrations
Note
Timings are from CI (db:migrate matrix, goose verbose) against an
empty database, in apply / rollback order per PG version.
Production-scale validation via Database Lab is not yet available. See
Database review evidence
for the matrix rationale and how to read the numbers.
| Migration | PG 16 | PG 17 | PG 18 |
|---|---|---|---|
20260618150000_add_maven_files_version_package_composite_fk.sql |
OK (336.02ms / 200.36ms) | OK (404.37ms / 361.23ms) | OK (414.43ms / 386.19ms) |