Skip to content

feat(uat): slot-aware daytime cluster-name convention (ADR-017) - #2081

Merged
njhensley merged 4 commits into
NVIDIA:mainfrom
njhensley:ci/uat-cluster-name-convention
Aug 7, 2026
Merged

njhensley merged 4 commits into
NVIDIA:mainfrom
njhensley:ci/uat-cluster-name-convention

Conversation

@njhensley

Copy link
Copy Markdown
Member

Summary

Introduce a slot-aware UAT daytime cluster-name convention — aicr-uat-day-<slug>-<slot>-<run_id> — replacing aicr-uat-day-<reservation>-<run_id>, so the cross-run teardown/guard discovery key is scoped per (reservation, slot) and stays within GKE's 40-char cap with durable headroom (design: ADR-017).

Motivation / Context

The daytime cluster name is a cross-run discovery key: daytime-down and the pre-batch guard find a held cluster by scanning a stable prefix (the exact name isn't reconstructable from a different run). The old aicr-uat-day-<reservation>- key was cloud-scoped (collides once an account holds >1 reservation), had no slot axis for the multi-slot end state, and burned GKE's 40-char budget on the verbose reservation name (~7 chars headroom, eroding as run_ids grow and reservation names lengthen).

The new scheme uses a short registry slug (2–4 chars) plus a slot index, giving a per-(reservation, slot) discovery key that survives to the "multiple reservations per account, each multi-slot" end state, with ≥6 chars GKE headroom permanently at a 13-digit run_id.

Fixes: N/A
Related: N/A

Type of Change

  • New feature (non-breaking change that adds functionality)
  • Build/CI/tooling
  • Documentation update

Component(s) Affected

  • Docs/examples (docs/, examples/)
  • Other: UAT CI workflows (.github/workflows/uat-*.yaml), reservation registry + broker (infra/uat/reservations.yaml, pkg/uatbroker, tools/uat-broker)

Implementation Notes

  • New convention: nightly aicr-uat-<run_id> (unchanged); daytime aicr-uat-day-<slug>-<slot>-<run_id> (slot 0 today).
  • Registry slug (new field, validated at load time: non-empty, unique, ^[a-z][a-z0-9]{1,3}$): aws-h100→ah1, gcp-h100→gh1, azure-h100→zh1, aws-gb200→ag2, kind-h100→kh1. Emitted by uat-broker reservations --name → threaded through uat-run.yaml to the cloud lanes, same pattern as accelerator.
  • slot is a runtime uat-run.yaml input (default 0), forwarded to aws/gcp/azure (not the kind lane, which provisions no cloud cluster) — forward-compatible with multi-slot without another rename.
  • Guard + teardown scan the new (slug, slot) prefix; fail-closed behavior preserved (list-error → block, bounded retry, >1-match → fail loud).
  • Length budget: GKE (40) is the only binding cap; worst case 34 chars → ≥6 headroom at a 13-digit run_id. AKS (63, incl. <name>-rg) and EKS (100) have ample room.

Testing

make qualify   # CI-equivalent gate — run in CI on this PR

The implementation ran go test -race ./pkg/uatbroker/... ./tools/uat-broker/..., golangci-lint, the broker slug= emit check, and workflow lint before committing. Table-driven tests added for slug validation (valid / empty / duplicate / bad-charset) and the slug= output. Note: make qualify was not run locally before push — CI is the first full gate.

Risk Assessment

  • Medium — Touches the UAT provision/teardown critical path across three cloud workflows plus the broker.

Rollout notes: The guard and teardown carry a dual-prefix migration shim — they match both the new aicr-uat-day-<slug>-<slot>- and the legacy aicr-uat-day-<reservation>- prefixes, so any live old-named daytime cluster is still discovered and torn down by the next daytime-down (no manual pre-merge teardown needed). Once a cycle has passed with no old-named clusters remaining, drop the legacy leg (marked in-code as a transitional shim).

Checklist

  • Tests pass locally (make test with -race) — broker/tool packages; full make qualify deferred to CI
  • Linter passes (make lint) — changed Go packages + workflows
  • I did not skip/disable tests to make CI green
  • I added/updated tests for new functionality
  • I updated docs if user-facing behavior changed
  • Changes follow existing patterns in the codebase
  • Commits are cryptographically signed (git commit -S)
@njhensley
njhensley requested a review from a team as a code owner August 5, 2026 22:56
@njhensley njhensley self-assigned this Aug 5, 2026
@njhensley
njhensley requested a review from a team as a code owner August 5, 2026 22:56
@njhensley njhensley added the theme/ci-dx CI pipelines, developer experience, and build tooling label Aug 5, 2026
@coderabbitai

coderabbitai Bot commented Aug 5, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds validated reservation slugs and runtime slots to daytime UAT cluster naming. The run workflow resolves and forwards these values to AWS, GCP, and Azure workflows. Provider workflows use slug-and-slot names and retain legacy reservation-prefix matching. Registry fixtures, broker tests, contributor documentation, and ADR-017 define and verify the convention.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: mchmarny

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: introducing a slot-aware daytime UAT cluster naming convention under ADR-017.
Description check ✅ Passed The description directly explains the naming convention, motivation, implementation, testing, migration support, and affected workflows.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/uat-aws.yaml:
- Around line 43-50: Validate inputs.slug against the registry slug format and
inputs.slot as numeric in every Validate inputs step before any guard, teardown
logic, DEPLOYMENT_ID, or prefix construction. Apply this in
.github/workflows/uat-aws.yaml lines 43-50, .github/workflows/uat-azure.yaml
lines 44-51, and .github/workflows/uat-gcp.yaml lines 43-50; malformed or blank
values must fail with an error rather than continue.

In @.github/workflows/uat-run.yaml:
- Around line 74-77: Validate inputs.slot in the resolve step before any
provider dispatch, accepting only the string value "0" and failing with an error
for every other value. Ensure this validation covers all daytime-up/daytime-down
forwarding paths, including the input declaration and dispatch blocks, so
unsupported slots cannot be provisioned or forwarded.

In `@docs/design/017-uat-cluster-name-convention.md`:
- Around line 22-24: Add the `text` language identifier to both fenced code
blocks in the UAT cluster naming documentation, including the block containing
the nightly/daytime examples and the additionally referenced block, so they
satisfy markdownlint MD040 while remaining illustrative output.

In `@pkg/uatbroker/registry_test.go`:
- Around line 267-300: The slug tests around the existing “bad-charset slug” and
“too-short slug” cases do not independently cover the length boundary. Add a
rejection case for the five-character slug “abcde”, plus valid cases for the
minimum-length “a1” and maximum-length “abcd” slugs, ensuring each exercises
slug validation without relying on character-set failures.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: e0ab2520-9bed-4fdb-9402-8adb81b77bbe

📥 Commits

Reviewing files that changed from the base of the PR and between 9a1064d and daa04e3.

📒 Files selected for processing (12)
  • .github/workflows/uat-aws.yaml
  • .github/workflows/uat-azure.yaml
  • .github/workflows/uat-gcp.yaml
  • .github/workflows/uat-run.yaml
  • docs/contributor/uat.md
  • docs/design/017-uat-cluster-name-convention.md
  • infra/uat/reservations.yaml
  • pkg/uatbroker/model.go
  • pkg/uatbroker/registry.go
  • pkg/uatbroker/registry_test.go
  • tools/uat-broker/main.go
  • tools/uat-broker/main_test.go
Comment thread .github/workflows/uat-aws.yaml
Comment thread .github/workflows/uat-run.yaml
Comment on lines +22 to +24
```
nightly: aicr-uat-<run_id> # run-scoped, per-run isolation
daytime: aicr-uat-day-<reservation>-<run_id> # ephemeral, reservation-scoped

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add language identifiers to both fenced code blocks.

markdownlint reports MD040 for both fences. Use text if the blocks are illustrative output.

Also applies to: 89-95

🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 22-22: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/design/017-uat-cluster-name-convention.md` around lines 22 - 24, Add the
`text` language identifier to both fenced code blocks in the UAT cluster naming
documentation, including the block containing the nightly/daytime examples and
the additionally referenced block, so they satisfy markdownlint MD040 while
remaining illustrative output.

Source: Linters/SAST tools

Comment thread pkg/uatbroker/registry_test.go
@njhensley
njhensley force-pushed the ci/uat-cluster-name-convention branch from daa04e3 to e837ed4 Compare August 5, 2026 23:14
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/uat-aws.yaml:
- Around line 422-423: Replace the regex-based prefix checks in
.github/workflows/uat-aws.yaml lines 422-423 and 491-492,
.github/workflows/uat-azure.yaml lines 379-380 and 446-447, and
.github/workflows/uat-gcp.yaml lines 363-364 with literal prefix parsing that
also requires a digit suffix, or escape both prefixes before ERE matching.
Ensure malformed reservation names produce an error rather than being treated as
no match, and preserve guard and teardown discovery behavior.

In `@docs/contributor/uat.md`:
- Around line 158-165: Update the preceding cluster-discovery instructions in
the UAT documentation to use the new (slug, slot)-scoped prefix, matching the
AWS and GCP examples. If legacy clusters remain human-accessible during
migration, document their reservation-scoped prefix as a separate fallback
rather than the primary discovery method.
- Around line 10-14: Update the daytime cluster naming logic described in the
daytime deployment documentation to include github.run_attempt alongside
github.run_id in the generated name, ensuring reruns produce a distinct cluster
name and preserve the unique-per-provision guarantee. Update the documented name
pattern and any related prefix/discovery examples consistently.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: e4c75ac4-8b8e-4c96-a809-43889ee47bf2

📥 Commits

Reviewing files that changed from the base of the PR and between 9a1064d and e837ed4.

📒 Files selected for processing (12)
  • .github/workflows/uat-aws.yaml
  • .github/workflows/uat-azure.yaml
  • .github/workflows/uat-gcp.yaml
  • .github/workflows/uat-run.yaml
  • docs/contributor/uat.md
  • docs/design/017-uat-cluster-name-convention.md
  • infra/uat/reservations.yaml
  • pkg/uatbroker/model.go
  • pkg/uatbroker/registry.go
  • pkg/uatbroker/registry_test.go
  • tools/uat-broker/main.go
  • tools/uat-broker/main_test.go
Comment thread .github/workflows/uat-aws.yaml
Comment thread docs/contributor/uat.md Outdated
Comment on lines +10 to +14
- **On demand — handoff.** The [daytime human-access deployment](#daytime-human-access-deployment) is stood up with `lifecycle=daytime-up`: provision, deploy the stack, and **hold** (no teardown) under an ephemeral, `(slug, slot)`-scoped cluster name (`aicr-uat-day-<slug>-<slot>-<run-id>`, ADR-017) — unique per provision, so a re-provision never reuses the name the previous teardown just deleted (which collides with cloud deletion tombstones and stale terraform-state locks). This is **on demand — there is no morning cron**: an operator dispatches it when they want a cluster (see [Requesting a daytime cluster](#requesting-a-daytime-cluster)). DC2 owns the provision-and-hold mechanic; DC8 (`uat-daytime.yaml`) owns *which* flavor lands on *which* cloud and how access is shared.
- **Day — human use.** The daytime cluster is used outside CI — humans reach it [out-of-band](#daytime-human-access-deployment), never through the CI path.
- **Evening — teardown.** `uat-daytime.yaml` fires `lifecycle=daytime-down` on an evening cron — the **only** scheduled daytime edge — to tear the daytime cluster down and release the reservation **before** the next night batch. It is an unconditional safety net: it runs whether or not anyone stood a cluster up, so a manually-provisioned daytime cluster left running is reclaimed without anyone remembering to ask. It is a teardown *attempt*, not a guarantee — if the destroy itself fails, the [pre-batch guard](#pre-batch-guard) blocks the nightly batch rather than letting it race the still-held cluster.

The phases are independently scheduled (cron edges), not chained: the per-reservation lease — plus a [pre-batch guard](#pre-batch-guard) — keeps them from overlapping, so a crashed or overrunning phase never orphans the reservation. A hosted GitHub Actions job is capped at the runner's timeout (hours, not a whole working day), so a single lease-holding run cannot span the day; the lease only needs to cover the brief transition windows, and the steady-state daytime cluster's existence is tracked by its reservation-scoped name **prefix** (`aicr-uat-day-<reservation>-*`, discovered by a list-and-match scan) rather than a continuously held run.
The phases are independently scheduled (cron edges), not chained: the per-reservation lease — plus a [pre-batch guard](#pre-batch-guard) — keeps them from overlapping, so a crashed or overrunning phase never orphans the reservation. A hosted GitHub Actions job is capped at the runner's timeout (hours, not a whole working day), so a single lease-holding run cannot span the day; the lease only needs to cover the brief transition windows, and the steady-state daytime cluster's existence is tracked by its `(slug, slot)`-scoped name **prefix** (`aicr-uat-day-<slug>-<slot>-*`, discovered by a list-and-match scan) rather than a continuously held run.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Expect: daytime cluster names use a value that changes on workflow re-runs.
rg -n -C 5 'aicr-uat-day-|run_id|run-id|run_attempt|run-attempt' \
  .github/workflows/uat-run.yaml \
  .github/workflows/uat-aws.yaml \
  .github/workflows/uat-gcp.yaml \
  .github/workflows/uat-azure.yaml

Repository: NVIDIA/aicr

Length of output: 50367


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- daytime DEPLOYMENT_ID definitions ---'
rg -n -A4 -B3 "DEPLOYMENT_ID:|format\\('aicr-uat-day|github\\.run_attempt|run_attempt" \
  .github/workflows/uat-aws.yaml \
  .github/workflows/uat-gcp.yaml \
  .github/workflows/uat-azure.yaml

printf '%s\n' '--- guard and lifecycle control flow ---'
rg -n -A8 -B8 "Pre-batch guard|DAYTIME_FOUND|Resolve the ephemeral daytime cluster|Destroy Cluster|inputs\\.lifecycle|daytime-up|daytime-down" \
  .github/workflows/uat-aws.yaml \
  .github/workflows/uat-gcp.yaml \
  .github/workflows/uat-azure.yaml \
  | head -n 500

printf '%s\n' '--- documentation context ---'
sed -n '1,22p' docs/contributor/uat.md

Repository: NVIDIA/aicr

Length of output: 50367


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

files = [
    Path(".github/workflows/uat-aws.yaml"),
    Path(".github/workflows/uat-gcp.yaml"),
    Path(".github/workflows/uat-azure.yaml"),
]
pattern = re.compile(
    r"format\('aicr-uat-day-\{0\}-\{1\}-\{2\}',\s*"
    r"inputs\.slug,\s*inputs\.slot,\s*github\.run_id\)"
)

for path in files:
    text = path.read_text()
    matches = pattern.findall(text)
    print(f"{path}: daytime_name_uses_github.run_id={bool(matches)}, "
          f"occurrences={len(matches)}, uses_github.run_attempt={'github.run_attempt' in text}")

# A rerun changes the attempt but preserves the run ID. The provider expression
# therefore produces the same daytime name for both executions.
slug, slot, run_id = "h100", "0", "12345"
attempts = [1, 2]
names = [
    f"aicr-uat-day-{slug}-{slot}-{run_id}"
    for _attempt in attempts
]
print(f"rerun_attempts={attempts}")
print(f"generated_names={names}")
print(f"same_name_on_rerun={names[0] == names[1]}")
PY

Repository: NVIDIA/aicr

Length of output: 617


🌐 Web query:

GitHub Actions official documentation GITHUB_RUN_ID same workflow run rerun GITHUB_RUN_ATTEMPT

💡 Result:

In GitHub Actions, GITHUB_RUN_ID and GITHUB_RUN_ATTEMPT serve different purposes when managing workflow executions and re-runs: GITHUB_RUN_ID This is a unique identifier for a specific workflow run within a repository [1]. Crucially, the GITHUB_RUN_ID remains constant for the entire lifecycle of that workflow run, even if you perform multiple re-runs [1][2]. GITHUB_RUN_ATTEMPT This is a unique number that tracks the specific attempt of a workflow run [1]. It begins at 1 for the initial attempt and increments by 1 with each subsequent re-run [1][3]. Combining these variables is a common practice for generating unique identifiers for artifacts, logs, or external resources that need to remain distinct across different attempts of the same workflow run [4][3]. You can access these as environment variables (e.g., $GITHUB_RUN_ID and $GITHUB_RUN_ATTEMPT) or via the github context (e.g., ${{ github.run_id }} and ${{ github.run_attempt }}) [1][5].

Citations:


Include github.run_attempt in daytime cluster names.

All provider workflows use github.run_id, which remains unchanged on reruns. A rerun can therefore reuse the same cluster name and violate the “unique per provision” guarantee. Add github.run_attempt to the suffix or remove that guarantee.

🧰 Tools
🪛 LanguageTool

[style] ~12-~12: ‘whether or not’ might be wordy. Consider a shorter alternative.
Context: ...is an unconditional safety net: it runs whether or not anyone stood a cluster up, so a manuall...

(EN_WORDINESS_PREMIUM_WHETHER_OR_NOT)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/contributor/uat.md` around lines 10 - 14, Update the daytime cluster
naming logic described in the daytime deployment documentation to include
github.run_attempt alongside github.run_id in the generated name, ensuring
reruns produce a distinct cluster name and preserve the unique-per-provision
guarantee. Update the documented name pattern and any related prefix/discovery
examples consistently.
Comment thread docs/contributor/uat.md Outdated
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

@njhensley this PR now has merge conflicts with main. Please rebase to resolve them.

@lockwobr lockwobr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-review of e837ed45, with each finding independently re-checked against the pinned tree before being reported. Details are inline.

Nothing here is a merge blocker: the one finding with an operational failure mode is latent (no current registry row can trigger it), and the rest is documentation drift, most of it inside the migration window this PR opens.

Open questions

  • Once the legacy aicr-uat-day-<reservation>- leg is dropped, the guard's discovery key becomes purely (slug, slot)-scoped, so a nightly run at the default slot=0 would no longer be blocked by a leaked daytime cluster in a different slot on the same reservation. A nightly batch consumes the whole reservation, not one slot. Unreachable today since uat-run.yaml rejects slot != 0, but the follow-up that removes the shim should decide the guard's slot scope deliberately rather than inheriting it.
  • Is the shim removal tracked anywhere? "Drop the legacy leg once none remain" appears in six places with no issue reference, and there is no TODO-with-issue or CI check, so nothing schedules the cleanup or flags it once the window closes.
  • docs/contributor/uat.md names ADR-017 in prose at lines 10, 94-97 and 185 but never links to ../design/017-uat-cluster-name-convention.md, unlike how ADR-016 is referenced from docs/user/cli-reference.md and docs/integrator/recipe-development.md. Stylistic only.
  • GitHub applying the workflow_dispatch default when a dispatcher omits -f slot= was reasoned from the workflow definitions, not executed. If defaults were not applied, inputs.slot would be empty and the per-cloud SLOT_RE preflight fails closed, so the failure direction is safe either way.
  • The GKE 40-char / AKS 63 / EKS 100 caps are taken from the ADR text and were not confirmed against upstream cloud docs. The ADR's length-budget arithmetic is internally consistent.
Comment thread .github/workflows/uat-aws.yaml
Comment thread .github/workflows/uat-gcp.yaml
Comment thread .github/workflows/uat-azure.yaml
Comment thread .github/workflows/uat-aws.yaml Outdated
Comment thread docs/contributor/uat.md Outdated
Comment thread docs/contributor/uat.md
Comment thread docs/contributor/uat.md Outdated
Comment thread tools/uat-broker/main.go
Introduce a per-(slug, slot) daytime cluster-name convention to replace
the cloud-scoped reservation-name discovery key:

  nightly:  aicr-uat-<run_id>                    (unchanged)
  daytime:  aicr-uat-day-<slug>-<slot>-<run_id>

Captures the problem (cloud-scoped collisions, no slot axis, GKE 40-char
length pressure), the convention, the length budget vs GKE/AKS/EKS caps,
the slug/slot length policy, the per-(reservation, slot) discovery-key
rationale, the consumer inventory, and the dual-prefix migration.

Signed-off-by: Nathan Hensley <nhensley@nvidia.com>
The daytime cluster name embeds a discovery key the guard and evening
teardown scan across runs to find a held cluster. The reservation name
is long (pressuring GKE's 40-char cap) and only cloud-unique, so it
cannot distinguish two reservations one account holds in the same cloud.

Add a 'slug' field (^[a-z][a-z0-9]{1,3}$, registry-unique) as the compact,
account-stable discovery key for aicr-uat-day-<slug>-<slot>-<run_id>
(ADR-017):

- reservations.yaml: slug on every row (ah1/gh1/zh1/ag2/kh1)
- pkg/uatbroker: Reservation.Slug + load-time validation (non-empty,
  unique, charset) in the same loop as the other fields
- tools/uat-broker: emit slug= in the reservations --name output so
  uat-run.yaml exposes needs.resolve.outputs.slug
- tests: valid/empty/duplicate/bad-charset/too-short slug cases, the
  committed-slug lock, and a slug= assertion on the resolver output

Signed-off-by: Nathan Hensley <nhensley@nvidia.com>
Consume the new registry slug and a runtime slot to name daytime clusters
aicr-uat-day-<slug>-<slot>-<run_id> (ADR-017), replacing the cloud-scoped
reservation-name discovery key:

- uat-run.yaml: workflow-level 'slot' input (default '0'); expose the
  resolved slug; forward slug/slot into the aws/gcp/azure with: blocks
  (not kind — the nvkind lane provisions no cloud cluster)
- uat-{aws,gcp,azure}.yaml: slug/slot workflow_call inputs; DEPLOYMENT_ID
  daytime branch derives the new name; the pre-batch guard and teardown
  resolve step key on the (slug, slot) prefix
- migration: guard + teardown scans are DUAL-PREFIX — they match both the
  new prefix and the legacy aicr-uat-day-<reservation>- prefix so an
  in-flight old-named daytime cluster is still discovered and torn down.
  A transitional shim, removed once none remain. All existing fail-closed
  behavior (list-error => exit 1, bounded retry, >1-match => fail loud)
  and the guard-broad/resolve-anchored asymmetry are preserved
- docs/contributor/uat.md: lifecycle table, discovery-prefix prose,
  reach-the-cluster examples, pre-batch-guard description, and the
  add-a-reservation example updated to the new scheme + required slug

Signed-off-by: Nathan Hensley <nhensley@nvidia.com>
@njhensley
njhensley force-pushed the ci/uat-cluster-name-convention branch from e837ed4 to 78d5fbb Compare August 7, 2026 15:46

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/uatbroker/registry.go`:
- Around line 150-154: Extend the reservation validation around seenSlug to
reject legacy-name/slug-slot prefix aliases: detect when one reservation name
matches another reservation’s slug followed by a hyphen and a one- or two-digit
slot, regardless of processing order, and return an invalid-request error. Add a
parsing test covering the ah1-0 name versus ah1 slug collision, ensuring
malformed or ambiguous alias checks fail explicitly.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 93916b16-1dac-4cec-9b3f-90d5c9035aa2

📥 Commits

Reviewing files that changed from the base of the PR and between e837ed4 and 78d5fbb.

📒 Files selected for processing (10)
  • .github/workflows/uat-aws.yaml
  • .github/workflows/uat-azure.yaml
  • .github/workflows/uat-gcp.yaml
  • .github/workflows/uat-run.yaml
  • docs/contributor/uat.md
  • docs/design/017-uat-cluster-name-convention.md
  • pkg/uatbroker/model.go
  • pkg/uatbroker/registry.go
  • pkg/uatbroker/registry_test.go
  • tools/uat-broker/README.md
Comment thread pkg/uatbroker/registry.go
Comment on lines +150 to +154
if prev, ok := seenSlug[res.Slug]; ok {
return errors.New(errors.ErrCodeInvalidRequest,
fmt.Sprintf("duplicate reservation slug %q (%s and %s)", res.Slug, prev, res.Name))
}
seenSlug[res.Slug] = res.Name

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reject legacy-name and slug-slot prefix aliases.

A reservation named ah1-0 passes namePattern. A different reservation can use slug ah1. Both then map to aicr-uat-day-ah1-0-.

During the migration window, the first reservation can block on or tear down the second reservation's new cluster. Reject a reservation name that equals another reservation's <slug>-<one-or-two-digit-slot> key. Add a parsing test for this collision.

As per coding guidelines, “malformed evaluator input, ambiguous negative checks, and override failures must produce errors rather than passing.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/uatbroker/registry.go` around lines 150 - 154, Extend the reservation
validation around seenSlug to reject legacy-name/slug-slot prefix aliases:
detect when one reservation name matches another reservation’s slug followed by
a hyphen and a one- or two-digit slot, regardless of processing order, and
return an invalid-request error. Add a parsing test covering the ah1-0 name
versus ah1 slug collision, ensuring malformed or ambiguous alias checks fail
explicitly.

Source: Coding guidelines

@lockwobr lockwobr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at 78d5fbb0. All eight findings from the previous round are addressed, and I've resolved those threads.

Verified against the current tree:

  • The unvalidated reservation name in the grep -E alternation is closed at the data source with namePattern (^[a-z]([a-z0-9-]*[a-z0-9])?$) in Registry.Validate, rather than per-workflow. That covers all six scan sites by construction, and the lookup path fails closed before any cloud lane runs. The four added charset cases (ERE metacharacter, dot, uppercase, trailing hyphen) lock the invariant.
  • The daytime-down no-match log now names both the new and legacy prefixes in all three pipelines.
  • docs/contributor/uat.md: the "run-scoped clusters" discovery claim is corrected, the discovery prose is (slug, slot)-scoped, and the operator snippets carry the legacy alternative for the life of the shim.
  • tools/uat-broker/README.md documents the slug= key.

The ADR-017 prose links raised as an open question were added too.

CI is green on this commit.

@njhensley
njhensley enabled auto-merge (squash) August 7, 2026 22:43
@njhensley
njhensley merged commit c0615e6 into NVIDIA:main Aug 7, 2026
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/ci area/docs area/infra needs-rebase size/XL theme/ci-dx CI pipelines, developer experience, and build tooling

2 participants