Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1362,6 +1362,25 @@ jobs:
- name: Run Protolint
run: protolint lint -config_path=.protolint.yaml crates/rpc/proto/

migration-police:
permissions:
contents: read
needs:
- changes
if: ${{ contains(github.ref, 'pull-request/') }}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
persist-credentials: false
fetch-depth: 0

- name: Check new migration timestamps
run: |
git fetch --no-tags origin main:refs/remotes/origin/main
bash scripts/check-migration-filenames.sh --base origin/main

proto-breaking-changes:
name: Proto Breaking Changes Check
runs-on: ubuntu-latest
Expand Down Expand Up @@ -2059,6 +2078,7 @@ jobs:
- build-release-artifacts-arm-host
- security-secret-scan
- lint-police
- migration-police
- check-rest-core-proto-sync
- build-machine-a-tron
- build-mat-k8s-controller
Expand Down
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,11 @@ verification expectations.
See [`STYLE_GUIDE.md`](STYLE_GUIDE.md) for detailed Rust coding conventions.
Make sure to review it to ensure changes meet the expected style of the codebase.

Name new Core database migrations with the fully populated
`YYYYMMDDhhmmss_description.sql` format described in
[`STYLE_GUIDE.md`](STYLE_GUIDE.md#database-migrations). The `migration-police`
CI job checks only newly added migrations, so existing filenames remain accepted.

### Documentation

Give every fenced code block a language identifier. Use `bash` or `sh` for
Expand Down
8 changes: 8 additions & 0 deletions STYLE_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,14 @@ your interface `async` just so you can use the tokio Mutex. That way callers can
async themselves. Async work should generally be traceable to some I/O or timer that needs to be used, otherwise
code should typically be synchronous.

## Database migrations

Name new Core database migration files with a fully populated 14-digit timestamp:
`YYYYMMDDhhmmss_description.sql`. Use the actual hour, minute, and second values instead of a
trailing `0000` minute-and-second placeholder so independently authored migrations are less likely
to collide. Existing migration filenames remain unchanged, and migrations already on `main` are
immutable.

## Database transactions

Transactions should be used to group write operations together such that they can be rolled back on failure. But do
Expand Down
74 changes: 74 additions & 0 deletions scripts/check-migration-filenames.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

set -euo pipefail

usage() {
echo "Usage: check-migration-filenames.sh --base REVISION"
echo " check-migration-filenames.sh MIGRATION_FILE..."
}

escape_workflow_data() {
local value="$1"

value="${value//'%'/'%25'}"
value="${value//$'\r'/'%0D'}"
value="${value//$'\n'/'%0A'}"
printf '%s' "${value}"
}

migration_files=()

if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
usage
exit 0
elif [[ "${1:-}" == "--base" ]]; then
if (( $# != 2 )); then
usage >&2
exit 2
fi

base_revision="$2"
migration_list="$(mktemp)"
trap 'rm -f "${migration_list}"' EXIT

git diff --no-renames --diff-filter=A --name-only -z \
"${base_revision}...HEAD" \
-- ':(top,glob)crates/api-db/migrations/*.sql' >"${migration_list}"

while IFS= read -r -d '' migration_file; do
migration_files+=("${migration_file}")
done <"${migration_list}"
elif (( $# > 0 )); then
migration_files=("$@")
else
usage >&2
exit 2
fi

failed=0

for migration_file in "${migration_files[@]}"; do
filename="${migration_file##*/}"
timestamp="${filename%%_*}"

if [[ ! "${filename}" =~ ^[0-9]{14}_.+\.sql$ ]]; then
message="New Core database migration ${migration_file} must use the YYYYMMDDhhmmss_description.sql filename format."
printf '::error title=Invalid migration filename::%s\n' \
"$(escape_workflow_data "${message}")"
failed=1
elif [[ "${timestamp}" =~ ^[0-9]{10}0000$ ]]; then
message="New Core database migration ${migration_file} must use a fully populated YYYYMMDDhhmmss timestamp; replace the trailing 0000 minute/second placeholder."
printf '::error title=Placeholder migration timestamp::%s\n' \
"$(escape_workflow_data "${message}")"
failed=1
fi
Comment thread
coderabbitai[bot] marked this conversation as resolved.
done

if (( failed )); then
echo "migration-police rejected one or more new database migrations."
exit 1
fi

printf 'migration-police checked %d new database migration(s).\n' "${#migration_files[@]}"
Loading