diff --git a/GEMINI.md b/GEMINI.md index ab00dcdb1..b46f4c986 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -44,6 +44,11 @@ This document outlines foundational mandates, architectural patterns, and projec - **Test Helpers & Timestamps:** If a static test helper method (like in `DatabaseHelper`) needs the database transaction time but might be called from outside a transaction, using `tm().reTransact(tm()::getTxTime)` is acceptable. However, NEVER wrap it redundantly like `tm().transact(() -> tm().reTransact(tm()::getTxTime))`. If you are just setting an arbitrary timestamp in a test where the exact DB transaction time isn't strictly required, prefer `Instant.now()` or `clock.now()` to avoid creating unnecessary database transactions. - **Production Code:** In production code, if a flow fails because it is calling `getTxTime()` outside of a transaction, you must wrap the *caller* in a transaction instead of adding an unnecessary `reTransact()` around `getTxTime()`. - **Transactional Time:** Ensure code that relies on `tm().getTransactionTime()` (or `tm().getTxTime()`) is executed within a transaction context. +- **Database Schema Migrations & 2-PR Split Mandate:** + - **Mandatory Consultation of `db/README.md`:** Before planning, drafting, or executing any database schema modifications (e.g., adding/altering columns, creating Flyway `.sql` migration scripts, or modifying JPA entity `@Column` mappings), you **MUST read and strictly adhere to `db/README.md`**. + - **Strict 2-PR Deployment Split:** Never propose or submit combining Flyway SQL scripts (`db/src/main/resources/sql/flyway/V*.sql`) and Java ORM changes (`.java` entity files + `db-schema.sql.generated`) into a single PR for submission to `master`. Because live servers during a rolling deployment will fail if Java code attempts to access unmigrated database columns/constraints, all schema additions must be split into two sequential PRs per `db/README.md`: + 1. **PR #1 (Database Schema Only):** Contains *only* the new Flyway `.sql` script, the `flyway.txt` index update (`:db:generateFlywayIndex`), the `nomulus.golden.sql` dump (`:nom:generate_golden_file`), and ER diagrams (`er_diagram/`). Must contain **zero `.java` files or `db-schema.sql.generated` changes**. + 2. **PR #2 (Java ORM, EPP Flows & Generated Schema Map):** Submitted *only after* PR #1 is deployed to production. Contains all `.java` entity/flow modifications, tests, and the regenerated `db-schema.sql.generated` (`generateSqlSchema`). ### 5. Testing Best Practices - **Mandatory Proactive Testing:** You MUST automatically write and update tests alongside your code changes WITHOUT waiting for the user to prompt you. If you add a new feature, fix a bug, or change core logic, you are explicitly required to identify the corresponding `*Test.java` file and implement comprehensive test coverage for your changes. @@ -51,6 +56,7 @@ This document outlines foundational mandates, architectural patterns, and projec - **Empirical Reproduction:** Before fixing a bug, always create a test case that reproduces the failure. - **Base Classes:** Leverage `CommandTestCase`, `EppToolCommandTestCase`, etc., to reduce boilerplate and ensure consistent setup (e.g., clock initialization). - **Gradle Test Patterns:** When running tests to investigate fixes in the "core" directory, try to first use the "standardTest" Gradle task. It is faster than the "test" task, which includes the "fragileTest" task. Only run the full "test" task after "standardTest" succeeds. +- **Mandatory SQL Integration Verification:** Whenever you modify any database schema, Flyway script (`.sql`), or JPA entity class, you MUST explicitly run both `./gradlew :db:test` and `./gradlew :core:sqlIntegrationTest` **in addition to** the standard test suites (`./gradlew standardTest` or `./gradlew test`) before finalizing the task or declaring completion. Do not rely solely on `standardTest` when database schemas or ORM mappings are touched; all three test suites (`standardTest`, `:db:test`, and `:core:sqlIntegrationTest`) are mandatory. ### 6. Project Dependencies - **Common Module:** When using `Clock` or other core utilities in a new or separate module (like `load-testing`), ensure `implementation project(':common')` is added to the module's `build.gradle`. diff --git a/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/ConsoleScreenshotTest_globalRole_registrars_registrarsPage.png b/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/ConsoleScreenshotTest_globalRole_registrars_registrarsPage.png index c07b26e9c..1c88e507e 100644 Binary files a/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/ConsoleScreenshotTest_globalRole_registrars_registrarsPage.png and b/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/ConsoleScreenshotTest_globalRole_registrars_registrarsPage.png differ diff --git a/db/src/main/resources/sql/er_diagram/brief_er_diagram.html b/db/src/main/resources/sql/er_diagram/brief_er_diagram.html index 757f25026..4fccb7d5f 100644 --- a/db/src/main/resources/sql/er_diagram/brief_er_diagram.html +++ b/db/src/main/resources/sql/er_diagram/brief_er_diagram.html @@ -261,11 +261,11 @@ td.section { generated on - 2026-06-23 01:40:35 + 2026-07-06 17:57:29 last flyway file - V223__tld_change_xap_enabled_to_transitions.sql + V224__add_registrar_expiry_access_period_enabled.sql @@ -273,7 +273,7 @@ td.section {

 

- SchemaCrawler_Diagram generated by SchemaCrawler 17.11.1 generated on 2026-06-23 01:40:35 + SchemaCrawler_Diagram generated by SchemaCrawler 17.11.1 generated on 2026-07-06 17:57:29 allocationtoken_a08ccbef public."AllocationToken" [table] token text not null domain_name text redemption_domain_repo_id text token_type text diff --git a/db/src/main/resources/sql/er_diagram/full_er_diagram.html b/db/src/main/resources/sql/er_diagram/full_er_diagram.html index 3c92ea897..c7efadbb9 100644 --- a/db/src/main/resources/sql/er_diagram/full_er_diagram.html +++ b/db/src/main/resources/sql/er_diagram/full_er_diagram.html @@ -261,11 +261,11 @@ td.section {
generated on - 2026-06-23 01:40:34 + 2026-07-06 17:57:27
last flyway file - V223__tld_change_xap_enabled_to_transitions.sql + V224__add_registrar_expiry_access_period_enabled.sql
@@ -273,7 +273,7 @@ td.section {

 

- SchemaCrawler_Diagram generated by SchemaCrawler 17.11.1 generated on 2026-06-23 01:40:34 + SchemaCrawler_Diagram generated by SchemaCrawler 17.11.1 generated on 2026-07-06 17:57:27 allocationtoken_a08ccbef public."AllocationToken" [table] token text not null update_timestamp timestamptz allowed_registrar_ids _text allowed_tlds _text creation_time timestamptz not null discount_fraction float8(17, 17) not null discount_premiums bool not null discount_years int4 not null domain_name text redemption_domain_repo_id text token_status_transitions hstore token_type text redemption_domain_history_id int8 renewal_price_behavior text not null registration_behavior text not null allowed_epp_actions _text renewal_price_amount numeric(19, 2) renewal_price_currency text discount_price_amount numeric(19, 2) discount_price_currency text @@ -295,11 +295,11 @@ td.section { - registrar_6e1503e3 public."Registrar" [table] registrar_id text not null allowed_tlds _text billing_account_map hstore block_premium_names bool not null client_certificate text client_certificate_hash text contacts_require_syncing bool not null creation_time timestamptz not null drive_folder_id text email_address text failover_client_certificate text failover_client_certificate_hash text fax_number text iana_identifier int8 icann_referral_email text i18n_address_city text i18n_address_country_code text i18n_address_state text i18n_address_street_line1 text i18n_address_street_line2 text i18n_address_street_line3 text i18n_address_zip text ip_address_allow_list _text last_certificate_update_time timestamptz last_update_time timestamptz not null localized_address_city text localized_address_country_code text localized_address_state text localized_address_street_line1 text localized_address_street_line2 text localized_address_street_line3 text localized_address_zip text password_hash text phone_number text phone_passcode text po_number text rdap_base_urls _text registrar_name text not null registry_lock_allowed bool not null password_salt text state text type text not null url text whois_server text last_expiring_cert_notification_sent_date timestamptz last_expiring_failover_cert_notification_sent_date timestamptz last_poc_verification_date timestamptz + registrar_6e1503e3 public."Registrar" [table] registrar_id text not null allowed_tlds _text billing_account_map hstore block_premium_names bool not null client_certificate text client_certificate_hash text contacts_require_syncing bool not null creation_time timestamptz not null drive_folder_id text email_address text failover_client_certificate text failover_client_certificate_hash text fax_number text iana_identifier int8 icann_referral_email text i18n_address_city text i18n_address_country_code text i18n_address_state text i18n_address_street_line1 text i18n_address_street_line2 text i18n_address_street_line3 text i18n_address_zip text ip_address_allow_list _text last_certificate_update_time timestamptz last_update_time timestamptz not null localized_address_city text localized_address_country_code text localized_address_state text localized_address_street_line1 text localized_address_street_line2 text localized_address_street_line3 text localized_address_zip text password_hash text phone_number text phone_passcode text po_number text rdap_base_urls _text registrar_name text not null registry_lock_allowed bool not null password_salt text state text type text not null url text whois_server text last_expiring_cert_notification_sent_date timestamptz last_expiring_failover_cert_notification_sent_date timestamptz last_poc_verification_date timestamptz expiry_access_period_enabled bool not null - billingevent_a57d1815:w->registrar_6e1503e3:e fk_billing_event_registrar_id + billingevent_a57d1815:w->registrar_6e1503e3:e fk_billing_event_registrar_id @@ -331,23 +331,23 @@ td.section { - domain_6c51cffa:w->registrar_6e1503e3:e fk2jc69qyg2tv9hhnmif6oa1cx1 + domain_6c51cffa:w->registrar_6e1503e3:e fk2jc69qyg2tv9hhnmif6oa1cx1 - domain_6c51cffa:w->registrar_6e1503e3:e fk2u3srsfbei272093m3b3xwj23 + domain_6c51cffa:w->registrar_6e1503e3:e fk2u3srsfbei272093m3b3xwj23 - domain_6c51cffa:w->registrar_6e1503e3:e fkjc0r9r5y1lfbt4gpbqw4wsuvq + domain_6c51cffa:w->registrar_6e1503e3:e fkjc0r9r5y1lfbt4gpbqw4wsuvq - domain_6c51cffa:w->registrar_6e1503e3:e fk_domain_transfer_gaining_registrar_id + domain_6c51cffa:w->registrar_6e1503e3:e fk_domain_transfer_gaining_registrar_id - domain_6c51cffa:w->registrar_6e1503e3:e fk_domain_transfer_losing_registrar_id + domain_6c51cffa:w->registrar_6e1503e3:e fk_domain_transfer_losing_registrar_id @@ -371,7 +371,7 @@ td.section { - domainhistory_a54cc226:w->registrar_6e1503e3:e fk_domain_history_registrar_id + domainhistory_a54cc226:w->registrar_6e1503e3:e fk_domain_history_registrar_id @@ -383,7 +383,7 @@ td.section { - billingcancellation_6eedf614:w->registrar_6e1503e3:e fk_billing_cancellation_registrar_id + billingcancellation_6eedf614:w->registrar_6e1503e3:e fk_billing_cancellation_registrar_id @@ -403,11 +403,11 @@ td.section { - graceperiod_cd3b2e8f:w->registrar_6e1503e3:e fk_grace_period_registrar_id + graceperiod_cd3b2e8f:w->registrar_6e1503e3:e fk_grace_period_registrar_id - billingrecurrence_5fa2cb01:w->registrar_6e1503e3:e fk_billing_recurrence_registrar_id + billingrecurrence_5fa2cb01:w->registrar_6e1503e3:e fk_billing_recurrence_registrar_id @@ -495,15 +495,15 @@ td.section { - host_f21b78de:w->registrar_6e1503e3:e fk_host_creation_registrar_id + host_f21b78de:w->registrar_6e1503e3:e fk_host_creation_registrar_id - host_f21b78de:w->registrar_6e1503e3:e fk_host_current_sponsor_registrar_id + host_f21b78de:w->registrar_6e1503e3:e fk_host_current_sponsor_registrar_id - host_f21b78de:w->registrar_6e1503e3:e fk_host_last_epp_update_registrar_id + host_f21b78de:w->registrar_6e1503e3:e fk_host_last_epp_update_registrar_id @@ -531,15 +531,15 @@ td.section { - pollmessage_614a523e:w->registrar_6e1503e3:e fk_poll_message_registrar_id + pollmessage_614a523e:w->registrar_6e1503e3:e fk_poll_message_registrar_id - pollmessage_614a523e:w->registrar_6e1503e3:e fk_poll_message_transfer_response_gaining_registrar_id + pollmessage_614a523e:w->registrar_6e1503e3:e fk_poll_message_transfer_response_gaining_registrar_id - pollmessage_614a523e:w->registrar_6e1503e3:e fk_poll_message_transfer_response_losing_registrar_id + pollmessage_614a523e:w->registrar_6e1503e3:e fk_poll_message_transfer_response_losing_registrar_id @@ -579,7 +579,7 @@ td.section { - hosthistory_56210c2:w->registrar_6e1503e3:e fk_history_registrar_id + hosthistory_56210c2:w->registrar_6e1503e3:e fk_history_registrar_id @@ -615,7 +615,7 @@ td.section { - registrarpoc_ab47054d:w->registrar_6e1503e3:e fk_registrar_poc_registrar_id + registrarpoc_ab47054d:w->registrar_6e1503e3:e fk_registrar_poc_registrar_id @@ -623,7 +623,7 @@ td.section { - registrarupdatehistory_8a38bed4:w->registrar_6e1503e3:e fkregistrarupdatehistoryregistrarid + registrarupdatehistory_8a38bed4:w->registrar_6e1503e3:e fkregistrarupdatehistoryregistrarid @@ -6807,6 +6807,16 @@ td.section { last_poc_verification_date timestamptz +
+ + expiry_access_period_enabled + bool not null +
+
+ + + default false +
diff --git a/db/src/main/resources/sql/flyway.txt b/db/src/main/resources/sql/flyway.txt index 8cfc53dcb..c7823c18a 100644 --- a/db/src/main/resources/sql/flyway.txt +++ b/db/src/main/resources/sql/flyway.txt @@ -221,3 +221,4 @@ V220__domain_package_token_idx.sql V221__remove_contact_history.sql V222__remove_contact.sql V223__tld_change_xap_enabled_to_transitions.sql +V224__add_registrar_expiry_access_period_enabled.sql diff --git a/db/src/main/resources/sql/flyway/V224__add_registrar_expiry_access_period_enabled.sql b/db/src/main/resources/sql/flyway/V224__add_registrar_expiry_access_period_enabled.sql new file mode 100644 index 000000000..3b1e16acd --- /dev/null +++ b/db/src/main/resources/sql/flyway/V224__add_registrar_expiry_access_period_enabled.sql @@ -0,0 +1,20 @@ +-- Copyright 2026 The Nomulus Authors. All Rights Reserved. +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- Add the XAP opt-in column to Registrar, defaulting to false for all existing registrars. +-- To ensure backward compatibility with running servers (old Java code) during +-- the transition phase of the deployment, we set DEFAULT false NOT NULL. +-- TODO(mcilwain): Drop this DEFAULT constraint in a subsequent schema release once the Java code has been fully deployed. +ALTER TABLE "Registrar" ADD COLUMN expiry_access_period_enabled boolean + DEFAULT false NOT NULL; diff --git a/db/src/main/resources/sql/schema/nomulus.golden.sql b/db/src/main/resources/sql/schema/nomulus.golden.sql index d45bf2060..27dc39a90 100644 --- a/db/src/main/resources/sql/schema/nomulus.golden.sql +++ b/db/src/main/resources/sql/schema/nomulus.golden.sql @@ -855,7 +855,8 @@ CREATE TABLE public."Registrar" ( whois_server text, last_expiring_cert_notification_sent_date timestamp with time zone, last_expiring_failover_cert_notification_sent_date timestamp with time zone, - last_poc_verification_date timestamp with time zone + last_poc_verification_date timestamp with time zone, + expiry_access_period_enabled boolean DEFAULT false NOT NULL );