From c81c9ab3ed25fd533987042078bcb9ef63b6ba11 Mon Sep 17 00:00:00 2001 From: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> Date: Fri, 31 Jul 2026 01:16:27 +0800 Subject: [PATCH] fix(db): resolve identity migration version collision Preserve the V44 compliance index reserved by PR #561 and renumber the unreleased identity migrations to V45-V48. Refs #561 Refs #652 Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> --- docs/verification/issue-640.md | 8 ++++---- .../tests/identity-binding-v2-postgres-test.sh | 6 +++--- .../V44__skill_version_compliance_index.sql | 3 +++ ... V45__identity_provider_authority_state.sql} | 0 ....sql => V46__identity_binding_v2_expand.sql} | 0 ... V47__identity_binding_v2_contract_gate.sql} | 2 +- ...e.sql => V48__user_profile_field_source.sql} | 0 .../IdentityBindingV2ContractPostgresTest.java | 6 +++--- .../IdentityBindingV2MigrationPostgresTest.java | 10 +++++----- ...ProfileFieldSourceMigrationPostgresTest.java | 4 ++-- .../db/FlywayMigrationGuardrailTest.java | 17 +++++++++++++++-- 11 files changed, 36 insertions(+), 20 deletions(-) create mode 100644 server/skillhub-app/src/main/resources/db/migration/V44__skill_version_compliance_index.sql rename server/skillhub-app/src/main/resources/db/migration/{V44__identity_provider_authority_state.sql => V45__identity_provider_authority_state.sql} (100%) rename server/skillhub-app/src/main/resources/db/migration/{V45__identity_binding_v2_expand.sql => V46__identity_binding_v2_expand.sql} (100%) rename server/skillhub-app/src/main/resources/db/migration/{V46__identity_binding_v2_contract_gate.sql => V47__identity_binding_v2_contract_gate.sql} (98%) rename server/skillhub-app/src/main/resources/db/migration/{V47__user_profile_field_source.sql => V48__user_profile_field_source.sql} (100%) diff --git a/docs/verification/issue-640.md b/docs/verification/issue-640.md index fb1ebc0d..3fd2370b 100644 --- a/docs/verification/issue-640.md +++ b/docs/verification/issue-640.md @@ -38,7 +38,7 @@ They did not connect to or modify the shared test-environment database. | Scenario | Observable result | |---|---| -| Fresh migration | Flyway V44 applied successfully and created `identity_provider_state` | +| Fresh migration | Flyway V45 applied successfully and created `identity_provider_state` after the reserved V44 compliance index | | Fixed GitHub authority vector | `oauth2-github`, `https://github.com`, fingerprint `b2a93d58465e3de9e8b6cd127ba18425ae0f80c49c85f18f76086832923ca619`, state `READY` | | Concurrent first pin | Two application instances converged to one READY row with the same fingerprint; no unique-constraint error | | Legacy OAuth binding | Existing `identity_binding` row remained byte-for-byte equivalent while the provider moved through first pin to READY | @@ -48,8 +48,8 @@ They did not connect to or modify the shared test-environment database. | Stale READY mismatch window | Recovery returned 409, persisted `AUTHORITY_MISMATCH`, retained the pinned authority/fingerprint, and wrote no recovery audit | | Transaction rollback | A forced audit insert failure returned 500; the provider state update rolled back and no audit record was added | | Unknown provider routes | Authorization and callback routes returned 403 without an upstream redirect | -| V43 to V44 upgrade | A database initialized by `v0.2.15` upgraded successfully and retained its legacy OAuth binding | -| Mixed-version and rollback | Current and `v0.2.15` servers were simultaneously healthy against the V44 database; the old provider endpoint returned 200 | +| V43 to V45 upgrade | A database initialized by `v0.2.15` upgraded successfully through reserved V44 and retained its legacy OAuth binding | +| Mixed-version and rollback | Current and `v0.2.15` servers were simultaneously healthy against the V45 database; the old provider endpoint returned 200 | | Redis session compatibility | A local session created by `v0.2.15` was accepted by the current server for the same user | ## Remaining integration gate @@ -60,7 +60,7 @@ the merge commit, deploy them to the shared test environment, and verify: 1. health, login catalog, and local-password login through the configured test domain; 2. unknown provider authorization/callback rejection; -3. V44 migration and READY provider state in the shared database; +3. reserved V44 compatibility migration, V45 identity migration, and READY provider state in the shared database; 4. existing Redis sessions and OAuth bindings; 5. recovery authorization and audit behavior; 6. logs contain no credentials or unexpected identity errors. diff --git a/scripts/tests/identity-binding-v2-postgres-test.sh b/scripts/tests/identity-binding-v2-postgres-test.sh index 4735b42d..30f0d698 100755 --- a/scripts/tests/identity-binding-v2-postgres-test.sh +++ b/scripts/tests/identity-binding-v2-postgres-test.sh @@ -128,7 +128,7 @@ run_test() { } run_test IdentityBindingV2MigrationPostgresTest -run_test IdentityBindingV2ContractPostgresTest 46 +run_test IdentityBindingV2ContractPostgresTest 47 run_test UserProfileFieldSourceMigrationPostgresTest -run_test IdentityBindingV2PostgresIntegrationTest 47 -run_test IdentityProfileProvisioningPostgresIntegrationTest 47 +run_test IdentityBindingV2PostgresIntegrationTest 48 +run_test IdentityProfileProvisioningPostgresIntegrationTest 48 diff --git a/server/skillhub-app/src/main/resources/db/migration/V44__skill_version_compliance_index.sql b/server/skillhub-app/src/main/resources/db/migration/V44__skill_version_compliance_index.sql new file mode 100644 index 00000000..e69d8458 --- /dev/null +++ b/server/skillhub-app/src/main/resources/db/migration/V44__skill_version_compliance_index.sql @@ -0,0 +1,3 @@ +CREATE INDEX IF NOT EXISTS idx_skill_version_compliance_mappings +ON skill_version +USING GIN ((parsed_metadata_json -> 'frontmatter' -> 'x-astron-compliance')); diff --git a/server/skillhub-app/src/main/resources/db/migration/V44__identity_provider_authority_state.sql b/server/skillhub-app/src/main/resources/db/migration/V45__identity_provider_authority_state.sql similarity index 100% rename from server/skillhub-app/src/main/resources/db/migration/V44__identity_provider_authority_state.sql rename to server/skillhub-app/src/main/resources/db/migration/V45__identity_provider_authority_state.sql diff --git a/server/skillhub-app/src/main/resources/db/migration/V45__identity_binding_v2_expand.sql b/server/skillhub-app/src/main/resources/db/migration/V46__identity_binding_v2_expand.sql similarity index 100% rename from server/skillhub-app/src/main/resources/db/migration/V45__identity_binding_v2_expand.sql rename to server/skillhub-app/src/main/resources/db/migration/V46__identity_binding_v2_expand.sql diff --git a/server/skillhub-app/src/main/resources/db/migration/V46__identity_binding_v2_contract_gate.sql b/server/skillhub-app/src/main/resources/db/migration/V47__identity_binding_v2_contract_gate.sql similarity index 98% rename from server/skillhub-app/src/main/resources/db/migration/V46__identity_binding_v2_contract_gate.sql rename to server/skillhub-app/src/main/resources/db/migration/V47__identity_binding_v2_contract_gate.sql index 211f6dd8..b3f28a30 100644 --- a/server/skillhub-app/src/main/resources/db/migration/V46__identity_binding_v2_contract_gate.sql +++ b/server/skillhub-app/src/main/resources/db/migration/V47__identity_binding_v2_contract_gate.sql @@ -1,7 +1,7 @@ -- Binding V2 contract gate. -- -- Deploy this migration only after every pre-Binding-V2 application instance --- has exited. Unlike the V45 expand migration, this gate rejects transactions +-- has exited. Unlike the V46 expand migration, this gate rejects transactions -- that leave an ACTIVE binding without exactly one ACTIVE primary subject. DO $$ diff --git a/server/skillhub-app/src/main/resources/db/migration/V47__user_profile_field_source.sql b/server/skillhub-app/src/main/resources/db/migration/V48__user_profile_field_source.sql similarity index 100% rename from server/skillhub-app/src/main/resources/db/migration/V47__user_profile_field_source.sql rename to server/skillhub-app/src/main/resources/db/migration/V48__user_profile_field_source.sql diff --git a/server/skillhub-app/src/test/java/com/iflytek/skillhub/auth/identity/IdentityBindingV2ContractPostgresTest.java b/server/skillhub-app/src/test/java/com/iflytek/skillhub/auth/identity/IdentityBindingV2ContractPostgresTest.java index e03807a8..a12c1f93 100644 --- a/server/skillhub-app/src/test/java/com/iflytek/skillhub/auth/identity/IdentityBindingV2ContractPostgresTest.java +++ b/server/skillhub-app/src/test/java/com/iflytek/skillhub/auth/identity/IdentityBindingV2ContractPostgresTest.java @@ -37,7 +37,7 @@ class IdentityBindingV2ContractPostgresTest { .schemas(PREFLIGHT_SCHEMA) .defaultSchema(PREFLIGHT_SCHEMA) .createSchemas(true) - .target(MigrationVersion.fromVersion("45")) + .target(MigrationVersion.fromVersion("46")) .load() .migrate(); @@ -95,7 +95,7 @@ class IdentityBindingV2ContractPostgresTest { WHERE success = TRUE ORDER BY installed_rank DESC LIMIT 1 - """)).isEqualTo("45"); + """)).isEqualTo("46"); assertThat(singleLong( statement, """ @@ -126,7 +126,7 @@ class IdentityBindingV2ContractPostgresTest { database.username(), database.password()) .locations("classpath:db/migration") - .target(MigrationVersion.fromVersion("46")) + .target(MigrationVersion.fromVersion("47")) .load() .migrate(); diff --git a/server/skillhub-app/src/test/java/com/iflytek/skillhub/auth/identity/IdentityBindingV2MigrationPostgresTest.java b/server/skillhub-app/src/test/java/com/iflytek/skillhub/auth/identity/IdentityBindingV2MigrationPostgresTest.java index 6f1f1192..ad39bb2f 100644 --- a/server/skillhub-app/src/test/java/com/iflytek/skillhub/auth/identity/IdentityBindingV2MigrationPostgresTest.java +++ b/server/skillhub-app/src/test/java/com/iflytek/skillhub/auth/identity/IdentityBindingV2MigrationPostgresTest.java @@ -41,7 +41,7 @@ class IdentityBindingV2MigrationPostgresTest { Flyway.configure() .dataSource(url, username, password) .locations("classpath:db/migration") - .target(MigrationVersion.fromVersion("44")) + .target(MigrationVersion.fromVersion("45")) .load() .migrate(); @@ -91,7 +91,7 @@ class IdentityBindingV2MigrationPostgresTest { Flyway.configure() .dataSource(url, username, password) .locations("classpath:db/migration") - .target(MigrationVersion.fromVersion("45")) + .target(MigrationVersion.fromVersion("46")) .load() .migrate(); @@ -237,7 +237,7 @@ class IdentityBindingV2MigrationPostgresTest { .schemas(PREFLIGHT_SCHEMA) .defaultSchema(PREFLIGHT_SCHEMA) .createSchemas(true) - .target(MigrationVersion.fromVersion("44")) + .target(MigrationVersion.fromVersion("45")) .load() .migrate(); @@ -315,7 +315,7 @@ class IdentityBindingV2MigrationPostgresTest { .schemas(PREFLIGHT_SCHEMA) .defaultSchema(PREFLIGHT_SCHEMA) .createSchemas(true) - .target(MigrationVersion.fromVersion("45")) + .target(MigrationVersion.fromVersion("46")) .load() .migrate()); @@ -343,7 +343,7 @@ class IdentityBindingV2MigrationPostgresTest { WHERE success = TRUE ORDER BY installed_rank DESC LIMIT 1 - """)).isEqualTo("44"); + """)).isEqualTo("45"); assertThat(singleLong( statement, """ diff --git a/server/skillhub-app/src/test/java/com/iflytek/skillhub/auth/identity/UserProfileFieldSourceMigrationPostgresTest.java b/server/skillhub-app/src/test/java/com/iflytek/skillhub/auth/identity/UserProfileFieldSourceMigrationPostgresTest.java index 7fb876b7..b448f9b6 100644 --- a/server/skillhub-app/src/test/java/com/iflytek/skillhub/auth/identity/UserProfileFieldSourceMigrationPostgresTest.java +++ b/server/skillhub-app/src/test/java/com/iflytek/skillhub/auth/identity/UserProfileFieldSourceMigrationPostgresTest.java @@ -37,7 +37,7 @@ class UserProfileFieldSourceMigrationPostgresTest { .schemas(SCHEMA) .defaultSchema(SCHEMA) .createSchemas(true) - .target(MigrationVersion.fromVersion("46")) + .target(MigrationVersion.fromVersion("47")) .load() .migrate(); @@ -86,7 +86,7 @@ class UserProfileFieldSourceMigrationPostgresTest { .schemas(SCHEMA) .defaultSchema(SCHEMA) .createSchemas(true) - .target(MigrationVersion.fromVersion("47")) + .target(MigrationVersion.fromVersion("48")) .load() .migrate(); diff --git a/server/skillhub-app/src/test/java/com/iflytek/skillhub/db/FlywayMigrationGuardrailTest.java b/server/skillhub-app/src/test/java/com/iflytek/skillhub/db/FlywayMigrationGuardrailTest.java index eb8627af..7204d418 100644 --- a/server/skillhub-app/src/test/java/com/iflytek/skillhub/db/FlywayMigrationGuardrailTest.java +++ b/server/skillhub-app/src/test/java/com/iflytek/skillhub/db/FlywayMigrationGuardrailTest.java @@ -102,11 +102,24 @@ class FlywayMigrationGuardrailTest { assertThat(migration).contains("bad_namespace.slug <> 'global'"); } + @Test + void complianceIndexMigration_mustMatchReservedV44() throws IOException { + String migration = Files.readString( + migrationPath( + "V44__skill_version_compliance_index.sql")); + + assertThat(migration).isEqualTo(""" + CREATE INDEX IF NOT EXISTS idx_skill_version_compliance_mappings + ON skill_version + USING GIN ((parsed_metadata_json -> 'frontmatter' -> 'x-astron-compliance')); + """); + } + @Test void identityBindingContractGate_mustRemainDeferred() throws IOException { String migration = Files.readString( migrationPath( - "V46__identity_binding_v2_contract_gate.sql")); + "V47__identity_binding_v2_contract_gate.sql")); assertThat(migration) .contains("CREATE CONSTRAINT TRIGGER") @@ -120,7 +133,7 @@ class FlywayMigrationGuardrailTest { throws IOException { String migration = Files.readString( migrationPath( - "V47__user_profile_field_source.sql")); + "V48__user_profile_field_source.sql")); assertThat(migration) .contains("LEGACY_LOCAL")