Skip to content

refactor(credential): carry wire key identifiers through the accepted set#729

Merged
aaron-zeisler merged 3 commits into
feat/concurrent-keysfrom
aaronz/SDK-2534/credential-key-identifiers
Jun 30, 2026
Merged

refactor(credential): carry wire key identifiers through the accepted set#729
aaron-zeisler merged 3 commits into
feat/concurrent-keysfrom
aaronz/SDK-2534/credential-key-identifiers

Conversation

@aaron-zeisler

@aaron-zeisler aaron-zeisler commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Note

Stacked PR — splitting #724 (review/merge bottom-up):

  1. fix(envfactory): reject primary mobile key absent from mobileKeys[] #728 — reject primary mobile key absent from mobileKeys[]
  2. refactor(credential): carry wire key identifiers through the accepted set #729 (this PR) — carry wire key identifiers through the accepted set
  3. feat(credential): expose the full accepted key set via AcceptedKeys #730 — expose the full accepted key set via AcceptedKeys
  4. feat(status): surface full sdkKeys[]/mobileKeys[] arrays on /status #731 — surface sdkKeys[] / mobileKeys[] on /status

Base: #728.

Summary

Carry the wire key identifier through the credential accepted-set model so it can later be surfaced on the /status endpoint. This is the storage/model layer only — no externally visible behaviour changes yet.

Jira: SDK-2534

Background

Second in the stack splitting #724, based on #728. Each accepted key needs to carry its optional non-secret identifier alongside its value and expiry.

Because this PR sits on #728, the per-entry primary-mobile designation introduced here is paired with #728's mobKey-not-in-mobileKeys[] rejection: a defined primary that is absent from the array is rejected outright rather than silently left undesignated.

Changes

  • Replace the builder's WithExpiring{SDK,Mobile}Key with a param-struct API (SDKKeyParams / MobileKeyParams).
  • AcceptedSet map values become acceptedKeyInfo (value, expiry, optional key).
  • Rotator reconcile loops thread the identifier through, refreshing it each reconcile and clearing it when a later payload carries none.
  • Add util.PtrOrNil for optional-field modelling.

Note

Medium Risk
Changes the credential reconcile/build path that gates which keys are accepted, but authentication semantics are unchanged; the main new behavior is metadata refresh on reconcile (identifiers can clear or update).

Overview
Extends the credential accepted-set model so each SDK/mobile key stores optional wire key identifiers (plus expiry) alongside the secret value, preparing later /status exposure without changing auth behavior in this PR.

The AcceptedSetBuilder API is consolidated: separate expiring-key helpers are replaced by SDKKeyParams / MobileKeyParams (value, optional identifier, optional expiry). WithAnchor / WithPrimaryMobileKey still force permanent keys and can overwrite earlier entries for the same value.

BuildAcceptedSet now designates anchor and primary mobile keys while walking the wire arrays and passes identifiers through util.PtrOrNil. Rename tests expect AcceptedSet equality to differ when only the display identifier changes.

The rotator stores acceptedKeyInfo by value and, on reconcile, replaces full per-key metadata so identifiers refresh and absent wire key fields clear stale names. Reconcile no longer re-injects anchor/primary into the desired map separately; it trusts the built set from the builder.

Reviewed by Cursor Bugbot for commit 5eceeb4. Bugbot is set up for automated code reviews on this repo. Configure here.

@aaron-zeisler aaron-zeisler marked this pull request as ready for review June 29, 2026 22:13
@aaron-zeisler aaron-zeisler requested a review from a team as a code owner June 29, 2026 22:13

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 072ed9f. Configure here.

Comment thread internal/envfactory/reconcile_helper.go
@aaron-zeisler aaron-zeisler force-pushed the aaronz/SDK-2534/credential-key-identifiers branch from 072ed9f to d3d3a0e Compare June 29, 2026 22:56
@aaron-zeisler aaron-zeisler changed the base branch from feat/concurrent-keys to aaronz/reject-primary-mobile-not-in-array June 29, 2026 22:56
Comment thread internal/credential/accepted_set_builder.go
Comment thread internal/credential/accepted_set_builder.go
Comment thread internal/credential/accepted_set_builder.go
Comment thread internal/credential/rotator.go Outdated
reconcileAcceptedKeys(desired, r.acceptedSDKKeys, &r.additions, &r.expirations, r.loggers, "SDK key")
// Refresh the wire "key" identifier for every key now in the accepted set, including clearing it
// when the new payload carries none — otherwise a stale identifier would linger in /status.
for key, info := range set.sdkKeys {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Iterating through this a second time is not ideal.

Right now, reconcileAcceptedKeys takes in a desired map of [keys]*time.Time. That function iterates the map and updates the expiration on the accepted keys.

If you updated the desired map to array the full value as I point out above, and update this function, then I think you can update both in a single iteration.

Comment thread internal/credential/rotator.go
Comment thread internal/credential/rotator.go
Comment thread internal/envfactory/reconcile_helper.go
Base automatically changed from aaronz/reject-primary-mobile-not-in-array to feat/concurrent-keys June 30, 2026 16:51
… set

Replace the builder's WithExpiring{SDK,Mobile}Key methods with a
param-struct API (SDKKeyParams/MobileKeyParams) so each accepted key can
carry its optional wire "key" identifier alongside its value and expiry.
AcceptedSet map values become acceptedKeyInfo (value, expiry, and the
optional key identifier); the rotator's reconcile loops thread the
identifier through, refreshing it on every reconcile and clearing it when
a later payload carries none. Add util.PtrOrNil for the optional-field
modelling.

This is the storage layer for surfacing per-key identifiers on the status
endpoint; no externally visible behaviour changes yet.
@aaron-zeisler aaron-zeisler force-pushed the aaronz/SDK-2534/credential-key-identifiers branch from d3d3a0e to a094998 Compare June 30, 2026 16:56
Per review: have the desired-set maps carry the full acceptedKeyInfo
(value metadata, not just expiry) and have reconcileAcceptedKeys refresh
both the expiry and the wire "key" identifier in its add/update pass.
This drops the separate second loop that reconcileSDKKeys/
reconcileMobileKeys used to run to refresh identifiers. Behaviour is
unchanged, including clearing a stale identifier when a later payload
carries none; the anchor and primary mobile key are still forced
permanent while keeping their identifiers.
Comment thread internal/credential/rotator.go Outdated
… builder

Per review:
- The rotator's acceptedSDKKeys/acceptedMobileKeys maps now hold
  acceptedKeyInfo by value rather than by pointer. The struct is two
  pointer fields, so copies are cheap, the per-key heap allocation goes
  away, and snapshots are free of aliasing into rotator state. The
  reconcile add/update collapses to a single map assignment.
- Drop the explicit anchor / primary-mobile "force permanent" handling in
  reconcileSDKKeys/reconcileMobileKeys. The builder already guarantees the
  designated keys are present and permanent, and Reconcile trusts a
  well-formed set, so the desired-set build is now a plain filter loop.
// acceptedMobileKeys is the full set of accepted mobile keys with optional per-key expiry.
// A nil expiry means the key is permanent.
acceptedMobileKeys map[config.MobileKey]*acceptedKeyInfo
acceptedMobileKeys map[config.MobileKey]acceptedKeyInfo

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.

Note for reviewers: Here I decided to go with values instead of pointers. This caused a handful of random-looking changes throughout the pull request.

@aaron-zeisler aaron-zeisler merged commit 46320e8 into feat/concurrent-keys Jun 30, 2026
16 checks passed
@aaron-zeisler aaron-zeisler deleted the aaronz/SDK-2534/credential-key-identifiers branch June 30, 2026 20:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants