Skip to content

fix(cp): credential rolebinding and project delete#1203

Merged
markturansky merged 6 commits intoalphafrom
fix/cp-credential-rolebinding-and-project-delete
Apr 3, 2026
Merged

fix(cp): credential rolebinding and project delete#1203
markturansky merged 6 commits intoalphafrom
fix/cp-credential-rolebinding-and-project-delete

Conversation

@markturansky
Copy link
Copy Markdown
Contributor

@markturansky markturansky commented Apr 3, 2026

Summary

  • Seeds credential:token-reader and credential:reader roles via migration 202603311216
  • Mounts runner BOT_TOKEN as file with 10-min background refresh loop in control-plane
  • Authorizes runner OIDC service account in WatchSessionMessages
  • Adds exponential backoff retry in informer error handler
  • Adds acpctl apply -f credential manifest support and role-binding commands to CLI

Test plan

  • Deploy to OSD ambient-s0 via ArgoCD (gitops MR !94 already merged)
  • Verify credential:token-reader and credential:reader appear in acpctl get roles
  • Create role binding for github-agent in credential-test project
  • Start agent session and confirm runner pod retrieves token via GET /credentials/{id}/token

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added role seeding and management capabilities to credentials system.
    • Enhanced CLI with updated login, project context, and resource management commands.
    • Introduced declarative manifest application via acpctl apply.
    • Added agent creation and session messaging commands.
  • Bug Fixes

    • Strengthened authorization validation for session message access.
  • Documentation

    • Expanded CLI reference with comprehensive command examples and usage patterns.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 3, 2026

📝 Walkthrough

Walkthrough

This pull request adds role database seeding via a new migration in the credentials plugin, registers it during initialization, tightens authorization checks for session message access, significantly expands CLI documentation with new commands and workflows, and updates test infrastructure with additional plugin imports.

Changes

Cohort / File(s) Summary
Role Seeding Migration
components/ambient-api-server/plugins/credentials/migration.go, components/ambient-api-server/plugins/credentials/plugin.go
Introduces rolesMigration() that seeds predefined roles into the database using an upsert pattern (FirstOrCreate by name), marshals permissions to JSON, and assigns fresh IDs. Migration is registered during plugin initialization.
Session Authorization
components/ambient-api-server/plugins/sessions/grpc_handler.go
Tightens WatchSessionMessages authorization by immediately rejecting requests with empty username (returns PermissionDenied), and skips ownership validation when username matches the service account.
CLI Documentation
components/ambient-cli/README.md
Comprehensive updates to command syntax (acpctl login <url> instead of flags), new project/agent/role-binding workflows, declarative manifest support via acpctl apply, session messaging and inspection commands, and credentials/role guidance section.
Test Setup
components/ambient-api-server/plugins/credentials/testmain_test.go
Adds blank-identifier imports for credential-dependent plugins (agents, inbox, projectSettings, projects, rbac, roleBindings, roles, sessions, users) and external plugin packages (events, generic) to ensure proper test initialization.
🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title follows Conventional Commits format (fix(cp): description) and describes core changes (credential role-binding and project delete), matching primary PR objectives.
Performance And Algorithmic Complexity ✅ Passed Migration iterates over 2 hardcoded seed roles (O(1)) with one FirstOrCreate per role—no N+1 pattern. WatchSessionMessages authorization performs single session lookup outside loop with early-return gate for empty usernames. Message streaming reads from channel without DB operations in loop. No O(n²) algorithms or unbounded growth detected.
Security And Secret Handling ✅ Passed No hardcoded secrets, tokens, API keys, or credentials detected. Authorization properly enforces empty-username rejection with PermissionDenied. GORM queries use safe parameterization. Documentation shows proper placeholder usage without exposing real credentials.
Kubernetes Resource Safety ✅ Passed All modified files are Go source files, test files, and README documentation. No Kubernetes manifest files (*.yaml, *.yml) are included.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/cp-credential-rolebinding-and-project-delete
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/cp-credential-rolebinding-and-project-delete

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (2)
components/ambient-api-server/plugins/sessions/grpc_handler.go (1)

289-299: ⚠️ Potential issue | 🔴 Critical

Block non-service callers when username is empty.

At Line 291, authorization only runs when username != "". A non-service caller with empty username currently skips ownership checks and can subscribe to arbitrary session streams.

Proposed fix
 if !middleware.IsServiceCaller(ctx) {
 	username := auth.GetUsernameFromContext(ctx)
-	if username != "" && (h.grpcServiceAccount == "" || username != h.grpcServiceAccount) {
+	if username == "" {
+		return status.Error(codes.PermissionDenied, "not authorized to watch this session")
+	}
+	if h.grpcServiceAccount == "" || username != h.grpcServiceAccount {
 		session, svcErr := h.service.Get(ctx, req.GetSessionId())
 		if svcErr != nil {
 			return grpcutil.ServiceErrorToGRPC(svcErr)
 		}
 		if session.CreatedByUserId == nil || *session.CreatedByUserId != username {
 			return status.Error(codes.PermissionDenied, "not authorized to watch this session")
 		}
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ambient-api-server/plugins/sessions/grpc_handler.go` around lines
289 - 299, Authorization currently only runs when username != "", allowing
anonymous non-service callers; update the check inside the
middleware.IsServiceCaller(ctx) block to treat empty username as unauthorized:
after obtaining username via auth.GetUsernameFromContext(ctx) return a
PermissionDenied if username == "" (for non-service callers), and then proceed
to call h.service.Get(req.GetSessionId()) and verify session.CreatedByUserId
matches username when h.grpcServiceAccount is empty or username !=
h.grpcServiceAccount; reference symbols: middleware.IsServiceCaller,
auth.GetUsernameFromContext, h.grpcServiceAccount, h.service.Get,
session.CreatedByUserId.
components/ambient-cli/cmd/acpctl/apply/cmd.go (1)

290-318: ⚠️ Potential issue | 🟠 Major

provider updates are still dropped for existing credentials.

strategicMerge() now carries provider through overlays, but buildCredentialPatch() never compares or sets it. A manifest that only changes provider will still come back as unchanged and never persist the update.

Also applies to: 652-663

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ambient-cli/cmd/acpctl/apply/cmd.go` around lines 290 - 318,
buildCredentialPatch() fails to detect or set changes to the credential's
provider field so provider-only updates are dropped; update the function to
compare doc.Provider (after env expansion if applicable) against
existing.Provider and, when different and non-empty, call the credential patch
builder's Provider(...) method and mark changed = true; apply the same
compare-and-set logic to the other duplicated credential-patch helper mentioned
(the occurrence covering the 652-663 range) so provider changes persist.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/ambient-api-server/plugins/credentials/migration.go`:
- Around line 67-96: The credentials plugin currently calls
db.RegisterMigration(rolesMigration()) during its import, which runs before the
roles table-creation migration (ID "202603100137") is registered and causes
"roles" table missing; stop registering the migration at import time and instead
export rolesMigration() (leave the function name rolesMigration) and register it
only after the roles table-creation migration is registered (e.g., from main.go
or the roles plugin) by calling db.RegisterMigration(rolesMigration()) after the
roles plugin registers its migration with ID "202603100137". This ensures the
roles table exists before the credentials migration runs.

In `@components/ambient-cli/cmd/acpctl/apply/cmd.go`:
- Around line 305-307: The apply logic currently compares the provided token
variable to existing.Token from client.Credentials().Get(...), but
existing.Token is always empty because PresentCredential() omits the token (only
PresentCredentialToken() / GET /{id}/token returns it), so patch.Token(token) is
always applied; fix by fetching the current token from the separate token
endpoint (the same route/function that uses PresentCredentialToken, e.g., call
the credentials token GET handler via the SDK/client method that returns the
credential token) and compare that returned token to the provided token before
calling patch.Token(token); alternatively, if fetching the current token is not
feasible, change the logic to skip comparing against existing.Token and
unconditionally apply patch.Token(token) when a token is provided (update the
token comparison around token, existing.Token, and patch.Token(token)).

In `@components/ambient-control-plane/internal/informer/informer.go`:
- Around line 163-174: retryLoop is processing retries in strict FIFO from
inf.retryCh so a long-delay entry (re.fireAt far in future) blocks handling of
nearer-term retries and can also block producers; change the scheduling so you
never block the loop on a single long wait: when pulling re := <-inf.retryCh, if
re.fireAt is in the future push it into a min-heap or priority queue keyed by
fireAt and continue the loop, and use a single timer that wakes when the
earliest heap item is due and then pop/dispatch via inf.dispatchHandler;
alternatively spawn a per-retry timer/goroutine (but prefer a heap + timer in
retryLoop) to ensure short-delay retries are dispatched promptly and inf.retryCh
is never blocked by long sleeps.

In `@credentials-testing.md`:
- Around line 18-31: Remove all live environment identifiers and PII from
credentials-testing.md: replace the local secret path
(`~/projects/secrets/github.ambient-pat.token`), the Cluster/Namespace values
(`OSD api.dev-osd-east-1...`, `ambient-code--ambient-s0`), the API server URL,
the acpctl login example URL, and the Active user email (`mturansk@redhat.com`)
with redacted placeholders or generic examples (e.g., <REDACTED_TOKEN_PATH>,
<CLUSTER_URL>, <NAMESPACE>, <API_SERVER_URL>, <LOGIN_CMD>, <ACTIVE_USER>) so no
real endpoints, namespaces, file paths, or personal emails remain in the
committed file; ensure placeholders are clearly named and do not contain any
sensitive fragments.

---

Outside diff comments:
In `@components/ambient-api-server/plugins/sessions/grpc_handler.go`:
- Around line 289-299: Authorization currently only runs when username != "",
allowing anonymous non-service callers; update the check inside the
middleware.IsServiceCaller(ctx) block to treat empty username as unauthorized:
after obtaining username via auth.GetUsernameFromContext(ctx) return a
PermissionDenied if username == "" (for non-service callers), and then proceed
to call h.service.Get(req.GetSessionId()) and verify session.CreatedByUserId
matches username when h.grpcServiceAccount is empty or username !=
h.grpcServiceAccount; reference symbols: middleware.IsServiceCaller,
auth.GetUsernameFromContext, h.grpcServiceAccount, h.service.Get,
session.CreatedByUserId.

In `@components/ambient-cli/cmd/acpctl/apply/cmd.go`:
- Around line 290-318: buildCredentialPatch() fails to detect or set changes to
the credential's provider field so provider-only updates are dropped; update the
function to compare doc.Provider (after env expansion if applicable) against
existing.Provider and, when different and non-empty, call the credential patch
builder's Provider(...) method and mark changed = true; apply the same
compare-and-set logic to the other duplicated credential-patch helper mentioned
(the occurrence covering the 652-663 range) so provider changes persist.
🪄 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: Pro

Run ID: 60cf1694-3172-4aca-94a2-86f9799171dd

📥 Commits

Reviewing files that changed from the base of the PR and between d0a19d9 and a25d7e8.

📒 Files selected for processing (13)
  • components/ambient-api-server/plugins/credentials/migration.go
  • components/ambient-api-server/plugins/credentials/plugin.go
  • components/ambient-api-server/plugins/sessions/grpc_handler.go
  • components/ambient-api-server/plugins/sessions/plugin.go
  • components/ambient-cli/README.md
  • components/ambient-cli/cmd/acpctl/apply/cmd.go
  • components/ambient-cli/credential.yaml
  • components/ambient-cli/demo-github.sh
  • components/ambient-control-plane/cmd/ambient-control-plane/main.go
  • components/ambient-control-plane/internal/informer/informer.go
  • components/ambient-control-plane/internal/kubeclient/kubeclient.go
  • components/ambient-control-plane/internal/reconciler/kube_reconciler.go
  • credentials-testing.md

Comment on lines +67 to +96
return &gormigrate.Migration{
ID: "202603311216",
Migrate: func(tx *gorm.DB) error {
for _, r := range seed {
permsJSON, err := json.Marshal(r.permissions)
if err != nil {
return err
}
row := roleRow{
ID: api.NewID(),
Name: r.name,
DisplayName: r.displayName,
Description: r.description,
Permissions: string(permsJSON),
BuiltIn: true,
}
if err := tx.Table("roles").
Where("name = ?", r.name).
FirstOrCreate(&row).Error; err != nil {
return err
}
}
return nil
},
Rollback: func(tx *gorm.DB) error {
names := make([]string, len(seed))
for i, r := range seed {
names[i] = r.name
}
return tx.Table("roles").Where("name IN ?", names).Delete(&roleRow{}).Error
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Migration registration / execution code ==="
rg -n -C4 --type=go 'func RegisterMigration|RegisterMigration\(|gormigrate\.New|\.Migrate\('

echo
echo "=== Roles table creation vs credential-role seeding ==="
rg -n -C3 --type=go 'AutoMigrate\(&Role|rolesMigration\(|tx\.Table\("roles"\)' components/ambient-api-server/plugins

Repository: ambient-code/platform

Length of output: 10184


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find RegisterMigration definition
echo "=== RegisterMigration function definition ==="
rg -n -B2 -A10 'func RegisterMigration' components/ambient-api-server

echo
echo "=== Migration execution (gormigrate.New and Run/Migrate) ==="
rg -n -B3 -A8 'gormigrate\.New|\.Migrate\(' components/ambient-api-server --type=go | head -100

Repository: ambient-code/platform

Length of output: 110


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find where db.RegisterMigration is defined - search in the whole codebase
echo "=== Searching for RegisterMigration in all Go files ==="
rg -n 'RegisterMigration' --type=go | grep -E 'func.*RegisterMigration|type.*RegisterMigration' | head -20

echo
echo "=== Looking for migration registration in server/database setup ==="
fd --type f -name '*.go' | xargs rg -l 'gormigrate.New\|Migrator\|RegisterMigration' | head -10

echo
echo "=== Search for where migrations are collected and executed ==="
rg -n -B5 -A10 'gormigrate\.New' --type=go

Repository: ambient-code/platform

Length of output: 120


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# First, let's find where the db variable is defined and RegisterMigration is used
echo "=== Find files that call RegisterMigration ==="
rg -l 'RegisterMigration' --type=go

echo
echo "=== Let's look at the package structure for db ==="
fd --type f -name '*.go' | xargs grep -l 'package.*db\|db\.RegisterMigration' | head -10

echo
echo "=== Check the credentials plugin file more closely ==="
cat -n components/ambient-api-server/plugins/credentials/plugin.go | head -100

echo
echo "=== Look for db import or interface definition ==="
rg -n 'import\|db\.' components/ambient-api-server/plugins/credentials/plugin.go | head -30

Repository: ambient-code/platform

Length of output: 929


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Credentials plugin file structure ==="
head -50 components/ambient-api-server/plugins/credentials/plugin.go

echo
echo "=== Look for db import/type in credentials plugin ==="
grep -n 'db\.' components/ambient-api-server/plugins/credentials/plugin.go | head -10

echo
echo "=== Check the main init/startup file ==="
fd --type f -name 'main.go' -o -name 'init.go' | grep -v vendor | head -5

echo
echo "=== Look for database initialization code ==="
rg -n 'gormigrate|Migrate\(' --type=go | grep -v test | head -20

Repository: ambient-code/platform

Length of output: 2322


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Find db package in the rh-trex-ai repo ==="
find . -type d -name db | grep -v vendor | grep -v node_modules

echo
echo "=== Look for RegisterMigration in db package ==="
rg -n 'func RegisterMigration\|RegisterMigration\s*=' --type=go | head -20

echo
echo "=== Search in common parent directory for db/migration code ==="
find . -type f -path '*/pkg/db*' -name '*.go' | head -20

Repository: ambient-code/platform

Length of output: 111


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== List top-level directories ==="
ls -la

echo
echo "=== Find all Go files that contain RegisterMigration ==="
rg -l 'RegisterMigration' --type=go

echo
echo "=== Search more broadly for where migrations might be run ==="
rg 'RegisterMigration|gormigrate' --type=go -B2 -A2 | head -100

Repository: ambient-code/platform

Length of output: 9466


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search for RegisterMigration definition in pkg/db or similar ==="
find components -type f -name '*.go' -exec grep -l 'RegisterMigration' {} \; | head -5

echo
echo "=== Check if there's a local db package ==="
find components -type d -name 'db'

echo
echo "=== Look for where migrations are actually executed/migrated ==="
rg 'Migrate\(\)|gormigrate\.New' --type=go -B5 -A5 | head -150

echo
echo "=== Search for any migration manager or collector ==="
rg 'migrations.*append|migrations.*\[\]' --type=go | head -20

Repository: ambient-code/platform

Length of output: 699


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Look for gormigrate usage and migration storage ==="
rg 'var.*migrations|migrations\s*:=|migrations.*append' --type=go

echo
echo "=== Search for where migrations slice/list is created or sorted ==="
rg 'sort.*migrations|migrations.*sort' --type=go

echo
echo "=== Look for go-gormigrate imports and usage pattern ==="
rg 'gormigrate' --type=go -B2 -A2 | grep -E 'New|migration|register' | head -40

echo
echo "=== Check for migration ID comparison/ordering ==="
rg 'Migration.*ID|\.ID.*Migration' --type=go -B1 -A1 | head -30

Repository: ambient-code/platform

Length of output: 120


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check the environments package for db setup ==="
find components -type f -name '*.go' -path '*environments*' | head -10

echo
echo "=== Look for any files that reference Migration or Migrator ==="
rg 'Migration\|Migrator' --type=go -l | head -15

echo
echo "=== Find the actual migration IDs to check ordering ==="
rg 'ID:.*202' --type=go | sort

echo
echo "=== Look at roles migration ID vs credentials rolesMigration ID ==="
rg 'ID.*202603' --type=go

Repository: ambient-code/platform

Length of output: 678


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check environments/types.go for migration setup ==="
cat components/ambient-api-server/cmd/ambient-api-server/environments/types.go | head -100

echo
echo "=== Check one of the environment files ==="
head -150 components/ambient-api-server/cmd/ambient-api-server/environments/e_development.go

echo
echo "=== Get migration IDs to check order ==="
rg 'ID:\s*"202' --type=go

Repository: ambient-code/platform

Length of output: 3591


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Verify the role migration ID and credentials rolesMigration ID ==="
echo "Roles table creation ID:"
rg 'ID:.*202603100137' --type=go
echo
echo "Credentials rolesMigration ID:"
rg 'ID:.*202603311216' --type=go

echo
echo "=== Search for where migrations are collected and run ==="
rg 'migrations\s*\[\]|migrations.*=.*\[\]|append.*migration' --type=go

echo
echo "=== Check if there's a sort or order function for migrations ==="
rg 'sort\.|SortBy|Less|Swap' --type=go | grep -i migrat

echo
echo "=== Look in pkg/db for migration registration ==="
find . -type f -path '*/pkg/db*' -name '*.go' 2>/dev/null | head -20

Repository: ambient-code/platform

Length of output: 415


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== List the plugin init order ==="
git ls-files 'components/ambient-api-server/plugins/*/plugin.go' | sort

echo
echo "=== Check the main server startup file ==="
find components/ambient-api-server -name 'main.go' -type f

echo
echo "=== Look for how plugins are initialized ==="
rg 'plugin\.|import.*plugin' --type=go | grep -v test | head -20

echo
echo "=== Search for where db is initialized and migrations run ==="
rg 'db\.|Database' --type=go components/ambient-api-server/cmd/ambient-api-server/ | head -30

Repository: ambient-code/platform

Length of output: 2902


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check main.go ==="
head -100 components/ambient-api-server/cmd/ambient-api-server/main.go

echo
echo "=== Look for where db.RegisterMigration is implemented ==="
rg 'RegisterMigration' --type=go -B5 -A10 | grep -v 'plugin.go:' | head -100

Repository: ambient-code/platform

Length of output: 6586


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search for where migrations are collected/stored globally ==="
rg 'var.*migrations|migrations.*append|migrationsRegistry' --type=go

echo
echo "=== Look for gormigrate.New calls and how migrations are passed ==="
rg 'gormigrate\.New|SetMigrations|migration' --type=go | grep -v test | grep -v '\.go-' | head -50

echo
echo "=== Check if db package has RegisterMigration locally ==="
find . -type f -path '*/pkg/db/*' -name '*.go' 2>/dev/null | xargs grep -l 'RegisterMigration' 2>/dev/null || echo "Not found locally"

echo
echo "=== Look in go.mod to understand upstream package ==="
grep 'rh-trex-ai' go.mod || echo "Check if exists"

Repository: ambient-code/platform

Length of output: 130


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check go.mod for the upstream db package version ==="
cat go.mod | head -50

echo
echo "=== Look for any migration execution or migrate command setup ==="
rg 'MigrateCommand|Migrate\(\)|gormigrate\.New' --type=go

echo
echo "=== Check pkg/cmd for migrate command ==="
rg 'MigrateCommand|migrate' --type=go | head -40

Repository: ambient-code/platform

Length of output: 177


🏁 Script executed:

#!/bin/bash
set -euo progfail

echo "=== Find all go.mod files ==="
find . -name 'go.mod' -type f | head -10

echo
echo "=== Check the import structure in the ambient-api-server ==="
find components/ambient-api-server -name 'go.mod' -type f

echo
echo "=== Look for db.RegisterMigration calls and trace back where it's defined ==="
rg 'db\.RegisterMigration\(' --type=go -B10 | head -100

echo
echo "=== Check if db is imported and from where ==="
rg 'import.*".*db"' components/ambient-api-server/plugins/credentials/plugin.go -A5

Repository: ambient-code/platform

Length of output: 118


Credentials plugin's rolesMigration depends on roles table but registers before roles table migration in import sequence.

The credentials plugin (imported before roles plugin in main.go) calls db.RegisterMigration(rolesMigration()) which writes to tx.Table("roles"). The roles plugin's table-creation migration has ID 202603100137 while the credentials rolesMigration has ID 202603311216. If migrations execute in plugin-registration/import order rather than by global ID sort, a fresh bootstrap will fail with "table 'roles' doesn't exist" since credentials plugin initializes before roles plugin.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ambient-api-server/plugins/credentials/migration.go` around lines
67 - 96, The credentials plugin currently calls
db.RegisterMigration(rolesMigration()) during its import, which runs before the
roles table-creation migration (ID "202603100137") is registered and causes
"roles" table missing; stop registering the migration at import time and instead
export rolesMigration() (leave the function name rolesMigration) and register it
only after the roles table-creation migration is registered (e.g., from main.go
or the roles plugin) by calling db.RegisterMigration(rolesMigration()) after the
roles plugin registers its migration with ID "202603100137". This ensures the
roles table exists before the credentials migration runs.

Comment on lines 305 to 307
token := os.ExpandEnv(doc.Token)
if token != "" {
if token != "" && token != existing.Token {
patch = patch.Token(token)
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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Server-side credential endpoints / payloads ==="
rg -n -C3 --type=go 'GetToken|json:"token"|type Credential struct|HandleFunc\("\{id\}/token"|HandleFunc\("/{id}/token"' components/ambient-api-server/plugins/credentials

echo
echo "=== SDK-side credential GET implementation ==="
rg -n -C3 --type=go 'func .*Credentials.*Get\(|func .*GetToken\(|"/credentials"|json:"token"|type Credential struct' components/ambient-sdk/go-sdk

Repository: ambient-code/platform

Length of output: 400


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Server-side: Credential struct definition ==="
rg -n "type Credential struct" --type=go components/ambient-api-server/

echo
echo "=== Server-side: GetToken endpoint/method ==="
rg -n "GetToken" --type=go components/ambient-api-server/plugins/credentials

echo
echo "=== Server-side: Token field in structs ==="
rg -n "json:\"token\"" --type=go components/ambient-api-server/

echo
echo "=== SDK-side: Credentials Get method ==="
rg -n "func.*Get\(" --type=go components/ambient-sdk/go-sdk | head -20

echo
echo "=== SDK-side: Credential struct ==="
rg -n "type Credential" --type=go components/ambient-sdk/go-sdk

echo
echo "=== SDK-side: Token field ==="
rg -n "json:\"token\"" --type=go components/ambient-sdk/go-sdk

Repository: ambient-code/platform

Length of output: 3337


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Server-side Credential struct (full definition) ==="
sed -n '8,20p' components/ambient-api-server/plugins/credentials/model.go

echo
echo "=== SDK-side Credential struct (full definition) ==="
sed -n '8,20p' components/ambient-sdk/go-sdk/types/credential.go

echo
echo "=== Server-side credential handler Get method ==="
rg -n "func.*Get\(" --type=go -A 15 components/ambient-api-server/plugins/credentials/handler.go | head -40

echo
echo "=== Check if Credential struct fields differ by endpoint ==="
rg -n "CredentialTokenResponse\|type Credential struct" --type=go components/ambient-api-server/plugins/credentials/

echo
echo "=== Server-side handler Get implementation details ==="
rg -n "credentialHandler\) Get\(" --type=go -A 20 components/ambient-api-server/plugins/credentials/handler.go

Repository: ambient-code/platform

Length of output: 1513


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== PresentCredential function ==="
rg -n "func.*PresentCredential" --type=go -A 20 components/ambient-api-server/plugins/credentials/

echo
echo "=== Check for Token field filtering/redaction ==="
rg -n "Token.*nil\|Token.*=\s*nil" --type=go components/ambient-api-server/plugins/credentials/

Repository: ambient-code/platform

Length of output: 2671


Token is never populated from credential reads—only the separate GET /{id}/token endpoint returns it.

The normal Get handler uses PresentCredential() which explicitly omits the Token field from responses (see presenter.go). Only the PresentCredentialToken() function includes it, used solely by the GET /{id}/token endpoint. This means existing.Token from client.Credentials().Get(...) will always be empty, so the condition token != "" && token != existing.Token will always be true if a token is provided, causing the patch to apply on every run and breaking idempotency.

The apply logic needs to either:

  • Call the separate token endpoint to fetch the current token for comparison, or
  • Omit the token comparison and always patch if a token is provided
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ambient-cli/cmd/acpctl/apply/cmd.go` around lines 305 - 307, The
apply logic currently compares the provided token variable to existing.Token
from client.Credentials().Get(...), but existing.Token is always empty because
PresentCredential() omits the token (only PresentCredentialToken() / GET
/{id}/token returns it), so patch.Token(token) is always applied; fix by
fetching the current token from the separate token endpoint (the same
route/function that uses PresentCredentialToken, e.g., call the credentials
token GET handler via the SDK/client method that returns the credential token)
and compare that returned token to the provided token before calling
patch.Token(token); alternatively, if fetching the current token is not
feasible, change the logic to skip comparing against existing.Token and
unconditionally apply patch.Token(token) when a token is provided (update the
token comparison around token, existing.Token, and patch.Token(token)).

Comment on lines 163 to +174
case re := <-inf.retryCh:
wait := time.Until(re.fireAt)
if wait > 0 {
timer := time.NewTimer(wait)
select {
case <-time.After(wait):
case <-timer.C:
case <-ctx.Done():
timer.Stop()
return
}
}
inf.dispatchEvent(ctx, re.event, re.attempt)
inf.dispatchHandler(ctx, re.event, re.handlerIndex, re.attempt)
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.

⚠️ Potential issue | 🟠 Major

Retry scheduling is FIFO, so ready retries can be blocked behind long-delay entries.

The single retryLoop sleeps on the first dequeued item. If a fireAt=+30s retry is read before a later fireAt=+2s retry, the shorter retry waits the full 30s. Under a few concurrent failures, that backlog also stops draining retryCh, and Line 220 can block producers entirely.

Also applies to: 204-221

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ambient-control-plane/internal/informer/informer.go` around lines
163 - 174, retryLoop is processing retries in strict FIFO from inf.retryCh so a
long-delay entry (re.fireAt far in future) blocks handling of nearer-term
retries and can also block producers; change the scheduling so you never block
the loop on a single long wait: when pulling re := <-inf.retryCh, if re.fireAt
is in the future push it into a min-heap or priority queue keyed by fireAt and
continue the loop, and use a single timer that wakes when the earliest heap item
is due and then pop/dispatch via inf.dispatchHandler; alternatively spawn a
per-retry timer/goroutine (but prefer a heap + timer in retryLoop) to ensure
short-delay retries are dispatched promptly and inf.retryCh is never blocked by
long sleeps.

Ambient Code Bot and others added 5 commits April 3, 2026 18:26
…roles via migration

Adds a second migration (202603311216) to the credentials plugin that
inserts the two credential-scoped built-in roles into the roles table.
Runs after the credentials table migration (202603311215) and after the
roles table migration (202603100137), satisfying the ordering dependency
without requiring cross-plugin imports.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Non-service callers with an empty username previously skipped ownership
checks, allowing unauthenticated access to arbitrary session streams.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@markturansky markturansky force-pushed the fix/cp-credential-rolebinding-and-project-delete branch from dcdb408 to 90ded74 Compare April 3, 2026 22:26
… roles table exists

rolesMigration() inserts into the roles table; without importing plugins/roles
the migration runs against a missing table, causing integration tests to fail.
Mirror the full plugin import set from main.go.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/ambient-cli/README.md`:
- Line 30: The README's RH SSO example uses a likely-removed flag (--url) in the
command for acpctl login; update the example to use the positional api-url form
used elsewhere (replace the --url flag usage with the <api-url> positional
argument) so the acpctl login example matches the updated flow and will work for
users; ensure the command string referencing acpctl login and the old --url flag
is replaced and any surrounding text reflects the positional <api-url> usage.
- Around line 140-142: The README example for the CLI command "acpctl agent
start" mixes the --project-id flag with a <project-name> placeholder; update the
example so the flag and placeholder match by replacing "<project-name>" with
"<project-id>" (or alternatively change the flag to --project-name if the CLI
actually accepts names) so the sample command reads e.g. acpctl agent start
<agent-name> --project-id <project-id> --prompt "Open a test issue in org/repo";
ensure the change is made in the snippet containing the "acpctl agent start"
example and keep the --project-id flag consistent with the CLI's expected
parameter name.
🪄 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: Pro

Run ID: a23c7292-e7fd-49fb-a394-9861ee90fcdb

📥 Commits

Reviewing files that changed from the base of the PR and between dcdb408 and ee37655.

📒 Files selected for processing (5)
  • components/ambient-api-server/plugins/credentials/migration.go
  • components/ambient-api-server/plugins/credentials/plugin.go
  • components/ambient-api-server/plugins/credentials/testmain_test.go
  • components/ambient-api-server/plugins/sessions/grpc_handler.go
  • components/ambient-cli/README.md
✅ Files skipped from review due to trivial changes (1)
  • components/ambient-api-server/plugins/credentials/testmain_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • components/ambient-api-server/plugins/credentials/plugin.go
  • components/ambient-api-server/plugins/sessions/grpc_handler.go
  • components/ambient-api-server/plugins/credentials/migration.go

acpctl login <api-url> --token <your-token> --insecure-skip-tls-verify

# Use RH SSO
acpctl login --use-auth-code --url https://ambient-api-server-ambient-code--ambient-s0.apps.int.spoke.dev.us-east-1.aws.paas.redhat.com
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.

⚠️ Potential issue | 🟠 Major

login example uses a likely deprecated flag and may fail.

Line 30 uses --url, but the updated flow in this README uses positional <api-url> (Line 24). If --url was removed, this RH SSO example is broken for users.

Proposed fix
-acpctl login --use-auth-code --url https://ambient-api-server-ambient-code--ambient-s0.apps.int.spoke.dev.us-east-1.aws.paas.redhat.com
+acpctl login https://ambient-api-server-ambient-code--ambient-s0.apps.int.spoke.dev.us-east-1.aws.paas.redhat.com --use-auth-code

As per coding guidelines, "Flag only errors, security risks, or functionality-breaking problems."

📝 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
acpctl login --use-auth-code --url https://ambient-api-server-ambient-code--ambient-s0.apps.int.spoke.dev.us-east-1.aws.paas.redhat.com
acpctl login https://ambient-api-server-ambient-code--ambient-s0.apps.int.spoke.dev.us-east-1.aws.paas.redhat.com --use-auth-code
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ambient-cli/README.md` at line 30, The README's RH SSO example
uses a likely-removed flag (--url) in the command for acpctl login; update the
example to use the positional api-url form used elsewhere (replace the --url
flag usage with the <api-url> positional argument) so the acpctl login example
matches the updated flow and will work for users; ensure the command string
referencing acpctl login and the old --url flag is replaced and any surrounding
text reflects the positional <api-url> usage.

Comment on lines +140 to +142
acpctl agent start <agent-name> \
--project-id <project-name> \
--prompt "Open a test issue in org/repo"
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.

⚠️ Potential issue | 🟠 Major

Agent start example mixes project-id flag with a project-name placeholder.

Line 141 uses --project-id <project-name>. This is a functional mismatch and can cause failed requests if users pass a name where an ID is required.

Proposed fix
-acpctl agent start <agent-name> \
-  --project-id <project-name> \
+acpctl agent start <agent-name> \
+  --project-id <project-id> \
   --prompt "Open a test issue in org/repo"

As per coding guidelines, "Flag only errors, security risks, or functionality-breaking problems."

📝 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
acpctl agent start <agent-name> \
--project-id <project-name> \
--prompt "Open a test issue in org/repo"
acpctl agent start <agent-name> \
--project-id <project-id> \
--prompt "Open a test issue in org/repo"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ambient-cli/README.md` around lines 140 - 142, The README example
for the CLI command "acpctl agent start" mixes the --project-id flag with a
<project-name> placeholder; update the example so the flag and placeholder match
by replacing "<project-name>" with "<project-id>" (or alternatively change the
flag to --project-name if the CLI actually accepts names) so the sample command
reads e.g. acpctl agent start <agent-name> --project-id <project-id> --prompt
"Open a test issue in org/repo"; ensure the change is made in the snippet
containing the "acpctl agent start" example and keep the --project-id flag
consistent with the CLI's expected parameter name.

@markturansky markturansky merged commit dda10eb into alpha Apr 3, 2026
42 of 44 checks passed
@markturansky markturansky deleted the fix/cp-credential-rolebinding-and-project-delete branch April 3, 2026 23:04
markturansky added a commit that referenced this pull request Apr 4, 2026
## Summary

- Seeds `credential:token-reader` and `credential:reader` roles via
migration `202603311216`
- Mounts runner BOT_TOKEN as file with 10-min background refresh loop in
control-plane
- Authorizes runner OIDC service account in `WatchSessionMessages`
- Adds exponential backoff retry in informer error handler
- Adds `acpctl apply -f` credential manifest support and role-binding
commands to CLI

## Test plan

- [ ] Deploy to OSD `ambient-s0` via ArgoCD (gitops MR \!94 already
merged)
- [ ] Verify `credential:token-reader` and `credential:reader` appear in
`acpctl get roles`
- [ ] Create role binding for `github-agent` in `credential-test`
project
- [ ] Start agent session and confirm runner pod retrieves token via
`GET /credentials/{id}/token`

🤖 Generated with [Claude Code](https://claude.ai/code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Release Notes

* **New Features**
* Added role seeding and management capabilities to credentials system.
* Enhanced CLI with updated login, project context, and resource
management commands.
  * Introduced declarative manifest application via `acpctl apply`.
  * Added agent creation and session messaging commands.

* **Bug Fixes**
  * Strengthened authorization validation for session message access.

* **Documentation**
* Expanded CLI reference with comprehensive command examples and usage
patterns.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Ambient Code Bot <bot@ambient-code.local>
Co-authored-by: Claude <noreply@anthropic.com>
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.

1 participant