This commit relaxes the upper bounds on several dependencies that were previously hardcapped to specific versions: - com.google.protobuf to [3.25.5,) and [3.17.3,) - org.apache.beam to [2.72.0,) - io.github.ss-bhatt to [1.0.0,) - io.protostuff to [1.8.0,) - redis.clients:jedis to [7.4.1,) - org.junit.jupiter and org.junit.platform to [5.6.2,) and [1.6.2,) - org.jcommander to [2.0,) - org.jline to [3.0,) - jakarta.servlet to [6.0,) Upgrading to the modern versions of jline introduced a breaking change where DefaultParser().parse(line, line.length()) strips trailing spaces when using the default ParseContext.UNSPECIFIED. This caused the autocompletion to misbehave and tests to fail. This commit fixes ShellCommandTest.java by explicitly passing ParseContext.COMPLETE when parsing test strings to perfectly mimic the real-world JLine completion context. Additionally, SqlIntegrationTestSuite was migrated to JUnit 5's @Suite annotation, fixing a NoClassDefFoundError introduced by uncapping the JUnit Platform dependencies, and the test suite was re-integrated into the standard :build lifecycle. The following dependencies remain explicitly capped: 1. Hibernate & Jakarta Persistence (Blocked by -Werror): These are held back because newer Jakarta Persistence versions deprecate executeUpdate(), setMaxResults(), and getResultStream() on Query. - org.hibernate.orm:hibernate-core:7.3.4.Final - org.hibernate.orm:hibernate-hikaricp:7.3.4.Final - org.hibernate.orm:hibernate-ant:7.3.4.Final - jakarta.persistence:jakarta.persistence-api:[3.2.0,4.0.0) 2. Netty (Blocked by abandoned v5): Netty 5.0.0 was an experimental release abandoned in 2015. We explicitly cap beneath 5.0.0 so Gradle doesn't resolve dead-end alphas. - io.netty:netty-codec-http:[4.1.59.Final, 5.0.0)!! - io.netty:netty-codec:[4.1.59.Final, 5.0.0)!! - io.netty:netty-common:[4.1.59.Final, 5.0.0)!! - io.netty:netty-handler:[4.1.59.Final, 5.0.0)!! - io.netty:netty-transport:[4.1.59.Final, 5.0.0)!! - io.netty:netty-buffer:[4.1.59.Final, 5.0.0)!! 3. Google API Services: Capped beneath their respective unstable beta/v1b4 versions: - com.google.apis:google-api-services-dataflow:[v1b3-rev20240430-2.0.0, v1b4)!! - com.google.apis:google-api-services-dns:[v1-rev20240419-2.0.0, v2beta) The lockfiles have been fully regenerated and all test suites ran successfully against the latest available transitive versions.
22 KiB
Engineering Standards for Gemini CLI
This document outlines foundational mandates, architectural patterns, and project-specific conventions to ensure high-quality, idiomatic, and consistent code from the first iteration. When modifying this file, always review the full document to prevent the introduction of duplicate instructions and ensure the content remains coherent and logically organized.
Core Mandates
1. Rigorous Import Management
- Addition: When adding new symbols, ensure the corresponding import is added.
- Removal: When removing the last usage of a class or symbol from a file (e.g., removing a
@Inject Clock clock;field), immediately remove the associated import. Do not wait for a build failure to identify unused imports. - No Redundant Qualifications: NEVER use fully qualified class names (e.g.,
java.time.temporal.ChronoUnit.DAYS) in code when an import can be used instead. Always prefer adding an import and using the simple name. - Static Imports for Utilities: Always statically import methods from utility classes like
DateTimeUtilsorCacheUtils. (e.g., usetoInstant(...)instead ofDateTimeUtils.toInstant(...)). - Checkstyle: Proactively fix common checkstyle errors (line length > 100, formatting, unused imports) during the initial code write. Do not wait for CI/build failures to address these, as iterative fixes are inefficient.
- Verification: Before finalizing any change, scan the imports section for redundancy.
- License Headers: When creating new files, ensure the license header uses the current year (e.g., 2026). Existing files should retain their original year.
2. Time and Precision Handling
- UTC Timezones: Do not use
ZoneId.of("UTC"). Use a statically importedUTCfromZoneOffsetinstead (import static java.time.ZoneOffset.UTC;). - Clock Injection:
- Avoid direct calls to
Instant.now(),OffsetDateTime.now(), orSystem.currentTimeMillis(). - Inject
google.registry.util.Clock(production) orgoogle.registry.testing.FakeClock(tests). - Use
clock.nowDate()to get aLocalDatein UTC, orclock.nowDateTime()to get anOffsetDateTimein UTC. - When defining timestamps for tests, prefer using a fixed, static constant (e.g.,
Instant.parse("2024-03-27T10:15:30.105Z")) over capturingclock.now()to prevent flaky tests caused by the passage of real time.
- Avoid direct calls to
- Beam Pipelines:
- Ensure
Clockis serializable (it is by default in this project) when used in BeamDoFns. - Pass the
Clockthrough the constructor or via Dagger provider methods in the pipeline module.
- Ensure
- Command-Line Tools:
- Use
@Inject Clock clock;inCommandimplementations. - The
clockfield should be package-private (no access modifier) to allow manual initialization in corresponding test classes. - In test classes (e.g.,
UpdateDomainCommandTest), manually setcommand.clock = fakeClock;in the@BeforeEachmethod. - Base test classes like
EppToolCommandTestCaseshould handle this assignment for their generic command types where applicable.
- Use
3. Dependency Injection (Dagger)
- Concrete Types: Dagger
injectmethods must use explicit concrete types. Genericinject(Command)methods will not work. - Test Components: Use
TestRegistryToolComponentfor command-line tool tests to bridge the gap betweenmainandnonprod/testsource sets.
4. Database Consistency
- Transaction Management:
- Top-Level: Define database transactions (
tm().transact(...)) at the highest possible level in the call chain (e.g., in an Action, a Command, or a Flow). This ensures all operations are atomic and handled by the retry logic. - DAO Methods: Avoid declaring transactions inside low-level DAO methods. Use
tm().assertInTransaction()to ensure that these methods are only called within a valid transactional context. - Utility/Cache Methods: Use
tm().reTransact(...)for utility methods or Caffeine cache loaders that might be invoked from both transactional and non-transactional paths.reTransactwill join an existing transaction if one is present (acting as a no-op) or start a new one if not.- This is particularly useful for in-memory caches where the loader must be able to fetch data regardless of whether the caller is currently in a transaction.
- 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, usingtm().reTransact(tm()::getTxTime)is acceptable. However, NEVER wrap it redundantly liketm().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, preferInstant.now()orclock.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 unnecessaryreTransact()aroundgetTxTime().
- Transactional Time: Ensure code that relies on
tm().getTransactionTime()(ortm().getTxTime()) is executed within a transaction context.
- Top-Level: Define database transactions (
5. Testing Best Practices
- FakeClock and Sleeper: Use
FakeClockandSleeperfor any logic involving timeouts, delays, or expiration. - 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.
6. Project Dependencies
- Common Module: When using
Clockor other core utilities in a new or separate module (likeload-testing), ensureimplementation project(':common')is added to the module'sbuild.gradle. - Updating Dependencies: When updating third-party dependencies, you MUST follow this exact, non-negotiable sequence. NEVER run the update script without first verifying the lockfiles locally.
- Modify
dependencies.gradle. - Run
./gradlew dependencies --write-locks --update-locks '*:*'to regenerate all Gradle lockfiles locally to resolve new versions. - Run
./gradlew clean buildto exhaustively compile and run all tests against the new transitive dependency tree. If there are compilation or test errors due to breaking API changes or output formatting changes in the new dependency version, your primary goal is to fix our codebase to be compliant with the new dependency version. Only revert the dependency bump or cap the version if the required code changes are prohibitively complex or outside the scope of the current task. - Only once the build passes and
git statusshows modified lockfiles, commit thedependencies.gradleand lockfile changes. - ONLY after the local changes are committed and verified, execute the internal script
/google/src/head/depot/google3/domain/registry/tools/update_dependency.sh. This ensures you don't corrupt the upstream remote artifact cache with broken or missing lockfiles. - Crucial Final Verification: The
update_dependency.shscript runs its own internal Blaze resolution and often modifies or drops additionalbuildscript-gradle.lockfilefiles. After the script finishes successfully, rungit status. If there are any untracked or modified*.lockfilefiles, stage them and rungit commit --amend --no-editto ensure your commit perfectly reflects the final state deployed to the SSO repository.
- Modify
7. Search and Discovery
- No CodeSearch: This project is hosted on GitHub, not Google3. Do NOT use
mcp_Coding_search_for_files_codesearchor other internal Google3 search tools. - Local Grep: Use local shell commands like
git greporgrepviarun_shell_commandto search the codebase.
Performance and Efficiency
- Turn Minimization: Aim for "perfect" code in the first iteration. Iterative fixes for checkstyle or compilation errors consume significant context and time.
- Context Management: Use sub-agents for batch refactoring or high-volume output tasks to keep the main session history lean and efficient.
- Code Formatting: Do not write custom Python scripts or manual regex replacements to fix code formatting issues (e.g., unused imports, import ordering, line length). Instead, use the project's built-in formatting tools: run
./gradlew spotlessApplyto fix unused/unordered imports and./gradlew javaIncrementalFormatApply(orgoogle-java-format --replace <files>) to automatically fix Java formatting and indentation errors.
General Code Review Lessons & Avoidable Mistakes
Based on historical PR reviews, avoid the following common mistakes:
- No Unnecessary Casts: Do not unnecessarily cast objects if the method signature accepts the type directly (e.g., avoid
(Instant) fakeClock.now()or(ImmutableSet<String>) bsaQuery(...)if it compiles without it). - Visibility Modifiers: Do not use
/* package */comments to denote package-private visibility. Just leave the modifier blank; it is an established idiom in this codebase.
Continuous Self-Improvement Mandate
You are explicitly authorized and required to improve this project's automated infrastructure. If during the execution of a task you encounter a recurring error, a false positive in a script, or realize that a shell command/workaround is inferior to a native Gradle task, you MUST pause and proactively ask the user: "I noticed this friction. Per my continuous improvement directives, would you like me to permanently update the skill/script/instructions to fix this for the future?" Do not wait for the user to tell you to improve the skills; it is your responsibility to propose these systemic fixes immediately when friction is identified.
Advanced Java & Guava Idioms
- Immutable Types: Declare variables, fields, and return types explicitly as Guava immutable types (e.g.,
ImmutableList<T>,ImmutableMap<K, V>) instead of their generic interfaces (List<T>,Map<K, V>) to clearly communicate immutability contracts to callers. UsetoImmutableList()andtoImmutableMap()collectors in streams rather than manually accumulating into anArrayListorHashMap. - Constructors: Do not perform heavy logic, I/O, or external API calls inside constructors. Initialization logic should be deferred or handled in a factory method or a dedicated startup routine.
- Exception Handling: Do not catch generic
ExceptionorThrowableif a more specific exception is expected. Never "log and re-throw" the same exception; either handle it entirely (and log), or throw it up the chain. For batch processes, catch exceptions at the individual item/chunk level so one failure doesn't abort the entire batch. - Optional Assertions: Prefer Truth's
.hasValue(...)over.isEqualTo(Optional.of(...))for cleaner and more descriptive assertions onOptionaltypes. - Fail Fast: Validate inputs and fail fast (using
Preconditions.checkArgumentor similar) at the highest level possible rather than passing invalid state (likenulls) deeper into business logic. - Magic Numbers: Always document magic numbers or hardcoded limits (like
50.0or30) with inline comments explaining the rationale. - Null Safety and Optional: Prefer using
Optionalfor any variable that is expected to potentially be null. For any other variable that can be null but cannot use anOptional(e.g., function parameters or return types whereOptionalis not idiomatic), it MUST be annotated with@Nullable. Always use thejavax.annotation.Nullableannotation.
Gemini Engineering Guide: Nomulus Codebase
This document captures high-level architectural patterns, lessons learned from large-scale refactorings (like the Joda-Time to java.time migration), and specific instructions to avoid common pitfalls in this environment.
🏛 Architecture Overview
- Transaction Management: The codebase uses a custom wrapper around JPA. Always use
tm()(fromTransactionManagerFactory) to interact with the database. - Dependency Injection: Dagger 2 is used extensively. If you see "cannot find symbol" errors for classes starting with
Dagger..., the project is in a state where annotation processing failed. Fix compilation in core models first to restore generated code. - Value Types: AutoValue and "ImmutableObject" patterns are dominant. Most models follow a
Buildablepattern with a nestedBuilder. - Temporal Logic: The project uses
java.timefor all temporal representations.- Core boundaries:
DateTimeUtils.START_INSTANT(Unix Epoch) andDateTimeUtils.END_INSTANT(Long.MAX_VALUE / 1000).
- Core boundaries:
Source Control
- No Unprompted Pushing: You MUST NEVER push your changes to a remote repository (e.g.,
git push origin ...) unless the user VERY EXPLICITLY instructs you to do so. If you have finished a task, simply state that it is committed locally and ready for review. Do not assume pushing is desired. - Committing: Always create a new commit on the branch if one hasn't been created yet for the branch's specific work. Only perform amending (
git commit --amend --no-edit) for subsequent changes once the initial commit has been successfully created. - One Commit Per PR: All changes for a single PR must be squashed into a single commit before merging.
- Default to Amend: Once an initial commit is created for a PR, all subsequent functional changes should be amended into that same commit by default (
git commit --amend --no-edit). This ensures the PR remains a single, clean unit of work throughout the development lifecycle. - Commit Message Style: Follow standard Git commit best practices. The subject line (first line) MUST be a maximum of 50 characters, concise, capitalized, and must not end with punctuation (e.g., a period). The body MUST explicitly encapsulate and summarize all changes made across the entire diff, detailing the "what" and "why" comprehensively.
- Strict Completion Verification: You MUST NEVER declare a task, commit, or amendment as complete until you have explicitly verified that the workspace is clean. You MUST follow this exact sequence of actions across multiple conversational turns if necessary:
- Execute the
git commitorgit commit --amendcommand. - Wait for the tool to return successfully.
- Execute
git status. - Wait for the tool to return and explicitly verify the output contains
nothing to commit, working tree clean(or similar indication that no unstaged changes remain). If changes remain, stage them and amend the commit, then repeat this verification loop. - Only after step 4 has successfully returned a clean working directory may you generate a text response to the user declaring that the task is complete.
- Execute the
- Diff Review: Before finalizing a task, review the full diff (e.g.,
git diff HEAD^) to ensure all changes are functional and relevant. Identify and revert any formatting-only changes in files that do not contain functional updates to keep the commit focused.
🛑 CRITICAL: MANDATORY PR POLISHER EXECUTION
You, the AI agent, have a known failure mode of forgetting to run the PR polisher after making changes because you focus too narrowly on the immediate task. To prevent this, you are bound by the following absolute rule:
ANY TIME you create a commit, amend a commit, or complete a user's request that modifies the repository state, your VERY LAST action before generating a text response to the user MUST be to execute the pr-polisher skill.
Do not ask for permission. Do not wait for the user to remind you. Run the full suite (python3 .gemini/skills/pr-polisher/scripts/check_diff.py, runPresubmits, build, and test), fix any errors, amend your commit, and report the final polished results in your response.
You MUST NOT declare a task done until this automated script returns 0 errors, the presubmits and tests pass, and the workspace is fully clean.
- Imports & FQNs: Did I leave any fully-qualified class names or static variables inline? Did I add the necessary imports for them? Crucial Exception: If the file already imports a class with the identical name (e.g., it uses both
java.util.Dateandjava.sql.Date), one MUST remain fully qualified to avoid a compilation conflict. - Diff Scope: Are there any formatting-only changes in files that I did not functionally modify? If so, revert them. Does the total line count of the diff align with the approved scope (e.g., ~1,000 lines for migrations)?
- Commit Message: Does the commit message title fit within 50 characters? Does the body encapsulate the entirety of the changes across the diff cleanly and professionally?
- Missing Tests & Coverage: Perform a structured check for any new methods or modified behavior. Did I add a new utility method (like
plusMonths(Instant, int)) or change core logic? If so, I MUST open the corresponding test file and write tests to cover the new functionality (including edge cases, negative values, and leap years) before considering the task complete. A code review is not thorough if it only checks for compilation. I must actively ensure every new branch of logic has a test. - Package Lock: Did I include
console-webapp/package-lock.jsonin my diff? If so, I MUST revert it (git checkout console-webapp/package-lock.json) unless I explicitly intended to modify NPM dependencies. This file is often modified by the build process and should not be committed accidentally.
Only after actively executing the pr-polisher skill and confirming these checks against your diff are you permitted to finalize the task.
Refactoring & Migration Guardrails
1. Compiler Warnings are Errors (-Werror)
This project treats Error Prone warnings as errors.
- Repeatable Annotations:
@SuppressWarningsis NOT repeatable in this environment. If a method or class already has a suppression (e.g.,@SuppressWarnings("unchecked")), you must merge them:- ❌
@SuppressWarnings("unchecked") @SuppressWarnings("MustBeClosedChecker") - ✅
@SuppressWarnings({"unchecked", "MustBeClosedChecker"})
- ❌
2. Build Strategy
- Validation: Always run
./gradlew build -x testbefore attempting to run unit tests. Unit tests will not run if there are compilation errors in any part of thecoremodule. Before finalizing a PR or declaring a task done, you MUST verify your changes. Prefer scoped builds (e.g.,./gradlew :core:build) if you are only modifying backend Java code. Running the global./gradlew buildtriggers the frontendconsole-webappbuild, which unnecessarily runsnpmInstallDepsand modifiespackage-lock.json. If you must run a global build, you must revertconsole-webapp/package-lock.jsonafterwards. Do not declare success if formatting checks (e.g.,spotlessCheckorjavaIncrementalFormatCheck) or tests fail. If formatting fails, run./gradlew spotlessApplyand then re-run your build command to verify everything passes.
🚫 Common Pitfalls to Avoid
- Serialization Precision (
.000Z): When asserting against or generating XML/YAML files, remember that millisecond precision (.000Z) is required. Always useDateTimeUtils.formatInstant(...)to formatInstantobjects (it preserves the.000Zsuffix) instead ofInstant.toString()(which drops it for exact seconds). We have added custom JacksonInstantKeySerializers for this purpose, but you must keep this precision in mind when manually updating.xmlor.yamltest data. - Dagger/AutoValue corruption: If you modify a builder or a component incorrectly, Dagger will fail to generate code, leading to hundreds of "cannot find symbol" errors. If this happens,
git checkoutthe last working state of the specific file and re-apply changes more surgically. replacetool context: When usingreplaceon large files (likeTld.javaorDomainBase.java), provide significant surrounding context. These files have many similar method signatures (getters/setters) that can lead to incorrect replacements.
GitHub and Pull Request Protocol
This protocol defines the standard for interacting with GitHub repositories and processing Pull Request (PR) feedback.
1. Interaction via gh CLI
- Primary Tool: ALWAYS use the
ghCLI for all GitHub-related operations (listing PRs, viewing PR content, checking status, adding comments). - Credential Safety: Never expose tokens or credentials in shell commands.
2. Processing PR Feedback
- Systematic Review: When asked to address PR comments, first fetch all comments using
gh pr view <number> --json reviews,comments. - Minimal Scope Expansion: Address comments surgically. If a fix requires changes beyond a few lines or expands the PR's original scope significantly, DO NOT implement it without explicit user approval. Instead, report the issue to the user.
- Verification: After addressing feedback, run the full build (
./gradlew build) and relevant tests to ensure no regressions were introduced.
3. PR Lifecycle Management
- One Commit Per PR: Ensure all changes are squashed into a single, clean commit. Use
git commit --amend --no-editfor follow-up fixes. - Clean Workspace: Always run
git statusand verify the repository state before declaring a task complete. - Package Lock: The Gradle build automatically modifies
console-webapp/package-lock.jsonvia thenpmInstallDepstask. ALWAYS revert this file (git checkout console-webapp/package-lock.json) before staging changes or finalizing a commit unless you explicitly modified NPM dependencies.