Skip to main content

Account-name backfill and legacy PII coverage

Manual account-name preservation and cleanup: service authorization, exact-count preview, conflict checks, encrypted membership labels and legacy coverage.

Last reviewed October 26, 2026Fresh

Overview

New signup and browser-bootstrap paths no longer copy account names into profiles.display_name. The dashboard renders the signed-in account's authentication metadata, and SCIM stores new organization labels as purpose-bound ciphertext per membership. Older profile names can still exist until an operator completes the guarded backfill.

shield-pii-migrate remains console-only. Existing platform-and-tenant admin actions inspect legacy coverage and preserve an encrypted counterpart. Clearing profile names requires the separate service-key-only operator action; it cannot be authorized by a dashboard session. Newsletter addresses already exist as ciphertext plus keyed digest: the old plaintext email column was dropped, and there is no subscriber column to shred.

See Data handling for the distinction between plaintext account data, encrypted operational values and keyed signal identifiers.

Call path and behaviour

The manual backfill-profile-names handler matches the Bearer credential against the actual configured service key, then uses one transaction per profile. It locks the profile and auth account, checks updated_at, preserves unrelated auth metadata, rejects a conflicting nonempty auth full_name, preserves encrypted labels for current memberships without replacing newer IdP labels, and clears the profile name only after preservation succeeds. It reads back the affected count and the cleared value.

Legacy envelopes bind the source name to its account because the source was global. New IdP labels also bind the organization. The older encrypted profile counterpart remains retained. No migration, cron or dashboard click invokes this operator cleanup automatically.

Apply the additive schema before dependent handlers and verify the ledger/readback: the automatic apply and deploy jobs begin independently on main. The source runbook is docs/runbooks/profile-name-backfill.md.

Actions and responses

shield-pii-migrate has two authorization lanes. The existing status and encrypt actions require a signed-in platform admin who is also an admin of the named tenant. They inspect or preserve the legacy encrypted profile counterpart. They do not preserve the account's authentication name or its organization-specific provisioning labels, and encryption coverage is not proof that account-name cleanup is complete.

Account-name cleanup uses `backfill-profile-names`. This is a manual, global operator action, never a dashboard button or a scheduled job. It accepts only the actual configured service key as its Bearer credential; an administrator session or a JWT claiming a service role is insufficient. The selected project's direct function endpoint is used, not api.passkeybridge.io. For staging, select the dashboard labeled PasskeyBridge-Staging; production must not be touched.

Preview is the default:

{"action":"backfill-profile-names","dry_run":true,"batch_size":50}

It returns dry_run, the exact global remaining count, selected and batch_size. Profiles are account-scoped rather than tenant-scoped. No name or authentication metadata is returned.

After reviewing that count, a write request explicitly supplies it:

{"action":"backfill-profile-names","dry_run":false,"expected_count":12,"batch_size":50,"confirm":"BACKFILL_AUTH_AND_CLEAR_PROFILE_NAMES"}

Replace 12 with the count just observed. Batches are 1 to 100 rows. For each row, one transaction locks the profile and auth account, checks the source timestamp, preserves unrelated authentication metadata, sets full_name only when it does not conflict, preserves encrypted labels for existing memberships without replacing newer IdP labels, then clears and reads back the profile name. Existing encrypted profile counterparts are retained.

A conflicting auth name, stale source or missing account stops the batch; earlier successful rows remain committed. The response carries counts, blocked_profile_id and the last successful next_cursor. Review the conflict securely rather than automatically overwriting it. Preview again before each write and use the current count. An optional after_id UUID advances past a reviewed row; skipped rows still count in remaining, and restarting without a cursor revisits them. Completion means an exact post-batch remaining of zero, not the length of a returned page.

shred on profiles now returns 409, so the older action cannot bypass these preservation steps. Settings, PII Migration Tooling shows Account backfill required instead of a profile shred control. A legacy card's encrypted count does not certify the auth backfill. No hosted cleanup is implied by this documentation.

The repository operator runbook is docs/runbooks/profile-name-backfill.md. Release the additive schema before the dependent handlers and confirm its ledger/readback; the apply and deploy jobs start independently. The SQL rollback restores only the previous signup hook and retains accepted ciphertext and auth metadata. Keep cipher-aware SCIM readers and auth-record rendering after names have migrated; reverting to older readers first would lose label readback.

Dashboard panel

Dashboard, Settings, Data governance and compliance, PII Migration Tooling. The panel still loads the legacy status action. Counts describe profiles.display_name and its retained encrypted counterpart globally, even though the request names a tenant for authorization. A failed count renders Unknown rather than complete.

Encrypt Unprotected Rows preserves up to 500 legacy names as ciphertext per call and never clears the original. Account backfill required explains why the old profile shred control is unavailable. Having an encrypted counterpart alone does not prove that the auth account name and SCIM labels have been preserved.

Migration History lists older encrypt and shred run records for the selected tenant. The new service-key backfill returns its own counts and cursor to the operator; it does not append those batches to this history. Keep the response in the operator's controlled change record. Refresh and run a fresh backfill preview to verify remaining plaintext; do not infer completion from a legacy coverage percentage.

Data model: shield_pii_migration_runs

This is the legacy shield_pii_migration_runs record for encrypt and historical shred actions. The service-key account-name backfill does not append here; retain its response in the controlled operator record.

ColumnTypeMeaning
iduuidPrimary key
tenant_iduuidThe tenant in the request body
actiontextencrypt or shred
target_tabletextnewsletter_subscribers or profiles
target_columntextemail or display_name
rows_processedintegerRows successfully written
rows_failedintegerRows that raised
errorsjsonbArray of messages, one per failed row
started_attimestamptzDefaults to insert time
completed_attimestamptzSet when the run finishes
created_byuuidThe operator's user id
created_attimestamptzInsert time

The only current encryption target is profiles.display_name; the subscriber entry was removed after its plaintext column was dropped. An encrypt that found nothing to do and hit no errors writes no run row. Historical rows can still name the older subscriber or shred actions.

Row-level security allows tenant admins to select and to insert rows for their own tenant. The function itself writes with the service role, so the insert policy is not what authorises these rows.

started_at is the insert default rather than a captured start time, so for a long shred it reads as the moment the row landed, close to completed_at.

Recommended workflow

  1. Confirm the additive schema and dependent handlers have landed in the intended environment. For staging, select the dashboard labeled PasskeyBridge-Staging; production must not be touched.
  2. Run backfill-profile-names with dry_run: true. Inspect the exact global remaining count. Keep credentials outside source files and shell history.
  3. Submit a bounded write with dry_run: false, the observed expected_count, and confirm: "BACKFILL_AUTH_AND_CLEAR_PROFILE_NAMES". This is authorized by the configured service key, not a tenant-admin session.
  4. Inspect processed, already_cleared, remaining and next_cursor. On a conflict or stale row, stop and review the canonical account name securely; do not overwrite it automatically. Earlier successful rows stay committed.
  5. Preview again before every write. When deliberately advancing with after_id, keep a record of skipped rows; they remain in the global count and need revisiting without the cursor.
  6. Verify the final aggregate count is zero, then confirm auth-record display and SCIM label readback. An existing auth session may need refresh before seeing updated metadata.

This clears the plaintext profile copy, not all account names. Authentication metadata and encrypted counterparts remain for their stated purposes. The SQL rollback restores only signup behavior; keep compatible encrypted-label readers and auth rendering after names have migrated.

Errors

StatusBody or conditionMeaning
401UnauthorizedMissing Bearer credential, or an invalid user session on a legacy action
403Service authentication requiredAccount-name backfill credential does not equal the configured service key
403ForbiddenLegacy action lacks platform-admin or tenant-admin authorization
400Invalid preview, batch_size or after_idMalformed operator options; batch size must be 1 to 100
400Write batches require the preview count and explicit confirmationWrite omitted its confirmation or nonnegative exact count
409Profile count changed; preview againThe global count differs from expected_count
409auth_name_conflict, stale or auth_user_missingThis row cannot be preserved safely; earlier successful rows remain committed
503Count, batch, encryption or transaction unavailableDo not report completion; inspect returned counts and retry only after reviewing the cause
409Profile names require the operator auth-record backfill before cleanupThe old shred action is blocked for profiles
400 / 413Invalid JSON / request body too largeThe bounded body could not be accepted
405Method not allowedAccount-name backfill requires POST

Operator responses contain counts, statuses and UUIDs, never a name or authentication metadata snapshot. The legacy encrypt action has its existing response and migration history; its success does not authorize bypassing the account-preservation checks.

Related from the blog