Skip to content

fix(db/migrate): admin-upsert SQL parity with post-057 schema + decouple bootstrap from migration step (closes #945) - #947

Merged
cristim merged 4 commits into
feat/multicloud-web-frontendfrom
fix/deployed-schema-drift-058
Jun 4, 2026
Merged

fix(db/migrate): admin-upsert SQL parity with post-057 schema + decouple bootstrap from migration step (closes #945)#947
cristim merged 4 commits into
feat/multicloud-web-frontendfrom
fix/deployed-schema-drift-058

Conversation

@cristim

@cristim cristim commented Jun 4, 2026

Copy link
Copy Markdown
Member

Hypothesis Settled: Hypothesis (a) is PARTIALLY correct, but the primary gap is a missing image rebuild

Settled on: The deployed Lambda image was built from a commit that predates migration 000058. m.Up() on the running image has no 000058 SQL file to apply, so the three audit columns were never added to purchase_executions. The admin-upsert failure (bug 2) is a separate regression: migration 000057 dropped users.role in PR #912 but migrate.go was not updated at the time, causing every post-057 cold start to fail the admin-bootstrap step with column "role" does not exist. Both failures are independently present in the deployed environment.

Two Bugs Fixed

Bug 1 -- Admin-upsert SQL references dropped users.role column (every cold start)

Migration 000057 dropped users.role and sessions.role. The bootstrap functions in migrate.go were not updated:

  • ensureAdminUser: INSERT INTO users included role in column list and VALUES
  • ensureAdminUserWithPassword: same INSERT pattern
  • assignAdminGroupAndWarn: two queries used WHERE role = 'admin'

CloudWatch evidence (every cold start):

[ERROR] Prepare ... ERROR: column "role" of relation "users" does not exist (SQLSTATE 42703)
⚠️ Migration failed — app continuing with existing schema: failed to create admin user: failed to upsert admin user: ERROR: column "role" of relation "users" does not exist (SQLSTATE 42703)

Fix: remove role from both INSERT statements; replace WHERE role = 'admin' with WHERE (group_ids IS NULL OR cardinality(group_ids) = 0) in assignAdminGroupAndWarn. Post-057, the users_min_one_group CHECK makes the backfill a permanent no-op (empty group_ids is structurally impossible), so this is defence-in-depth only.

Bug 2 -- Migration 000058 not applied (deployed image predates the commit)

Migration 000058_purchase_executions_direct_execute_audit.up.sql adds three audit columns to purchase_executions. The deployed image was built before this migration was merged, so its /app/migrations directory does not include the 000058 SQL files. m.Up() cannot apply a migration whose file does not exist. The Go scanner in scanExecutionRows (store_postgres.go) expects 25 columns; the table has 22.

CloudWatch evidence:

[ERROR] API error: failed to get planned executions: failed to scan execution: number of field descriptions must equal number of destinations, got 22 and 25
[ERROR] API error: failed to get pending executions: failed to query executions: ERROR: column "executed_by_user_id" does not exist (SQLSTATE 42703)
[ERROR] API error: failed to save execution (row 1/3): failed to save purchase execution: ERROR: column "executed_by_user_id" of relation "purchase_executions" does not exist (SQLSTATE 42703)

This is an ops action, not a code change: once this PR merges and the Lambda image is rebuilt from the updated branch, m.Up() will apply 000058 on the next cold start, adding the three missing columns.

Additional fix -- Decouple admin-bootstrap failure from RunMigrations return value

Previously, if ensureAdminUser failed, RunMigrations returned an error, making the health endpoint report migration failure even when the schema (m.Up()) was fully current. Now: if m.Up() succeeds but admin-bootstrap fails, RunMigrations logs WARNING: admin bootstrap failed (schema migration completed successfully) and returns nil. This makes cold-start diagnostics unambiguous and prevents a stale migrate.go from causing the app to misreport its own health.

Migration 000058 Application Path

Image rebuild required. After this PR merges:

  1. Rebuild the Lambda Docker image from this branch (which includes 000058's SQL files in /app/migrations)
  2. Redeploy
  3. On the next cold start, m.Up() will detect version 57 in schema_migrations and apply 000058 (and 000059/000063 if also missing)
  4. Verify in CloudWatch: look for Database migrations completed successfully (version: 63) or similar and zero column "executed_by_user_id" does not exist errors

If schema_migrations shows a dirty state (unlikely given the soft-continue boot path), set env var CUDLY_FORCE_MIGRATION_VERSION=57 on the Lambda, redeploy once to clear the dirty flag, then remove the env var on the next deploy.

Test Plan

  • go build ./... passes
  • go test ./internal/database/postgres/migrations/ passes (4 unit tests)
  • go test ./internal/server/... passes (334 tests)
  • Integration tests: go test -tags integration ./internal/database/postgres/migrations/... (requires Docker for the testcontainers)
  • Post-deploy: CloudWatch shows zero column "role" of relation "users" errors on cold start
  • Post-image-rebuild + redeploy: CloudWatch shows zero column "executed_by_user_id" does not exist errors

Ops Follow-up Required

Yes. The deployed image must be rebuilt from the current branch and redeployed to apply migration 000058. The code fix in this PR alone does not resolve the purchase_executions column errors - it only fixes the cold-start admin-upsert failure. A new deploy is required to ship 000058's SQL file inside the Lambda image.

Follow-up issue: track the image rebuild + redeploy as a separate ops action against issue #945.

Summary by CodeRabbit

  • Bug Fixes

    • Improved authorization logic for purchase operations to be more robust and resilient.
    • Enhanced admin bootstrap process with better error handling—failures during initialization no longer block migrations.
  • Tests

    • Expanded test coverage for authorization paths and admin setup idempotency scenarios.

cristim added 3 commits June 4, 2026 00:47
…ect gate (closes #940)

PR #912 (#907) removed the Role field from api.Session, leaving
authorizeSessionExecuteDirect with a dead `if session.Role == "admin"`
reference that prevented the base branch from compiling.

Remove the three-line shortcut and update the gate-logic doc comment to
match the sibling authorizeSessionApprove/authorizeSessionCancel pattern:
admins pass via the execute-any HasPermissionAPI check because the
Administrators group carries the {admin,*} wildcard permission.

Also fix all test Session struct literals that still set Role (now an
unknown field), update the admin-role-shortcut test to properly stub the
HasPermissionAPI path, and add three regression tests:
- AdminGroupViaExecuteAny: confirms admin users still pass via execute-any
- NoGrant: confirms non-admin without execute-any/own gets 403
- NilAuth: confirms nil auth component returns 500 (fail-closed)
…000059 (base collision from #803+#924)

The base feat/multicloud-web-frontend merged two 000058 migrations from the
recent wave, which the check-migration-conflicts pre-commit hook rejects:
- 000058_purchase_executions_direct_execute_audit (#803, schema change)
- 000058_seed_purchaser_group (#924, data seed)

Keep the schema-change audit migration at 000058 and renumber the seed
migration to the next free slot, 000059 (base jumps 000058 -> 000063, so
000059-000062 are open). golang-migrate discovers migrations by filename
glob, so no embed list needs updating; only two self-references were
adjusted:
- auth/types.go GroupPurchaser doc comment pointing at the filename
- the self-referencing error message inside the up.sql

Safe to renumber without applied-state reconciliation: the base has not
compiled since the #803/#907 break, so neither 000058 migration has been
applied to any database yet.
…ple bootstrap from migration step (closes #945)

Migration 000057 dropped the users.role column and sessions.role column
(PR #912). The Go bootstrap functions in migrate.go were not updated at
the time, leaving three SQL statements that reference the dropped column:

- ensureAdminUser: INSERT INTO users included `role` in column list / VALUES
- ensureAdminUserWithPassword: same INSERT pattern
- assignAdminGroupAndWarn: two queries used WHERE role = 'admin'

This caused every Lambda cold start to fail the admin-upsert step with
"column role of relation users does not exist (SQLSTATE 42703)".

The admin-upsert failure also masked a second deployment gap: migration
000058 (purchase_executions direct-execute audit columns) was missing
from the deployed image, causing all purchase-execution reads/writes to
fail with column-not-found errors. The image rebuild (ops action) will
apply 000058 on the next cold start.

Changes:
- Remove `role` from INSERT column list and VALUES in both
  ensureAdminUser and ensureAdminUserWithPassword
- Replace `WHERE role = 'admin'` in assignAdminGroupAndWarn with
  `WHERE (group_ids IS NULL OR cardinality(group_ids) = 0)`;
  post-057, the users_min_one_group CHECK makes this a permanent no-op
  so the backfill path is defence-in-depth for rollback scenarios only
- Decouple admin-bootstrap failure from RunMigrations return value:
  if m.Up() succeeds but ensureAdminUser fails, RunMigrations now logs
  a WARNING and returns nil so the health endpoint correctly reports
  migration success and the app boots cleanly
- Update ensure_admin_user_test.go: the pre-057 drift scenario (empty
  group_ids on an existing admin row) is structurally impossible
  post-057 due to the CHECK constraint; replace with an idempotency
  test that verifies re-running RunMigrations on an already-correct
  admin row is a no-op
- Update migrate_security_integration_test.go: the same drift simulation
  (INSERT with role + empty group_ids after migrations) is impossible
  post-057; test now exercises the "Admin user created" log path instead
@cristim cristim added triaged Item has been triaged priority/p1 Next up; this sprint severity/high Significant harm urgency/now Drop other things impact/all-users Affects every user effort/s Hours type/bug Defect labels Jun 4, 2026
@cristim

cristim commented Jun 4, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

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

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

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

Review profile: CHILL

Plan: Pro

Run ID: 78bff442-aa64-4510-8237-d98b581edc47

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
📝 Walkthrough

Walkthrough

This PR transitions authorization in the purchases handler from role-based checks to a stateless admin API key sentinel combined with group-membership RBAC matrix consultation. Corresponding migration changes remove role column references and make admin bootstrap failures non-fatal. Tests are updated to remove Role dependency and verify idempotency.

Changes

Authorization & Admin Bootstrap Refactoring

Layer / File(s) Summary
Purchases Handler Authorization Gate
internal/api/handler_purchases.go
Replaces role-based admin gate (session.Role == "admin") with sentinel user ID check (session.UserID == apiKeyAdminUserID); documents three authorization branches and clarifies execute-own creatorID non-empty requirement.
Purchase Authorization Test Updates
internal/api/handler_purchases_test.go
Removes Session.Role from test fixtures; adds explicit HasPermissionAPI and GetAllowedAccountsAPI mock assertions; adds new tests for admin-group permission via execute-any, rejection when no execute permissions granted (403), and nil-auth fail-closed (500).
Admin Bootstrap: Remove Role Column, Handle Errors Non-Fatally
internal/database/postgres/migrations/migrate.go
Updates RunMigrations to log warnings instead of failing when admin bootstrap fails after schema migrations; removes role column references from no-password and password-hashed insert/upsert; updates assignAdminGroupAndWarn to filter on group_ids emptiness without role conditions.
Admin Bootstrap Idempotency and Logging Tests
internal/database/postgres/migrations/ensure_admin_user_test.go, internal/database/postgres/migrations/migrate_security_integration_test.go
Replaces post-migration drift repair test with idempotency test asserting re-run remains a no-op when admin already has correct group_ids; replaces backfill-logging test with bootstrap-logging test validating "Admin user created" appears in stderr-bound logger (not stdout).
Migration Comment and Message Updates
internal/auth/types.go, internal/database/postgres/migrations/000059_seed_purchaser_group.up.sql
Updates GroupPurchaser type comment and purchaser-group seeding migration error message to reference migration 000059 instead of 000058.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • LeanerCloud/CUDly#945: Changes to migrate.go (removing users.role references and making ensureAdminUser failures non-fatal) directly address admin-upsert/bootstrap failures described in this issue.

Possibly related PRs

  • LeanerCloud/CUDly#912: Purchases authorization refactoring to swap Session.Role-based gating for apiKeyAdminUserID sentinel and HasPermissionAPI-based checks aligns directly with this PR's authorization changes.
  • LeanerCloud/CUDly#941: Updates to authorizeSessionExecuteDirect and handler_purchases_test.go removing Session.Role shortcut and adding HasPermissionAPI-based tests match this PR's changes.
  • LeanerCloud/CUDly#579: Admin bootstrap/group backfill changes and migrate_security_integration_test.go updates for stderr vs stdout logging expectations overlap with this PR's migration-related changes.

Suggested labels

urgency/this-sprint, impact/many, effort/m

Poem

🐰 A sentinel stands where roles once lived,
Groups now guard what permissions give,
Migration cleaned the role away,
Bootstrap heals itself each day,
Authorization's path shines bright and clear!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: removing role references from admin-upsert SQL to align with post-057 schema and decoupling bootstrap failures from migration steps.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/deployed-schema-drift-058

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

@cristim

cristim commented Jun 4, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

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

@cristim

cristim commented Jun 4, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
internal/database/postgres/migrations/ensure_admin_user_test.go (1)

145-148: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Test SQL still references dropped users.role column.

The update predicate uses role = 'admin', but users.role was removed by migration 000057. This can fail the integration test on current schema.

✅ Proposed fix
 		_, err = pool.Exec(ctx, `
 			UPDATE users SET group_ids = ARRAY[$1]::UUID[]
-			WHERE email = $2 AND role = 'admin'
+			WHERE email = $2
 		`, customGroupID, adminEmail)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/database/postgres/migrations/ensure_admin_user_test.go` around lines
145 - 148, The SQL update in the Exec call still filters by the removed column
`role = 'admin'`; remove that predicate and update the WHERE clause to use only
the email (i.e., change the SQL in the pool.Exec call that sets group_ids to
"WHERE email = $2" using the existing variables customGroupID and adminEmail).
This keeps the test aligned with the post-migration schema; if you need to
assert admin membership instead, replace the predicate with the new
authorization model (e.g., join or check appropriate user_groups/group_ids
field) rather than referencing `role`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/auth/types.go`:
- Line 320: Update the migration comment string from "000058" to "000059"
wherever the frontend references the backend migration: specifically change the
comment above the exported PERMISSIONS constant in permissions.ts and the
comment above the user actions (exported actions in userActions.ts, e.g.,
createUser/fetchUsers action helpers) to "000059" so the frontend comments match
the backend migration renumbering.

In `@internal/database/postgres/migrations/migrate.go`:
- Around line 224-226: The UPDATE/COUNT SQL currently affects all users with
empty group_ids; restrict it to only the bootstrap admin user by adding a user
id predicate and passing the bootstrap admin's UUID as a second parameter.
Update the SQL fragments that use groupID (the two places around the shown query
and the similar block at lines ~245-250) to include "AND id = $2::UUID" (or the
equivalent user id column) and pass the bootstrapAdminID (or adminUserID) as the
new second argument so only the bootstrap admin gets the Administrators group
assigned / counted.

---

Outside diff comments:
In `@internal/database/postgres/migrations/ensure_admin_user_test.go`:
- Around line 145-148: The SQL update in the Exec call still filters by the
removed column `role = 'admin'`; remove that predicate and update the WHERE
clause to use only the email (i.e., change the SQL in the pool.Exec call that
sets group_ids to "WHERE email = $2" using the existing variables customGroupID
and adminEmail). This keeps the test aligned with the post-migration schema; if
you need to assert admin membership instead, replace the predicate with the new
authorization model (e.g., join or check appropriate user_groups/group_ids
field) rather than referencing `role`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c263ab93-76f0-438a-afd7-49234bf83807

📥 Commits

Reviewing files that changed from the base of the PR and between f035285 and 6ed8041.

📒 Files selected for processing (8)
  • internal/api/handler_purchases.go
  • internal/api/handler_purchases_test.go
  • internal/auth/types.go
  • internal/database/postgres/migrations/000059_seed_purchaser_group.down.sql
  • internal/database/postgres/migrations/000059_seed_purchaser_group.up.sql
  • internal/database/postgres/migrations/ensure_admin_user_test.go
  • internal/database/postgres/migrations/migrate.go
  • internal/database/postgres/migrations/migrate_security_integration_test.go

Comment thread internal/auth/types.go
// GroupPurchaser is the canonical name of the system-managed Purchaser
// group. MUST match the literal name inserted by migration
// 000058_seed_purchaser_group.up.sql so name-based lookups agree with
// 000059_seed_purchaser_group.up.sql so name-based lookups agree with

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find frontend files still referencing the old migration number 000058 for the Purchaser group.

# Search for references to migration 000058 in frontend files
rg -n --type=ts --type=tsx -C2 '000058.*[Pp]urchas|[Pp]urchas.*000058' frontend/ || echo "No matches found"

Repository: LeanerCloud/CUDly

Length of output: 124


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show where frontend directory exists (if at all)
ls -la | head -n 50
ls -la frontend 2>/dev/null || true

# Locate the targeted files
echo "== Locate permissions.ts and userActions.ts =="
fd -e ts -e tsx -e js -e jsx 'permissions.ts|userActions.ts' . || true
fd -e ts -e tsx -e js -e jsx 'permissions.ts$|userActions.ts$' . || true

# Search for raw migration number occurrences in frontend (no invalid --type flags)
echo "== Search for 000058 in frontend/ (all file types) =="
rg -n '000058' frontend/ 2>/dev/null || echo "No matches found in frontend/"

# Search for purchaser-related + 000058 patterns in repo (broader, avoids relying on filetype flags)
echo "== Search for purchaser + 000058 across repo =="
rg -n -i '000058.*purchas|purchas.*000058' . || echo "No matches found for purchaser + 000058"

# Check the two specific files if found
echo "== If permissions.ts exists, search inside it =="
file_permissions="$(fd 'permissions\.ts$' . || true | head -n 1)"
if [ -n "${file_permissions}" ]; then
  rg -n '000058|000059' "${file_permissions}" || true
else
  echo "permissions.ts not found"
fi

echo "== If userActions.ts exists, search inside it =="
file_useractions="$(fd 'userActions\.ts$' . || true | head -n 1)"
if [ -n "${file_useractions}" ]; then
  rg -n '000058|000059' "${file_useractions}" || true
else
  echo "userActions.ts not found"
fi

Repository: LeanerCloud/CUDly

Length of output: 4249


Update frontend migration comment references (000058 → 000059)

Frontend still references migration 000058 in:

  • frontend/src/permissions.ts:81
  • frontend/src/users/userActions.ts:101

Update these comments to 000059 to match the backend migration renumbering.

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

In `@internal/auth/types.go` at line 320, Update the migration comment string from
"000058" to "000059" wherever the frontend references the backend migration:
specifically change the comment above the exported PERMISSIONS constant in
permissions.ts and the comment above the user actions (exported actions in
userActions.ts, e.g., createUser/fetchUsers action helpers) to "000059" so the
frontend comments match the backend migration renumbering.

Comment on lines 224 to 226
WHERE (group_ids IS NULL OR cardinality(group_ids) = 0)
AND EXISTS (SELECT 1 FROM groups WHERE id = $1::UUID)
`, groupID)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Scope the admin-group backfill to the bootstrap admin user only.

This UPDATE/COUNT currently targets all users with empty group_ids. In pre-057/rollback states, that can assign the Administrators group to non-admin users.

🔧 Proposed fix
@@
-	if err := assignAdminGroupAndWarn(ctx, pool, defaultAdminGroupID); err != nil {
+	if err := assignAdminGroupAndWarn(ctx, pool, defaultAdminGroupID, email); err != nil {
 		return fmt.Errorf("failed to assign admin group: %w", err)
 	}
@@
-	if err := assignAdminGroupAndWarn(ctx, pool, defaultAdminGroupID); err != nil {
+	if err := assignAdminGroupAndWarn(ctx, pool, defaultAdminGroupID, email); err != nil {
 		return fmt.Errorf("failed to assign admin group: %w", err)
 	}
@@
-func assignAdminGroupAndWarn(ctx context.Context, pool *pgxpool.Pool, groupID string) error {
+func assignAdminGroupAndWarn(ctx context.Context, pool *pgxpool.Pool, groupID string, adminEmail string) error {
 	res, err := pool.Exec(ctx, `
 		UPDATE users
@@
-		WHERE (group_ids IS NULL OR cardinality(group_ids) = 0)
+		WHERE email = $2
+		  AND (group_ids IS NULL OR cardinality(group_ids) = 0)
 		  AND EXISTS (SELECT 1 FROM groups WHERE id = $1::UUID)
-	`, groupID)
+	`, groupID, adminEmail)
@@
 	if err := pool.QueryRow(ctx, `
 		SELECT COUNT(*) FROM users
-		WHERE (group_ids IS NULL OR cardinality(group_ids) = 0)
-	`).Scan(&remaining); err != nil {
+		WHERE email = $1
+		  AND (group_ids IS NULL OR cardinality(group_ids) = 0)
+	`, adminEmail).Scan(&remaining); err != nil {
 		return fmt.Errorf("failed to check admin group_ids invariant: %w", err)
 	}

Also applies to: 245-250

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

In `@internal/database/postgres/migrations/migrate.go` around lines 224 - 226, The
UPDATE/COUNT SQL currently affects all users with empty group_ids; restrict it
to only the bootstrap admin user by adding a user id predicate and passing the
bootstrap admin's UUID as a second parameter. Update the SQL fragments that use
groupID (the two places around the shown query and the similar block at lines
~245-250) to include "AND id = $2::UUID" (or the equivalent user id column) and
pass the bootstrapAdminID (or adminUserID) as the new second argument so only
the bootstrap admin gets the Administrators group assigned / counted.

- Scope assignAdminGroupAndWarn to the bootstrap admin email only;
  the previous UPDATE/COUNT targeted all users with empty group_ids,
  which would assign the Administrators group to non-admin users in
  pre-057 or rollback states (CR finding, major severity)
- Remove dropped `role = 'admin'` predicate from test SQL in the
  operator-customisation sub-case; the column was dropped by
  migration 000057 so the WHERE clause would always return zero rows
  on the current schema (CR outside-diff finding)
- Deferred: frontend 000058->000059 comment update is out of PR scope;
  filed as issue #955
@cristim
cristim merged commit cf521ca into feat/multicloud-web-frontend Jun 4, 2026
4 checks passed
@cristim
cristim deleted the fix/deployed-schema-drift-058 branch July 27, 2026 11:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/s Hours impact/all-users Affects every user priority/p1 Next up; this sprint severity/high Significant harm triaged Item has been triaged type/bug Defect urgency/now Drop other things

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant