Skip to content

refactor: remove isPrimary from key and change primary on keyconfig patch#208

Open
jmpTeixeira02 wants to merge 3 commits intomainfrom
task/key-keyconfig-refactor
Open

refactor: remove isPrimary from key and change primary on keyconfig patch#208
jmpTeixeira02 wants to merge 3 commits intomainfrom
task/key-keyconfig-refactor

Conversation

@jmpTeixeira02
Copy link
Copy Markdown
Contributor

@jmpTeixeira02 jmpTeixeira02 commented Mar 19, 2026

Summary by CodeRabbit

  • New Features

    • Primary key is now tracked at the key-configuration level via a new primaryKeyID field.
  • Breaking Changes

    • Removed isPrimary from key API and persisted key records.
    • Primary selection is managed via primaryKeyID; attempts to change primaryKeyID may be blocked by workflow rules.
  • Database

    • Migration removes the is_primary column from keys table.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 19, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4dbdb31a-ff73-403b-a2fe-605bfc0a7896

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

Walkthrough

Primary-key boolean was removed from Key records; primary identity moved to KeyConfiguration.PrimaryKeyID. KeyConfiguration updates now support transactional primary changes that validate key enabled state, rewrite related retry/system events, and emit system-switch events via an injected EventFactory. API, repo, manager, controllers, migrations, and tests updated.

Changes

Cohort / File(s) Summary
API schemas & transforms
apis/cmk/cmk-ui.yaml, internal/api/cmkapi/cmkapi.go, internal/api/transform/key/key.go, internal/api/transform/key/key_test.go
Removed isPrimary/IsPrimary from API schemas and transforms; added primaryKeyID to KeyConfigurationPatch types.
Model & Repo
internal/model/key.go, internal/repo/repository.go
Removed Key.IsPrimary field; added IsPrimaryKey(ctx, r, key) repo helper to derive primary from KeyConfiguration.PrimaryKeyID.
Manager logic (keys & keyconfigs)
internal/manager/key.go, internal/manager/keyconfiguration.go, internal/manager/key_test.go, internal/manager/keyconfiguration_test.go, internal/manager/keyversion_test.go, internal/manager/system_test.go, internal/manager/tenant_test.go, internal/manager/workflow_test.go
Eliminated per-Key primary helpers and transitions; use KeyConfiguration.PrimaryKeyID for primary semantics; keyconfig manager now accepts eventFactory, updates PrimaryKeyID inside a transaction, validates key enabled state, rewrites retry/system events, and emits system-switch events. Tests updated accordingly.
Workflow integration
internal/workflow/key_configuration_actions.go, internal/workflow/key_configuration_actions_test.go
Added UpdateKeyConfigurationByID to interface; lifecycle updates now patch configuration-level PrimaryKeyID instead of Key-level IsPrimary.
Controllers & API behavior
internal/controllers/cmk/key_controller.go, internal/controllers/cmk/keyconfiguration_controller.go, internal/controllers/cmk/*_test.go
Removed workflow-branch for Key.IsPrimary in key updates; added workflow enforcement when patching PrimaryKeyID on KeyConfiguration; tests adjusted to new flow.
Error mappings
internal/manager/errors.go, internal/apierrors/key.go
Removed ErrPrimaryKeyUnmark and its API mapping.
Wiring & dependency injection
internal/manager/base.go, cmd/task-worker/main.go, cmd/tenant-manager/main.go, cmd/tenant-manager-cli/..., test/..., internal/operator/operator_test.go, test/integration/...
Propagate eventFactory into NewKeyConfigManager at runtime and tests (often passing nil in unit tests).
DB migration & tests
migrations/tenant/schema/00005_delete_key_primary.sql, test/db-migration/migration_test.go
Add migration to drop is_primary from keys and tests for migrating to/from version 5.
Misc tests & fixtures
multiple internal/..._test.go, test/... files (see diff)
Updated many tests to stop using Key.IsPrimary, set KeyConfiguration.PrimaryKeyID where appropriate, and adjust manager/test wiring for eventFactory.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Controller as KeyConfigController
    participant Workflow as WorkflowService
    participant Manager as KeyConfigManager
    participant Repo as Repository
    participant EventFactory as EventFactory

    Client->>Controller: PATCH /keyConfigurations/{id} { primaryKeyID }
    Controller->>Workflow: IsWorkflowRequired(ctx)
    alt workflow required
        Workflow-->>Controller: required==true
        Controller-->>Client: 403 ErrActionRequireWorkflow
    else workflow not required
        Workflow-->>Controller: required==false
        Controller->>Manager: UpdateKeyConfigurationByID(id, patch)
        Manager->>Repo: Begin Transaction
        Manager->>Repo: First(key) to validate enabled state
        alt key disabled
            Repo-->>Manager: key.disabled
            Manager-->>Controller: ErrKeyIsNotEnabled
            Controller-->>Client: 4xx error
        else key enabled
            Manager->>Repo: Query retry/system events for old primary
            Repo-->>Manager: events
            Manager->>EventFactory: Update event payloads (replace KeyIDTo)
            EventFactory-->>Manager: events updated
            Manager->>EventFactory: Emit SystemSwitchNewPrimaryKey events
            EventFactory-->>Manager: events created
            Manager->>Repo: Patch KeyConfiguration.PrimaryKeyID
            Repo-->>Manager: patched config
            Manager->>Repo: Commit
            Manager-->>Controller: updated KeyConfiguration
            Controller-->>Client: 200 OK
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • bkieling
  • MarkMcAleese
  • minh-nghia
  • doanhluongsap

"🐰
Once keys wore crowns of boolean light,
Now configs wear the crown by right.
Transactions hum and events take flight,
Systems hop, switch, and sleep at night.
Hop on, dear code, — the meadow's bright."

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description is missing entirely. The template requires at least 'What this PR does / why we need it' section. Add a comprehensive description explaining the refactoring rationale, migration from isPrimary field to primaryKeyID, and any breaking changes or migration considerations.
Docstring Coverage ⚠️ Warning Docstring coverage is 10.34% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: removing isPrimary from key and shifting primary key tracking to keyconfig patch.

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


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.

@jmpTeixeira02 jmpTeixeira02 force-pushed the task/key-keyconfig-refactor branch from fe23c38 to aeeaad9 Compare March 19, 2026 12:26
Copy link
Copy Markdown

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

Caution

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

⚠️ Outside diff range comments (1)
internal/manager/key.go (1)

264-302: ⚠️ Potential issue | 🟠 Major

Clear or replace PrimaryKeyID when deleting the primary key.

After this delete succeeds, the owning KeyConfiguration still points at the removed key because nothing patches PrimaryKeyID. Since repo.IsPrimaryKey now resolves through that field, later primary checks and editable-region logic will read dangling state.

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

In `@internal/manager/key.go` around lines 264 - 302, The deletion path currently
removes the provider key and DB records (km.deleteProviderKey, km.repo.Delete of
model.KeyVersion and model.Key) but does not clear or replace the
KeyConfiguration.PrimaryKeyID, leaving a dangling reference; inside the same
transaction (km.repo.Transaction) after deleting the key/version rows, load the
owning KeyConfiguration (using key.KeyConfigurationID), check if its
PrimaryKeyID equals keyID, and if so either set PrimaryKeyID to null/empty (or
pick another existing key ID if business logic requires) and persist the update
via km.repo.Update (or equivalent) so repo.IsPrimaryKey and subsequent checks no
longer reference the deleted key. Ensure this update is performed inside the
same transaction and wrap any DB errors with ErrDeleteKeyDB as done for the
other deletes.
🧹 Nitpick comments (1)
test/db-migration/migration_test.go (1)

340-350: Assert the v5 schema change itself.

These cases only verify the goose version. A broken tenant/00005_delete_key_primary.sql can still pass without proving that keys.is_primary disappears on upgrade and comes back on rollback. Add an assertMigration for column presence/absence here; while you’re there, rename the second case to “down” so the subtest names stay distinct.

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

In `@test/db-migration/migration_test.go` around lines 340 - 350, Rename the
second test case's name field from "Should up
tenant/00005_delete_key_primary.sql" to "Should down
tenant/00005_delete_key_primary.sql" so subtest names are unique, and add
assertMigration checks around the v5 migration cases: after running the upgrade
case (version: 5, downgrade: false, target: db.TenantTarget) call
assertMigration to confirm the keys.is_primary column is absent, and after
running the rollback case (downgrade: true) call assertMigration to confirm
keys.is_primary is present again; locate these hooks near the existing test
table entries and use the assertMigration helper and the migration identifier
tenant/00005_delete_key_primary.sql to validate column presence/absence.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/controllers/cmk/keyconfiguration_controller_test.go`:
- Around line 593-600: The test case named "Should 403 when update primary key
and workflow is required" has invalid JSON and lacks an authenticated context so
it can return 403 for the wrong reason; fix inputJSON by removing the trailing
comma (e.g. `"primaryKeyID": "id"`) and supply an authenticated request context
(the same auth context used by other tests, e.g. set authContext or include the
auth field on the test case) so the request is authenticated but still fails due
to workflow requirement; update the test case fields (name, keyConfigID,
inputJSON, expectedStatus) accordingly in keyconfiguration_controller_test.go so
the test validates the workflow-forbidden rule.

In `@internal/controllers/cmk/keyconfiguration_controller.go`:
- Around line 127-136: The current check gates on the mere presence of
request.Body.PrimaryKeyID and wrongly requires a workflow even if the value is
unchanged; update the logic in the handler around request.Body.PrimaryKeyID to
first load the current key configuration (the existing primary key id on the
resource), compare the requested PrimaryKeyID with the current primary key
(handling nil vs non-nil and string/ID equality), and only call
c.Manager.Workflow.IsWorkflowRequired(ctx) and return
apierrors.ErrActionRequireWorkflow when the primary key is actually changing.
Use the same request.Body.PrimaryKeyID symbol and the controller's existing
access to the current key configuration (via the controller/manager methods that
retrieve the resource) to perform the comparison before invoking
IsWorkflowRequired.

In `@internal/manager/key.go`:
- Around line 697-705: The current guard only rejects DISABLED and may
auto-promote keys in other unusable states; change the check around the
promotion (the block using key, key.State, exist, and setting
KeyConfiguration.PrimaryKeyID then calling km.repo.Patch) to require key.State
equals string(cmkapi.KeyStateENABLED) and return ErrKeyIsNotEnabled for any
other state, so only keys with state ENABLED are made primary; update the
conditional and error path accordingly where KeyConfiguration{PrimaryKeyID:
&key.ID} is assembled before km.repo.Patch.

In `@internal/manager/keyconfiguration_test.go`:
- Around line 935-937: The test creates an independent context with
testutils.CreateCtxWithTenant("orbital") which breaks the parent context chain;
replace that call with cmkcontext.CreateTenantContext(ctx, "orbital") so the
Orbital tenant context is derived from the existing parent ctx (preserving
cancellation and context values). Update the variable usage where orbitalCtx is
assigned to call cmkcontext.CreateTenantContext(ctx, "orbital") and ensure the
rest of the test (e.g., the r.First call using jobFromDB) continues to use
orbitalCtx.
- Line 875: The test currently compares only the 32-bit DCE identifier by
calling key.ID.ID() and keyConfig.PrimaryKeyID.ID(); replace that assertion to
compare the full UUID values instead: directly compare key.ID with
keyConfig.PrimaryKeyID (dereference keyConfig.PrimaryKeyID if it is a pointer)
so the test verifies the complete UUID, not the truncated DCE ID.

In `@internal/manager/keyconfiguration.go`:
- Around line 456-463: The lookup currently only verifies the key exists but
doesn't ensure it belongs to the same configuration; after retrieving model.Key
(primaryKeyID) via m.r.First, add a check that key.KeyConfigurationID equals the
current keyConfig.ID (or return a new/appropriate error like
ErrKeyNotOwnedByConfiguration) before proceeding to the state check; keep the
existing disabled-state check (key.State == string(cmkapi.KeyStateDISABLED)) but
ensure ownership is validated first to prevent cross-configuration primary key
assignment.
- Around line 507-534: The current updatePrimaryKeySystemEvents function
rewrites any event with data->>'keyIDTo'==oldPkey; restrict it to only
SYSTEM_SWITCH events with retryable statuses by adding type and status
predicates to the repo query (e.g., include
repo.Field(repo.TypeField).Equals(model.EventTypeSystemSwitch) and
repo.Field(repo.StatusField).In(<retryable status constants>)) and/or add a
guard inside the batch callback to skip events whose e.Type is not
model.EventTypeSystemSwitch or whose e.Status is not one of the intended
retryable statuses before unmarshalling/modifying systemJobData and calling
m.r.Patch.
- Around line 464-492: The batch loop builds a SystemSwitch event using
keyConfig.PrimaryKeyID.String(), which will panic when PrimaryKeyID is nil;
compute an oldPrimary string safely (e.g., using the same
ptr.GetSafeDeref(keyConfig.PrimaryKeyID).String() used earlier) before the
repo.ProcessInBatch call or at the top of the closure and pass that safe
oldPrimary into m.eventFactory.SystemSwitchNewPrimaryKey instead of
dereferencing keyConfig.PrimaryKeyID; updatePrimaryKeySystemEvents and the
closure that calls m.eventFactory.SystemSwitchNewPrimaryKey should use this safe
value to avoid nil deref.

In `@internal/manager/workflow_test.go`:
- Around line 283-286: The fixture key isn't linked to its configuration: after
creating keyConfig with testutils.NewKeyConfig and before persisting the key
created with testutils.NewKey, set the key's KeyConfigurationID
(key.KeyConfigurationID = keyConfig.ID) so the key truly belongs to that
configuration, and remove the duplicated PrimaryKeyID assignment (leave only
kc.PrimaryKeyID = ptr.PointTo(key.ID) on the keyConfig setup); apply the same
change to the other occurrence referenced (lines ~313-317) to ensure the tests
assert a key that actually belongs to the configuration.

In `@internal/repo/repository.go`:
- Around line 365-384: The IsPrimaryKey helper relies on Repo.First inspecting
keyConfig.ID via *NewQuery(), which the Repo contract doesn't guarantee; instead
construct an explicit query predicate setting IDField = key.KeyConfigurationID
(e.g., q := NewQuery(); q.IDField = key.KeyConfigurationID or
q.WhereID(key.KeyConfigurationID) depending on query API) and pass that query to
r.First so the lookup is scoped to the correct KeyConfiguration ID; update the
call in IsPrimaryKey to use this explicit query before invoking r.First with
keyConfig.

In `@migrations/tenant/schema/00005_delete_key_primary.sql`:
- Line 5: The Down section in 00005_delete_key_primary.sql is empty; open that
file, inspect the Up statements (what primary key, constraint or index was
dropped and on which table), and add the inverse SQL under the line "-- +goose
Down" to make the migration reversible (e.g., ALTER TABLE <table> ADD CONSTRAINT
<constraint_name> PRIMARY KEY (<cols>); or recreate any dropped indexes/columns
with their original definitions). Ensure the SQL names match the ones removed in
Up (use the original constraint/index names) and test the Down path to confirm
the database returns to the pre-migration state.

---

Outside diff comments:
In `@internal/manager/key.go`:
- Around line 264-302: The deletion path currently removes the provider key and
DB records (km.deleteProviderKey, km.repo.Delete of model.KeyVersion and
model.Key) but does not clear or replace the KeyConfiguration.PrimaryKeyID,
leaving a dangling reference; inside the same transaction (km.repo.Transaction)
after deleting the key/version rows, load the owning KeyConfiguration (using
key.KeyConfigurationID), check if its PrimaryKeyID equals keyID, and if so
either set PrimaryKeyID to null/empty (or pick another existing key ID if
business logic requires) and persist the update via km.repo.Update (or
equivalent) so repo.IsPrimaryKey and subsequent checks no longer reference the
deleted key. Ensure this update is performed inside the same transaction and
wrap any DB errors with ErrDeleteKeyDB as done for the other deletes.

---

Nitpick comments:
In `@test/db-migration/migration_test.go`:
- Around line 340-350: Rename the second test case's name field from "Should up
tenant/00005_delete_key_primary.sql" to "Should down
tenant/00005_delete_key_primary.sql" so subtest names are unique, and add
assertMigration checks around the v5 migration cases: after running the upgrade
case (version: 5, downgrade: false, target: db.TenantTarget) call
assertMigration to confirm the keys.is_primary column is absent, and after
running the rollback case (downgrade: true) call assertMigration to confirm
keys.is_primary is present again; locate these hooks near the existing test
table entries and use the assertMigration helper and the migration identifier
tenant/00005_delete_key_primary.sql to validate column presence/absence.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 571cd0cb-a1a1-427b-9cb3-57a3271b1112

📥 Commits

Reviewing files that changed from the base of the PR and between 8462f6b and fe23c38.

📒 Files selected for processing (34)
  • apis/cmk/cmk-ui.yaml
  • cmd/task-worker/main.go
  • cmd/tenant-manager-cli/cli_test.go
  • cmd/tenant-manager-cli/commands/commands.go
  • cmd/tenant-manager/main.go
  • internal/api/cmkapi/cmkapi.go
  • internal/api/transform/key/key.go
  • internal/api/transform/key/key_test.go
  • internal/apierrors/key.go
  • internal/controllers/cmk/key_controller.go
  • internal/controllers/cmk/key_controller_test.go
  • internal/controllers/cmk/keyconfiguration_controller.go
  • internal/controllers/cmk/keyconfiguration_controller_test.go
  • internal/manager/base.go
  • internal/manager/errors.go
  • internal/manager/key.go
  • internal/manager/key_test.go
  • internal/manager/keyconfiguration.go
  • internal/manager/keyconfiguration_test.go
  • internal/manager/keyversion_test.go
  • internal/manager/system_test.go
  • internal/manager/tenant_test.go
  • internal/manager/workflow_test.go
  • internal/model/key.go
  • internal/operator/operator_test.go
  • internal/repo/repository.go
  • internal/workflow/key_configuration_actions.go
  • internal/workflow/key_configuration_actions_test.go
  • migrations/tenant/schema/00005_delete_key_primary.sql
  • test/db-migration/migration_test.go
  • test/integration/async_test/common_test.go
  • test/integration/async_test/workflowassign_test.go
  • test/integration/tenant-manager/provisioning_test.go
  • test/security/xss/key_controller_test.go
💤 Files with no reviewable changes (6)
  • test/security/xss/key_controller_test.go
  • internal/apierrors/key.go
  • internal/api/transform/key/key.go
  • internal/api/transform/key/key_test.go
  • internal/controllers/cmk/key_controller.go
  • internal/manager/errors.go

Copy link
Copy Markdown

@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

Caution

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

⚠️ Outside diff range comments (1)
internal/manager/key.go (1)

259-302: ⚠️ Potential issue | 🟠 Major

Clear the configuration primary when deleting the current primary key.

After moving primary ownership to KeyConfiguration.PrimaryKeyID, this path deletes the key but never updates the owning configuration. If isPrimary is true, the configuration is left pointing at a non-existent key ID, which can break later reads and workflow checks. Clear or reassign the primary in the same transaction before removing the key rows.

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

In `@internal/manager/key.go` around lines 259 - 302, When deleting a key that
IsPrimaryKey returned true for, you must in the same repo.Transaction clear or
reassign the owning KeyConfiguration.PrimaryKeyID before removing the key rows;
modify the transaction passed to km.repo.Transaction (the block that currently
creates ck via repo.NewCompositeKey and deletes KeyVersion and Key) to first
call the repo update for the KeyConfiguration owning this key (use
key.KeyConfigurationID) to set PrimaryKeyID to nil (or a new value) and wrap
that update in the same transaction so the configuration no longer points at the
soon-to-be-deleted key; keep the existing deleteProviderKey call and existing
delete steps but ensure the configuration update happens before the Delete
calls.
♻️ Duplicate comments (5)
internal/manager/workflow_test.go (1)

283-292: ⚠️ Potential issue | 🟡 Minor

Link each fixture key back to its key configuration.

Both subtests set PrimaryKeyID, but the new model.Key still keeps the zero KeyConfigurationID, so the fixture does not prove that the chosen key actually belongs to that configuration. Also, PrimaryKeyID is assigned twice in each block.

🧪 Suggested test cleanup
 key := testutils.NewKey(func(k *model.Key) {})
 keyConfig := testutils.NewKeyConfig(func(kc *model.KeyConfiguration) {
-	kc.PrimaryKeyID = &key.ID
 	kc.PrimaryKeyID = ptr.PointTo(key.ID)
 })
+key.KeyConfigurationID = keyConfig.ID

Apply the same cleanup to the second subtest as well.

Also applies to: 313-320

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

In `@internal/manager/workflow_test.go` around lines 283 - 292, The fixtures are
assigning PrimaryKeyID on KeyConfiguration but not linking the created model.Key
back via its KeyConfigurationID and they erroneously set PrimaryKeyID twice;
update the test fixture builders (calls to testutils.NewKey and
testutils.NewKeyConfig) so the created key's KeyConfigurationID is set to the
keyConfig.ID (e.g., in NewKey’s init func set k.KeyConfigurationID =
&keyConfig.ID) and remove the duplicate PrimaryKeyID assignment in NewKeyConfig
(keep only one assignment to kc.PrimaryKeyID = ptr.PointTo(key.ID)); apply the
same fix to both subtests so the key truly belongs to the key configuration.
internal/manager/keyconfiguration_test.go (1)

867-875: ⚠️ Potential issue | 🟡 Minor

Compare full UUIDs instead of UUID.ID().

uuid.UUID.ID() only returns the DCE local identifier, so this assertion can pass for different UUIDs that share the same first 32 bits. Compare key.ID with *keyConfig.PrimaryKeyID directly.

🧪 Suggested fix
-		assert.Equal(t, key.ID.ID(), keyConfig.PrimaryKeyID.ID())
+		assert.Equal(t, key.ID, *keyConfig.PrimaryKeyID)
What does github.com/google/uuid UUID.ID() return, and does it represent the full UUID value or only the DCE Security local identifier?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/manager/keyconfiguration_test.go` around lines 867 - 875, The test
currently compares only the DCE local identifier via uuid.UUID.ID(), which can
yield false positives; update the assertion after calling
UpdateKeyConfigurationByID to compare the full UUID values directly—use key.ID
and the returned pointer value in keyConfig.PrimaryKeyID (dereference as needed)
instead of calling .ID() so the test verifies the entire UUID matches (refer to
UpdateKeyConfigurationByID, keyConfig, key.ID, and PrimaryKeyID to locate the
change).
internal/manager/key.go (1)

696-705: ⚠️ Potential issue | 🟠 Major

Require ENABLED before auto-promoting the first key.

This guard only rejects DISABLED, so a first key in PENDING_IMPORT, UNKNOWN, or FORBIDDEN can still become the configuration primary. Check for ENABLED explicitly instead.

🛠️ Suggested fix
-		if key.State == string(cmkapi.KeyStateDISABLED) {
+		if key.State != string(cmkapi.KeyStateENABLED) {
 			return ErrKeyIsNotEnabled
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/manager/key.go` around lines 696 - 705, The code currently only
rejects DISABLED states before auto-promoting the first key, allowing
PENDING_IMPORT/UNKNOWN/FORBIDDEN to be promoted; update the guard in the block
that sets KeyConfiguration.PrimaryKeyID (the branch using exist, keyConfig,
km.repo.Patch) to require explicit ENABLED state by checking key.State ==
string(cmkapi.KeyStateENABLED) and returning ErrKeyIsNotEnabled otherwise,
referencing the symbols key.State, cmkapi.KeyStateENABLED, and
ErrKeyIsNotEnabled.
internal/controllers/cmk/keyconfiguration_controller_test.go (1)

593-600: ⚠️ Potential issue | 🟠 Major

Make the new 403 case reach the workflow gate.

This payload is malformed and uses an invalid UUID, so the request can fail before the controller evaluates ErrActionRequireWorkflow. The case also omits auth context, which makes the 403 source ambiguous.

🧪 Suggested test fix
 		{
 			name:        "Should 403 when update primary key and workflow is required",
 			keyConfigID: keyConfig.ID.String(),
 			inputJSON: `{
-                "primaryKeyID": "id",
-            }`,
+                "primaryKeyID": "12345678-90ab-cdef-1234-567890abcdef"
+            }`,
 			expectedStatus: http.StatusForbidden,
+			expectedBody:   "error",
+			additionalContext: authClient.GetClientMap(),
 		},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controllers/cmk/keyconfiguration_controller_test.go` around lines
593 - 600, The test case "Should 403 when update primary key and workflow is
required" sends a malformed payload and lacks auth, so it never reaches the
workflow gate; update the test to send a valid UUID for "primaryKeyID" (e.g. use
an existing primary key ID from the fixture like
keyConfig.PrimaryKey.ID.String() or another valid UUID) and include the same
auth context used by other 403 workflow tests so the request is authenticated;
this ensures the controller hits the ErrActionRequireWorkflow branch (refer to
the test name and ErrActionRequireWorkflow handling in
keyconfiguration_controller_test.go and the keyConfig fixture).
internal/controllers/cmk/keyconfiguration_controller.go (1)

127-136: ⚠️ Potential issue | 🟠 Major

Gate this on an actual primary-key transition.

Any PATCH that merely includes primaryKeyID now gets blocked when workflows are required, even if the client is just re-sending the existing value while updating another field. Load the current key configuration first and only require a workflow when the requested PrimaryKeyID differs from the stored one.

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

In `@internal/controllers/cmk/keyconfiguration_controller.go` around lines 127 -
136, The current check gates any PATCH that includes request.Body.PrimaryKeyID
regardless of whether it actually changes the stored value; modify the logic to
first load the existing key configuration (retrieve the stored PrimaryKeyID for
this resource) and only call c.Manager.Workflow.IsWorkflowRequired(ctx) and
return apierrors.ErrActionRequireWorkflow when request.Body.PrimaryKeyID is
non-nil AND differs from the stored PrimaryKeyID; update the block around
request.Body.PrimaryKeyID to fetch the current configuration, compare the two
IDs, and only enforce the workflow requirement on a true transition.
🧹 Nitpick comments (3)
internal/manager/system_test.go (1)

85-88: Prefer wiring a real eventFactory into NewKeyConfigManager in shared setup.

Passing nil here makes the shared fixture diverge from production wiring and can hide nil-dependent paths in future test additions.

♻️ Suggested refactor
-	keyConfigManager := manager.NewKeyConfigManager(dbRepository, certManager, userManager, tagManager, nil, nil, &cfg)
-
 	eventFactory, err := eventprocessor.NewEventFactory(t.Context(), &cfg, dbRepository)
 	require.NoError(t, err)
+	keyConfigManager := manager.NewKeyConfigManager(
+		dbRepository,
+		certManager,
+		userManager,
+		tagManager,
+		nil,
+		eventFactory,
+		&cfg,
+	)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/manager/system_test.go` around lines 85 - 88, The test creates
keyConfigManager with a nil eventFactory which diverges from production wiring;
create the real eventFactory via eventprocessor.NewEventFactory(t.Context(),
&cfg, dbRepository) before calling manager.NewKeyConfigManager, check
require.NoError on its creation, and pass that eventFactory instance (instead of
nil) into NewKeyConfigManager so the shared fixture uses the real event factory
path.
internal/workflow/key_configuration_actions_test.go (1)

52-67: Use a different target key in the update-primary case.

keyConfig.PrimaryKeyID and wf.Parameters both resolve to the same keyID, so this path only confirms a no-op and can still pass without proving the lifecycle switched the configuration primary. Create a current primary key and a second candidate key, then assert the stored KeyConfiguration.PrimaryKeyID changed after ApplyTransition.

Also applies to: 76-110

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

In `@internal/workflow/key_configuration_actions_test.go` around lines 52 - 67,
The test uses the same key for KeyConfiguration.PrimaryKeyID and wf.Parameters
so the update-primary transition is a no-op; modify the test to create two
distinct keys (e.g., currentPrimary and newPrimary), set kc.PrimaryKeyID to
currentPrimary.ID and wf.Parameters to newPrimary.ID (instead of k.ID), run
ApplyTransition, then assert that the persisted
model.KeyConfiguration.PrimaryKeyID changed from currentPrimary.ID to
newPrimary.ID; update both the test case at the shown block and the similar
cases around lines 76-110 to use distinct keys and assert the PrimaryKeyID was
updated after ApplyTransition.
internal/manager/key_test.go (1)

434-454: This now tests “no primary configured,” not “non-primary key.”

kc.PrimaryKeyID is left nil here, so this case can still pass even if EditableRegions breaks specifically when another key is primary. Seed PrimaryKeyID with a different key ID so the test exercises the real non-primary branch.

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

In `@internal/manager/key_test.go` around lines 434 - 454, The test is asserting
the "non-primary key" path but leaves kc.PrimaryKeyID nil so it actually tests
"no primary configured"; set kc.PrimaryKeyID to a different key's ID before
creating entities so the code exercises the non-primary branch. Locate the
KeyConfiguration instance created by testutils.NewKeyConfig (kc) and assign
kc.PrimaryKeyID to a UUID (or the ID of a separate test key) that is different
from the key created by testutils.NewKey, ensuring the PrimaryKeyID is non-nil
and not equal to key.ID before calling testutils.CreateTestEntities.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/manager/key_test.go`:
- Around line 929-939: The system fixture created with testutils.NewSystem(...)
lacks a Status, so change the initializer for sys to mark it as connected (e.g.,
in the anonymous func passed to testutils.NewSystem, set s.Status =
model.SystemStatusConnected or the string "connected" used by your model) so the
test actually exercises the “primary-with-connected-systems” guard; keep the
existing assignments to KeyConfigurationID but add the Status assignment in the
same initializer.

In `@test/db-migration/migration_test.go`:
- Around line 346-351: The test case entry with fields (name, downgrade, target,
version) has a copy-paste error: the name says "Should up
tenant/00005_delete_key_primary.sql" while downgrade is set to true; update the
name string in that test case to "Should down
tenant/00005_delete_key_primary.sql" so it accurately reflects the
downgrade=true setting and aligns with other downgrade cases (locate the struct
literal where name: "Should up tenant/00005_delete_key_primary.sql" is declared
and change only the name value).

---

Outside diff comments:
In `@internal/manager/key.go`:
- Around line 259-302: When deleting a key that IsPrimaryKey returned true for,
you must in the same repo.Transaction clear or reassign the owning
KeyConfiguration.PrimaryKeyID before removing the key rows; modify the
transaction passed to km.repo.Transaction (the block that currently creates ck
via repo.NewCompositeKey and deletes KeyVersion and Key) to first call the repo
update for the KeyConfiguration owning this key (use key.KeyConfigurationID) to
set PrimaryKeyID to nil (or a new value) and wrap that update in the same
transaction so the configuration no longer points at the soon-to-be-deleted key;
keep the existing deleteProviderKey call and existing delete steps but ensure
the configuration update happens before the Delete calls.

---

Duplicate comments:
In `@internal/controllers/cmk/keyconfiguration_controller_test.go`:
- Around line 593-600: The test case "Should 403 when update primary key and
workflow is required" sends a malformed payload and lacks auth, so it never
reaches the workflow gate; update the test to send a valid UUID for
"primaryKeyID" (e.g. use an existing primary key ID from the fixture like
keyConfig.PrimaryKey.ID.String() or another valid UUID) and include the same
auth context used by other 403 workflow tests so the request is authenticated;
this ensures the controller hits the ErrActionRequireWorkflow branch (refer to
the test name and ErrActionRequireWorkflow handling in
keyconfiguration_controller_test.go and the keyConfig fixture).

In `@internal/controllers/cmk/keyconfiguration_controller.go`:
- Around line 127-136: The current check gates any PATCH that includes
request.Body.PrimaryKeyID regardless of whether it actually changes the stored
value; modify the logic to first load the existing key configuration (retrieve
the stored PrimaryKeyID for this resource) and only call
c.Manager.Workflow.IsWorkflowRequired(ctx) and return
apierrors.ErrActionRequireWorkflow when request.Body.PrimaryKeyID is non-nil AND
differs from the stored PrimaryKeyID; update the block around
request.Body.PrimaryKeyID to fetch the current configuration, compare the two
IDs, and only enforce the workflow requirement on a true transition.

In `@internal/manager/key.go`:
- Around line 696-705: The code currently only rejects DISABLED states before
auto-promoting the first key, allowing PENDING_IMPORT/UNKNOWN/FORBIDDEN to be
promoted; update the guard in the block that sets KeyConfiguration.PrimaryKeyID
(the branch using exist, keyConfig, km.repo.Patch) to require explicit ENABLED
state by checking key.State == string(cmkapi.KeyStateENABLED) and returning
ErrKeyIsNotEnabled otherwise, referencing the symbols key.State,
cmkapi.KeyStateENABLED, and ErrKeyIsNotEnabled.

In `@internal/manager/keyconfiguration_test.go`:
- Around line 867-875: The test currently compares only the DCE local identifier
via uuid.UUID.ID(), which can yield false positives; update the assertion after
calling UpdateKeyConfigurationByID to compare the full UUID values directly—use
key.ID and the returned pointer value in keyConfig.PrimaryKeyID (dereference as
needed) instead of calling .ID() so the test verifies the entire UUID matches
(refer to UpdateKeyConfigurationByID, keyConfig, key.ID, and PrimaryKeyID to
locate the change).

In `@internal/manager/workflow_test.go`:
- Around line 283-292: The fixtures are assigning PrimaryKeyID on
KeyConfiguration but not linking the created model.Key back via its
KeyConfigurationID and they erroneously set PrimaryKeyID twice; update the test
fixture builders (calls to testutils.NewKey and testutils.NewKeyConfig) so the
created key's KeyConfigurationID is set to the keyConfig.ID (e.g., in NewKey’s
init func set k.KeyConfigurationID = &keyConfig.ID) and remove the duplicate
PrimaryKeyID assignment in NewKeyConfig (keep only one assignment to
kc.PrimaryKeyID = ptr.PointTo(key.ID)); apply the same fix to both subtests so
the key truly belongs to the key configuration.

---

Nitpick comments:
In `@internal/manager/key_test.go`:
- Around line 434-454: The test is asserting the "non-primary key" path but
leaves kc.PrimaryKeyID nil so it actually tests "no primary configured"; set
kc.PrimaryKeyID to a different key's ID before creating entities so the code
exercises the non-primary branch. Locate the KeyConfiguration instance created
by testutils.NewKeyConfig (kc) and assign kc.PrimaryKeyID to a UUID (or the ID
of a separate test key) that is different from the key created by
testutils.NewKey, ensuring the PrimaryKeyID is non-nil and not equal to key.ID
before calling testutils.CreateTestEntities.

In `@internal/manager/system_test.go`:
- Around line 85-88: The test creates keyConfigManager with a nil eventFactory
which diverges from production wiring; create the real eventFactory via
eventprocessor.NewEventFactory(t.Context(), &cfg, dbRepository) before calling
manager.NewKeyConfigManager, check require.NoError on its creation, and pass
that eventFactory instance (instead of nil) into NewKeyConfigManager so the
shared fixture uses the real event factory path.

In `@internal/workflow/key_configuration_actions_test.go`:
- Around line 52-67: The test uses the same key for
KeyConfiguration.PrimaryKeyID and wf.Parameters so the update-primary transition
is a no-op; modify the test to create two distinct keys (e.g., currentPrimary
and newPrimary), set kc.PrimaryKeyID to currentPrimary.ID and wf.Parameters to
newPrimary.ID (instead of k.ID), run ApplyTransition, then assert that the
persisted model.KeyConfiguration.PrimaryKeyID changed from currentPrimary.ID to
newPrimary.ID; update both the test case at the shown block and the similar
cases around lines 76-110 to use distinct keys and assert the PrimaryKeyID was
updated after ApplyTransition.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 49299c46-2966-4b3f-aecc-eaf9a3ee66e4

📥 Commits

Reviewing files that changed from the base of the PR and between fe23c38 and aeeaad9.

📒 Files selected for processing (34)
  • apis/cmk/cmk-ui.yaml
  • cmd/task-worker/main.go
  • cmd/tenant-manager-cli/cli_test.go
  • cmd/tenant-manager-cli/commands/commands.go
  • cmd/tenant-manager/main.go
  • internal/api/cmkapi/cmkapi.go
  • internal/api/transform/key/key.go
  • internal/api/transform/key/key_test.go
  • internal/apierrors/key.go
  • internal/controllers/cmk/key_controller.go
  • internal/controllers/cmk/key_controller_test.go
  • internal/controllers/cmk/keyconfiguration_controller.go
  • internal/controllers/cmk/keyconfiguration_controller_test.go
  • internal/manager/base.go
  • internal/manager/errors.go
  • internal/manager/key.go
  • internal/manager/key_test.go
  • internal/manager/keyconfiguration.go
  • internal/manager/keyconfiguration_test.go
  • internal/manager/keyversion_test.go
  • internal/manager/system_test.go
  • internal/manager/tenant_test.go
  • internal/manager/workflow_test.go
  • internal/model/key.go
  • internal/operator/operator_test.go
  • internal/repo/repository.go
  • internal/workflow/key_configuration_actions.go
  • internal/workflow/key_configuration_actions_test.go
  • migrations/tenant/schema/00005_delete_key_primary.sql
  • test/db-migration/migration_test.go
  • test/integration/async_test/common_test.go
  • test/integration/async_test/workflowassign_test.go
  • test/integration/tenant-manager/provisioning_test.go
  • test/security/xss/key_controller_test.go
💤 Files with no reviewable changes (6)
  • internal/api/transform/key/key.go
  • test/security/xss/key_controller_test.go
  • internal/controllers/cmk/key_controller.go
  • internal/apierrors/key.go
  • internal/manager/errors.go
  • internal/api/transform/key/key_test.go
✅ Files skipped from review due to trivial changes (2)
  • test/integration/async_test/workflowassign_test.go
  • cmd/task-worker/main.go
🚧 Files skipped from review as they are similar to previous changes (14)
  • migrations/tenant/schema/00005_delete_key_primary.sql
  • test/integration/tenant-manager/provisioning_test.go
  • internal/manager/keyversion_test.go
  • cmd/tenant-manager/main.go
  • cmd/tenant-manager-cli/commands/commands.go
  • test/integration/async_test/common_test.go
  • internal/manager/tenant_test.go
  • cmd/tenant-manager-cli/cli_test.go
  • internal/repo/repository.go
  • internal/manager/base.go
  • internal/model/key.go
  • internal/workflow/key_configuration_actions.go
  • internal/api/cmkapi/cmkapi.go
  • internal/manager/keyconfiguration.go

@jmpTeixeira02 jmpTeixeira02 force-pushed the task/key-keyconfig-refactor branch 3 times, most recently from 957c8fc to 3633eac Compare March 19, 2026 13:20
Copy link
Copy Markdown

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

Caution

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

⚠️ Outside diff range comments (2)
internal/workflow/key_configuration_actions_test.go (1)

51-67: ⚠️ Potential issue | 🟡 Minor

The "Update primary key" case is currently a no-op.

This fixture seeds keyConfig.PrimaryKeyID with the same keyID that the workflow later patches back in via wf.Parameters, so the test still passes even if UpdateKeyConfigurationByID never moves the primary. Use a second key as the promotion target and assert that PrimaryKeyID changed after the transition.

Also applies to: 76-96

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

In `@internal/workflow/key_configuration_actions_test.go` around lines 51 - 67,
The "Update primary key" test is a no-op because kc.PrimaryKeyID is seeded with
the same key ID used in wf.Parameters; change the fixture to create a second
model.Key (e.g., newKey) and set wf.Parameters to newKey.ID.String(), keep
kc.PrimaryKeyID set to the original key's ID, then assert after invoking the
workflow transition (the code path that calls UpdateKeyConfigurationByID / the
workflow state transition) that kc.PrimaryKeyID was updated to newKey.ID; apply
the same fix to the second similar test case around lines 76-96 so both tests
actually exercise promotion of a different key.
internal/manager/key.go (1)

264-301: ⚠️ Potential issue | 🟠 Major

Clear or reassign PrimaryKeyID when deleting the primary key.

If this path deletes the current primary, model.KeyConfiguration.PrimaryKeyID is left untouched, so the configuration still points at a key that no longer exists. That leaves a dangling primary reference for later reads and repo.IsPrimaryKey checks. At minimum, patch it to nil in the same transaction when no replacement is chosen.

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

In `@internal/manager/key.go` around lines 264 - 301, The code deletes a primary
key (isPrimary) but never clears model.KeyConfiguration.PrimaryKeyID, leaving a
dangling reference; inside the same km.repo.Transaction (the func passed to
km.repo.Transaction that currently removes KeyVersion and Key), add a step to
load or construct the KeyConfiguration for key.KeyConfigurationID and set its
PrimaryKeyID to nil (or assign a replacement if chosen), then persist that
change via the repo update call (use the same transaction context). Ensure this
update occurs before committing the deletion of the Key so repo.IsPrimaryKey and
later reads don't point at a deleted key; reference symbols: isPrimary,
key.KeyConfigurationID, keyID, km.repo.Transaction,
model.KeyConfiguration.PrimaryKeyID, km.deleteProviderKey.
♻️ Duplicate comments (5)
internal/manager/key.go (1)

696-700: ⚠️ Potential issue | 🟠 Major

Only promote the first key once it is actually ENABLED.

This still assigns PrimaryKeyID for non-usable states because it only rejects DISABLED. A first BYOK key in PENDING_IMPORT can still become the configuration primary here, which leaves the config pointing at a key that cannot be used yet.

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

In `@internal/manager/key.go` around lines 696 - 700, The code currently allows
setting PrimaryKeyID for any non-existing key as long as it's not DISABLED;
change the guard to require the key be in the ENABLED state before promoting it.
In the block that checks !exist, replace the current state check (which only
rejects cmkapi.KeyStateDISABLED) with an explicit equality check for
cmkapi.KeyStateENABLED (using key.State == string(cmkapi.KeyStateENABLED)) and
return ErrKeyIsNotEnabled for any other state so PrimaryKeyID is only assigned
when the key is actually usable; update any related comments and tests
referencing PrimaryKeyID, key.State, cmkapi.KeyStateENABLED,
cmkapi.KeyStateDISABLED, ErrKeyIsNotEnabled and exist accordingly.
internal/manager/workflow_test.go (1)

316-323: ⚠️ Potential issue | 🟡 Minor

Link the fixture key back to keyConfig.

PrimaryKeyID points at key.ID, but the key itself is never reassigned to keyConfig.ID here. This subtest can still pass with a key that belongs to some other configuration, and the duplicated PrimaryKeyID assignment just adds noise.

Suggested fix
 		key := testutils.NewKey(func(k *model.Key) {})
 
 		keyConfig := testutils.NewKeyConfig(func(kc *model.KeyConfiguration) {
-			kc.PrimaryKeyID = &key.ID
 			kc.PrimaryKeyID = ptr.PointTo(key.ID)
 		})
+		key.KeyConfigurationID = keyConfig.ID
 
 		testutils.CreateTestEntities(ctxSys, t, repo, key, keyConfig)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/manager/workflow_test.go` around lines 316 - 323, The key fixture is
not linked back to the keyConfig fixture: after creating key and keyConfig via
testutils.NewKey and testutils.NewKeyConfig you should set the key's
configuration foreign key to the keyConfig's ID (e.g., key.KeyConfigurationID =
keyConfig.ID or key.ConfigurationID = keyConfig.ID depending on the model) and
remove the duplicated PrimaryKeyID assignment on keyConfig (leave a single
kc.PrimaryKeyID = ptr.PointTo(key.ID)). Ensure you then pass these updated
fixtures to testutils.CreateTestEntities so the key truly belongs to keyConfig.
internal/controllers/cmk/keyconfiguration_controller_test.go (1)

593-600: ⚠️ Potential issue | 🟡 Minor

Use an authenticated request and a valid UUID in this 403 case.

As written, this can fail before the workflow-required branch: primaryKeyID is not a valid UUID and the request has no auth context. That means the test can still pass without proving the workflow guard is what produced the 403.

Suggested fix
 		{
 			name:        "Should 403 when update primary key and workflow is required",
 			keyConfigID: keyConfig.ID.String(),
-			inputJSON: `{
-                "primaryKeyID": "id"
-            }`,
+			inputJSON: fmt.Sprintf(`{"primaryKeyID":"%s"}`, uuid.NewString()),
 			expectedStatus: http.StatusForbidden,
+			expectedBody:   "error",
+			additionalContext: authClient.GetClientMap(),
 		},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controllers/cmk/keyconfiguration_controller_test.go` around lines
593 - 600, The test case "Should 403 when update primary key and workflow is
required" currently sends an unauthenticated request with primaryKeyID set to
"id"; update it to use a valid UUID (e.g. primaryKeyID: keyConfig.ID.String() or
another generated UUID) and ensure the request is made with an authenticated
context (use the same auth helper or test fixture used by other authenticated
cases) so the request reaches the workflow-required guard and the handler
returns 403 as expected while keeping expectedStatus http.StatusForbidden.
internal/manager/key_test.go (1)

929-939: ⚠️ Potential issue | 🟠 Major

Make the system fixture explicitly connected for this delete-guard test.

Line 954 expects the “connected systems” protection, but the fixture at Line 933-Line 935 does not set a connected status. This can let the test pass without covering the intended guard.

🧪 Minimal fix
 sys := testutils.NewSystem(func(s *model.System) {
 	s.KeyConfigurationID = ptr.PointTo(keyConfigWSystems.ID)
+	s.Status = cmkapi.SystemStatusCONNECTED
 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/manager/key_test.go` around lines 929 - 939, The system fixture
created with testutils.NewSystem for the delete-guard test is not marked as
connected so the "connected systems" protection isn't actually exercised; update
the system fixture (the model.System created in the test via
testutils.NewSystem) to be explicitly connected (set the appropriate field on
model.System — e.g., s.Connected = true or s.Status = "connected" depending on
the struct) while keeping s.KeyConfigurationID =
ptr.PointTo(keyConfigWSystems.ID) so the delete guard for connected systems is
exercised by the test.
internal/manager/keyconfiguration.go (1)

518-545: ⚠️ Potential issue | 🟠 Major

Scope event payload rewrites to the intended switch/retry events only.

Current filtering by data->>'keyIDTo' alone can rewrite unrelated or historical events sharing that field. Restrict this path to system-switch events (and retryable statuses) before patching.

🔒 Minimal guard inside batch loop
 for _, e := range events {
+	if e.Type != model.EventTypeSystemSwitch {
+		continue
+	}
+	// Optional: also gate by your retryable statuses before mutating payload.
+
 	systemJobData, err := eventprocessor.GetSystemJobData(e)
 	if err != nil {
 		return err
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/manager/keyconfiguration.go` around lines 518 - 545, The current
updatePrimaryKeySystemEvents(...) broad query only filters on data->>'keyIDTo'
and may rewrite unrelated events; narrow it to target only system-switch events
and retryable statuses by extending the repo query (the
repo.NewQuery()/repo.NewCompositeKey()/repo.JSONBField conditions) to also
require the system-switch event type and that data->>'status' is one of the
retryable statuses, and add a defensive runtime guard inside the
repo.ProcessInBatch loop before patching (in updatePrimaryKeySystemEvents's
inner loop) that checks e.Type equals the system-switch type and that the parsed
systemJobData.Status is in the allowed retry set (using
eventprocessor.GetSystemJobData and systemJobData.Status) to avoid touching
unrelated/historical events.
🧹 Nitpick comments (1)
internal/workflow/key_configuration_actions.go (1)

16-20: Drop the unused *model.KeyConfiguration return from this workflow-facing interface.

Lifecycle.updatePrimaryKey ignores the value, so this signature only adds coupling from internal/workflow to internal/model. Returning just error keeps the boundary narrower and avoids the extra dependency in this package.

Also applies to: 38-40

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

In `@internal/workflow/key_configuration_actions.go` around lines 16 - 20, Change
the workflow interface method UpdateKeyConfigurationByID to return only error
(UpdateKeyConfigurationByID(ctx context.Context, keyConfigID uuid.UUID,
patchKeyConfig cmkapi.KeyConfigurationPatch) error), update its implementations
and all callers (including Lifecycle.updatePrimaryKey) to stop expecting a
*model.KeyConfiguration value, and remove any resulting dependency/import on
internal/model from the internal/workflow package; apply the same change to the
other workflow-facing method with the same unused *model.KeyConfiguration return
(the method around the 38-40 region) so the interface only returns error
everywhere.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/manager/keyconfiguration.go`:
- Line 455: Replace the incorrect nolint directive `// nolint:nestif` with the
correct `//nolint:nestif` (remove the space) so golangci-lint recognizes it;
locate the directive in internal/manager/keyconfiguration.go (the existing `//
nolint:nestif` comment) and update it to the no-space form.

---

Outside diff comments:
In `@internal/manager/key.go`:
- Around line 264-301: The code deletes a primary key (isPrimary) but never
clears model.KeyConfiguration.PrimaryKeyID, leaving a dangling reference; inside
the same km.repo.Transaction (the func passed to km.repo.Transaction that
currently removes KeyVersion and Key), add a step to load or construct the
KeyConfiguration for key.KeyConfigurationID and set its PrimaryKeyID to nil (or
assign a replacement if chosen), then persist that change via the repo update
call (use the same transaction context). Ensure this update occurs before
committing the deletion of the Key so repo.IsPrimaryKey and later reads don't
point at a deleted key; reference symbols: isPrimary, key.KeyConfigurationID,
keyID, km.repo.Transaction, model.KeyConfiguration.PrimaryKeyID,
km.deleteProviderKey.

In `@internal/workflow/key_configuration_actions_test.go`:
- Around line 51-67: The "Update primary key" test is a no-op because
kc.PrimaryKeyID is seeded with the same key ID used in wf.Parameters; change the
fixture to create a second model.Key (e.g., newKey) and set wf.Parameters to
newKey.ID.String(), keep kc.PrimaryKeyID set to the original key's ID, then
assert after invoking the workflow transition (the code path that calls
UpdateKeyConfigurationByID / the workflow state transition) that kc.PrimaryKeyID
was updated to newKey.ID; apply the same fix to the second similar test case
around lines 76-96 so both tests actually exercise promotion of a different key.

---

Duplicate comments:
In `@internal/controllers/cmk/keyconfiguration_controller_test.go`:
- Around line 593-600: The test case "Should 403 when update primary key and
workflow is required" currently sends an unauthenticated request with
primaryKeyID set to "id"; update it to use a valid UUID (e.g. primaryKeyID:
keyConfig.ID.String() or another generated UUID) and ensure the request is made
with an authenticated context (use the same auth helper or test fixture used by
other authenticated cases) so the request reaches the workflow-required guard
and the handler returns 403 as expected while keeping expectedStatus
http.StatusForbidden.

In `@internal/manager/key_test.go`:
- Around line 929-939: The system fixture created with testutils.NewSystem for
the delete-guard test is not marked as connected so the "connected systems"
protection isn't actually exercised; update the system fixture (the model.System
created in the test via testutils.NewSystem) to be explicitly connected (set the
appropriate field on model.System — e.g., s.Connected = true or s.Status =
"connected" depending on the struct) while keeping s.KeyConfigurationID =
ptr.PointTo(keyConfigWSystems.ID) so the delete guard for connected systems is
exercised by the test.

In `@internal/manager/key.go`:
- Around line 696-700: The code currently allows setting PrimaryKeyID for any
non-existing key as long as it's not DISABLED; change the guard to require the
key be in the ENABLED state before promoting it. In the block that checks
!exist, replace the current state check (which only rejects
cmkapi.KeyStateDISABLED) with an explicit equality check for
cmkapi.KeyStateENABLED (using key.State == string(cmkapi.KeyStateENABLED)) and
return ErrKeyIsNotEnabled for any other state so PrimaryKeyID is only assigned
when the key is actually usable; update any related comments and tests
referencing PrimaryKeyID, key.State, cmkapi.KeyStateENABLED,
cmkapi.KeyStateDISABLED, ErrKeyIsNotEnabled and exist accordingly.

In `@internal/manager/keyconfiguration.go`:
- Around line 518-545: The current updatePrimaryKeySystemEvents(...) broad query
only filters on data->>'keyIDTo' and may rewrite unrelated events; narrow it to
target only system-switch events and retryable statuses by extending the repo
query (the repo.NewQuery()/repo.NewCompositeKey()/repo.JSONBField conditions) to
also require the system-switch event type and that data->>'status' is one of the
retryable statuses, and add a defensive runtime guard inside the
repo.ProcessInBatch loop before patching (in updatePrimaryKeySystemEvents's
inner loop) that checks e.Type equals the system-switch type and that the parsed
systemJobData.Status is in the allowed retry set (using
eventprocessor.GetSystemJobData and systemJobData.Status) to avoid touching
unrelated/historical events.

In `@internal/manager/workflow_test.go`:
- Around line 316-323: The key fixture is not linked back to the keyConfig
fixture: after creating key and keyConfig via testutils.NewKey and
testutils.NewKeyConfig you should set the key's configuration foreign key to the
keyConfig's ID (e.g., key.KeyConfigurationID = keyConfig.ID or
key.ConfigurationID = keyConfig.ID depending on the model) and remove the
duplicated PrimaryKeyID assignment on keyConfig (leave a single kc.PrimaryKeyID
= ptr.PointTo(key.ID)). Ensure you then pass these updated fixtures to
testutils.CreateTestEntities so the key truly belongs to keyConfig.

---

Nitpick comments:
In `@internal/workflow/key_configuration_actions.go`:
- Around line 16-20: Change the workflow interface method
UpdateKeyConfigurationByID to return only error (UpdateKeyConfigurationByID(ctx
context.Context, keyConfigID uuid.UUID, patchKeyConfig
cmkapi.KeyConfigurationPatch) error), update its implementations and all callers
(including Lifecycle.updatePrimaryKey) to stop expecting a
*model.KeyConfiguration value, and remove any resulting dependency/import on
internal/model from the internal/workflow package; apply the same change to the
other workflow-facing method with the same unused *model.KeyConfiguration return
(the method around the 38-40 region) so the interface only returns error
everywhere.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 24fd8401-b206-4c34-ab23-9b161715a3cf

📥 Commits

Reviewing files that changed from the base of the PR and between aeeaad9 and 3633eac.

📒 Files selected for processing (34)
  • apis/cmk/cmk-ui.yaml
  • cmd/task-worker/main.go
  • cmd/tenant-manager-cli/cli_test.go
  • cmd/tenant-manager-cli/commands/commands.go
  • cmd/tenant-manager/main.go
  • internal/api/cmkapi/cmkapi.go
  • internal/api/transform/key/key.go
  • internal/api/transform/key/key_test.go
  • internal/apierrors/key.go
  • internal/controllers/cmk/key_controller.go
  • internal/controllers/cmk/key_controller_test.go
  • internal/controllers/cmk/keyconfiguration_controller.go
  • internal/controllers/cmk/keyconfiguration_controller_test.go
  • internal/manager/base.go
  • internal/manager/errors.go
  • internal/manager/key.go
  • internal/manager/key_test.go
  • internal/manager/keyconfiguration.go
  • internal/manager/keyconfiguration_test.go
  • internal/manager/keyversion_test.go
  • internal/manager/system_test.go
  • internal/manager/tenant_test.go
  • internal/manager/workflow_test.go
  • internal/model/key.go
  • internal/operator/operator_test.go
  • internal/repo/repository.go
  • internal/workflow/key_configuration_actions.go
  • internal/workflow/key_configuration_actions_test.go
  • migrations/tenant/schema/00005_delete_key_primary.sql
  • test/db-migration/migration_test.go
  • test/integration/async_test/common_test.go
  • test/integration/async_test/workflowassign_test.go
  • test/integration/tenant-manager/provisioning_test.go
  • test/security/xss/key_controller_test.go
💤 Files with no reviewable changes (6)
  • internal/apierrors/key.go
  • internal/api/transform/key/key.go
  • test/security/xss/key_controller_test.go
  • internal/controllers/cmk/key_controller.go
  • internal/manager/errors.go
  • internal/api/transform/key/key_test.go
✅ Files skipped from review due to trivial changes (4)
  • migrations/tenant/schema/00005_delete_key_primary.sql
  • cmd/tenant-manager-cli/cli_test.go
  • internal/manager/base.go
  • internal/manager/keyconfiguration_test.go
🚧 Files skipped from review as they are similar to previous changes (13)
  • cmd/task-worker/main.go
  • internal/manager/keyversion_test.go
  • test/integration/tenant-manager/provisioning_test.go
  • test/db-migration/migration_test.go
  • cmd/tenant-manager/main.go
  • cmd/tenant-manager-cli/commands/commands.go
  • internal/controllers/cmk/keyconfiguration_controller.go
  • internal/manager/tenant_test.go
  • test/integration/async_test/common_test.go
  • internal/repo/repository.go
  • internal/model/key.go
  • internal/api/cmkapi/cmkapi.go
  • apis/cmk/cmk-ui.yaml

type: string
maxLength: 4096
example: This Key is used for DB key store encryption.
isPrimary:
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.

The DB column may be redundant but I don't think we should remove it in the API response at all. In the UI keys view we still have to annotate which one is primary key

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.

We can keep it even though it would result in extra calls, but it's something we can maintain. This could also be a match in UI to show element key.ID == keyconfig.ID

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.

I still believe we should not remove such useful information and make the life of this API's consumer harder 😃

@jmpTeixeira02 jmpTeixeira02 self-assigned this Mar 20, 2026
@jmpTeixeira02 jmpTeixeira02 force-pushed the task/key-keyconfig-refactor branch 2 times, most recently from c858b22 to 0a63363 Compare March 24, 2026 13:45
@jmpTeixeira02 jmpTeixeira02 force-pushed the task/key-keyconfig-refactor branch from 0a63363 to 981b970 Compare March 24, 2026 13:47
Copy link
Copy Markdown

@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

♻️ Duplicate comments (3)
internal/controllers/cmk/keyconfiguration_controller_test.go (1)

593-600: ⚠️ Potential issue | 🟠 Major

Authenticate this case and use a valid UUID.

As written, this request can still return 403 because it is unauthenticated, and Line 597 uses a non-UUID primaryKeyID. With expectedBody left empty, the shared body assertion is vacuous as well. That means the test can pass without ever exercising the workflow-required branch.

🧪 Suggested test fix
 		{
 			name:        "Should 403 when update primary key and workflow is required",
 			keyConfigID: keyConfig.ID.String(),
 			inputJSON: `{
-                "primaryKeyID": "id"
+                "primaryKeyID": "12345678-90ab-cdef-1234-567890abcdef"
             }`,
-			expectedStatus: http.StatusForbidden,
+			expectedStatus:    http.StatusForbidden,
+			expectedBody:      "error",
+			additionalContext: authClient.GetClientMap(),
 		},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controllers/cmk/keyconfiguration_controller_test.go` around lines
593 - 600, The test case "Should 403 when update primary key and workflow is
required" is unauthenticated and uses a non-UUID primaryKeyID so it can pass
without exercising the workflow-required branch; update the test to authenticate
the request (use the same auth helper the other cases use or attach a valid auth
token/session used elsewhere in these tests) and replace the inputJSON
primaryKeyID value with a real UUID (e.g. the existing primaryKey.ID.String() or
uuid generation used in other tests) and populate expectedBody to assert the
specific 403 response payload so the test actually validates the
workflow-required denial path instead of passing vacuously.
internal/manager/key.go (1)

696-705: ⚠️ Potential issue | 🟠 Major

Only auto-promote keys that are actually ENABLED.

Line 698 only rejects DISABLED, so the first key can still become PrimaryKeyID while it is in another unusable state, e.g. a BYOK key in PENDING_IMPORT. That makes a fresh configuration advertise a primary key that cannot be used.

✅ Minimal fix
 	if !exist {
-		if key.State == string(cmkapi.KeyStateDISABLED) {
+		if key.State != string(cmkapi.KeyStateENABLED) {
 			return ErrKeyIsNotEnabled
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/manager/key.go` around lines 696 - 705, The code currently only
rejects DISABLED keys before auto-promoting (checking key.State ==
string(cmkapi.KeyStateDISABLED)), which allows other non-usable states to be
promoted; change the check to require the key be explicitly ENABLED by
validating key.State == string(cmkapi.KeyStateENABLED) (or equivalently return
ErrKeyIsNotEnabled when key.State != string(cmkapi.KeyStateENABLED)) before
constructing model.KeyConfiguration and calling km.repo.Patch so only genuinely
enabled keys become PrimaryKeyID.
internal/manager/workflow_test.go (1)

316-320: ⚠️ Potential issue | 🟡 Minor

Link the fixture key back to keyConfig.

This case still leaves the candidate key with a zero KeyConfigurationID, so it can pass by matching PrimaryKeyID alone without proving the selected key belongs to the configuration. The duplicate PrimaryKeyID assignment is redundant too.

🧪 Suggested test fix
 		key := testutils.NewKey(func(k *model.Key) {})
 
 		keyConfig := testutils.NewKeyConfig(func(kc *model.KeyConfiguration) {
-			kc.PrimaryKeyID = &key.ID
 			kc.PrimaryKeyID = ptr.PointTo(key.ID)
 		})
+		key.KeyConfigurationID = keyConfig.ID
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/manager/workflow_test.go` around lines 316 - 320, The test creates a
key and a keyConfig but doesn't link the key back to the configuration and
redundantly assigns PrimaryKeyID twice; update the fixture so the key's
KeyConfigurationID points to the created keyConfig.ID (e.g., set
key.KeyConfigurationID = ptr.PointTo(keyConfig.ID) or the appropriate field on
model.Key) and remove the duplicate PrimaryKeyID assignment on the keyConfig
(only set kc.PrimaryKeyID once) so the candidate key both references and belongs
to the KeyConfiguration in testutils.NewKey / testutils.NewKeyConfig setup.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/controllers/cmk/keyconfiguration_controller.go`:
- Around line 140-142: The code dereferences kc.PrimaryKeyID without checking
for nil which can panic; update the comparison in the handler that uses
kc.PrimaryKeyID and request.Body.PrimaryKeyID so you first check for nil (e.g.,
if kc.PrimaryKeyID == nil || request.Body.PrimaryKeyID == nil) and only
dereference when both are non-nil, and maintain the existing
ErrActionRequireWorkflow return only when both IDs exist and are unequal (use
the symbols kc.PrimaryKeyID and request.Body.PrimaryKeyID to locate the change).

In `@internal/manager/key.go`:
- Around line 259-264: The delete path currently checks primary status via
repo.IsPrimaryKey(ctx, km.repo, key) and, if allowed, deletes the key without
updating model.KeyConfiguration.PrimaryKeyID; you must update the owning key
configuration inside the same DB transaction that deletes the key: when
IsPrimaryKey returns true, within the transaction that performs the delete (the
code handling the key delete for `key` in the same function), either clear
PrimaryKeyID or set it to a different valid key on the associated
KeyConfiguration and persist that change (update the KeyConfiguration's
PrimaryKeyID) before committing; locate uses of `repo.IsPrimaryKey`, `km.repo`,
`key`, and `model.KeyConfiguration.PrimaryKeyID` to add the configuration update
in that transaction.

---

Duplicate comments:
In `@internal/controllers/cmk/keyconfiguration_controller_test.go`:
- Around line 593-600: The test case "Should 403 when update primary key and
workflow is required" is unauthenticated and uses a non-UUID primaryKeyID so it
can pass without exercising the workflow-required branch; update the test to
authenticate the request (use the same auth helper the other cases use or attach
a valid auth token/session used elsewhere in these tests) and replace the
inputJSON primaryKeyID value with a real UUID (e.g. the existing
primaryKey.ID.String() or uuid generation used in other tests) and populate
expectedBody to assert the specific 403 response payload so the test actually
validates the workflow-required denial path instead of passing vacuously.

In `@internal/manager/key.go`:
- Around line 696-705: The code currently only rejects DISABLED keys before
auto-promoting (checking key.State == string(cmkapi.KeyStateDISABLED)), which
allows other non-usable states to be promoted; change the check to require the
key be explicitly ENABLED by validating key.State ==
string(cmkapi.KeyStateENABLED) (or equivalently return ErrKeyIsNotEnabled when
key.State != string(cmkapi.KeyStateENABLED)) before constructing
model.KeyConfiguration and calling km.repo.Patch so only genuinely enabled keys
become PrimaryKeyID.

In `@internal/manager/workflow_test.go`:
- Around line 316-320: The test creates a key and a keyConfig but doesn't link
the key back to the configuration and redundantly assigns PrimaryKeyID twice;
update the fixture so the key's KeyConfigurationID points to the created
keyConfig.ID (e.g., set key.KeyConfigurationID = ptr.PointTo(keyConfig.ID) or
the appropriate field on model.Key) and remove the duplicate PrimaryKeyID
assignment on the keyConfig (only set kc.PrimaryKeyID once) so the candidate key
both references and belongs to the KeyConfiguration in testutils.NewKey /
testutils.NewKeyConfig setup.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 45b6ea6e-414d-49fa-bd03-735bbaf759b5

📥 Commits

Reviewing files that changed from the base of the PR and between 3633eac and 981b970.

📒 Files selected for processing (34)
  • apis/cmk/cmk-ui.yaml
  • cmd/task-worker/main.go
  • cmd/tenant-manager-cli/cli_test.go
  • cmd/tenant-manager-cli/commands/commands.go
  • cmd/tenant-manager/main.go
  • internal/api/cmkapi/cmkapi.go
  • internal/api/transform/key/key.go
  • internal/api/transform/key/key_test.go
  • internal/apierrors/key.go
  • internal/controllers/cmk/key_controller.go
  • internal/controllers/cmk/key_controller_test.go
  • internal/controllers/cmk/keyconfiguration_controller.go
  • internal/controllers/cmk/keyconfiguration_controller_test.go
  • internal/manager/base.go
  • internal/manager/errors.go
  • internal/manager/key.go
  • internal/manager/key_test.go
  • internal/manager/keyconfiguration.go
  • internal/manager/keyconfiguration_test.go
  • internal/manager/keyversion_test.go
  • internal/manager/system_test.go
  • internal/manager/tenant_test.go
  • internal/manager/workflow_test.go
  • internal/model/key.go
  • internal/operator/operator_test.go
  • internal/repo/repository.go
  • internal/workflow/key_configuration_actions.go
  • internal/workflow/key_configuration_actions_test.go
  • migrations/tenant/schema/00005_delete_key_primary.sql
  • test/db-migration/migration_test.go
  • test/integration/async_test/common_test.go
  • test/integration/async_test/workflowassign_test.go
  • test/integration/tenant-manager/provisioning_test.go
  • test/security/xss/key_controller_test.go
💤 Files with no reviewable changes (6)
  • internal/apierrors/key.go
  • internal/api/transform/key/key.go
  • internal/manager/errors.go
  • test/security/xss/key_controller_test.go
  • internal/controllers/cmk/key_controller.go
  • internal/api/transform/key/key_test.go
✅ Files skipped from review due to trivial changes (2)
  • internal/repo/repository.go
  • migrations/tenant/schema/00005_delete_key_primary.sql
🚧 Files skipped from review as they are similar to previous changes (12)
  • internal/manager/tenant_test.go
  • test/integration/async_test/workflowassign_test.go
  • test/integration/tenant-manager/provisioning_test.go
  • internal/manager/base.go
  • cmd/tenant-manager-cli/commands/commands.go
  • cmd/tenant-manager/main.go
  • cmd/task-worker/main.go
  • test/db-migration/migration_test.go
  • internal/workflow/key_configuration_actions_test.go
  • internal/model/key.go
  • apis/cmk/cmk-ui.yaml
  • internal/api/cmkapi/cmkapi.go

@jmpTeixeira02 jmpTeixeira02 force-pushed the task/key-keyconfig-refactor branch from 981b970 to 08b957c Compare March 25, 2026 12:56
Copy link
Copy Markdown

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

♻️ Duplicate comments (5)
internal/manager/workflow_test.go (1)

316-323: ⚠️ Potential issue | 🟡 Minor

Link the fixture key to keyConfig in this remaining case.

This test still leaves key.KeyConfigurationID at the zero UUID, so it can hit ErrAlreadyPrimaryKey without proving the selected key actually belongs to the configuration. Also, the first PrimaryKeyID assignment is dead code because the second one overwrites it.

Suggested fixture fix
 		key := testutils.NewKey(func(k *model.Key) {})
 
 		keyConfig := testutils.NewKeyConfig(func(kc *model.KeyConfiguration) {
-			kc.PrimaryKeyID = &key.ID
 			kc.PrimaryKeyID = ptr.PointTo(key.ID)
 		})
+		key.KeyConfigurationID = keyConfig.ID
 
 		testutils.CreateTestEntities(ctxSys, t, repo, key, keyConfig)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/manager/workflow_test.go` around lines 316 - 323, The fixture isn't
linking the key to the configuration and the first PrimaryKeyID assignment is
overwritten; update the fixtures so keyConfig.PrimaryKeyID is set once to key.ID
and also set the created key's KeyConfigurationID to the keyConfig.ID (so the
key truly belongs to that configuration) before calling
testutils.CreateTestEntities; reference the test fixtures created via
testutils.NewKey (key), testutils.NewKeyConfig (keyConfig), and the fields
Key.KeyConfigurationID and model.KeyConfiguration.PrimaryKeyID.
internal/controllers/cmk/keyconfiguration_controller_test.go (1)

593-600: ⚠️ Potential issue | 🟠 Major

Use a valid, authorized PATCH to exercise the workflow guard.

"primaryKeyID": "id" will not decode into KeyConfigurationPatch.PrimaryKeyID, and this case also omits the admin context used by the other PATCH tests. The 4xx can therefore come from request/auth handling instead of the new workflow check. Use a valid UUID that differs from the current primary key, pass authClient.GetClientMap(), and make sure the fixture explicitly enables workflow-required behavior before asserting 403.

Suggested test adjustment
 		{
 			name:        "Should 403 when update primary key and workflow is required",
 			keyConfigID: keyConfig.ID.String(),
 			inputJSON: `{
-                "primaryKeyID": "id"
+                "primaryKeyID": "12345678-90ab-cdef-1234-567890abcdef"
             }`,
 			expectedStatus: http.StatusForbidden,
+			expectedBody:   "error",
+			additionalContext: authClient.GetClientMap(),
 		},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controllers/cmk/keyconfiguration_controller_test.go` around lines
593 - 600, The test case "Should 403 when update primary key and workflow is
required" is using an invalid primaryKeyID ("id") and lacks the admin auth
context so the failure may come from decoding/auth rather than the workflow
guard; change inputJSON to set "primaryKeyID" to a valid UUID string that
differs from the existing primary key (so it decodes into
KeyConfigurationPatch.PrimaryKeyID), ensure the test uses the admin auth map
(pass authClient.GetClientMap()) like the other PATCH tests, and configure the
fixture to explicitly enable workflow-required behavior before asserting
http.StatusForbidden so the 403 is produced by the workflow check.
internal/manager/keyconfiguration.go (2)

469-502: ⚠️ Potential issue | 🟠 Major

Still fan out switch jobs on the first primary assignment.

This early return skips Lines 483-509 entirely. If a configuration already has systems but no current PrimaryKeyID, the patch updates the row and exits without creating any SystemSwitchNewPrimaryKey jobs for those systems.

💡 Suggested fix
-	// Key is valid. If keyconfig has no existing key no need for further validations
-	if keyConfig.PrimaryKeyID == nil {
-		return nil
-	}
-
-	err = m.updatePrimaryKeySystemEvents(
-		ctx,
-		ptr.GetSafeDeref(keyConfig.PrimaryKeyID).String(),
-		primaryKeyID.String(),
-	)
-	if err != nil {
-		return err
-	}
+	var oldPrimary string
+	if keyConfig.PrimaryKeyID != nil {
+		oldPrimary = keyConfig.PrimaryKeyID.String()
+		if err := m.updatePrimaryKeySystemEvents(ctx, oldPrimary, primaryKeyID.String()); err != nil {
+			return err
+		}
+	}
 
 	// Send system switches for systems in keyconfig
 	query := repo.NewQuery().Where(
@@
 				_, err := m.eventFactory.SystemSwitchNewPrimaryKey(
 					ctx,
 					s,
 					primaryKeyID.String(),
-					keyConfig.PrimaryKeyID.String(),
+					oldPrimary,
 				)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/manager/keyconfiguration.go` around lines 469 - 502, The early
return on keyConfig.PrimaryKeyID == nil prevents creating
SystemSwitchNewPrimaryKey jobs when assigning the first primary; remove that
return and ensure the fan-out still runs in that case. Specifically, in the
method referencing keyConfig.PrimaryKeyID and primaryKeyID, do not return when
keyConfig.PrimaryKeyID == nil; instead proceed to call
m.updatePrimaryKeySystemEvents(...) and then run the repo.ProcessInBatch(...)
block that uses query and m.eventFactory.SystemSwitchNewPrimaryKey to enqueue
switches for systems associated with the key config so first-time primary
assignments get fan-out jobs.

517-544: ⚠️ Potential issue | 🟠 Major

Scope event rewrites to retryable SYSTEM_SWITCH rows.

The current query matches any event whose JSON payload happens to contain keyIDTo, then decodes it as switch data. That can rewrite completed history or fail the whole update on an unrelated event type.

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

In `@internal/manager/keyconfiguration.go` around lines 517 - 544, The
updatePrimaryKeySystemEvents function currently matches any event whose JSON
contains keyIDTo; restrict this to only retryable SYSTEM_SWITCH system events by
adding an explicit filter on the event type and retryable flag (rather than
relying on JSON content). Modify the repo query built in
updatePrimaryKeySystemEvents to include conditions for the event type being
"SYSTEM_SWITCH" and retryable=true (e.g., add repo.FieldEquals(repo.TypeField,
"SYSTEM_SWITCH") and repo.FieldEquals(repo.RetryableField, true) or the
equivalent fields/keys used in your repo/model), and/or defensively check inside
the batch loop that e.Type == "SYSTEM_SWITCH" and e.Retryable == true before
calling eventprocessor.GetSystemJobData and rewriting e.Data. Ensure only those
identified events are patched back via m.r.Patch.
internal/manager/key_test.go (1)

933-935: ⚠️ Potential issue | 🟠 Major

Mark this system fixture as connected.

This case is supposed to cover deleting a primary key while connected systems exist, but sys never gets a connected status here. As written, the test can pass without exercising the guard named in the subtest.

💡 Minimal fix
 sys := testutils.NewSystem(func(s *model.System) {
 	s.KeyConfigurationID = ptr.PointTo(keyConfigWSystems.ID)
+	s.Status = cmkapi.SystemStatusCONNECTED
 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/manager/key_test.go` around lines 933 - 935, The system fixture
`sys` created via testutils.NewSystem currently only sets `s.KeyConfigurationID`
and never marks the system as connected; update the initializer closure passed
to testutils.NewSystem (the func(s *model.System) { ... } that sets
`s.KeyConfigurationID = ptr.PointTo(keyConfigWSystems.ID)`) to also mark the
system as connected (for example set `s.Connected = true` or the equivalent
connected status field on model.System) so the subtest actually exercises the
delete-primary-key guard when connected systems exist.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/controllers/cmk/keyconfiguration_controller.go`:
- Around line 129-145: The patch currently detects when the requested
PrimaryKeyID equals the existing one but still passes that non-nil value into
KeyConfig.UpdateKeyConfigurationByID, which triggers handleUpdatePrimaryKey and
unwanted switch/job work; before calling KeyConfig.UpdateKeyConfigurationByID,
compare kc.PrimaryKeyID with request.Body.PrimaryKeyID (using the existing
lookup via c.Manager.KeyConfig.GetKeyConfigurationByID and the
IsWorkflowRequired guard) and if they are equal set request.Body.PrimaryKeyID =
nil so the update becomes a true no-op for primary key changes; keep all
existing error checks and only null out the field when unchanged.

In `@internal/manager/keyconfiguration.go`:
- Around line 465-467: The current check only rejects cmkapi.KeyStateDISABLED so
keys in any other non-usable state could be promoted; update the condition to
accept only ENABLED keys by replacing the check on key.State (and the constant
cmkapi.KeyStateDISABLED) with an explicit allow: if key.State !=
string(cmkapi.KeyStateENABLED) return ErrKeyIsNotEnabled, referencing the
key.State field and the ErrKeyIsNotEnabled error to enforce the single allowed
primary state.
- Around line 242-247: Currently you always call handleUpdatePrimaryKey and
enqueue switch jobs even when patchKeyConfig.PrimaryKeyID equals the existing
keyConfig.PrimaryKeyID; change the branch that handles
patchKeyConfig.PrimaryKeyID so it first compares the current value and only
proceeds if they differ: if patchKeyConfig.PrimaryKeyID != nil and
(keyConfig.PrimaryKeyID == nil or *patchKeyConfig.PrimaryKeyID !=
*keyConfig.PrimaryKeyID) then call m.handleUpdatePrimaryKey and update
keyConfig.PrimaryKeyID, otherwise skip the update to avoid rewriting pending
payloads and enqueuing redundant switch jobs.

---

Duplicate comments:
In `@internal/controllers/cmk/keyconfiguration_controller_test.go`:
- Around line 593-600: The test case "Should 403 when update primary key and
workflow is required" is using an invalid primaryKeyID ("id") and lacks the
admin auth context so the failure may come from decoding/auth rather than the
workflow guard; change inputJSON to set "primaryKeyID" to a valid UUID string
that differs from the existing primary key (so it decodes into
KeyConfigurationPatch.PrimaryKeyID), ensure the test uses the admin auth map
(pass authClient.GetClientMap()) like the other PATCH tests, and configure the
fixture to explicitly enable workflow-required behavior before asserting
http.StatusForbidden so the 403 is produced by the workflow check.

In `@internal/manager/key_test.go`:
- Around line 933-935: The system fixture `sys` created via testutils.NewSystem
currently only sets `s.KeyConfigurationID` and never marks the system as
connected; update the initializer closure passed to testutils.NewSystem (the
func(s *model.System) { ... } that sets `s.KeyConfigurationID =
ptr.PointTo(keyConfigWSystems.ID)`) to also mark the system as connected (for
example set `s.Connected = true` or the equivalent connected status field on
model.System) so the subtest actually exercises the delete-primary-key guard
when connected systems exist.

In `@internal/manager/keyconfiguration.go`:
- Around line 469-502: The early return on keyConfig.PrimaryKeyID == nil
prevents creating SystemSwitchNewPrimaryKey jobs when assigning the first
primary; remove that return and ensure the fan-out still runs in that case.
Specifically, in the method referencing keyConfig.PrimaryKeyID and primaryKeyID,
do not return when keyConfig.PrimaryKeyID == nil; instead proceed to call
m.updatePrimaryKeySystemEvents(...) and then run the repo.ProcessInBatch(...)
block that uses query and m.eventFactory.SystemSwitchNewPrimaryKey to enqueue
switches for systems associated with the key config so first-time primary
assignments get fan-out jobs.
- Around line 517-544: The updatePrimaryKeySystemEvents function currently
matches any event whose JSON contains keyIDTo; restrict this to only retryable
SYSTEM_SWITCH system events by adding an explicit filter on the event type and
retryable flag (rather than relying on JSON content). Modify the repo query
built in updatePrimaryKeySystemEvents to include conditions for the event type
being "SYSTEM_SWITCH" and retryable=true (e.g., add
repo.FieldEquals(repo.TypeField, "SYSTEM_SWITCH") and
repo.FieldEquals(repo.RetryableField, true) or the equivalent fields/keys used
in your repo/model), and/or defensively check inside the batch loop that e.Type
== "SYSTEM_SWITCH" and e.Retryable == true before calling
eventprocessor.GetSystemJobData and rewriting e.Data. Ensure only those
identified events are patched back via m.r.Patch.

In `@internal/manager/workflow_test.go`:
- Around line 316-323: The fixture isn't linking the key to the configuration
and the first PrimaryKeyID assignment is overwritten; update the fixtures so
keyConfig.PrimaryKeyID is set once to key.ID and also set the created key's
KeyConfigurationID to the keyConfig.ID (so the key truly belongs to that
configuration) before calling testutils.CreateTestEntities; reference the test
fixtures created via testutils.NewKey (key), testutils.NewKeyConfig (keyConfig),
and the fields Key.KeyConfigurationID and model.KeyConfiguration.PrimaryKeyID.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fb52aed6-dd02-4faf-84a9-ff9a872c88b8

📥 Commits

Reviewing files that changed from the base of the PR and between 981b970 and 08b957c.

📒 Files selected for processing (34)
  • apis/cmk/cmk-ui.yaml
  • cmd/task-worker/main.go
  • cmd/tenant-manager-cli/cli_test.go
  • cmd/tenant-manager-cli/commands/commands.go
  • cmd/tenant-manager/main.go
  • internal/api/cmkapi/cmkapi.go
  • internal/api/transform/key/key.go
  • internal/api/transform/key/key_test.go
  • internal/apierrors/key.go
  • internal/controllers/cmk/key_controller.go
  • internal/controllers/cmk/key_controller_test.go
  • internal/controllers/cmk/keyconfiguration_controller.go
  • internal/controllers/cmk/keyconfiguration_controller_test.go
  • internal/manager/base.go
  • internal/manager/errors.go
  • internal/manager/key.go
  • internal/manager/key_test.go
  • internal/manager/keyconfiguration.go
  • internal/manager/keyconfiguration_test.go
  • internal/manager/keyversion_test.go
  • internal/manager/system_test.go
  • internal/manager/tenant_test.go
  • internal/manager/workflow_test.go
  • internal/model/key.go
  • internal/operator/operator_test.go
  • internal/repo/repository.go
  • internal/workflow/key_configuration_actions.go
  • internal/workflow/key_configuration_actions_test.go
  • migrations/tenant/schema/00005_delete_key_primary.sql
  • test/db-migration/migration_test.go
  • test/integration/async_test/common_test.go
  • test/integration/async_test/workflowassign_test.go
  • test/integration/tenant-manager/provisioning_test.go
  • test/security/xss/key_controller_test.go
💤 Files with no reviewable changes (6)
  • internal/apierrors/key.go
  • internal/api/transform/key/key.go
  • internal/controllers/cmk/key_controller.go
  • test/security/xss/key_controller_test.go
  • internal/manager/errors.go
  • internal/api/transform/key/key_test.go
✅ Files skipped from review due to trivial changes (5)
  • migrations/tenant/schema/00005_delete_key_primary.sql
  • cmd/tenant-manager-cli/cli_test.go
  • internal/operator/operator_test.go
  • cmd/tenant-manager/main.go
  • cmd/task-worker/main.go
🚧 Files skipped from review as they are similar to previous changes (11)
  • test/integration/async_test/workflowassign_test.go
  • test/integration/async_test/common_test.go
  • test/integration/tenant-manager/provisioning_test.go
  • internal/manager/keyversion_test.go
  • test/db-migration/migration_test.go
  • internal/manager/base.go
  • internal/repo/repository.go
  • internal/model/key.go
  • apis/cmk/cmk-ui.yaml
  • internal/api/cmkapi/cmkapi.go
  • internal/manager/key.go

@jmpTeixeira02 jmpTeixeira02 force-pushed the task/key-keyconfig-refactor branch 4 times, most recently from 4a2c53f to 63e6652 Compare March 27, 2026 10:54
@jmpTeixeira02 jmpTeixeira02 force-pushed the task/key-keyconfig-refactor branch 3 times, most recently from d6ed062 to f9bafb0 Compare March 27, 2026 14:09
@jmpTeixeira02 jmpTeixeira02 force-pushed the task/key-keyconfig-refactor branch from f9bafb0 to 45ec4b7 Compare March 27, 2026 14:13
@jmpTeixeira02 jmpTeixeira02 force-pushed the task/key-keyconfig-refactor branch 2 times, most recently from 22e8069 to bbc8361 Compare March 27, 2026 18:04
@jmpTeixeira02 jmpTeixeira02 force-pushed the task/key-keyconfig-refactor branch from bbc8361 to ed9efe0 Compare March 27, 2026 18:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants