Skip to content

feat(status): surface full sdkKeys[]/mobileKeys[] arrays on /status#731

Open
aaron-zeisler wants to merge 1 commit into
aaronz/SDK-2534/rotator-accepted-keysfrom
aaronz/SDK-2534/status-endpoint-arrays
Open

feat(status): surface full sdkKeys[]/mobileKeys[] arrays on /status#731
aaron-zeisler wants to merge 1 commit into
aaronz/SDK-2534/rotator-accepted-keysfrom
aaronz/SDK-2534/status-endpoint-arrays

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 — 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 (this PR) — surface sdkKeys[] / mobileKeys[] on /status

Base: #730. Top of the stack — completes the #724 split.

Summary

Surface the full accepted credential set on /status as sdkKeys[] / mobileKeys[] arrays — each entry carrying the obscured value, the optional wire identifier, and an optional Unix-millisecond expiry. The existing scalar sdkKey / mobileKey fields are preserved and now designate which array entry is the anchor / primary.

expiringSdkKey is now computed deterministically from the accepted set: the previous code overwrote on each loop iteration (arbitrary with multiple expiring keys); it now reports the soonest-expiring non-anchor key, with a (expiry, value) tie-break.

Jira: SDK-2534

Background

The concurrent-keys epic (SDK-2453) lets an environment accept multiple SDK keys and mobile keys at once. Per the backend tech spec and RAC payload spec, the sdkKeys / mobileKeys arrays are the authoritative full accepted set, including the default/anchor key (also duplicated on the legacy scalar fields). The status endpoint mirrors that: the arrays carry every accepted key; the scalars designate the anchor / primary.

The wire format carries a non-secret key identifier alongside each credential's value (threaded through the model in #729 and exposed via AcceptedKeys() in #730). The identifier is genuinely optional — manual configuration and old-format (pre-concurrent-keys) payloads carry none — so it is modeled as a pointer and omitted from the JSON when absent.

Response schema

EnvironmentStatusRep gains two arrays plus a KeyStatus element type. The scalar fields are unchanged.

// KeyStatus is one accepted credential in the sdkKeys[] / mobileKeys[] arrays.
type KeyStatus struct {
    Key    string `json:"key,omitempty"`    // non-secret identifier; omitted when the source carried none
    Value  string `json:"value"`            // obscured credential secret
    Expiry *int64 `json:"expiry,omitempty"` // Unix-millis expiry; omitted for permanent keys
}

type EnvironmentStatusRep struct {
    SDKKey           string               `json:"sdkKey"`                   // obscured ANCHOR (designates which sdkKeys entry is the anchor)
    SDKKeys          []KeyStatus          `json:"sdkKeys"`                  // full server-key set, incl. anchor; always present, always ≥1
    EnvID            string               `json:"envId,omitempty"`
    EnvKey           string               `json:"envKey,omitempty"`
    EnvName          string               `json:"envName,omitempty"`
    ProjKey          string               `json:"projKey,omitempty"`
    ProjName         string               `json:"projName,omitempty"`
    MobileKey        string               `json:"mobileKey,omitempty"`      // obscured PRIMARY mobile key
    MobileKeys       []KeyStatus          `json:"mobileKeys"`               // full mobile-key set, incl. primary; always present, may be empty
    ExpiringSDKKey   string               `json:"expiringSdkKey,omitempty"` // soonest-expiring non-anchor SDK key (deterministic)
    Status           string               `json:"status"`
    ConnectionStatus ConnectionStatusRep  `json:"connectionStatus"`
    DataStoreStatus  DataStoreStatusRep   `json:"dataStoreStatus"`
    BigSegmentStatus *BigSegmentStatusRep `json:"bigSegmentStatus,omitempty"`
}

Array entry order is unspecified. The arrays are always present (never null): sdkKeys always contains at least the anchor; mobileKeys is empty for an environment with no mobile key (e.g. server-side only).

Example (environment mid-rotation: an extra service key + an expiring key)

"environment1": {
  "sdkKey": "sdk-********-****-****-****-*******00001",
  "sdkKeys": [
    { "key": "default",   "value": "sdk-********-****-****-****-*******00001" },
    { "key": "service-a", "value": "sdk-********-****-****-****-*******00002" },
    { "key": "old-key",   "value": "sdk-********-****-****-****-*******00003", "expiry": 1735689600000 }
  ],
  "mobileKey": "mob-********-****-****-****-*******00009",
  "mobileKeys": [
    { "key": "default-mobile", "value": "mob-********-****-****-****-*******00009" }
  ],
  "expiringSdkKey": "sdk-********-****-****-****-*******00003",
  "status": "connected",
  "connectionStatus": { "state": "VALID", "stateSince": 10000000 },
  "dataStoreStatus":  { "state": "VALID", "stateSince": 10000000 }
}

Changes

  • Add KeyStatus and the sdkKeys[] / mobileKeys[] fields to EnvironmentStatusRep.
  • The status handler partitions GetAcceptedKeys() (from feat(credential): expose the full accepted key set via AcceptedKeys #730) by type, obscuring each value, and fills the arrays (always present, never null).
  • expiringSdkKey becomes the soonest-expiring non-anchor SDK key via slices.MinFunc, comparing (expiry, value) so the pick is deterministic on a tie.

@aaron-zeisler aaron-zeisler marked this pull request as ready for review June 29, 2026 22:14
@aaron-zeisler aaron-zeisler requested a review from a team as a code owner June 29, 2026 22:14
Add a KeyStatus representation and sdkKeys[]/mobileKeys[] arrays to
EnvironmentStatusRep, populated from GetAcceptedKeys() partitioned by
type. Each entry carries the obscured value, the optional wire identifier,
and an optional Unix-millisecond expiry. The arrays are always present
(never null); a server-only environment has an empty mobileKeys.

expiringSdkKey is now computed from the accepted set as the soonest-
expiring non-anchor SDK key, with a (expiry, value) tie-break so the pick
is deterministic when several keys share an expiry.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant