Skip to content

AIR CLI Migration: resolve usage_policy_name to a policy id - #6152

Merged
riddhibhagwat-db merged 4 commits into
air-clifrom
air-usage-policy-resolution
Aug 5, 2026
Merged

AIR CLI Migration: resolve usage_policy_name to a policy id#6152
riddhibhagwat-db merged 4 commits into
air-clifrom
air-usage-policy-resolution

Conversation

@riddhibhagwat-db

Copy link
Copy Markdown
Contributor

Changes & Why

usage_policy_id was also validated and then silently dropped: nothing wired it into the runs/submit payload. Both paths now populate budget_policy_id, the field the AI Runtime backend reads (matching the Python CLI).

The resolver pages GET /api/2.0/serverless-policies with the partial, case-insensitive filter_by.policy_name filter, then re-applies an exact case-insensitive match locally. Not-found errors list candidate names; an ambiguous match refuses to guess rather than pick the wrong policy. Resolution happens before any artifact upload so a bad name fails fast.

Also ports the UUID-shape check on usage_policy_id, so a policy name pasted into the id field gets an error pointing at usage_policy_name.

Tests

Unit tests: usagepolicy_test.go (new)

  • Wire format: filter_by.policy_name arrives as a flattened dotted key (not a nested map), page_size=1000
  • Pagination: follows next_page_token; terminates on self-repeat and on A→B→A cycles; dedupes the same policy_id across pages
  • Resolution: exact match; case-insensitive exact wins over a partial sibling; not-found with candidate suggestions; not-found with no candidates omits the hint; suggestions capped at 10 with ...; ambiguous match refuses to guess; match missing policy_id; blank name rejected with zero API calls

Unit tests: runsubmit_test.go / runconfig_test.go (modified)

  • TestSubmitWorkloadSendsUsagePolicy: id reaches BudgetPolicyId on the wire via both a literal id and a resolved name (asserted against the captured jobs.SubmitRun)
  • Unresolvable name fails before any workspace write — asserted by recording served paths
  • Empty payload case: no policy configured leaves BudgetPolicyId empty
  • Validation: non-UUID id rejected, a name pasted into the id field gets pointed at usage_policy_name, valid UUID accepted
  • Replaced the old usage_policy_name is not yet supported guard test

Acceptance tests

  • go test ./acceptance -run 'TestAccept/experimental/air' passes with no golden-file changes needed
  • No new acceptance test added: the feature needs a workspace API response, which the unit tests cover via testserver

@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: b458f66

Run: 30962016179

Env 💚​RECOVERED 🙈​SKIP ✅​pass 🙈​skip Time
💚​ aws linux 4 4 322 1069 5:17
💚​ aws windows 4 4 324 1067 7:39
💚​ azure linux 4 4 322 1068 5:02
💚​ azure windows 4 4 324 1066 7:47
💚​ gcp linux 1 5 321 1070 5:11
💚​ gcp windows 1 5 323 1068 9:50
8 interesting tests: 4 RECOVERED, 4 SKIP
Test Name aws linux aws windows azure linux azure windows gcp linux gcp windows
💚​ TestAccept 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
🙈​ TestAccept/bundle/invariant/no_drift 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_endpoints/drift/recreated_same_name 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_indexes/recreate/embedding_dimension 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/ssh/connection 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
💚​ TestFetchRepositoryInfoAPI_FromRepo 💚​R 💚​R 💚​R 💚​R 🙈​S 🙈​S
💚​ TestFetchRepositoryInfoAPI_FromRepo/root 💚​R 💚​R 💚​R 💚​R
💚​ TestFetchRepositoryInfoAPI_FromRepo/subdir 💚​R 💚​R 💚​R 💚​R
Top 6 slowest tests (at least 2 minutes):
duration env testname
8:38 gcp windows TestAccept
6:40 azure windows TestAccept
6:32 aws windows TestAccept
2:59 aws linux TestAccept
2:55 gcp linux TestAccept
2:54 azure linux TestAccept

Base automatically changed from air-integration-m6 to air-cli August 4, 2026 00:33
@riddhibhagwat-db
riddhibhagwat-db force-pushed the air-usage-policy-resolution branch from d1b0449 to 6a3b428 Compare August 4, 2026 00:47
Ports the usage policy name->id resolution from the Python CLI
(cli/serverless_policy_client.py), which `air run` previously rejected with
"usage_policy_name is not yet supported".

usage_policy_id was also validated and then silently dropped: nothing wired it
into the runs/submit payload. Both paths now populate budget_policy_id, the
field the AI Runtime backend reads (matching the Python CLI).

The resolver pages GET /api/2.0/serverless-policies with the partial,
case-insensitive filter_by.policy_name filter, then re-applies an exact
case-insensitive match locally. Not-found errors list candidate names; an
ambiguous match refuses to guess rather than pick the wrong policy. Resolution
happens before any artifact upload so a bad name fails fast.

Also ports the UUID-shape check on usage_policy_id, so a policy name pasted
into the id field gets an error pointing at usage_policy_name.

Co-authored-by: Isaac
- Dedupe policies by policy_id across pages and track seen page tokens, matching
  the pager in aitraining.go. A repeated page previously appended the same policy
  twice, so an unambiguous name failed as "multiple usage policies match".
  The token guard now also catches an A->B->A cycle, not just a self-repeat.
- Run the local idempotency-token check before the policy lookup, so a bad
  --idempotency-key fails without spending an API round trip.
- Assert the "resolve before any artifact upload" ordering instead of only
  claiming it in a comment.
- Require a non-empty filter in listUsagePolicies; the unfiltered list mode had
  no production caller.
- Soften the budget_policy_id comment to cite the Python CLI and ssh connect
  rather than assert unverified backend behavior.

Co-authored-by: Isaac
Two gaps found reviewing my own change after the agent pass:

- The --override path re-decodes and re-validates, so mutual exclusion and the
  UUID check apply to overridden values too. That was untested; both hold.
- A 403/5xx from the policy list call must surface rather than fall through to
  an empty (= no policy) id. It does, via the %w wrap.

Co-authored-by: Isaac
@riddhibhagwat-db
riddhibhagwat-db force-pushed the air-usage-policy-resolution branch from 6a3b428 to be6a637 Compare August 4, 2026 18:06

@ben-hansen-db ben-hansen-db 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.

Looks good, thanks for adding


// suggestionHint renders a deduplicated, sorted "did you mean" clause for the
// candidates the partial filter returned, or "" when there are none.
func suggestionHint(candidates []usagePolicy) string {

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.

nice, this is great

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.

one UX to consider. Display name and policy name are different. policy_name is unique display_name. If users post display_name instead probably policy_name will come up.

Maybe add a comment about this

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.

Added in a comment!

…name

The serverless-policy API returns both policy_name and display_name, and
filter_by can only match policy_name. Per serverless-policy/api/proto/
message.proto, display_name equals policy_name for user-created policies and is
fixed to "Default Policy" for the system defaults, so a user who supplies a UI
display name lands on the not-found error, which lists real policy_names as
candidates. Record why display_name is deliberately not read.

Co-authored-by: Isaac
@riddhibhagwat-db
riddhibhagwat-db merged commit 11d3f7f into air-cli Aug 5, 2026
24 checks passed
@riddhibhagwat-db
riddhibhagwat-db deleted the air-usage-policy-resolution branch August 5, 2026 00:31
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.

3 participants