Skip to content

feat(rest-api): Tenant TargetedInstanceCreation capability implementation#2134

Open
hwadekar-nv wants to merge 20 commits into
NVIDIA:mainfrom
hwadekar-nv:feat/tenant-capabilities-update
Open

feat(rest-api): Tenant TargetedInstanceCreation capability implementation#2134
hwadekar-nv wants to merge 20 commits into
NVIDIA:mainfrom
hwadekar-nv:feat/tenant-capabilities-update

Conversation

@hwadekar-nv

@hwadekar-nv hwadekar-nv commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Description

Problem

Privileged tenant access (TargetedInstanceCreation) was stored on the global Tenant.config and surfaced as a single Tenant.capabilities.targetedInstanceCreation flag. That model is too coarse: the capability can differ by Provider and Site, and tenants may need per-site overrides. A tenant-wide flag also made it easy to over-grant access on list endpoints (e.g. machines/sites) when only some sites should be privileged.

What this PR does
Moves capability configuration and enforcement to the account/site model and makes authorization site-aware end to end.

Configuration model

  1. TenantAccount.config — provider-scoped defaults (targetedInstanceCreation, enableSshAccess)
  2. TenantSite.config — optional per-site override for targetedInstanceCreation
  3. TenantAccount PATCH siteCapabilities — Provider Admin API to set capability globally or for explicit site lists (global / limited scope), with server validation (exactly one global entry, no duplicate site IDs, etc.)
  4. Migration — backfills tenant_account.config from legacy tenant.config, then drops tenant.config
  5. Tenant.capabilities.targetedInstanceCreation — kept for compatibility but deprecated (still derived from account state)

TenantAccount API (siteCapabilities)

  1. POST /tenant/account — Provider Admin creates a Tenant Account invite (tenantOrg / tenantId). Capability configuration is not set at create time.
  2. PATCH /tenant/account/{accountId} — two mutually exclusive update paths:
    Provider Admin + siteCapabilities — replace capability configuration globally (scope: global) or for explicit sites (scope: limited + siteIds). Server validates: non-empty array, exactly one global entry, no duplicate siteIds, siteIds required for limited / omitted for global.
    Tenant Admin + tenantContactId — accept an Invited account (existing flow). Cannot be combined with siteCapabilities in the same request (400).
  3. GET /tenant/account/{accountId} — returns siteCapabilities on the TenantAccount response (one global entry from TenantAccount.config, plus limited entries for per-site overrides that differ from the global default).

Authorization / enforcement

  1. New helpers in common.go:
    TenantHasTargetedInstanceCreation(tenant, site?) — tenant-wide or effective per-site check (account default + TenantSite override)
    GetPrivilegedAccessSiteIDsForTenant — returns the exact set of privileged site IDs
  2. List and action handlers updated to use effective per-site access instead of tenant-wide expansion:
    Sites, Machines, Expected Machines, Instances, VPCs, SKUs, etc.
  3. TenantSite update validates mutually exclusive inputs before writing (no partial updates on bad requests)

API / docs

  1. OpenAPI updated for siteCapabilities schema and validation rules
  2. SDK models regenerated

Typical capability setup flow

  1. Provider Admin POSTs a Tenant Account invite.
  2. Tenant Admin PATCHes with tenantContactId to accept → account becomes Ready.
  3. Provider Admin PATCHes with siteCapabilities to enable/configure targetedInstanceCreation globally or per site.

Reviewer focus areas

  1. Effective capability resolution — TenantAccount.config + TenantSite.config override logic in common.go
  2. List filtering — privileged tenants are scoped to GetPrivilegedAccessSiteIDsForTenant, not all provider sites
  3. TenantAccount PATCH — siteCapabilities replace semantics and advisory-lock behavior
  4. Migration — backfill correctness for existing tenants with targetedInstanceCreation / enableSshAccess
    Related

Closes #2104

Type of Change

  • Add - New feature or capability

Related Issues (Optional)

#2104

Breaking Changes

  • This PR contains breaking changes

Testing

  • Unit tests added/updated

Additional Notes

@hwadekar-nv hwadekar-nv self-assigned this Jun 2, 2026
@hwadekar-nv hwadekar-nv requested a review from a team as a code owner June 2, 2026 21:51
@hwadekar-nv hwadekar-nv marked this pull request as draft June 2, 2026 21:51
@copy-pr-bot

copy-pr-bot Bot commented Jun 2, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown

🔐 TruffleHog Secret Scan

No secrets or credentials found!

Your code has been scanned for 700+ types of secrets and credentials. All clear! 🎉

🔗 View scan details

🕐 Last updated: 2026-06-02 21:53:17 UTC | Commit: 1feb71c

@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown

🔍 Container Scan Summary

Service Total Critical High Medium Low Other
nico-flow 15 1 3 3 0 8
nico-nsm 7 0 1 5 0 1
nico-psm 15 1 3 3 0 8
nico-rest-api 17 1 4 4 0 8
nico-rest-cert-manager 14 1 3 3 0 7
nico-rest-db 15 1 3 3 0 8
nico-rest-site-agent 15 1 3 3 0 8
nico-rest-site-manager 14 1 3 3 0 7
nico-rest-workflow 15 1 3 3 0 8
TOTAL 127 8 26 30 0 63

Per-CVE detail lives in the per-service grype-* artifacts (JSON + SARIF). Severity counts only — no CVE IDs published here.

@hwadekar-nv hwadekar-nv changed the title feat: Tenant TargetedInstanceCreation capability implementation feat(rest-api): Tenant TargetedInstanceCreation capability implementation Jun 3, 2026
@hwadekar-nv hwadekar-nv force-pushed the feat/tenant-capabilities-update branch from 2fff422 to 7d6bcc6 Compare June 3, 2026 22:10
@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Summary by CodeRabbit

  • New Features

    • Added provider- and site-scoped configuration for targeted instance creation.
    • Tenant account responses now include site capabilities.
    • Added support for updating site capabilities and accepting tenant invitations.
    • Added validation for global and limited site capability settings.
  • Bug Fixes

    • Improved authorization accuracy across instance, machine, site, SKU, VPC, and expected-resource operations.
    • Invalid site, provider, tenant, and instance-type requests now return clearer client errors.
  • Documentation

    • Documented the new tenant-account capability settings.
    • Marked tenant-level targeted instance creation capability as deprecated and read-only.

Walkthrough

This PR moves targeted instance creation from tenant-level configuration to tenant-account and tenant-site scoped JSONB configuration. It adds site-capability APIs and resolution helpers, updates authorization paths and persistence models, migrates existing data, and documents the deprecation and replacement contract.

Changes

Targeted Instance Creation Capability Rework

Layer / File(s) Summary
Capability storage and migration
rest-api/db/pkg/db/model/*, rest-api/db/pkg/migrations/*, rest-api/db/pkg/db/model/*_test.go
Moves capability configuration to typed tenant-account and tenant-site JSONB fields, removes tenant-level configuration, updates DAO merge/removal behavior, and migrates existing values.
Capability resolution
rest-api/api/pkg/api/handler/util/common/*
Adds tenant-level and site-level effective capability resolution and applies it to shared privilege checks.
Tenant-account API flow
rest-api/api/pkg/api/model/tenantaccount*, rest-api/api/pkg/api/handler/tenantaccount.go, serviceaccount.go
Adds validated siteCapabilities request/response models, provider-admin replacement handling, tenant-admin invite acceptance, and tenant-account capability initialization.
Handler authorization
rest-api/api/pkg/api/handler/{instance,machine,expectedmachine,site,sku,vpc,tenant}.go, associated tests
Replaces tenant-level checks with aggregate or site-scoped capability evaluation and updates authorization fixtures.
API documentation
rest-api/openapi/spec.yaml, oasdiff-breaking-changes-ignore.txt
Documents the deprecated aggregate capability, site-capability schemas, update rules, examples, and compatibility changes.
Test support
rest-api/api/pkg/api/handler/util/common/testing.go, rest-api/workflow/pkg/util/testing.go, rest-api/workflow/pkg/activity/*_test.go
Adds tenant-account capability fixtures and updates workflow tenant construction after tenant configuration removal.

Estimated code review effort: 5 (Critical) | ~120 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the new TargetedInstanceCreation capability work.
Description check ✅ Passed The description is clearly related to the implemented capability, API, migration, and authorization changes.
Linked Issues check ✅ Passed The changes align with #2104 by adding tenant-account/site capability toggles, migration, API support, and site-aware enforcement.
Out of Scope Changes check ✅ Passed The diff stays focused on the capability migration and its dependent auth, model, and test updates; no clear unrelated feature work appears.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.44.1)
rest-api/docs/index.html

ast-grep timed out on this file


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

@hwadekar-nv hwadekar-nv force-pushed the feat/tenant-capabilities-update branch 2 times, most recently from ec337d8 to eeb42e0 Compare June 4, 2026 20:23
@hwadekar-nv

Copy link
Copy Markdown
Contributor Author

Will start working on this PR after 2.0. We have new strategies to allow tenants to update capabilities via TenantAccount and TenantSite across all Sites or specific Site.

@hwadekar-nv hwadekar-nv force-pushed the feat/tenant-capabilities-update branch 2 times, most recently from 685f8f1 to c3402d9 Compare July 2, 2026 18:32
@hwadekar-nv hwadekar-nv added this to the v2.1 milestone Jul 2, 2026
@hwadekar-nv hwadekar-nv force-pushed the feat/tenant-capabilities-update branch 2 times, most recently from 482fc88 to d20920e Compare July 7, 2026 21:31
@hwadekar-nv hwadekar-nv marked this pull request as ready for review July 7, 2026 21:37
@hwadekar-nv

Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

@hwadekar-nv hwadekar-nv force-pushed the feat/tenant-capabilities-update branch from d20920e to 3c8f601 Compare July 7, 2026 21: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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
rest-api/db/pkg/db/model/tenantaccount.go (1)

546-564: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Make mixed TenantAccount updates atomic.

Update can run the scalar-column update and the config merge as separate statements. If a caller passes tx == nil with both normal fields and Config, the first update can commit before the config merge fails. Prefer building one UPDATE, or require/wrap mixed updates in cdb.WithTx.

🤖 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 `@rest-api/db/pkg/db/model/tenantaccount.go` around lines 546 - 564, The
TenantAccount update path in the update method currently splits scalar field
changes and Config merging into separate UPDATE statements, which can leave
mixed updates partially applied when tx is nil. Refactor the logic around
updatedFields/input.Config in TenantAccount.Update to perform a single atomic
update when both kinds of changes are present, or ensure mixed updates are
always executed inside cdb.WithTx by wrapping/requiring a transaction before
running db.GetIDB(...).NewUpdate().Exec.

Source: Path instructions

🧹 Nitpick comments (3)
rest-api/api/pkg/api/model/tenantaccountsitecapability.go (1)

199-206: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer a receiver method over a free function on the named type.

GlobalTargetedInstanceCreationFromRequest operates entirely on APITenantAccountSiteCapabilitiesUpdateRequest; expressing it as a method (e.g. caps.GlobalTargetedInstanceCreation()) keeps behavior colocated with the type and reads more naturally at the call site.

As per path instructions: "discourage scattered independent functions when a receiver method would make ownership and responsibilities clearer."

♻️ Proposed conversion to a receiver method
-func GlobalTargetedInstanceCreationFromRequest(caps APITenantAccountSiteCapabilitiesUpdateRequest) *bool {
-	for _, cap := range caps {
+func (caps APITenantAccountSiteCapabilitiesUpdateRequest) GlobalTargetedInstanceCreation() *bool {
+	for _, cap := range caps {
 		if cap.Scope == TenantAccountSiteCapabilityScopeGlobal {
 			return cutil.GetPtr(cap.TargetedInstanceCreation)
 		}
 	}
 	return nil
 }
🤖 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 `@rest-api/api/pkg/api/model/tenantaccountsitecapability.go` around lines 199 -
206, Convert GlobalTargetedInstanceCreationFromRequest into a receiver method on
APITenantAccountSiteCapabilitiesUpdateRequest so the behavior lives with the
type and can be called as caps.GlobalTargetedInstanceCreation(). Keep the same
logic that scans for TenantAccountSiteCapabilityScopeGlobal and returns the
targeted instance creation value, but move it onto the named type and update any
call sites accordingly.

Source: Path instructions

rest-api/db/pkg/db/model/tenantsite_test.go (2)

287-293: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use range-over-integer for this counting loop.

-	for i := 0; i < siteCount; i++ {
+	for i := range siteCount {

As per coding guidelines, “Counting loop → range-over-integer.” As per path instructions, prefer range-based iteration over C-style for loops.

🤖 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 `@rest-api/db/pkg/db/model/tenantsite_test.go` around lines 287 - 293, The
counting loop in the tenant site test should use range-over-integer instead of a
C-style for loop. Update the iteration in the site-building block within the
test that uses TestBuildSite and TestBuildTenantSite so it iterates with a
range-based integer loop over siteCount, while preserving the existing indexing
and even/odd behavior.

Sources: Coding guidelines, Path instructions


602-701: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test case for RemoveTargetedInstanceCreation.

The DAO now has a clear-override branch, but this table only verifies config merge. Add a case that starts with targetedInstanceCreation=true, updates with RemoveTargetedInstanceCreation: true, and asserts the pointer is cleared.

🤖 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 `@rest-api/db/pkg/db/model/tenantsite_test.go` around lines 602 - 701, Add a
missing Update test for the clear-override path in TenantSiteSQLDAO.Update: the
current TestTenantSiteSQLDAO_Update only covers config merge, so add a case that
seeds TenantSiteConfig.TargetedInstanceCreation as true, calls Update with
TenantSiteConfigUpdateInput.RemoveTargetedInstanceCreation set to true, and
asserts the resulting TenantSite.Config.TargetedInstanceCreation is cleared/nil.
Use the existing TenantSiteSQLDAO.Update, TenantSiteUpdateInput, and
TenantSiteConfigUpdateInput symbols to locate the right spot in the table-driven
test.

Source: Path instructions

🤖 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 `@rest-api/api/pkg/api/handler/machine.go`:
- Line 245: Update the privileged machine read authorization to use the
site-effective targeted-instance capability instead of the legacy tenant
flag/provider-account check. In the machine read paths around the
TenantHasTargetedInstanceCreation logic, switch single-machine reads to
EffectiveTargetedInstanceCreation(ctx, nil, ..., tenant, machine.SiteID), and
for list reads apply the same site-aware rule when siteId is present so
disabled-site machines are not returned in tenant-privileged results. Make sure
the behavior in the machine handler keeps honoring TenantAccount.config, Ready
status, and TenantSite.config overrides.
- Around line 762-768: The site-scoped capability check in IsProviderOrTenant is
being bypassed by the early requirePrivilegedTenant gate, which can reject
legacy TenantAccount tenants before machine.SiteID is evaluated. Update the
IsProviderOrTenant flow to defer the privileged-tenant rejection until after the
EffectiveTargetedInstanceCreation check runs, keeping the machine.SiteID-based
logic as the authoritative permission decision and removing the premature
pre-gate.

In `@rest-api/api/pkg/api/handler/util/common/common.go`:
- Around line 1467-1483: The privileged pre-check is still using the deprecated
tenant-level flag in TenantHasTargetedInstanceCreation, which can incorrectly
reject tenants enabled via the new TenantAccount.config path. Update the
requirePrivileged / requirePrivilegedTenant flow in common.go so non-site-scoped
privilege is resolved from the ready TenantAccount config, or remove this early
gate for site-aware callers and let EffectiveTargetedInstanceCreation be the
authoritative check. Keep the fix localized around
TenantHasTargetedInstanceCreation and the caller(s) that enforce the privileged
gate.

In `@rest-api/db/pkg/db/model/tenantsite.go`:
- Around line 314-334: The TenantSite update path currently applies Config
merging and RemoveTargetedInstanceCreation as separate updates, which can leave
partial state and makes conflicting input ambiguous. Update the logic in the
TenantSite model update flow to validate that both inputs are not used together,
then build a single NewUpdate/Exec path that applies either the JSONB merge or
the targetedInstanceCreation removal, not both. Use the existing update block
around input.Config and input.RemoveTargetedInstanceCreation to centralize the
decision and preserve one atomic update per request.

In
`@rest-api/db/pkg/migrations/20260701120000_tenant_account_site_capabilities.go`:
- Around line 26-33: The backfill in the migration only applies to Ready
tenant_account rows, so invited or pending accounts that later become Ready will
miss the legacy targetedInstanceCreation capability. Update the migration logic
in the tenant_account/tenant backfill to copy the capability for all matching
tenant_account rows tied to tenants with
tenant.config.targetedInstanceCreation=true, and if the status filter must
remain elsewhere, ensure the status transition path in tenant account handling
also propagates this config field when moving to Ready.

---

Outside diff comments:
In `@rest-api/db/pkg/db/model/tenantaccount.go`:
- Around line 546-564: The TenantAccount update path in the update method
currently splits scalar field changes and Config merging into separate UPDATE
statements, which can leave mixed updates partially applied when tx is nil.
Refactor the logic around updatedFields/input.Config in TenantAccount.Update to
perform a single atomic update when both kinds of changes are present, or ensure
mixed updates are always executed inside cdb.WithTx by wrapping/requiring a
transaction before running db.GetIDB(...).NewUpdate().Exec.

---

Nitpick comments:
In `@rest-api/api/pkg/api/model/tenantaccountsitecapability.go`:
- Around line 199-206: Convert GlobalTargetedInstanceCreationFromRequest into a
receiver method on APITenantAccountSiteCapabilitiesUpdateRequest so the behavior
lives with the type and can be called as caps.GlobalTargetedInstanceCreation().
Keep the same logic that scans for TenantAccountSiteCapabilityScopeGlobal and
returns the targeted instance creation value, but move it onto the named type
and update any call sites accordingly.

In `@rest-api/db/pkg/db/model/tenantsite_test.go`:
- Around line 287-293: The counting loop in the tenant site test should use
range-over-integer instead of a C-style for loop. Update the iteration in the
site-building block within the test that uses TestBuildSite and
TestBuildTenantSite so it iterates with a range-based integer loop over
siteCount, while preserving the existing indexing and even/odd behavior.
- Around line 602-701: Add a missing Update test for the clear-override path in
TenantSiteSQLDAO.Update: the current TestTenantSiteSQLDAO_Update only covers
config merge, so add a case that seeds TenantSiteConfig.TargetedInstanceCreation
as true, calls Update with
TenantSiteConfigUpdateInput.RemoveTargetedInstanceCreation set to true, and
asserts the resulting TenantSite.Config.TargetedInstanceCreation is cleared/nil.
Use the existing TenantSiteSQLDAO.Update, TenantSiteUpdateInput, and
TenantSiteConfigUpdateInput symbols to locate the right spot in the table-driven
test.
🪄 Autofix (Beta)

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: CHILL

Plan: Enterprise

Run ID: aef13451-a675-454a-9c90-57bbe59434be

📥 Commits

Reviewing files that changed from the base of the PR and between 1c22f4d and d20920e.

⛔ Files ignored due to path filters (40)
  • rest-api/sdk/standard/api_tenant_account.go is excluded by !rest-api/sdk/standard/api_*.go
  • rest-api/sdk/standard/client.go is excluded by !rest-api/sdk/standard/client.go
  • rest-api/sdk/standard/model_batch_instance_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_machine.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_machine_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_machine_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_power_shelf.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_power_shelf_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_power_shelf_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_rack.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_rack_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_rack_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_switch.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_switch_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_switch_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_infini_band_partition.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_infini_band_partition_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_infini_band_partition_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_instance.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_instance_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_instance_type.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_instance_type_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_instance_type_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_instance_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_interface.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_interface_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_machine.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_machine_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_network_security_group.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_network_security_group_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_network_security_group_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account_site_capability.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account_site_capability_scope.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_capabilities.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_vpc.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_vpc_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_vpc_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
📒 Files selected for processing (21)
  • rest-api/api/pkg/api/handler/instance.go
  • rest-api/api/pkg/api/handler/machine.go
  • rest-api/api/pkg/api/handler/serviceaccount.go
  • rest-api/api/pkg/api/handler/tenantaccount.go
  • rest-api/api/pkg/api/handler/util/common/common.go
  • rest-api/api/pkg/api/handler/util/common/common_test.go
  • rest-api/api/pkg/api/handler/vpc.go
  • rest-api/api/pkg/api/model/operatingsystem_test.go
  • rest-api/api/pkg/api/model/sshkeygroup_test.go
  • rest-api/api/pkg/api/model/tenant.go
  • rest-api/api/pkg/api/model/tenant_test.go
  • rest-api/api/pkg/api/model/tenantaccount.go
  • rest-api/api/pkg/api/model/tenantaccount_test.go
  • rest-api/api/pkg/api/model/tenantaccountsitecapability.go
  • rest-api/db/pkg/db/model/tenantaccount.go
  • rest-api/db/pkg/db/model/tenantsite.go
  • rest-api/db/pkg/db/model/tenantsite_test.go
  • rest-api/db/pkg/db/model/testing.go
  • rest-api/db/pkg/migrations/20230412224951_tenant_site.go
  • rest-api/db/pkg/migrations/20260701120000_tenant_account_site_capabilities.go
  • rest-api/openapi/spec.yaml

Comment thread rest-api/api/pkg/api/handler/machine.go Outdated
Comment thread rest-api/api/pkg/api/handler/machine.go Outdated
Comment thread rest-api/api/pkg/api/handler/util/common/common.go Outdated
Comment thread rest-api/db/pkg/db/model/tenantsite.go
Comment thread rest-api/db/pkg/migrations/20260701120000_tenant_account_site_capabilities.go Outdated
@hwadekar-nv hwadekar-nv force-pushed the feat/tenant-capabilities-update branch from 4a12952 to b0ee8a6 Compare July 8, 2026 00:01

@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

🧹 Nitpick comments (2)
rest-api/db/pkg/db/model/tenantsite_test.go (1)

287-297: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer range-over-integer for this counting loop.

The index is used solely as a counter (i%2, Sprintf), so this is a textbook candidate for Go's range-over-integer form.

♻️ Suggested refactor
-	sites := []*Site{}
-	siteCount := 30
-	for i := 0; i < siteCount; i++ {
+	sites := []*Site{}
+	siteCount := 30
+	for i := range siteCount {

As per path instructions: "Counting loop → range-over-integer. When the bound is a count ... use for i := range n".

🤖 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 `@rest-api/db/pkg/db/model/tenantsite_test.go` around lines 287 - 297, The
counting loop in the tenant site test uses an index only as a counter for naming
and parity checks, so refactor the loop to use Go’s range-over-integer form
instead of a classic three-part loop. Update the loop in the test setup that
builds sites and calls TestBuildSite/TestBuildTenantSite so the iteration
variable still supports the Sprintf name generation and i%2 branch, but the loop
itself follows the preferred range-over-integer pattern.

Source: Path instructions

rest-api/api/pkg/api/handler/machine.go (1)

305-331: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Redundant per-site resolution creates an N+1 on the machine-list hot path.

EffectiveTargetedInstanceCreation internally re-fetches the Site by ID and re-queries Ready TenantAccounts per provider, yet this loop already holds each site from siteDAO.GetAll and the provider is fixed for the inner iteration. The cost is O(providers × sites) with three DB round-trips per site. Consider resolving the provider's Ready TenantAccount once outside the inner loop and evaluating the per-site TenantSite override directly against the already-loaded site.

🤖 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 `@rest-api/api/pkg/api/handler/machine.go` around lines 305 - 331, The machine
list path has an N+1 problem because the loop in the handler around
siteDAO.GetAll and common.EffectiveTargetedInstanceCreation re-fetches each Site
and re-queries TenantAccount data for every site/provider pair. Refactor this
block so the provider’s Ready TenantAccount is resolved once per provider
outside the inner site loop, then use the already-loaded site data to check the
per-site TenantSite override directly instead of calling
EffectiveTargetedInstanceCreation for every site. Keep the existing error
handling/logging structure in the same handler flow.
🤖 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 `@rest-api/api/pkg/api/handler/machine.go`:
- Around line 294-337: The machine list filtering in the tenant branch is using
provider IDs even though EffectiveTargetedInstanceCreation is evaluated per
site, which can widen results to disabled sites. Update the logic in machine.go
around the tenant/filterSite handling to collect and append enabled site IDs
instead of provider IDs, and apply them through MachineFilterInput.SiteIDs
rather than InfrastructureProviderIDs. Keep the existing
EffectiveTargetedInstanceCreation and SiteDAO.GetAll flow, but change the
aggregation variable and final filter assignment so the list stays scoped to
sites where targeted instance creation is enabled.

---

Nitpick comments:
In `@rest-api/api/pkg/api/handler/machine.go`:
- Around line 305-331: The machine list path has an N+1 problem because the loop
in the handler around siteDAO.GetAll and
common.EffectiveTargetedInstanceCreation re-fetches each Site and re-queries
TenantAccount data for every site/provider pair. Refactor this block so the
provider’s Ready TenantAccount is resolved once per provider outside the inner
site loop, then use the already-loaded site data to check the per-site
TenantSite override directly instead of calling
EffectiveTargetedInstanceCreation for every site. Keep the existing error
handling/logging structure in the same handler flow.

In `@rest-api/db/pkg/db/model/tenantsite_test.go`:
- Around line 287-297: The counting loop in the tenant site test uses an index
only as a counter for naming and parity checks, so refactor the loop to use Go’s
range-over-integer form instead of a classic three-part loop. Update the loop in
the test setup that builds sites and calls TestBuildSite/TestBuildTenantSite so
the iteration variable still supports the Sprintf name generation and i%2
branch, but the loop itself follows the preferred range-over-integer pattern.
🪄 Autofix (Beta)

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: CHILL

Plan: Enterprise

Run ID: 8bd15369-c9f6-45e7-b4dd-b1d7c80569ca

📥 Commits

Reviewing files that changed from the base of the PR and between 3c8f601 and b0ee8a6.

⛔ Files ignored due to path filters (40)
  • rest-api/sdk/standard/api_tenant_account.go is excluded by !rest-api/sdk/standard/api_*.go
  • rest-api/sdk/standard/client.go is excluded by !rest-api/sdk/standard/client.go
  • rest-api/sdk/standard/model_batch_instance_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_machine.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_machine_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_machine_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_power_shelf.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_power_shelf_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_power_shelf_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_rack.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_rack_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_rack_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_switch.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_switch_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_switch_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_infini_band_partition.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_infini_band_partition_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_infini_band_partition_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_instance.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_instance_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_instance_type.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_instance_type_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_instance_type_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_instance_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_interface.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_interface_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_machine.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_machine_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_network_security_group.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_network_security_group_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_network_security_group_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account_site_capability.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account_site_capability_scope.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_capabilities.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_vpc.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_vpc_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_vpc_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
📒 Files selected for processing (23)
  • rest-api/api/pkg/api/handler/instance.go
  • rest-api/api/pkg/api/handler/machine.go
  • rest-api/api/pkg/api/handler/machine_test.go
  • rest-api/api/pkg/api/handler/serviceaccount.go
  • rest-api/api/pkg/api/handler/tenantaccount.go
  • rest-api/api/pkg/api/handler/util/common/common.go
  • rest-api/api/pkg/api/handler/util/common/common_test.go
  • rest-api/api/pkg/api/handler/vpc.go
  • rest-api/api/pkg/api/model/operatingsystem_test.go
  • rest-api/api/pkg/api/model/sshkeygroup_test.go
  • rest-api/api/pkg/api/model/tenant.go
  • rest-api/api/pkg/api/model/tenant_test.go
  • rest-api/api/pkg/api/model/tenantaccount.go
  • rest-api/api/pkg/api/model/tenantaccount_test.go
  • rest-api/api/pkg/api/model/tenantaccountsitecapability.go
  • rest-api/db/pkg/db/model/tenantaccount.go
  • rest-api/db/pkg/db/model/tenantaccount_test.go
  • rest-api/db/pkg/db/model/tenantsite.go
  • rest-api/db/pkg/db/model/tenantsite_test.go
  • rest-api/db/pkg/db/model/testing.go
  • rest-api/db/pkg/migrations/20230412224951_tenant_site.go
  • rest-api/db/pkg/migrations/20260701120000_tenant_account_site_capabilities.go
  • rest-api/openapi/spec.yaml
✅ Files skipped from review due to trivial changes (1)
  • rest-api/api/pkg/api/model/sshkeygroup_test.go
🚧 Files skipped from review as they are similar to previous changes (14)
  • rest-api/db/pkg/migrations/20230412224951_tenant_site.go
  • rest-api/db/pkg/db/model/testing.go
  • rest-api/api/pkg/api/model/tenant_test.go
  • rest-api/api/pkg/api/model/operatingsystem_test.go
  • rest-api/openapi/spec.yaml
  • rest-api/api/pkg/api/model/tenantaccount_test.go
  • rest-api/api/pkg/api/handler/instance.go
  • rest-api/api/pkg/api/handler/vpc.go
  • rest-api/db/pkg/db/model/tenantaccount.go
  • rest-api/api/pkg/api/model/tenantaccount.go
  • rest-api/api/pkg/api/model/tenantaccountsitecapability.go
  • rest-api/api/pkg/api/model/tenant.go
  • rest-api/db/pkg/db/model/tenantsite.go
  • rest-api/api/pkg/api/handler/tenantaccount.go

Comment thread rest-api/api/pkg/api/handler/machine.go Outdated
@hwadekar-nv hwadekar-nv force-pushed the feat/tenant-capabilities-update branch from fdc9a2d to ffd8c7b Compare July 8, 2026 16:38

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
rest-api/api/pkg/api/handler/tenantaccount.go (2)

397-464: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

GetAllTenantAccountHandler silently drops per-site capability overrides.

Every list-response APITenantAccount is built with tenantSites=nil (Line 447), while GetTenantAccountHandler (Line 585) and the new PUT response (Line 932) now fetch and pass the actual TenantSite slice. Since NewAPITenantAccount derives SiteCapabilities from tenantSites, the list endpoint will only ever report the global scope and silently omit any limited per-site overrides — producing an inconsistent contract between GET /tenant-account (list) and GET /tenant-account/{id} for the same brand-new field.

Consider batch-fetching TenantSite rows grouped by tenant ID (mirroring the existing allocation-count batching pattern already in this handler) so list responses stay consistent with the detail view.

🤖 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 `@rest-api/api/pkg/api/handler/tenantaccount.go` around lines 397 - 464,
GetAllTenantAccountHandler is building APITenantAccount list items with
tenantSites left nil, so NewAPITenantAccount cannot compute SiteCapabilities and
per-site limited overrides are lost. Update the list path in tenantaccount.go to
batch-load TenantSite rows for the tenants in tas, grouped by tenant ID similar
to the existing allocation-count fetch, and pass the matching tenantSites into
model.NewAPITenantAccount just like GetTenantAccountHandler and the PUT response
do.

907-937: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Hardcoded allocationCount: 0 in the siteCapabilities update response.

model.NewAPITenantAccount(ta, ssds, 0, tenantSites) always reports zero allocations, even though GetTenantAccountHandler computes the real value via aDAO.GetCount(...) for the exact same entity. A Provider Admin updating siteCapabilities on an active TenantAccount with existing allocations will get back a response claiming zero allocations.

🐛 Proposed fix
+	aDAO := cdbm.NewAllocationDAO(utah.dbSession)
+	total := 0
+	if ta.TenantID != nil {
+		cnt, cerr := aDAO.GetCount(ctx, nil, cdbm.AllocationFilterInput{
+			InfrastructureProviderIDs: []uuid.UUID{ta.InfrastructureProviderID},
+			TenantIDs:                 []uuid.UUID{*ta.TenantID},
+		})
+		if cerr != nil {
+			logger.Error().Err(cerr).Msg("error retrieving allocation count for Tenant Account from DB")
+			return cutil.NewAPIErrorResponse(c, http.StatusInternalServerError, "Failed to retrieve Allocations to determine total allocation for tenant account", nil)
+		}
+		total = cnt
+	}
+
 	sdDAO := cdbm.NewStatusDetailDAO(utah.dbSession)
 	ssds, _, err := sdDAO.GetAll(ctx, nil, cdbm.StatusDetailFilterInput{EntityIDs: []string{ta.ID.String()}}, cdbp.PageInput{Limit: cutil.GetPtr(pagination.MaxPageSize)})
 	if err != nil {
 		logger.Error().Err(err).Msg("error retrieving Status Details for TenantAccount from DB")
 		return cutil.NewAPIErrorResponse(c, http.StatusInternalServerError, "Failed to retrieve Status Details for TenantAccount", nil)
 	}
 
-	apiInstance := model.NewAPITenantAccount(ta, ssds, 0, tenantSites)
+	apiInstance := model.NewAPITenantAccount(ta, ssds, total, tenantSites)
🤖 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 `@rest-api/api/pkg/api/handler/tenantaccount.go` around lines 907 - 937, The
update response is hardcoding allocationCount to 0 in the call to
model.NewAPITenantAccount, so the returned TenantAccount can incorrectly show no
allocations. Reuse the same allocation-count logic already used in
GetTenantAccountHandler by fetching the real count for ta.ID before building the
response, then pass that value instead of 0 when constructing apiInstance.
rest-api/api/pkg/api/handler/expectedmachine.go (1)

351-365: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Mirror the TenantAccount fallback in the ExpectedMachine list filter.

GetAllExpectedMachineHandler only adds TenantSite.SiteID values to filterInput.SiteIDs. A privileged tenant that is authorized through a ready TenantAccount but has no explicit TenantSite row for a provider site will get an empty result set, and a siteId query for that site will be rejected, even though ValidateProviderOrTenantSiteAccess allows the same access path. Reuse the same access resolution here or extract a shared helper.

🤖 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 `@rest-api/api/pkg/api/handler/expectedmachine.go` around lines 351 - 365, The
ExpectedMachine list filter only uses TenantSite rows, so privileged tenants can
miss access granted through a ready TenantAccount. Update
GetAllExpectedMachineHandler to resolve site access the same way
ValidateProviderOrTenantSiteAccess does, or extract and reuse a shared helper,
so filterInput.SiteIDs includes both TenantSite.SiteID values and any provider
sites reachable via the TenantAccount fallback before applying the list filter.

Source: Path instructions

🧹 Nitpick comments (1)
rest-api/api/pkg/api/handler/tenantaccount.go (1)

785-932: 🎯 Functional Correctness | 🔵 Trivial | 🏗️ Heavy lift

Add handler-level test coverage for the new provider siteCapabilities flow.

This introduces a non-trivial, security-sensitive authorization path (advisory-locked transaction, global vs. limited scope resolution, stale-override cleanup, cross-provider site scoping) with no accompanying tenantaccount_test.go changes in this layer. Given the privilege-granting nature of this endpoint, targeted tests (ownership rejection, stale-override cleanup, limited-site-not-owned-by-provider rejection) would materially reduce regression risk.

Based on path instructions: "Review Go code for correctness, clean control flow, error handling, context propagation, test coverage, performance, and cohesive organization..."

🤖 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 `@rest-api/api/pkg/api/handler/tenantaccount.go` around lines 785 - 932, Add
handler-level tests for handleProviderSiteCapabilitiesUpdate in tenantaccount.go
to cover the new provider siteCapabilities flow. Exercise the security-sensitive
branches by verifying ownership/provider rejection, limited-site validation when
a Site is not owned by the Tenant Account Infrastructure Provider, and stale
TenantSite override cleanup when a site is removed from the limited set. Use the
existing UpdateTenantAccountHandler, TenantAccountDAO/TenantSiteDAO behavior,
and the advisory-locked transaction path to locate the right handler logic.

Source: Path instructions

🤖 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.

Outside diff comments:
In `@rest-api/api/pkg/api/handler/expectedmachine.go`:
- Around line 351-365: The ExpectedMachine list filter only uses TenantSite
rows, so privileged tenants can miss access granted through a ready
TenantAccount. Update GetAllExpectedMachineHandler to resolve site access the
same way ValidateProviderOrTenantSiteAccess does, or extract and reuse a shared
helper, so filterInput.SiteIDs includes both TenantSite.SiteID values and any
provider sites reachable via the TenantAccount fallback before applying the list
filter.

In `@rest-api/api/pkg/api/handler/tenantaccount.go`:
- Around line 397-464: GetAllTenantAccountHandler is building APITenantAccount
list items with tenantSites left nil, so NewAPITenantAccount cannot compute
SiteCapabilities and per-site limited overrides are lost. Update the list path
in tenantaccount.go to batch-load TenantSite rows for the tenants in tas,
grouped by tenant ID similar to the existing allocation-count fetch, and pass
the matching tenantSites into model.NewAPITenantAccount just like
GetTenantAccountHandler and the PUT response do.
- Around line 907-937: The update response is hardcoding allocationCount to 0 in
the call to model.NewAPITenantAccount, so the returned TenantAccount can
incorrectly show no allocations. Reuse the same allocation-count logic already
used in GetTenantAccountHandler by fetching the real count for ta.ID before
building the response, then pass that value instead of 0 when constructing
apiInstance.

---

Nitpick comments:
In `@rest-api/api/pkg/api/handler/tenantaccount.go`:
- Around line 785-932: Add handler-level tests for
handleProviderSiteCapabilitiesUpdate in tenantaccount.go to cover the new
provider siteCapabilities flow. Exercise the security-sensitive branches by
verifying ownership/provider rejection, limited-site validation when a Site is
not owned by the Tenant Account Infrastructure Provider, and stale TenantSite
override cleanup when a site is removed from the limited set. Use the existing
UpdateTenantAccountHandler, TenantAccountDAO/TenantSiteDAO behavior, and the
advisory-locked transaction path to locate the right handler logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 337a24c6-0bfa-4a62-ab56-b6e48d7222c7

📥 Commits

Reviewing files that changed from the base of the PR and between b0ee8a6 and ffd8c7b.

⛔ Files ignored due to path filters (40)
  • rest-api/sdk/standard/api_tenant_account.go is excluded by !rest-api/sdk/standard/api_*.go
  • rest-api/sdk/standard/client.go is excluded by !rest-api/sdk/standard/client.go
  • rest-api/sdk/standard/model_batch_instance_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_machine.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_machine_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_machine_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_power_shelf.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_power_shelf_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_power_shelf_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_rack.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_rack_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_rack_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_switch.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_switch_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_expected_switch_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_infini_band_partition.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_infini_band_partition_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_infini_band_partition_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_instance.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_instance_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_instance_type.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_instance_type_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_instance_type_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_instance_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_interface.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_interface_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_machine.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_machine_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_network_security_group.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_network_security_group_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_network_security_group_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account_site_capability.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account_site_capability_scope.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_capabilities.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_vpc.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_vpc_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_vpc_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
📒 Files selected for processing (31)
  • rest-api/api/pkg/api/handler/dpureprovision_test.go
  • rest-api/api/pkg/api/handler/expectedmachine.go
  • rest-api/api/pkg/api/handler/expectedmachine_test.go
  • rest-api/api/pkg/api/handler/instance.go
  • rest-api/api/pkg/api/handler/instance_test.go
  • rest-api/api/pkg/api/handler/machine.go
  • rest-api/api/pkg/api/handler/machine_test.go
  • rest-api/api/pkg/api/handler/machinehealth_test.go
  • rest-api/api/pkg/api/handler/serviceaccount.go
  • rest-api/api/pkg/api/handler/sku_test.go
  • rest-api/api/pkg/api/handler/tenantaccount.go
  • rest-api/api/pkg/api/handler/util/common/common.go
  • rest-api/api/pkg/api/handler/util/common/common_test.go
  • rest-api/api/pkg/api/handler/util/common/testing.go
  • rest-api/api/pkg/api/handler/vpc.go
  • rest-api/api/pkg/api/handler/vpc_test.go
  • rest-api/api/pkg/api/model/operatingsystem_test.go
  • rest-api/api/pkg/api/model/sshkeygroup_test.go
  • rest-api/api/pkg/api/model/tenant.go
  • rest-api/api/pkg/api/model/tenant_test.go
  • rest-api/api/pkg/api/model/tenantaccount.go
  • rest-api/api/pkg/api/model/tenantaccount_test.go
  • rest-api/api/pkg/api/model/tenantaccountsitecapability.go
  • rest-api/db/pkg/db/model/tenantaccount.go
  • rest-api/db/pkg/db/model/tenantaccount_test.go
  • rest-api/db/pkg/db/model/tenantsite.go
  • rest-api/db/pkg/db/model/tenantsite_test.go
  • rest-api/db/pkg/db/model/testing.go
  • rest-api/db/pkg/migrations/20230412224951_tenant_site.go
  • rest-api/db/pkg/migrations/20260701120000_tenant_account_site_capabilities.go
  • rest-api/openapi/spec.yaml
✅ Files skipped from review due to trivial changes (2)
  • rest-api/api/pkg/api/model/sshkeygroup_test.go
  • rest-api/db/pkg/migrations/20230412224951_tenant_site.go
🚧 Files skipped from review as they are similar to previous changes (20)
  • rest-api/api/pkg/api/model/tenantaccount_test.go
  • rest-api/api/pkg/api/model/operatingsystem_test.go
  • rest-api/api/pkg/api/model/tenant_test.go
  • rest-api/db/pkg/migrations/20260701120000_tenant_account_site_capabilities.go
  • rest-api/db/pkg/db/model/testing.go
  • rest-api/openapi/spec.yaml
  • rest-api/api/pkg/api/handler/vpc.go
  • rest-api/api/pkg/api/handler/instance.go
  • rest-api/api/pkg/api/handler/machine_test.go
  • rest-api/api/pkg/api/model/tenantaccountsitecapability.go
  • rest-api/api/pkg/api/handler/util/common/common.go
  • rest-api/api/pkg/api/handler/util/common/common_test.go
  • rest-api/api/pkg/api/model/tenant.go
  • rest-api/api/pkg/api/handler/serviceaccount.go
  • rest-api/db/pkg/db/model/tenantaccount_test.go
  • rest-api/db/pkg/db/model/tenantsite.go
  • rest-api/api/pkg/api/handler/machine.go
  • rest-api/api/pkg/api/model/tenantaccount.go
  • rest-api/db/pkg/db/model/tenantaccount.go
  • rest-api/db/pkg/db/model/tenantsite_test.go

@hwadekar-nv hwadekar-nv force-pushed the feat/tenant-capabilities-update branch from 1375da6 to 04ce4d9 Compare July 8, 2026 17:09
@hwadekar-nv

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

@hwadekar-nv Understood — kicking off another full review of all changes in this PR now.

ᕕ( ᐛ )ᕗ

✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

@hwadekar-nv Understood — kicking off another full review of all changes in this PR now.

✅ Action performed

Full review finished.

@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: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
rest-api/api/pkg/api/handler/machine.go (2)

348-386: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Missing page limit can cause valid tenantId values to be wrongly rejected.

tenantAccountDAO.GetAll(...) at Line 362-365 is called with cdbp.PageInput{} (no Limit), so if the number of matching TenantAccount rows exceeds the DAO's default page size, some legitimately-associated tenantIds won't appear in tenantAccounts, and the loop at Line 374-378 will incorrectly return 403 for them. Elsewhere in this codebase, cdbp.PageInput{Limit: cutil.GetPtr(cdbp.TotalLimit)} is used explicitly whenever all matching rows are required.

🐛 Proposed fix
 			tenantAccounts, _, err := tenantAccountDAO.GetAll(ctx, nil, cdbm.TenantAccountFilterInput{
 				TenantIDs:                tenantIDs,
 				InfrastructureProviderID: &infrastructureProvider.ID,
-			}, cdbp.PageInput{}, nil)
+			}, cdbp.PageInput{Limit: cutil.GetPtr(cdbp.TotalLimit)}, nil)
🤖 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 `@rest-api/api/pkg/api/handler/machine.go` around lines 348 - 386, Update the
tenantAccountDAO.GetAll call in the tenantId validation flow to request all
matching records by passing a PageInput with Limit set to cdbp.TotalLimit via
cutil.GetPtr. Keep the tenantIDsMap construction and subsequent authorization
checks unchanged.

239-288: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Dual-role machine filters are over-constrained.

When both infrastructureProvider and tenant are set, this branch applies InfrastructureProviderIDs and SiteIDs together. MachineSQLDAO.setQueryWithFilter adds both predicates, so the query becomes an intersection and can drop valid machines for callers that hold both roles. Build a single SiteIDs list that unions provider-owned and tenant-privileged sites, as the adjacent handlers do.

🤖 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 `@rest-api/api/pkg/api/handler/machine.go` around lines 239 - 288, Update the
machine filter construction around filterInput, infrastructureProvider, and
privilegedSiteIDs so dual-role requests use one SiteIDs list that unions
infrastructure-provider-owned sites with tenant-privileged sites instead of
applying InfrastructureProviderIDs and SiteIDs as an intersection. Preserve the
existing siteId validation and authorization behavior, while ensuring callers
with both roles can retrieve machines from either authorized source.
rest-api/api/pkg/api/model/tenantaccount.go (1)

72-86: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject empty siteCapabilities payloads. HasSiteCapabilities() treats [] the same as an omitted field, so an explicit empty array falls through to tenant invite acceptance instead of returning 400. Track field presence explicitly so a provided but empty siteCapabilities body is rejected.

🤖 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 `@rest-api/api/pkg/api/model/tenantaccount.go` around lines 72 - 86, Update
APITenantAccountUpdateRequest and HasSiteCapabilities so field presence is
tracked separately from slice length, allowing an explicitly provided empty
siteCapabilities array to be distinguished from an omitted field. Reject the
explicit empty payload with the existing validation error path before tenant
invite acceptance, while preserving current handling for omitted and non-empty
payloads.

Source: Path instructions

rest-api/api/pkg/api/handler/tenantaccount.go (1)

919-948: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

AllocationCount hardcoded to 0 in the siteCapabilities update response.

Unlike GetTenantAccountHandler (Lines 551-563), which computes the real count via aDAO.GetCount(...), this path always returns AllocationCount: 0 at Line 944 — even though this endpoint updates capabilities on Ready TenantAccounts that may already have active Allocations. Clients relying on this response field after a PATCH will see a misleading 0.

🐛 Proposed fix mirroring GetTenantAccountHandler
+	aDAO := cdbm.NewAllocationDAO(utah.dbSession)
+	allocationCount := 0
+	if ta.TenantID != nil {
+		cnt, cerr := aDAO.GetCount(ctx, nil, cdbm.AllocationFilterInput{
+			InfrastructureProviderIDs: []uuid.UUID{ta.InfrastructureProviderID},
+			TenantIDs:                 []uuid.UUID{*ta.TenantID},
+		})
+		if cerr != nil {
+			logger.Error().Err(cerr).Msg("error retrieving allocation count for Tenant Account from DB")
+			return cutil.NewAPIErrorResponse(c, http.StatusInternalServerError, "Failed to retrieve Allocations to determine total allocation for tenant account", nil)
+		}
+		allocationCount = cnt
+	}
+
-	apiInstance := model.NewAPITenantAccount(ta, ssds, 0, tenantSites)
+	apiInstance := model.NewAPITenantAccount(ta, ssds, allocationCount, tenantSites)
🤖 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 `@rest-api/api/pkg/api/handler/tenantaccount.go` around lines 919 - 948,
Replace the hardcoded allocation count passed to model.NewAPITenantAccount in
the site-capabilities update handler with the real count, mirroring
GetTenantAccountHandler: initialize the allocation DAO, call GetCount for the
updated TenantAccount, handle any error consistently with the surrounding
database queries, and pass the resulting count into the response.
🧹 Nitpick comments (2)
rest-api/api/pkg/api/handler/tenantaccount.go (1)

397-449: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

List endpoint drops per-site capability overrides.

GetAllTenantAccountHandler always passes nil for tenantSites (Line 447), so tenantAccountSiteCapabilitiesToAPI only ever emits the global entry for every account in a list response — any "limited" per-site override is silently omitted, unlike GetTenantAccountHandler's single-account response which fetches and includes tenantSites. This creates a data-completeness discrepancy between list and detail views of the same resource.

Consider batching one TenantSiteDAO.GetAll call keyed by the union of TenantIDs present in tas, grouping the results by TenantID, and passing the matching slice per account instead of nil.

🤖 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 `@rest-api/api/pkg/api/handler/tenantaccount.go` around lines 397 - 449, Update
GetAllTenantAccountHandler to batch-fetch tenant sites once using the union of
non-nil TenantIDs in tas, group the results by TenantID, and pass each account’s
matching tenant-site slice to NewAPITenantAccount instead of nil. Preserve empty
slices for accounts without sites and keep the existing allocation-count logic
unchanged.
rest-api/api/pkg/api/model/tenantaccountsitecapability.go (1)

186-201: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the dead helper or route the handler through it

parseSiteCapabilitySiteIDs in rest-api/api/pkg/api/model/tenantaccountsitecapability.go:186 is unused, while rest-api/api/pkg/api/handler/tenantaccount.go:851 parses the same Limited-scope SiteIDs inline. Delete the helper or call it from the handler so this parsing logic has a single owner.

🤖 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 `@rest-api/api/pkg/api/model/tenantaccountsitecapability.go` around lines 186 -
201, The helper parseSiteCapabilitySiteIDs is unused while tenantaccount.go
duplicates its Limited-scope SiteIDs parsing inline. Route the handler through
parseSiteCapabilitySiteIDs and remove the duplicated parsing logic, or delete
the helper if the handler should remain inline; ensure only one implementation
owns this parsing behavior.
🤖 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 `@rest-api/api/pkg/api/handler/expectedmachine_test.go`:
- Around line 633-643: Adjust the Ready TenantAccount setup in the external-site
handler tests so it does not globally enable the capability for unmanagedIP.
Disable TargetedInstanceCreation in its Config or remove this account, ensuring
TenantSite authorization remains the sole access grant while preserving the
intended test setup.

In `@rest-api/api/pkg/api/handler/expectedpowershelf.go`:
- Line 74: Resolve each requested Site before authorization and pass a populated
TenantPrivilegeScope containing its SiteID and InfrastructureProviderID to all
authorization calls in rest-api/api/pkg/api/handler/expectedpowershelf.go at
lines 74, 251, 408, 516, and 686; expectedrack.go at lines 74, 250, 407, 513,
696, 824, and 989; and expectedswitch.go at lines 74, 278, 435, 543, and 742.
Preserve tenant/resource ownership checks; use nil only if these operations are
intentionally not capability-gated.

In `@rest-api/api/pkg/api/handler/instance.go`:
- Around line 894-903: Move the targeted instance creation capability checks in
CreateInstanceHandler and DeleteInstanceHandler before their respective
cdb.WithTx calls, invoking common.TenantHasTargetedInstanceCreation with nil for
the transaction. Preserve the existing error and forbidden responses, using the
already available site and instance tenant/site/provider values, and remove the
in-transaction checks so validation occurs before any transaction writes or
locks.

In `@rest-api/api/pkg/api/model/tenantaccountsitecapability.go`:
- Around line 168-184: Update filterTenantSitesForAccount to fail closed when
TenantSite.Site is nil: remove the branch that appends entries with missing Site
relations, so only sites whose InfrastructureProviderID matches the tenant
account are included.
- Around line 61-104: Normalize each validated siteID to its canonical UUID
representation before checking or recording it in seenSiteIDs within the
capability validation loop. Use the parsed UUID’s canonical String value as the
deduplication key, while preserving the existing duplicate-siteID validation
error and validation flow.

In `@rest-api/db/pkg/db/model/tenantaccount.go`:
- Around line 550-559: The tenant-account update flow must keep ordinary-field
and JSONB configuration changes atomic when tx is nil. Update the surrounding
method containing the input.Config block to combine both changes into one
UPDATE, or require and use a transaction whenever both updates are needed,
ensuring a configuration failure cannot leave the earlier field changes
committed.

In `@rest-api/db/pkg/migrations/migrations_test.go`:
- Around line 902-909: Update the tenantConfigUpMigration test to query
tenant1.config and assert the backfilled value equals an empty JSON object,
rather than only counting rows where config IS NULL. Preserve the existing error
assertion and use the test’s established JSON/value representation.

---

Outside diff comments:
In `@rest-api/api/pkg/api/handler/machine.go`:
- Around line 348-386: Update the tenantAccountDAO.GetAll call in the tenantId
validation flow to request all matching records by passing a PageInput with
Limit set to cdbp.TotalLimit via cutil.GetPtr. Keep the tenantIDsMap
construction and subsequent authorization checks unchanged.
- Around line 239-288: Update the machine filter construction around
filterInput, infrastructureProvider, and privilegedSiteIDs so dual-role requests
use one SiteIDs list that unions infrastructure-provider-owned sites with
tenant-privileged sites instead of applying InfrastructureProviderIDs and
SiteIDs as an intersection. Preserve the existing siteId validation and
authorization behavior, while ensuring callers with both roles can retrieve
machines from either authorized source.

In `@rest-api/api/pkg/api/handler/tenantaccount.go`:
- Around line 919-948: Replace the hardcoded allocation count passed to
model.NewAPITenantAccount in the site-capabilities update handler with the real
count, mirroring GetTenantAccountHandler: initialize the allocation DAO, call
GetCount for the updated TenantAccount, handle any error consistently with the
surrounding database queries, and pass the resulting count into the response.

In `@rest-api/api/pkg/api/model/tenantaccount.go`:
- Around line 72-86: Update APITenantAccountUpdateRequest and
HasSiteCapabilities so field presence is tracked separately from slice length,
allowing an explicitly provided empty siteCapabilities array to be distinguished
from an omitted field. Reject the explicit empty payload with the existing
validation error path before tenant invite acceptance, while preserving current
handling for omitted and non-empty payloads.

---

Nitpick comments:
In `@rest-api/api/pkg/api/handler/tenantaccount.go`:
- Around line 397-449: Update GetAllTenantAccountHandler to batch-fetch tenant
sites once using the union of non-nil TenantIDs in tas, group the results by
TenantID, and pass each account’s matching tenant-site slice to
NewAPITenantAccount instead of nil. Preserve empty slices for accounts without
sites and keep the existing allocation-count logic unchanged.

In `@rest-api/api/pkg/api/model/tenantaccountsitecapability.go`:
- Around line 186-201: The helper parseSiteCapabilitySiteIDs is unused while
tenantaccount.go duplicates its Limited-scope SiteIDs parsing inline. Route the
handler through parseSiteCapabilitySiteIDs and remove the duplicated parsing
logic, or delete the helper if the handler should remain inline; ensure only one
implementation owns this parsing behavior.
🪄 Autofix (Beta)

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: CHILL

Plan: Enterprise

Run ID: e34d3bd1-3c9c-4069-bdc1-dba212dba6ad

📥 Commits

Reviewing files that changed from the base of the PR and between 1e103e9 and 835c4e9.

⛔ Files ignored due to path filters (9)
  • rest-api/sdk/standard/api_tenant.go is excluded by !rest-api/sdk/standard/api_*.go
  • rest-api/sdk/standard/api_tenant_account.go is excluded by !rest-api/sdk/standard/api_*.go
  • rest-api/sdk/standard/model_tenant.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account_site_capability.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account_site_capability_scope.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_capabilities.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_summary.go is excluded by !rest-api/sdk/standard/model_*.go
📒 Files selected for processing (63)
  • rest-api/api/pkg/api/handler/allocation.go
  • rest-api/api/pkg/api/handler/bmcreset.go
  • rest-api/api/pkg/api/handler/dpureprovision.go
  • rest-api/api/pkg/api/handler/dpureprovision_test.go
  • rest-api/api/pkg/api/handler/expectedmachine.go
  • rest-api/api/pkg/api/handler/expectedmachine_test.go
  • rest-api/api/pkg/api/handler/expectedpowershelf.go
  • rest-api/api/pkg/api/handler/expectedrack.go
  • rest-api/api/pkg/api/handler/expectedswitch.go
  • rest-api/api/pkg/api/handler/fabric.go
  • rest-api/api/pkg/api/handler/instance.go
  • rest-api/api/pkg/api/handler/instance_test.go
  • rest-api/api/pkg/api/handler/instancebatch_test.go
  • rest-api/api/pkg/api/handler/instancetype.go
  • rest-api/api/pkg/api/handler/ipblock.go
  • rest-api/api/pkg/api/handler/machine.go
  • rest-api/api/pkg/api/handler/machine_test.go
  • rest-api/api/pkg/api/handler/machinehealth.go
  • rest-api/api/pkg/api/handler/machinehealth_test.go
  • rest-api/api/pkg/api/handler/machinepower.go
  • rest-api/api/pkg/api/handler/rack_test.go
  • rest-api/api/pkg/api/handler/serviceaccount.go
  • rest-api/api/pkg/api/handler/site.go
  • rest-api/api/pkg/api/handler/site_test.go
  • rest-api/api/pkg/api/handler/sku.go
  • rest-api/api/pkg/api/handler/sku_test.go
  • rest-api/api/pkg/api/handler/tenant.go
  • rest-api/api/pkg/api/handler/tenantaccount.go
  • rest-api/api/pkg/api/handler/tray_test.go
  • rest-api/api/pkg/api/handler/util/common/common.go
  • rest-api/api/pkg/api/handler/util/common/common_test.go
  • rest-api/api/pkg/api/handler/util/common/testing.go
  • rest-api/api/pkg/api/handler/vpc.go
  • rest-api/api/pkg/api/handler/vpc_test.go
  • rest-api/api/pkg/api/handler/vpcpeering.go
  • rest-api/api/pkg/api/model/operatingsystem_test.go
  • rest-api/api/pkg/api/model/sshkeygroup_test.go
  • rest-api/api/pkg/api/model/tenant.go
  • rest-api/api/pkg/api/model/tenant_test.go
  • rest-api/api/pkg/api/model/tenantaccount.go
  • rest-api/api/pkg/api/model/tenantaccount_test.go
  • rest-api/api/pkg/api/model/tenantaccountsitecapability.go
  • rest-api/db/pkg/db/model/tenant.go
  • rest-api/db/pkg/db/model/tenant_test.go
  • rest-api/db/pkg/db/model/tenantaccount.go
  • rest-api/db/pkg/db/model/tenantsite.go
  • rest-api/db/pkg/db/model/tenantsite_test.go
  • rest-api/db/pkg/db/model/testing.go
  • rest-api/db/pkg/migrations/20230412224951_tenant_site.go
  • rest-api/db/pkg/migrations/20260701120000_tenant_account_site_capabilities.go
  • rest-api/db/pkg/migrations/migrations_test.go
  • rest-api/docs/index.html
  • rest-api/openapi/oasdiff-breaking-changes-ignore.txt
  • rest-api/openapi/spec.yaml
  • rest-api/workflow/pkg/activity/dpuextensionservice/dpuextensionservice_test.go
  • rest-api/workflow/pkg/activity/infinibandpartition/infinibandpartition_test.go
  • rest-api/workflow/pkg/activity/instance/instance_test.go
  • rest-api/workflow/pkg/activity/nvlinklogicalpartition/nvlinklogicalpartition_test.go
  • rest-api/workflow/pkg/activity/operatingsystem/operatingsystem_test.go
  • rest-api/workflow/pkg/activity/site/site_test.go
  • rest-api/workflow/pkg/activity/sshkeygroup/sshkeygroup_test.go
  • rest-api/workflow/pkg/activity/tenant/tenant_test.go
  • rest-api/workflow/pkg/util/testing.go

Comment thread rest-api/api/pkg/api/handler/expectedmachine_test.go

// ensure our user is a provider or tenant for the org
infrastructureProvider, tenant, apiError := common.IsProviderOrTenant(ctx, logger, cepsh.dbSession, org, dbUser, false, true)
infrastructureProvider, tenant, apiError := common.IsProviderOrTenant(ctx, logger, cepsh.dbSession, org, dbUser, false, &common.TenantPrivilegeScope{})

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.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Resolve expected-resource authorization with concrete Site scope.

All three handlers pass a non-nil empty TenantPrivilegeScope, so site-specific capability configuration cannot be evaluated against the requested Site. Resolve the Site before the privilege check and pass its SiteID and InfrastructureProviderID, or pass nil if these operations are not intended to be capability-gated.

  • rest-api/api/pkg/api/handler/expectedpowershelf.go#L74-L74: apply the concrete scope to all five ExpectedPowerShelf authorization calls at Lines 74, 251, 408, 516, and 686.
  • rest-api/api/pkg/api/handler/expectedrack.go#L74-L74: apply the concrete scope to all seven ExpectedRack authorization calls at Lines 74, 250, 407, 513, 696, 824, and 989.
  • rest-api/api/pkg/api/handler/expectedswitch.go#L74-L74: apply the concrete scope to all five ExpectedSwitch authorization calls at Lines 74, 278, 435, 543, and 742.

As per path instructions, REST API authorization must preserve tenant/resource ownership checks. Based on the supplied authorization contract, non-nil scopes invoke site-aware capability enforcement.

📍 Affects 3 files
  • rest-api/api/pkg/api/handler/expectedpowershelf.go#L74-L74 (this comment)
  • rest-api/api/pkg/api/handler/expectedrack.go#L74-L74
  • rest-api/api/pkg/api/handler/expectedswitch.go#L74-L74
🤖 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 `@rest-api/api/pkg/api/handler/expectedpowershelf.go` at line 74, Resolve each
requested Site before authorization and pass a populated TenantPrivilegeScope
containing its SiteID and InfrastructureProviderID to all authorization calls in
rest-api/api/pkg/api/handler/expectedpowershelf.go at lines 74, 251, 408, 516,
and 686; expectedrack.go at lines 74, 250, 407, 513, 696, 824, and 989; and
expectedswitch.go at lines 74, 278, 435, 543, and 742. Preserve tenant/resource
ownership checks; use nil only if these operations are intentionally not
capability-gated.

Source: Path instructions

Comment thread rest-api/api/pkg/api/handler/instance.go Outdated
Comment thread rest-api/api/pkg/api/model/tenantaccountsitecapability.go
Comment thread rest-api/api/pkg/api/model/tenantaccountsitecapability.go
Comment thread rest-api/db/pkg/db/model/tenantaccount.go
Comment thread rest-api/db/pkg/migrations/migrations_test.go Outdated
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

@hwadekar-nv Understood — kicking off another full review of all changes in this PR now.

ᕕ( ᐛ )ᕗ

✅ Action performed

Full review finished.

@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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
rest-api/api/pkg/api/handler/machine.go (2)

2037-2063: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

DPU handler doesn't actually check the Site-effective capability for Tenants.

isAssociated for the Tenant path only confirms some TenantAccount row exists between the Tenant and the Site's Infrastructure Provider — no Statuses: []string{cdbm.TenantAccountStatusReady} filter and no Config.TargetedInstanceCreation/TenantSite override check at all. Combined with the coarse pre-gate above (which only requires the capability to be effective somewhere, not on this Site), a Tenant with TargetedInstanceCreation enabled on an unrelated Site, plus a merely-existing (e.g. Pending, capability-disabled) TenantAccount for this Site's Provider, can view DPU details for Machines they have no capability-based access to. Every sibling handler in this file (GetMachineHandler, UpdateMachineHandler) already resolves this correctly via common.TenantHasTargetedInstanceCreation(ctx, nil, dbSession, tenant, common.SiteScope(site)), and site is already available here.

🔒 Proposed fix
-	// Validate role: Provider Admins, or privileged Tenant Admins
-	provider, tenant, apiError := common.IsProviderOrTenant(ctx, logger, gadmh.dbSession, org, dbUser, false, &common.TenantPrivilegeScope{})
+	// Validate role: Provider Admins, or Tenant Admins (site-effective capability enforced below)
+	provider, tenant, apiError := common.IsProviderOrTenant(ctx, logger, gadmh.dbSession, org, dbUser, false, nil)
 	if apiError != nil {
 		return cutil.NewAPIErrorResponse(c, apiError.Code, apiError.Message, apiError.Data)
 	}
 
 	// Validate org is associated with the Machine's Site: the org's Provider
-	// owns the Site, or the org's (privileged) Tenant has a Tenant Account on the
-	// Site's Infrastructure Provider.
+	// owns the Site, or the org's Tenant has effective TargetedInstanceCreation
+	// for the Site.
 	isAssociated := false
 	if provider != nil {
 		isAssociated = site.InfrastructureProviderID == provider.ID
 	} else if tenant != nil {
-		taDAO := cdbm.NewTenantAccountDAO(gadmh.dbSession)
-		_, taCount, serr := taDAO.GetAll(ctx, nil, cdbm.TenantAccountFilterInput{
-			InfrastructureProviderID: &site.InfrastructureProviderID,
-			TenantIDs:                []uuid.UUID{tenant.ID},
-		}, cdbp.PageInput{}, []string{})
+		enabled, serr := common.TenantHasTargetedInstanceCreation(ctx, nil, gadmh.dbSession, tenant, common.SiteScope(site))
 		if serr != nil {
-			logger.Error().Err(serr).Msg("error retrieving Tenant Account with Site's Infrastructure Provider")
+			logger.Error().Err(serr).Msg("error resolving TargetedInstanceCreation for Tenant/Site")
 			return cutil.NewAPIErrorResponse(c, http.StatusInternalServerError, "Failed to determine org's association with Site, DB error", nil)
 		}
-		isAssociated = taCount > 0
+		isAssociated = enabled
 	}
🤖 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 `@rest-api/api/pkg/api/handler/machine.go` around lines 2037 - 2063, Update the
Tenant branch of the machine association check in the relevant DPU handler to
use common.TenantHasTargetedInstanceCreation(ctx, nil, gadmh.dbSession, tenant,
common.SiteScope(site)) instead of counting TenantAccount rows. Preserve the
existing provider ownership check, error response behavior, and final forbidden
response while ensuring the tenant capability is effective for this specific
site.

Source: Path instructions


348-369: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Set an explicit limit for the tenant lookup
cdbp.PageInput{} falls back to the paginator default limit of 20. If a request includes more than 20 distinct tenantId values, the lookup can miss valid matches and return a false 403. Use an explicit limit or an unpaged path here.

🤖 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 `@rest-api/api/pkg/api/handler/machine.go` around lines 348 - 369, Update the
tenant lookup using tenantAccountDAO.GetAll in the infrastructureProvider branch
to avoid the default paginator limit of 20. Supply an explicit PageInput limit
large enough for all requested tenantIDs, or use the available unpaged lookup
path, so every tenantId is evaluated before authorization.
rest-api/api/pkg/api/handler/sku_test.go (1)

179-220: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

tenantWithoutCapability no longer differs from tenantWithoutAccount — the "capability disabled" branch is untested.

Previously tenantWithoutCapability carried cdbm.TenantConfig{TargetedInstanceCreation: false} on the Tenant itself, distinguishing it from tenantWithoutAccount (no account at all). Now neither tenantWithoutCapability nor tenantWithoutAccount has any TenantAccount inserted for their org — only tenantWithCapability/tenantOrg gets one (lines 190-200 / 465-475). As a result, the "tenant without TargetedInstanceCreation capability is denied" test case and "tenant without TenantAccount with Provider is denied" test case now exercise the exact same "no account at all" code path, and the intended "Ready account exists but capability disabled" branch is no longer covered by either test file.

Add a TenantAccount fixture for tenantOrgNoCapability with Status: cdbm.TenantAccountStatusReady and Config omitted (or explicitly false) so the disabled-capability branch is actually exercised, distinct from the missing-account branch.

🧪 Proposed fix for TestGetAllSkuHandler_Handle (apply analogously in TestGetSkuHandler_Handle)
 	tenantOrgNoCapability := "test-tenant-org-no-capability"
 	tenantWithoutCapability := &cdbm.Tenant{
 		ID:   uuid.New(),
 		Name: "test-tenant-no-capability",
 		Org:  tenantOrgNoCapability,
 	}
 	_, err = dbSession.DB.NewInsert().Model(tenantWithoutCapability).Exec(ctx)
 	assert.Nil(t, err)
+
+	// Account exists and is Ready, but capability is explicitly disabled —
+	// distinct from the "no account at all" scenario below.
+	tenantAccountNoCapability := &cdbm.TenantAccount{
+		ID:                       uuid.New(),
+		AccountNumber:            "test-account-no-capability",
+		TenantID:                 &tenantWithoutCapability.ID,
+		TenantOrg:                tenantOrgNoCapability,
+		InfrastructureProviderID: infraProv.ID,
+		Status:                   cdbm.TenantAccountStatusReady,
+		Config:                   cdbm.TenantAccountConfig{TargetedInstanceCreation: false},
+	}
+	_, err = dbSession.DB.NewInsert().Model(tenantAccountNoCapability).Exec(ctx)
+	assert.Nil(t, err)

Based on learnings, "ensure each failure-path test intentionally targets a single validation branch ... construct fixtures so they satisfy all prerequisite conditions except the one being tested. Avoid reusing a fixture that would trigger a different validation earlier."

Also applies to: 454-495

🤖 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 `@rest-api/api/pkg/api/handler/sku_test.go` around lines 179 - 220, Add a ready
TenantAccount fixture for tenantOrgNoCapability, linked to
tenantWithoutCapability and the existing infrastructure provider, with
TargetedInstanceCreation omitted or false; keep tenantWithoutAccount without an
account so the two tests exercise distinct validation branches. Apply the same
fixture correction in TestGetSkuHandler_Handle.

Source: Learnings

rest-api/db/pkg/db/model/tenant.go (1)

55-66: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Scope the Deprecated: annotation to TargetedInstanceCreation only, not the whole TenantConfig type.

Per prior review feedback, only the TargetedInstanceCreation-related capability was meant to be marked deprecated here — not EnableSSHAccess, which has no documented replacement path in this PR (TenantAccount.config/TenantSite.config only cover TargetedInstanceCreation). Marking the entire type Deprecated: risks misleading future maintainers into thinking EnableSSHAccess is also being retired.

✍️ Suggested doc scoping
-// TenantConfig captures legacy per-Tenant capability flags.
-//
-// Deprecated: Tenant capabilities now live on TenantAccount.config
-// (provider-scoped) and TenantSite.config (per-site overrides). This type and
-// the Tenant.Config column it maps are retained only so that, during a rolling
-// deployment, API pods still running the previous release can keep reading the
-// column after the capability migration has run. Do not add new readers or
-// writers; remove once no supported release reads tenant.config.
+// TenantConfig captures legacy per-Tenant capability flags.
 type TenantConfig struct {
-	EnableSSHAccess          bool `json:"enableSshAccess"`
-	TargetedInstanceCreation bool `json:"targetedInstanceCreation"`
+	EnableSSHAccess bool `json:"enableSshAccess"`
+
+	// Deprecated: TargetedInstanceCreation now lives on TenantAccount.config
+	// (provider-scoped) and TenantSite.config (per-site overrides). Retained
+	// only so that, during a rolling deployment, API pods still running the
+	// previous release can keep reading the column after the capability
+	// migration has run. Do not add new readers or writers; remove once no
+	// supported release reads tenant.config.targetedInstanceCreation.
+	TargetedInstanceCreation bool `json:"targetedInstanceCreation"`
 }
🤖 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 `@rest-api/db/pkg/db/model/tenant.go` around lines 55 - 66, Move the
deprecation documentation from the TenantConfig type to the
TargetedInstanceCreation field only. Keep EnableSSHAccess and the TenantConfig
type undocumented as deprecated, and scope the legacy migration explanation to
TargetedInstanceCreation without changing its JSON mapping.
rest-api/db/pkg/db/model/tenantsite.go (1)

291-330: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Comment overstates a guarantee the code doesn't implement.

"Validate mutually exclusive inputs before applying any write so a rejected request cannot leave the row partially updated" implies active runtime validation/rejection, but no such check exists — mutual exclusivity between the merge and remove branches is only guaranteed structurally (single pointer field, can't be both nil and non-nil). Meanwhile EnableSerialConsole and Config are still applied via two separate Exec() calls; if this DAO method is ever invoked with tx == nil, a failure in the second call after the first succeeds would leave the row partially updated — the exact scenario the comment claims is prevented.

Recommend rewriting the comment to describe what's actually guaranteed (type-safe exclusivity between merge/remove), or, for full correctness, collapsing both writes into a single Exec()/CTE so partial application is impossible regardless of caller.

📝 Minimal fix: correct the comment
-	// Validate mutually exclusive inputs before applying any write so a
-	// rejected request cannot leave the row partially updated.
+	// EnableSerialConsole and Config are applied via separate statements below.
+	// Config.TargetedInstanceCreation nil vs non-nil selects between removing
+	// and merging the key; the two are structurally exclusive since they share
+	// a single pointer field. Callers must supply a transaction (tx) if both
+	// EnableSerialConsole and Config are set, to avoid a partially applied
+	// update on failure.
 	updatedFields := []string{}
🤖 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 `@rest-api/db/pkg/db/model/tenantsite.go` around lines 291 - 330, Rewrite the
comment above updatedFields in the tenant-site update flow to state only that
the Config merge and removal paths are mutually exclusive through the single
TargetedInstanceCreation pointer; remove the unsupported claim about pre-write
validation and preventing partial updates. Do not change the separate
EnableSerialConsole and Config Exec operations.

Source: Path instructions

🧹 Nitpick comments (4)
rest-api/api/pkg/api/model/tenantaccountsitecapability.go (1)

98-104: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Duplicate-siteId check is case-sensitive on raw strings.

seenSiteIDs compares cap.SiteIDs verbatim; two entries carrying the same UUID with different casing (e.g. mixed-case vs. lowercase) would slip past this check even though they resolve to one Site, since duplicates are only caught when the raw strings match. Since limited-scope entries are already validated as UUIDs by this point, canonicalize before comparing.

🛡️ Proposed fix
-		for _, siteID := range cap.SiteIDs {
-			if _, ok := seenSiteIDs[siteID]; ok {
-				return validation.Errors{"siteCapabilities": fmt.Errorf(validationErrorDuplicateSiteID)}
-			}
-			seenSiteIDs[siteID] = struct{}{}
-		}
+		for _, siteIDStr := range cap.SiteIDs {
+			id, err := uuid.Parse(siteIDStr)
+			if err != nil {
+				continue // format already validated above
+			}
+			if _, ok := seenSiteIDs[id]; ok {
+				return validation.Errors{"siteCapabilities": fmt.Errorf(validationErrorDuplicateSiteID)}
+			}
+			seenSiteIDs[id] = struct{}{}
+		}

(and change seenSiteIDs := map[string]struct{}{} to map[uuid.UUID]struct{}{})

🤖 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 `@rest-api/api/pkg/api/model/tenantaccountsitecapability.go` around lines 98 -
104, Update the duplicate check in the capability validation loop to parse each
validated cap.SiteIDs value into a uuid.UUID and track seenSiteIDs as
map[uuid.UUID]struct{} instead of raw strings. Compare the canonical UUID values
so differently cased representations are treated as duplicates, while preserving
the existing validation error and registration flow.
rest-api/api/pkg/api/handler/instance.go (1)

890-903: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Hoist the capability check above the transaction boundary.

This is a pure authorization read with no dependency on the transaction (the Machine advisory lock that follows is unrelated to it), yet it executes inside cdb.WithTx. As per path instructions, "Perform pure validation reads outside transactions; keep reads inside only when required for locking, race-safe write decisions, or read-your-writes consistency." tenant and site are already resolved before the transaction starts, so this check (and its DeleteInstanceHandler counterpart) can be moved above cdb.WithTx, avoiding an unrelated DB round-trip while holding a transaction open — matching how vpc.go already places the identical check outside its transaction.

♻️ Proposed refactor
+	// Verify capability before opening the transaction — this is a pure
+	// authorization read unrelated to the Machine advisory lock below.
+	var privilegedAccess bool
+	if apiRequest.MachineID != nil {
+		privilegedAccess, err = common.TenantHasTargetedInstanceCreation(ctx, nil, cih.dbSession, tenant, common.SiteScope(site))
+		if err != nil {
+			logger.Error().Err(err).Msg("error checking effective targeted instance creation for Site")
+			return cutil.NewAPIErrorResponse(c, http.StatusInternalServerError, "Failed to verify capability for Site", nil)
+		}
+		if !privilegedAccess {
+			logger.Warn().Msg("tenant does not have capability to create instances from specific machine")
+			return cutil.NewAPIErrorResponse(c, http.StatusForbidden, "Tenant does not have capability to create Instances using specific Machine ID", nil)
+		}
+	}
+
 	err = cdb.WithTx(ctx, cih.dbSession, func(tx *cdb.Tx) error {
 		// ==================== Step 4: Machine Selection  ====================

 		// Begin validating Machine ID
 		if apiRequest.MachineID != nil {
-			privilegedAccess, derr := common.TenantHasTargetedInstanceCreation(ctx, tx, cih.dbSession, tenant, common.SiteScope(site))
-			if derr != nil {
-				logger.Error().Err(derr).Msg("error checking effective targeted instance creation for Site")
-				return cutil.NewAPIError(http.StatusInternalServerError, "Failed to verify capability for Site", nil)
-			}
-			if !privilegedAccess {
-				logger.Warn().Msg("tenant does not have capability to create instances from specific machine")
-				return cutil.NewAPIError(http.StatusForbidden, "Tenant does not have capability to create Instances using specific Machine ID", nil)
-			}
-
 			mDAO := cdbm.NewMachineDAO(cih.dbSession)
🤖 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 `@rest-api/api/pkg/api/handler/instance.go` around lines 890 - 903, Move the
TenantHasTargetedInstanceCreation capability check for apiRequest.MachineID out
of the cdb.WithTx callback and execute it before the transaction begins, using
the already resolved tenant and site values. Preserve its existing error
responses and authorization behavior, and apply the same hoisting to the
corresponding DeleteInstanceHandler check.

Source: Path instructions

rest-api/db/pkg/migrations/20260701120000_tenant_account_site_capabilities.go (1)

101-104: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document why the down migration is a no-op.

Unlike the up migration, which explicitly documents why tenant.config isn't dropped (rolling-deploy compatibility), the down migration silently does nothing — it neither drops the new tenant_account.config column nor reverts the tenant_site.config normalization. A short comment explaining this (e.g., forward-only migration policy, or explicit call-out that reverting would be unsafe post rolling-deploy) would match the up-migration's documentation standard.

As per path instructions, "Document intentionally omitted code that readers might expect and add TODO comments for deferred features or nuances."

🤖 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
`@rest-api/db/pkg/migrations/20260701120000_tenant_account_site_capabilities.go`
around lines 101 - 104, Document the intentional no-op in the down migration
callback for tenant_account.config with a concise comment explaining why
rollback is omitted, such as the forward-only policy or rolling-deploy safety
concerns. Keep the existing callback behavior unchanged and align the
explanation with the up migration’s compatibility documentation.

Source: Path instructions

rest-api/api/pkg/api/handler/tenantaccount.go (1)

781-913: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

No audit trail recorded for provider siteCapabilities changes; dbUser is unused.

handleProviderSiteCapabilitiesUpdate receives dbUser but never references it, and — unlike CreateTenantAccountHandler and handleTenantInviteAcceptance in this same file — never creates a StatusDetail record for the change. A Provider Admin can silently flip a tenant's global/per-site TargetedInstanceCreation capability with no attributable history of who made the change or when.

As per path instructions, REST API handler changes should be reviewed for "audit logging." Consider recording a StatusDetail (or equivalent) entry referencing dbUser.ID before the closure returns.

🔍 Suggested addition
 		for _, ts := range allTenantSites {
 			...
 		}
 
+		sdDAO := cdbm.NewStatusDetailDAO(utah.dbSession)
+		_, derr = sdDAO.Create(ctx, tx, cdbm.StatusDetailCreateInput{
+			EntityID: ta.ID.String(),
+			Status:   ta.Status,
+			Message:  cutil.GetPtr(fmt.Sprintf("siteCapabilities updated by user %s", dbUser.ID.String())),
+		})
+		if derr != nil {
+			logger.Error().Err(derr).Msg("error creating Status Detail for TenantAccount siteCapabilities update")
+			return cutil.NewAPIError(http.StatusInternalServerError, "Failed to update Tenant Account capabilities", nil)
+		}
+
 		return nil
 	})
🤖 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 `@rest-api/api/pkg/api/handler/tenantaccount.go` around lines 781 - 913, Update
handleProviderSiteCapabilitiesUpdate to use dbUser and record an attributable
StatusDetail (or the established equivalent) for the provider siteCapabilities
change, including dbUser.ID, before the transaction closure returns. Follow the
existing audit-recording patterns in CreateTenantAccountHandler and
handleTenantInviteAcceptance, and ensure the audit write participates in the
same transaction as the capability updates.

Source: Path instructions

🤖 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 `@rest-api/api/pkg/api/handler/bmcreset.go`:
- Line 87: Bind TargetedInstanceCreation authorization to each target machine’s
provider and site instead of using an empty TenantPrivilegeScope. In
bmcreset.go:87, machinehealth.go:79, machinehealth.go:224, machinehealth.go:359,
and dpureprovision.go:91, load the target machine/site before calling
common.IsProviderOrTenant, populate the scope with that machine’s provider and
site capabilities, and perform the scoped authorization check before the
corresponding operation.

In `@rest-api/api/pkg/api/handler/machine.go`:
- Around line 263-288: Update the no-siteId branch in the machine listing
handler so filterInput.SiteIDs is assigned privilegedSiteIDs only for
tenant-only callers, where infrastructureProvider is nil. Prevent dual-role
callers with both tenant and infrastructureProvider from having their
provider-wide filter narrowed by tenant-privileged sites; preserve the existing
provider and explicit-site filtering behavior.

In `@rest-api/api/pkg/api/handler/util/common/testing.go`:
- Around line 351-362: Update the tenant-account fixture creation in the helper
around taDAO.Create to use require.NoError(t, err) instead of assert.Nil(t,
err), ensuring setup failures stop execution before returning ta.

In `@rest-api/api/pkg/api/model/tenantaccountsitecapability.go`:
- Around line 63-89: In the validation rules for cap.SiteIDs within the tenant
capability validation, replace both validation.By closures with ozzo-validation
built-ins: use validation.Empty when cap.Scope is
TenantAccountSiteCapabilityScopeGlobal and validation.Required when it is
TenantAccountSiteCapabilityScopeLimited. Preserve the existing validationis.UUID
rule and the corresponding error messages by applying the appropriate Error
mappings to the built-in rules.

In `@rest-api/db/pkg/migrations/migrations_test.go`:
- Around line 894-909: Expand the test around tenantConfigUpMigration to seed
multiple tenants with NULL configs and at least one tenant retaining a non-NULL
config. After the migration, verify every NULL config is backfilled and the
pre-existing non-NULL value remains unchanged, preserving multi-row coverage.

In `@rest-api/openapi/spec.yaml`:
- Around line 109-117: Update the Tenant Account description to remove the
sentence about site-scoped overrides via Tenant Site configuration, unless this
specification also defines the corresponding TenantSite schema and REST
configuration path; keep the existing account capability and deprecation details
unchanged.

---

Outside diff comments:
In `@rest-api/api/pkg/api/handler/machine.go`:
- Around line 2037-2063: Update the Tenant branch of the machine association
check in the relevant DPU handler to use
common.TenantHasTargetedInstanceCreation(ctx, nil, gadmh.dbSession, tenant,
common.SiteScope(site)) instead of counting TenantAccount rows. Preserve the
existing provider ownership check, error response behavior, and final forbidden
response while ensuring the tenant capability is effective for this specific
site.
- Around line 348-369: Update the tenant lookup using tenantAccountDAO.GetAll in
the infrastructureProvider branch to avoid the default paginator limit of 20.
Supply an explicit PageInput limit large enough for all requested tenantIDs, or
use the available unpaged lookup path, so every tenantId is evaluated before
authorization.

In `@rest-api/api/pkg/api/handler/sku_test.go`:
- Around line 179-220: Add a ready TenantAccount fixture for
tenantOrgNoCapability, linked to tenantWithoutCapability and the existing
infrastructure provider, with TargetedInstanceCreation omitted or false; keep
tenantWithoutAccount without an account so the two tests exercise distinct
validation branches. Apply the same fixture correction in
TestGetSkuHandler_Handle.

In `@rest-api/db/pkg/db/model/tenant.go`:
- Around line 55-66: Move the deprecation documentation from the TenantConfig
type to the TargetedInstanceCreation field only. Keep EnableSSHAccess and the
TenantConfig type undocumented as deprecated, and scope the legacy migration
explanation to TargetedInstanceCreation without changing its JSON mapping.

In `@rest-api/db/pkg/db/model/tenantsite.go`:
- Around line 291-330: Rewrite the comment above updatedFields in the
tenant-site update flow to state only that the Config merge and removal paths
are mutually exclusive through the single TargetedInstanceCreation pointer;
remove the unsupported claim about pre-write validation and preventing partial
updates. Do not change the separate EnableSerialConsole and Config Exec
operations.

---

Nitpick comments:
In `@rest-api/api/pkg/api/handler/instance.go`:
- Around line 890-903: Move the TenantHasTargetedInstanceCreation capability
check for apiRequest.MachineID out of the cdb.WithTx callback and execute it
before the transaction begins, using the already resolved tenant and site
values. Preserve its existing error responses and authorization behavior, and
apply the same hoisting to the corresponding DeleteInstanceHandler check.

In `@rest-api/api/pkg/api/handler/tenantaccount.go`:
- Around line 781-913: Update handleProviderSiteCapabilitiesUpdate to use dbUser
and record an attributable StatusDetail (or the established equivalent) for the
provider siteCapabilities change, including dbUser.ID, before the transaction
closure returns. Follow the existing audit-recording patterns in
CreateTenantAccountHandler and handleTenantInviteAcceptance, and ensure the
audit write participates in the same transaction as the capability updates.

In `@rest-api/api/pkg/api/model/tenantaccountsitecapability.go`:
- Around line 98-104: Update the duplicate check in the capability validation
loop to parse each validated cap.SiteIDs value into a uuid.UUID and track
seenSiteIDs as map[uuid.UUID]struct{} instead of raw strings. Compare the
canonical UUID values so differently cased representations are treated as
duplicates, while preserving the existing validation error and registration
flow.

In
`@rest-api/db/pkg/migrations/20260701120000_tenant_account_site_capabilities.go`:
- Around line 101-104: Document the intentional no-op in the down migration
callback for tenant_account.config with a concise comment explaining why
rollback is omitted, such as the forward-only policy or rolling-deploy safety
concerns. Keep the existing callback behavior unchanged and align the
explanation with the up migration’s compatibility documentation.
🪄 Autofix (Beta)

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: CHILL

Plan: Enterprise

Run ID: e34d3bd1-3c9c-4069-bdc1-dba212dba6ad

📥 Commits

Reviewing files that changed from the base of the PR and between 1e103e9 and 835c4e9.

⛔ Files ignored due to path filters (9)
  • rest-api/sdk/standard/api_tenant.go is excluded by !rest-api/sdk/standard/api_*.go
  • rest-api/sdk/standard/api_tenant_account.go is excluded by !rest-api/sdk/standard/api_*.go
  • rest-api/sdk/standard/model_tenant.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account_site_capability.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account_site_capability_scope.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_capabilities.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_summary.go is excluded by !rest-api/sdk/standard/model_*.go
📒 Files selected for processing (63)
  • rest-api/api/pkg/api/handler/allocation.go
  • rest-api/api/pkg/api/handler/bmcreset.go
  • rest-api/api/pkg/api/handler/dpureprovision.go
  • rest-api/api/pkg/api/handler/dpureprovision_test.go
  • rest-api/api/pkg/api/handler/expectedmachine.go
  • rest-api/api/pkg/api/handler/expectedmachine_test.go
  • rest-api/api/pkg/api/handler/expectedpowershelf.go
  • rest-api/api/pkg/api/handler/expectedrack.go
  • rest-api/api/pkg/api/handler/expectedswitch.go
  • rest-api/api/pkg/api/handler/fabric.go
  • rest-api/api/pkg/api/handler/instance.go
  • rest-api/api/pkg/api/handler/instance_test.go
  • rest-api/api/pkg/api/handler/instancebatch_test.go
  • rest-api/api/pkg/api/handler/instancetype.go
  • rest-api/api/pkg/api/handler/ipblock.go
  • rest-api/api/pkg/api/handler/machine.go
  • rest-api/api/pkg/api/handler/machine_test.go
  • rest-api/api/pkg/api/handler/machinehealth.go
  • rest-api/api/pkg/api/handler/machinehealth_test.go
  • rest-api/api/pkg/api/handler/machinepower.go
  • rest-api/api/pkg/api/handler/rack_test.go
  • rest-api/api/pkg/api/handler/serviceaccount.go
  • rest-api/api/pkg/api/handler/site.go
  • rest-api/api/pkg/api/handler/site_test.go
  • rest-api/api/pkg/api/handler/sku.go
  • rest-api/api/pkg/api/handler/sku_test.go
  • rest-api/api/pkg/api/handler/tenant.go
  • rest-api/api/pkg/api/handler/tenantaccount.go
  • rest-api/api/pkg/api/handler/tray_test.go
  • rest-api/api/pkg/api/handler/util/common/common.go
  • rest-api/api/pkg/api/handler/util/common/common_test.go
  • rest-api/api/pkg/api/handler/util/common/testing.go
  • rest-api/api/pkg/api/handler/vpc.go
  • rest-api/api/pkg/api/handler/vpc_test.go
  • rest-api/api/pkg/api/handler/vpcpeering.go
  • rest-api/api/pkg/api/model/operatingsystem_test.go
  • rest-api/api/pkg/api/model/sshkeygroup_test.go
  • rest-api/api/pkg/api/model/tenant.go
  • rest-api/api/pkg/api/model/tenant_test.go
  • rest-api/api/pkg/api/model/tenantaccount.go
  • rest-api/api/pkg/api/model/tenantaccount_test.go
  • rest-api/api/pkg/api/model/tenantaccountsitecapability.go
  • rest-api/db/pkg/db/model/tenant.go
  • rest-api/db/pkg/db/model/tenant_test.go
  • rest-api/db/pkg/db/model/tenantaccount.go
  • rest-api/db/pkg/db/model/tenantsite.go
  • rest-api/db/pkg/db/model/tenantsite_test.go
  • rest-api/db/pkg/db/model/testing.go
  • rest-api/db/pkg/migrations/20230412224951_tenant_site.go
  • rest-api/db/pkg/migrations/20260701120000_tenant_account_site_capabilities.go
  • rest-api/db/pkg/migrations/migrations_test.go
  • rest-api/docs/index.html
  • rest-api/openapi/oasdiff-breaking-changes-ignore.txt
  • rest-api/openapi/spec.yaml
  • rest-api/workflow/pkg/activity/dpuextensionservice/dpuextensionservice_test.go
  • rest-api/workflow/pkg/activity/infinibandpartition/infinibandpartition_test.go
  • rest-api/workflow/pkg/activity/instance/instance_test.go
  • rest-api/workflow/pkg/activity/nvlinklogicalpartition/nvlinklogicalpartition_test.go
  • rest-api/workflow/pkg/activity/operatingsystem/operatingsystem_test.go
  • rest-api/workflow/pkg/activity/site/site_test.go
  • rest-api/workflow/pkg/activity/sshkeygroup/sshkeygroup_test.go
  • rest-api/workflow/pkg/activity/tenant/tenant_test.go
  • rest-api/workflow/pkg/util/testing.go

Comment thread rest-api/api/pkg/api/handler/bmcreset.go Outdated
Comment thread rest-api/api/pkg/api/handler/machine.go
Comment thread rest-api/api/pkg/api/handler/util/common/testing.go
Comment thread rest-api/api/pkg/api/model/tenantaccountsitecapability.go
Comment thread rest-api/db/pkg/migrations/migrations_test.go Outdated
Comment thread rest-api/openapi/spec.yaml

@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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
rest-api/api/pkg/api/handler/sku_test.go (1)

202-210: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Create a Ready account with the capability disabled for these fixtures.

Both “without capability” tenants currently have no TenantAccount, so they duplicate the separate missing-account cases instead of testing TargetedInstanceCreation: false.

  • rest-api/api/pkg/api/handler/sku_test.go#L202-L210: add a Ready TenantAccount for tenantWithoutCapability with the same provider and disabled configuration.
  • rest-api/api/pkg/api/handler/sku_test.go#L477-L485: add the equivalent disabled Ready TenantAccount for the single-SKU test.

Based on learnings, each failure-path test should satisfy all prerequisites except the exact condition under test.

🤖 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 `@rest-api/api/pkg/api/handler/sku_test.go` around lines 202 - 210, The
“without capability” fixtures currently lack TenantAccounts, so they test
missing accounts instead of disabled TargetedInstanceCreation. In
rest-api/api/pkg/api/handler/sku_test.go lines 202-210 and 477-485, add a Ready
TenantAccount for each corresponding tenant using the same provider and
configuration with TargetedInstanceCreation disabled; leave other prerequisites
unchanged.

Source: Learnings

rest-api/api/pkg/api/model/tenantaccount.go (1)

69-86: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Track field presence independently from slice length.

An explicit "siteCapabilities":[] makes HasSiteCapabilities return false, bypassing the required non-empty validation and routing the request into tenant invite handling. Use a pointer or explicit presence flag so an empty supplied array reaches Validate() and returns HTTP 400.

Proposed fix
 type APITenantAccountUpdateRequest struct {
 	TenantContactID *string `json:"tenantContactId"`
-	SiteCapabilities APITenantAccountSiteCapabilitiesUpdateRequest `json:"siteCapabilities"`
+	SiteCapabilities *APITenantAccountSiteCapabilitiesUpdateRequest `json:"siteCapabilities"`
 }

 func (taur APITenantAccountUpdateRequest) HasSiteCapabilities() bool {
-	return len(taur.SiteCapabilities) > 0
+	return taur.SiteCapabilities != nil
 }

Update downstream calls to dereference the validated payload.

As per path instructions, PATCH siteCapabilities payloads must be non-empty and mutually exclusive with tenantContactId.

🤖 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 `@rest-api/api/pkg/api/model/tenantaccount.go` around lines 69 - 86, Track
whether siteCapabilities was supplied independently of its slice length in
APITenantAccountUpdateRequest, so an explicit empty array is recognized as
present. Update HasSiteCapabilities and downstream validated-payload usage to
use the presence-aware representation, ensuring Validate rejects supplied empty
payloads and enforces mutual exclusivity with TenantContactID while preserving
normal non-empty payload handling.

Source: Path instructions

🤖 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 `@rest-api/api/pkg/api/handler/expectedmachine.go`:
- Around line 60-68: The ExpectedMachine authorization currently lets TenantSite
association access override an explicit false Site capability. In
rest-api/api/pkg/api/handler/expectedmachine.go lines 60-68, call
TenantHasTargetedInstanceCreation with SiteScope(site) for every Tenant request
and overwrite association-based access with its result, preserving existing
error handling. In rest-api/api/pkg/api/handler/expectedmachine_test.go lines
1596-1605, add an isolated single-item test covering global enablement with an
explicit false Site override and assert HTTP 403.

In `@rest-api/api/pkg/api/handler/machine.go`:
- Line 2037: Update the IsProviderOrTenant call in the machine handler to pass
common.SiteScope(site) instead of an empty TenantPrivilegeScope, ensuring DPU
access is evaluated against the Machine’s Site and its capability/ownership
rules.

In `@rest-api/api/pkg/api/handler/machinepower.go`:
- Line 96: Update the IsProviderOrTenant call in the machine power authorization
flow to disable viewer-role access and avoid tenant capability resolution,
ensuring only Provider Admins receive a non-nil provider for the existing
authorization check and machine power control remains restricted accordingly.

In `@rest-api/api/pkg/api/handler/serviceaccount.go`:
- Around line 161-171: The service-account handler must not change an existing
false TargetedInstanceCreation setting to true during reads. Remove the update
logic around taDAO.Update and leave the tenant account configuration unchanged
when the capability is disabled; add a regression test confirming disabled
accounts remain disabled, and ensure any siteCapabilities replacement requires
PROVIDER_ADMIN.

In `@rest-api/api/pkg/api/handler/tenantaccount.go`:
- Line 447: Update the collection response flow around NewAPITenantAccount to
batch-load TenantSite records for all returned tenant accounts, map each account
ID to its corresponding rows, and pass that data instead of nil. Preserve the
existing single-item endpoint behavior and ensure accounts without overrides
still receive an empty or otherwise compatible site-capabilities value.

---

Outside diff comments:
In `@rest-api/api/pkg/api/handler/sku_test.go`:
- Around line 202-210: The “without capability” fixtures currently lack
TenantAccounts, so they test missing accounts instead of disabled
TargetedInstanceCreation. In rest-api/api/pkg/api/handler/sku_test.go lines
202-210 and 477-485, add a Ready TenantAccount for each corresponding tenant
using the same provider and configuration with TargetedInstanceCreation
disabled; leave other prerequisites unchanged.

In `@rest-api/api/pkg/api/model/tenantaccount.go`:
- Around line 69-86: Track whether siteCapabilities was supplied independently
of its slice length in APITenantAccountUpdateRequest, so an explicit empty array
is recognized as present. Update HasSiteCapabilities and downstream
validated-payload usage to use the presence-aware representation, ensuring
Validate rejects supplied empty payloads and enforces mutual exclusivity with
TenantContactID while preserving normal non-empty payload handling.
🪄 Autofix (Beta)

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: CHILL

Plan: Enterprise

Run ID: e34d3bd1-3c9c-4069-bdc1-dba212dba6ad

📥 Commits

Reviewing files that changed from the base of the PR and between 1e103e9 and 835c4e9.

⛔ Files ignored due to path filters (9)
  • rest-api/sdk/standard/api_tenant.go is excluded by !rest-api/sdk/standard/api_*.go
  • rest-api/sdk/standard/api_tenant_account.go is excluded by !rest-api/sdk/standard/api_*.go
  • rest-api/sdk/standard/model_tenant.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account_site_capability.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account_site_capability_scope.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_capabilities.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_summary.go is excluded by !rest-api/sdk/standard/model_*.go
📒 Files selected for processing (63)
  • rest-api/api/pkg/api/handler/allocation.go
  • rest-api/api/pkg/api/handler/bmcreset.go
  • rest-api/api/pkg/api/handler/dpureprovision.go
  • rest-api/api/pkg/api/handler/dpureprovision_test.go
  • rest-api/api/pkg/api/handler/expectedmachine.go
  • rest-api/api/pkg/api/handler/expectedmachine_test.go
  • rest-api/api/pkg/api/handler/expectedpowershelf.go
  • rest-api/api/pkg/api/handler/expectedrack.go
  • rest-api/api/pkg/api/handler/expectedswitch.go
  • rest-api/api/pkg/api/handler/fabric.go
  • rest-api/api/pkg/api/handler/instance.go
  • rest-api/api/pkg/api/handler/instance_test.go
  • rest-api/api/pkg/api/handler/instancebatch_test.go
  • rest-api/api/pkg/api/handler/instancetype.go
  • rest-api/api/pkg/api/handler/ipblock.go
  • rest-api/api/pkg/api/handler/machine.go
  • rest-api/api/pkg/api/handler/machine_test.go
  • rest-api/api/pkg/api/handler/machinehealth.go
  • rest-api/api/pkg/api/handler/machinehealth_test.go
  • rest-api/api/pkg/api/handler/machinepower.go
  • rest-api/api/pkg/api/handler/rack_test.go
  • rest-api/api/pkg/api/handler/serviceaccount.go
  • rest-api/api/pkg/api/handler/site.go
  • rest-api/api/pkg/api/handler/site_test.go
  • rest-api/api/pkg/api/handler/sku.go
  • rest-api/api/pkg/api/handler/sku_test.go
  • rest-api/api/pkg/api/handler/tenant.go
  • rest-api/api/pkg/api/handler/tenantaccount.go
  • rest-api/api/pkg/api/handler/tray_test.go
  • rest-api/api/pkg/api/handler/util/common/common.go
  • rest-api/api/pkg/api/handler/util/common/common_test.go
  • rest-api/api/pkg/api/handler/util/common/testing.go
  • rest-api/api/pkg/api/handler/vpc.go
  • rest-api/api/pkg/api/handler/vpc_test.go
  • rest-api/api/pkg/api/handler/vpcpeering.go
  • rest-api/api/pkg/api/model/operatingsystem_test.go
  • rest-api/api/pkg/api/model/sshkeygroup_test.go
  • rest-api/api/pkg/api/model/tenant.go
  • rest-api/api/pkg/api/model/tenant_test.go
  • rest-api/api/pkg/api/model/tenantaccount.go
  • rest-api/api/pkg/api/model/tenantaccount_test.go
  • rest-api/api/pkg/api/model/tenantaccountsitecapability.go
  • rest-api/db/pkg/db/model/tenant.go
  • rest-api/db/pkg/db/model/tenant_test.go
  • rest-api/db/pkg/db/model/tenantaccount.go
  • rest-api/db/pkg/db/model/tenantsite.go
  • rest-api/db/pkg/db/model/tenantsite_test.go
  • rest-api/db/pkg/db/model/testing.go
  • rest-api/db/pkg/migrations/20230412224951_tenant_site.go
  • rest-api/db/pkg/migrations/20260701120000_tenant_account_site_capabilities.go
  • rest-api/db/pkg/migrations/migrations_test.go
  • rest-api/docs/index.html
  • rest-api/openapi/oasdiff-breaking-changes-ignore.txt
  • rest-api/openapi/spec.yaml
  • rest-api/workflow/pkg/activity/dpuextensionservice/dpuextensionservice_test.go
  • rest-api/workflow/pkg/activity/infinibandpartition/infinibandpartition_test.go
  • rest-api/workflow/pkg/activity/instance/instance_test.go
  • rest-api/workflow/pkg/activity/nvlinklogicalpartition/nvlinklogicalpartition_test.go
  • rest-api/workflow/pkg/activity/operatingsystem/operatingsystem_test.go
  • rest-api/workflow/pkg/activity/site/site_test.go
  • rest-api/workflow/pkg/activity/sshkeygroup/sshkeygroup_test.go
  • rest-api/workflow/pkg/activity/tenant/tenant_test.go
  • rest-api/workflow/pkg/util/testing.go

Comment thread rest-api/api/pkg/api/handler/expectedmachine.go Outdated
Comment thread rest-api/api/pkg/api/handler/machine.go Outdated
Comment thread rest-api/api/pkg/api/handler/machinepower.go Outdated
Comment on lines +161 to +171
} else if !tas[0].Config.TargetedInstanceCreation {
_, serr = taDAO.Update(ctx, nil, cdbm.TenantAccountUpdateInput{
TenantAccountID: tas[0].ID,
Config: &cdbm.TenantAccountConfig{
TargetedInstanceCreation: true,
},
})
if serr != nil {
logger.Error().Err(serr).Msg("error updating Tenant Account capabilities for org")
return cutil.NewAPIErrorResponse(c, http.StatusInternalServerError, "Failed to update Tenant Account capabilities for org, DB error", nil)
}

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not re-enable a provider-disabled capability on read.

An existing false value is an explicit disabled state, but this handler silently changes it to true. Calling this endpoint can therefore bypass the provider-admin capability decision and restore machine-ID privilege. Leave existing account configuration untouched and add a regression test proving a disabled account remains disabled.

As per path instructions, siteCapabilities replacement requires PROVIDER_ADMIN.

🤖 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 `@rest-api/api/pkg/api/handler/serviceaccount.go` around lines 161 - 171, The
service-account handler must not change an existing false
TargetedInstanceCreation setting to true during reads. Remove the update logic
around taDAO.Update and leave the tenant account configuration unchanged when
the capability is disabled; add a regression test confirming disabled accounts
remain disabled, and ensure any siteCapabilities replacement requires
PROVIDER_ADMIN.

Source: Path instructions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It make sense that only PROVIDER_ADMIN can update TargetedInstanceCreation for Tenant, let us confirm this with @thossain-nv , for time being will keep the implementation as it is

Comment thread rest-api/api/pkg/api/handler/tenantaccount.go Outdated
@hwadekar-nv hwadekar-nv force-pushed the feat/tenant-capabilities-update branch from 47ce928 to 945ba84 Compare July 15, 2026 20:04
@hwadekar-nv

Copy link
Copy Markdown
Contributor Author

@coderabbitai full_review, thanks !

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

@hwadekar-nv Understood — kicking off another full review of all changes in this PR now.

ᕕ( ᐛ )ᕗ

✅ Action performed

Full review finished.

@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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
rest-api/api/pkg/api/model/tenantaccount.go (1)

69-86: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Distinguish an empty replacement payload from an omitted field.

len(...) > 0 treats an explicit "siteCapabilities":[] as absent, bypassing its non-empty validation and the mutual-exclusion check with tenantContactId. Check the slice against nil so the handler returns HTTP 400 for a supplied empty replacement.

Proposed fix
 func (taur APITenantAccountUpdateRequest) HasSiteCapabilities() bool {
-	return len(taur.SiteCapabilities) > 0
+	return taur.SiteCapabilities != nil
 }

As per path instructions, REST API models must preserve client-facing request semantics and remain consistent with spec.yaml.

🤖 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 `@rest-api/api/pkg/api/model/tenantaccount.go` around lines 69 - 86, Update
APITenantAccountUpdateRequest.HasSiteCapabilities to check whether
SiteCapabilities is non-nil rather than whether its length is greater than zero,
so an explicitly supplied empty array is recognized and reaches validation and
mutual-exclusion checks while an omitted field remains absent.

Sources: Coding guidelines, Path instructions

rest-api/api/pkg/api/handler/sku_test.go (1)

202-210: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Give the disabled-capability tenants a Ready account with TargetedInstanceCreation: false.

These fixtures currently have no account, so both “without capability” tests exercise the same missing-account branch as the dedicated “without TenantAccount” cases. Add a Ready account with disabled configuration in both test setups to preserve distinct authorization coverage.

Based on learnings, failure-path fixtures should satisfy every prerequisite except the condition under test. As per coding guidelines, endpoint tests must cover authorization behavior.

Also applies to: 477-485

🤖 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 `@rest-api/api/pkg/api/handler/sku_test.go` around lines 202 - 210, Update both
disabled-capability tenant fixtures, including the setup around
tenantOrgNoCapability and the corresponding second fixture, to create and
associate a Ready account configured with TargetedInstanceCreation set to false.
Preserve the existing no-account fixtures for dedicated missing TenantAccount
cases so each authorization test isolates only its intended condition.

Sources: Coding guidelines, Learnings

🤖 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 `@rest-api/api/pkg/api/handler/expectedmachine.go`:
- Around line 330-356: The GetAll Tenant authorization flow must not include
association-only TenantSite IDs: in
rest-api/api/pkg/api/handler/expectedmachine.go lines 330-356, remove that Site
ID union and scope access through GetPrivilegedAccessSiteIDsForTenant so
effective capabilities are enforced. In
rest-api/api/pkg/api/handler/expectedmachine_test.go lines 633-645, update the
test to grant access via an explicit true Site override, or expect 403 for the
association-only case; ensure each failure-path test exercises the intended
authorization branch.

In `@rest-api/api/pkg/api/handler/tenantaccount.go`:
- Around line 770-776: Update the transaction closure containing taDAO.Update to
acquire the TenantAccount-specific advisory lock first, then re-read the
TenantAccount through tx and repeat the Invited status and tenant-ownership
checks before assigning TenantContactID. Preserve the existing update and
success behavior only when the locked, freshly read record still passes both
validations.
- Around line 965-972: Update the TenantAccount update response flow around
NewAPITenantAccount to retrieve the provider-and-tenant allocation count using
the same query or helper as the GET handlers, then pass that count instead of 0.
Preserve the existing status-detail and tenant-site handling so PATCH responses
match subsequent GET responses.

In `@rest-api/api/pkg/api/handler/util/common/common.go`:
- Around line 1631-1641: Update the privilege check around the tas loop and
getPrivilegedAccessSiteIDsForTenant so TargetedInstanceCreation does not return
immediately. Resolve Ready TenantAccount.siteCapabilities together with each
Site’s explicit override, honoring disabled overrides in both coarse and
provider-scoped checks; report the Tenant as privileged only when at least one
effective Site capability remains enabled.

In `@rest-api/api/pkg/api/model/tenantaccountsitecapability.go`:
- Around line 42-45: The APITenantAccountSiteCapability model currently cannot
distinguish an omitted targetedInstanceCreation from an explicit false value.
Introduce a separate update DTO using *bool for targetedInstanceCreation,
require and validate its presence, then convert it to the response/domain
model’s bool; preserve the existing required/nullable semantics for the related
fields and update all affected request handling.

---

Outside diff comments:
In `@rest-api/api/pkg/api/handler/sku_test.go`:
- Around line 202-210: Update both disabled-capability tenant fixtures,
including the setup around tenantOrgNoCapability and the corresponding second
fixture, to create and associate a Ready account configured with
TargetedInstanceCreation set to false. Preserve the existing no-account fixtures
for dedicated missing TenantAccount cases so each authorization test isolates
only its intended condition.

In `@rest-api/api/pkg/api/model/tenantaccount.go`:
- Around line 69-86: Update APITenantAccountUpdateRequest.HasSiteCapabilities to
check whether SiteCapabilities is non-nil rather than whether its length is
greater than zero, so an explicitly supplied empty array is recognized and
reaches validation and mutual-exclusion checks while an omitted field remains
absent.
🪄 Autofix (Beta)

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: CHILL

Plan: Enterprise

Run ID: 81b5f771-4eb4-4940-be68-86745eee9b4e

📥 Commits

Reviewing files that changed from the base of the PR and between 2d274e6 and 7f63057.

⛔ Files ignored due to path filters (9)
  • rest-api/sdk/standard/api_tenant.go is excluded by !rest-api/sdk/standard/api_*.go
  • rest-api/sdk/standard/api_tenant_account.go is excluded by !rest-api/sdk/standard/api_*.go
  • rest-api/sdk/standard/model_tenant.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account_site_capability.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account_site_capability_scope.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_account_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_capabilities.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_tenant_summary.go is excluded by !rest-api/sdk/standard/model_*.go
📒 Files selected for processing (63)
  • rest-api/api/pkg/api/handler/allocation.go
  • rest-api/api/pkg/api/handler/bmcreset.go
  • rest-api/api/pkg/api/handler/dpureprovision.go
  • rest-api/api/pkg/api/handler/dpureprovision_test.go
  • rest-api/api/pkg/api/handler/expectedmachine.go
  • rest-api/api/pkg/api/handler/expectedmachine_test.go
  • rest-api/api/pkg/api/handler/expectedpowershelf.go
  • rest-api/api/pkg/api/handler/expectedrack.go
  • rest-api/api/pkg/api/handler/expectedswitch.go
  • rest-api/api/pkg/api/handler/fabric.go
  • rest-api/api/pkg/api/handler/instance.go
  • rest-api/api/pkg/api/handler/instance_test.go
  • rest-api/api/pkg/api/handler/instancebatch_test.go
  • rest-api/api/pkg/api/handler/instancetype.go
  • rest-api/api/pkg/api/handler/ipblock.go
  • rest-api/api/pkg/api/handler/machine.go
  • rest-api/api/pkg/api/handler/machine_test.go
  • rest-api/api/pkg/api/handler/machinehealth.go
  • rest-api/api/pkg/api/handler/machinehealth_test.go
  • rest-api/api/pkg/api/handler/machinepower.go
  • rest-api/api/pkg/api/handler/rack_test.go
  • rest-api/api/pkg/api/handler/serviceaccount.go
  • rest-api/api/pkg/api/handler/site.go
  • rest-api/api/pkg/api/handler/site_test.go
  • rest-api/api/pkg/api/handler/sku.go
  • rest-api/api/pkg/api/handler/sku_test.go
  • rest-api/api/pkg/api/handler/tenant.go
  • rest-api/api/pkg/api/handler/tenantaccount.go
  • rest-api/api/pkg/api/handler/tray_test.go
  • rest-api/api/pkg/api/handler/util/common/common.go
  • rest-api/api/pkg/api/handler/util/common/common_test.go
  • rest-api/api/pkg/api/handler/util/common/testing.go
  • rest-api/api/pkg/api/handler/vpc.go
  • rest-api/api/pkg/api/handler/vpc_test.go
  • rest-api/api/pkg/api/handler/vpcpeering.go
  • rest-api/api/pkg/api/model/operatingsystem_test.go
  • rest-api/api/pkg/api/model/sshkeygroup_test.go
  • rest-api/api/pkg/api/model/tenant.go
  • rest-api/api/pkg/api/model/tenant_test.go
  • rest-api/api/pkg/api/model/tenantaccount.go
  • rest-api/api/pkg/api/model/tenantaccount_test.go
  • rest-api/api/pkg/api/model/tenantaccountsitecapability.go
  • rest-api/db/pkg/db/model/tenant.go
  • rest-api/db/pkg/db/model/tenant_test.go
  • rest-api/db/pkg/db/model/tenantaccount.go
  • rest-api/db/pkg/db/model/tenantsite.go
  • rest-api/db/pkg/db/model/tenantsite_test.go
  • rest-api/db/pkg/db/model/testing.go
  • rest-api/db/pkg/migrations/20230412224951_tenant_site.go
  • rest-api/db/pkg/migrations/20260701120000_tenant_account_site_capabilities.go
  • rest-api/db/pkg/migrations/migrations_test.go
  • rest-api/docs/index.html
  • rest-api/openapi/oasdiff-breaking-changes-ignore.txt
  • rest-api/openapi/spec.yaml
  • rest-api/workflow/pkg/activity/dpuextensionservice/dpuextensionservice_test.go
  • rest-api/workflow/pkg/activity/infinibandpartition/infinibandpartition_test.go
  • rest-api/workflow/pkg/activity/instance/instance_test.go
  • rest-api/workflow/pkg/activity/nvlinklogicalpartition/nvlinklogicalpartition_test.go
  • rest-api/workflow/pkg/activity/operatingsystem/operatingsystem_test.go
  • rest-api/workflow/pkg/activity/site/site_test.go
  • rest-api/workflow/pkg/activity/sshkeygroup/sshkeygroup_test.go
  • rest-api/workflow/pkg/activity/tenant/tenant_test.go
  • rest-api/workflow/pkg/util/testing.go

Comment on lines +330 to +356
// Sites the Tenant is associated with (TenantSite) are visible for
// listing, unioned with the Sites where the Tenant has effective
// TargetedInstanceCreation (per-site TenantSite.config overrides honored).
tsDAO := cdbm.NewTenantSiteDAO(gaemh.dbSession)
tenantSites, _, err := tsDAO.GetAll(ctx, nil,
cdbm.TenantSiteFilterInput{TenantIDs: []uuid.UUID{tenant.ID}},
paginator.PageInput{Limit: cutil.GetPtr(math.MaxInt)},
nil,
)
if err != nil {
logger.Error().Err(err).Msg("error retrieving Tenant Site associations from DB")
return cutil.NewAPIErrorResponse(c, http.StatusInternalServerError, "Failed to retrieve Tenant Site associations due to DB error", nil)
}
for _, ts := range tenantSites {
filterInput.SiteIDs = append(filterInput.SiteIDs, ts.SiteID)
}

for _, tenantSite := range tenantSites {
filterInput.SiteIDs = append(filterInput.SiteIDs, tenantSite.SiteID)
}
privilegedSiteIDs, err := common.GetPrivilegedAccessSiteIDsForTenant(ctx, nil, gaemh.dbSession, tenant)
if err != nil {
logger.Error().Err(err).Msg("error resolving privileged Site access for Tenant")
return cutil.NewAPIErrorResponse(c, http.StatusInternalServerError, "Failed to resolve Tenant capability due to DB error", nil)
}
filterInput.SiteIDs = append(filterInput.SiteIDs, privilegedSiteIDs...)
}

if infrastructureProvider == nil && tenant != nil && len(filterInput.SiteIDs) == 0 {
return cutil.NewAPIErrorResponse(c, http.StatusForbidden, "Tenant is not associated with any Site", nil)

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not let TenantSite association bypass ExpectedMachine capability enforcement.

Single-resource operations make the exact Site capability authoritative, but GetAll unions every TenantSite into its authorization scope. A false override can therefore hide an ExpectedMachine from GET-by-ID while still exposing it in the list.

  • rest-api/api/pkg/api/handler/expectedmachine.go#L330-L356: remove association-only Site IDs; scope Tenant access using GetPrivilegedAccessSiteIDsForTenant.
  • rest-api/api/pkg/api/handler/expectedmachine_test.go#L633-L645: grant the external Site through an explicit true Site override, or change the association-only case to expect 403.

As per path instructions, REST authorization must enforce effective Site capabilities; based on learnings, each failure-path test should target the exact authorization branch exercised.

📍 Affects 2 files
  • rest-api/api/pkg/api/handler/expectedmachine.go#L330-L356 (this comment)
  • rest-api/api/pkg/api/handler/expectedmachine_test.go#L633-L645
🤖 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 `@rest-api/api/pkg/api/handler/expectedmachine.go` around lines 330 - 356, The
GetAll Tenant authorization flow must not include association-only TenantSite
IDs: in rest-api/api/pkg/api/handler/expectedmachine.go lines 330-356, remove
that Site ID union and scope access through GetPrivilegedAccessSiteIDsForTenant
so effective capabilities are enforced. In
rest-api/api/pkg/api/handler/expectedmachine_test.go lines 633-645, update the
test to grant access via an explicit true Site override, or expect 403 for the
association-only case; ensure each failure-path test exercises the intended
authorization branch.

Sources: Path instructions, Learnings

Comment on lines 770 to +776
err = cdb.WithTx(ctx, utah.dbSession, func(tx *cdb.Tx) error {
var derr error
uta, derr = taDAO.Update(ctx, tx, cdbm.TenantAccountUpdateInput{TenantAccountID: taID, TenantContactID: cutil.GetPtr(dbUser.ID), Status: cutil.GetPtr(cdbm.TenantAccountStatusReady)})
uta, derr = taDAO.Update(ctx, tx, cdbm.TenantAccountUpdateInput{
TenantAccountID: taID,
TenantContactID: cutil.GetPtr(dbUser.ID),
Status: cutil.GetPtr(cdbm.TenantAccountStatusReady),
})

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

Serialize invite acceptance before assigning the Tenant contact.

The status check on Line 761 occurs before the transaction. Two Tenant Admins can both observe Invited, then overwrite TenantContactID and both receive success. Acquire a TenantAccount-specific advisory lock at the start of the closure, re-read through tx, and repeat the status and tenant-ownership checks before updating.

As per coding guidelines, “For TOCTOU-prone read-then-mutate flows, acquire the advisory lock at the start of the transaction closure and re-read state after acquiring it.”

🤖 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 `@rest-api/api/pkg/api/handler/tenantaccount.go` around lines 770 - 776, Update
the transaction closure containing taDAO.Update to acquire the
TenantAccount-specific advisory lock first, then re-read the TenantAccount
through tx and repeat the Invited status and tenant-ownership checks before
assigning TenantContactID. Preserve the existing update and success behavior
only when the locked, freshly read record still passes both validations.

Source: Coding guidelines

Comment on lines +965 to +972
sdDAO := cdbm.NewStatusDetailDAO(utah.dbSession)
ssds, _, err := sdDAO.GetAll(ctx, nil, cdbm.StatusDetailFilterInput{EntityIDs: []string{ta.ID.String()}}, cdbp.PageInput{Limit: cutil.GetPtr(pagination.MaxPageSize)})
if err != nil {
logger.Error().Err(err).Msg("error retrieving Status Details for TenantAccount from DB")
return cutil.NewAPIErrorResponse(c, http.StatusInternalServerError, "Failed to retrieve Status Details for TenantAccount", nil)
}

apiInstance := model.NewAPITenantAccount(ta, ssds, 0, tenantSites)

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

Return the actual allocation count in the update response.

A Ready TenantAccount may already have allocations, but this new update path always passes 0 to NewAPITenantAccount. Query the provider-and-tenant allocation count as the GET handlers do; otherwise a successful PATCH returns an object inconsistent with subsequent GET responses.

Proposed fix
+	allocationDAO := cdbm.NewAllocationDAO(utah.dbSession)
+	allocationCount, err := allocationDAO.GetCount(ctx, nil, cdbm.AllocationFilterInput{
+		InfrastructureProviderIDs: []uuid.UUID{ta.InfrastructureProviderID},
+		TenantIDs:                 []uuid.UUID{*ta.TenantID},
+	})
+	if err != nil {
+		logger.Error().Err(err).Msg("error retrieving allocation count for updated Tenant Account")
+		return cutil.NewAPIErrorResponse(c, http.StatusInternalServerError, "Failed to retrieve allocation count for Tenant Account", nil)
+	}
+
-	apiInstance := model.NewAPITenantAccount(ta, ssds, 0, tenantSites)
+	apiInstance := model.NewAPITenantAccount(ta, ssds, allocationCount, tenantSites)

As per path instructions, REST API changes must be reviewed for “response compatibility.”

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
sdDAO := cdbm.NewStatusDetailDAO(utah.dbSession)
ssds, _, err := sdDAO.GetAll(ctx, nil, cdbm.StatusDetailFilterInput{EntityIDs: []string{ta.ID.String()}}, cdbp.PageInput{Limit: cutil.GetPtr(pagination.MaxPageSize)})
if err != nil {
logger.Error().Err(err).Msg("error retrieving Status Details for TenantAccount from DB")
return cutil.NewAPIErrorResponse(c, http.StatusInternalServerError, "Failed to retrieve Status Details for TenantAccount", nil)
}
apiInstance := model.NewAPITenantAccount(ta, ssds, 0, tenantSites)
allocationDAO := cdbm.NewAllocationDAO(utah.dbSession)
allocationCount, err := allocationDAO.GetCount(ctx, nil, cdbm.AllocationFilterInput{
InfrastructureProviderIDs: []uuid.UUID{ta.InfrastructureProviderID},
TenantIDs: []uuid.UUID{*ta.TenantID},
})
if err != nil {
logger.Error().Err(err).Msg("error retrieving allocation count for updated Tenant Account")
return cutil.NewAPIErrorResponse(c, http.StatusInternalServerError, "Failed to retrieve allocation count for Tenant Account", nil)
}
sdDAO := cdbm.NewStatusDetailDAO(utah.dbSession)
ssds, _, err := sdDAO.GetAll(ctx, nil, cdbm.StatusDetailFilterInput{EntityIDs: []string{ta.ID.String()}}, cdbp.PageInput{Limit: cutil.GetPtr(pagination.MaxPageSize)})
if err != nil {
logger.Error().Err(err).Msg("error retrieving Status Details for TenantAccount from DB")
return cutil.NewAPIErrorResponse(c, http.StatusInternalServerError, "Failed to retrieve Status Details for TenantAccount", nil)
}
apiInstance := model.NewAPITenantAccount(ta, ssds, allocationCount, tenantSites)
🤖 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 `@rest-api/api/pkg/api/handler/tenantaccount.go` around lines 965 - 972, Update
the TenantAccount update response flow around NewAPITenantAccount to retrieve
the provider-and-tenant allocation count using the same query or helper as the
GET handlers, then pass that count instead of 0. Preserve the existing
status-detail and tenant-site handling so PATCH responses match subsequent GET
responses.

Source: Path instructions

Comment on lines +1631 to +1641
for _, ta := range tas {
if ta.Config.TargetedInstanceCreation {
return true, nil
}
}

privilegedSiteIDs, err := getPrivilegedAccessSiteIDsForTenant(ctx, tx, dbSession, tenant, providerFilter)
if err != nil {
return false, err
}
return len(privilegedSiteIDs) > 0, nil

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Honor Site overrides in coarse and provider-scoped checks.

Returning immediately for a globally enabled account ignores explicit false overrides. If every Site is disabled, this still reports the Tenant as privileged and makes /tenant/current advertise an ineffective capability.

Proposed fix
-	for _, ta := range tas {
-		if ta.Config.TargetedInstanceCreation {
-			return true, nil
-		}
-	}
-
 	privilegedSiteIDs, err := getPrivilegedAccessSiteIDsForTenant(ctx, tx, dbSession, tenant, providerFilter)

As per path instructions, privileged Tenant behavior is determined by Ready TenantAccount.siteCapabilities with per-Site overrides.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for _, ta := range tas {
if ta.Config.TargetedInstanceCreation {
return true, nil
}
}
privilegedSiteIDs, err := getPrivilegedAccessSiteIDsForTenant(ctx, tx, dbSession, tenant, providerFilter)
if err != nil {
return false, err
}
return len(privilegedSiteIDs) > 0, nil
privilegedSiteIDs, err := getPrivilegedAccessSiteIDsForTenant(ctx, tx, dbSession, tenant, providerFilter)
if err != nil {
return false, err
}
return len(privilegedSiteIDs) > 0, nil
🤖 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 `@rest-api/api/pkg/api/handler/util/common/common.go` around lines 1631 - 1641,
Update the privilege check around the tas loop and
getPrivilegedAccessSiteIDsForTenant so TargetedInstanceCreation does not return
immediately. Resolve Ready TenantAccount.siteCapabilities together with each
Site’s explicit override, honoring disabled overrides in both coarse and
provider-scoped checks; report the Tenant as privileged only when at least one
effective Site capability remains enabled.

Source: Path instructions

Comment on lines +42 to +45
type APITenantAccountSiteCapability struct {
SiteIDs []string `json:"siteIds,omitempty"`
Scope TenantAccountSiteCapabilityScope `json:"scope"`
TargetedInstanceCreation bool `json:"targetedInstanceCreation"`

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 | 🏗️ Heavy lift

Preserve presence for the required capability boolean.

An omitted targetedInstanceCreation binds as false and passes validation, silently turning a malformed replacement request into capability disablement. Use a separate update DTO with *bool, validate it as required, then convert to the response/domain boolean.

As per path instructions, targetedInstanceCreation is required and model changes must preserve matching required/nullable semantics.

Also applies to: 63-74

🤖 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 `@rest-api/api/pkg/api/model/tenantaccountsitecapability.go` around lines 42 -
45, The APITenantAccountSiteCapability model currently cannot distinguish an
omitted targetedInstanceCreation from an explicit false value. Introduce a
separate update DTO using *bool for targetedInstanceCreation, require and
validate its presence, then convert it to the response/domain model’s bool;
preserve the existing required/nullable semantics for the related fields and
update all affected request handling.

Source: Path instructions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: REST API should expose targetedInstanceCreation tenant capability toggle

2 participants