Identity Service — User Stories
Service: identity-service Story prefix: IDENT-US Last updated: 2026-04-18
Stories
IDENT-US-001 — Email + password login with session issuance
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | Platform user logs in with email + password and receives JWT |
| Epic link | IDENT-EPIC-01 |
| Status | To Do |
| Priority | Must |
| Story points | 5 |
| Labels | service:identity, type:backend, type:api, slice:S0 |
| Components | auth-module, session-module |
| FR references | FR-IDENT-AUTH-001, FR-IDENT-AUTH-002, FR-IDENT-AUTH-003 |
| Legacy FR refs | FR-IAM-AUTH-001, FR-IAM-AUTH-002, FR-IAM-AUTH-003 |
| Dependencies | IDENT-US-004 (user creation) |
User story: As a platform user, when I submit valid credentials, I want to receive a short-lived access JWT and rotating refresh token so that I can authenticate across platform services without re-entering credentials.
Acceptance criteria (Gherkin):
- Given a user with
activestatus and valid argon2id password hash, whenPOST /api/v1/auth/loginis called with correct email + password, then response containsaccessToken(15 min),refreshToken(8 h), and user identity. - Given valid credentials and MFA not required, when login succeeds, then
SessionCreatedandUserLoggedIndomain events are emitted via outbox. - Given a suspended or deactivated user, when login is attempted, then
401 IDENT_INVALID_CREDENTIALSis returned (timing-safe; no user existence disclosure). - Given 6 failed attempts within 5 minutes from same IP, when 7th attempt arrives, then
429with retry-after is returned.
Technical notes:
- Argon2id params:
m=65536, t=3, p=1. Breach-list check on new passwords only. POST /api/v1/auth/login— rate limit 5/min/IP via Kong plugin.- JWT claim shape frozen at F01:
{ sub, tid, tids, jti, amr, context_node_id?, exp, iat, iss, aud }. - Refresh token stored as argon2id hash in
sessions.refresh_hash.
Definition of Done:
- Unit + integration tests added; coverage ≥ 80%.
- OpenAPI contract updated; Pact consumer tests green.
identity.session.created.v1andidentity.user.logged_in.v1schema conformance tests green.- Telemetry spans/metrics added (
identity.login.duration,identity.login.outcome). - Documentation updated in API_CONTRACTS, EVENT_SCHEMAS.
IDENT-US-002 — Session refresh and logout
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | Rotate refresh token and revoke sessions on logout |
| Epic link | IDENT-EPIC-01 |
| Status | To Do |
| Priority | Must |
| Story points | 3 |
| Labels | service:identity, type:backend, type:api, slice:S0 |
| Components | session-module |
| FR references | FR-IDENT-AUTH-004, FR-IDENT-AUTH-005 |
| Legacy FR refs | FR-IAM-AUTH-004 |
| Dependencies | IDENT-US-001 |
User story: As an authenticated user, when my access token expires, I want to silently rotate my refresh token and receive a new access token so that my session continues without interruption; and when I log out, I want all tokens revoked immediately.
Acceptance criteria (Gherkin):
- Given a valid, unused refresh token, when
POST /api/v1/auth/refreshis called, then a new access token and new refresh token are returned; the old refresh hash is invalidated. - Given a refresh token used a second time (replay), when
POST /api/v1/auth/refreshis called, then401 IDENT_REFRESH_REPLAYis returned; the entire session is revoked;identity.session.revoked.v1is emitted. - Given an authenticated user, when
POST /api/v1/auth/logoutis called, then the session is markedrevoked_at; subsequent refresh attempts return 401. - Given
POST /api/v1/auth/logout-all, when called, then all sessions for the user are revoked and Redis revocation set is updated.
Technical notes:
- Refresh tokens are one-time-use; stored in
sessions.refresh_hash. - Redis revocation bitmap for fast JWT blacklist check at Kong.
- Replay detection emits
identity.user.security_incident.v1(for audit-service).
Definition of Done:
- Replay detection integration test passes.
identity.session.revoked.v1schema conformance test green.- Telemetry:
identity.session.revoke.totalcounter. - API_CONTRACTS updated.
IDENT-US-003 — User lifecycle (create, suspend, reactivate, deactivate)
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | Tenant admin manages user status transitions with audit events |
| Epic link | IDENT-EPIC-01 |
| Status | To Do |
| Priority | Must |
| Story points | 5 |
| Labels | service:identity, type:backend, type:api, slice:S0 |
| Components | user-lifecycle |
| FR references | FR-IDENT-USR-001..006 |
| Legacy FR refs | FR-IAM-USR-001..006 |
| Dependencies | IDENT-US-001 |
User story: As a tenant administrator, when I manage users in my tenant, I want deterministic lifecycle APIs so that identity data remains complete, consistent, and auditable.
Acceptance criteria (Gherkin):
- Given a valid create payload, when
POST /api/v1/usersis called, then user is created withpending_verificationstatus;identity.user.registered.v1is emitted. - Given an active user, when
POST /api/v1/users/:id/suspendis called, then status becomessuspended; all sessions revoked;identity.user.suspended.v1emitted. - Given a suspended user, when
POST /api/v1/users/:id/reactivateis called, then status becomesactive;identity.user.reactivated.v1emitted. - Given a GDPR erasure request, when
DELETE /api/v1/users/:idis called, then user transitions todeactivated; PII fields zeroed;identity.user.deactivated.v1emitted. - Given a cross-tenant request (tenant admin in tenant A targeting user in tenant B), when endpoint is called, then
403 IDENT_CROSS_TENANTreturned.
Technical notes:
- Duplicate email check via
UNIQUE(tenant_id, email)constraint —409 IDENT_EMAIL_DUPLICATE. - GDPR erasure:
first_name,last_name,emailzeroed;deactivated_atset. - All mutations go through outbox; consumed by communication-service.
Definition of Done:
- Tenant-isolation integration test passes.
- GDPR erasure integration test passes.
identity.user.registered.v1schema test green.
IDENT-US-004 — Email verification flow
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | Send and confirm email verification token to activate user |
| Epic link | IDENT-EPIC-01 |
| Status | To Do |
| Priority | Must |
| Story points | 3 |
| Labels | service:identity, type:backend, type:api, slice:S0 |
| Components | user-lifecycle |
| FR references | FR-IDENT-USR-002 |
| Legacy FR refs | FR-IAM-USR-002 |
| Dependencies | IDENT-US-003, cross-service: COMMS-US-001 |
User story: As a newly registered user, when I receive an email with a verification link, I want to confirm my email address so that my account becomes active and I can log in.
Acceptance criteria (Gherkin):
- Given a
pending_verificationuser, whenPOST /api/v1/auth/verify-emailis called with valid token, then user status changes toactive;identity.user.email_verified.v1emitted. - Given an expired or already-used verification token, when confirm is called, then
422error is returned. - Given
POST /api/v1/auth/registerfor patient portal tenant, when called, then verification email event emitted for communication-service to deliver.
Technical notes:
- Token is short-lived JWT (24 h) signed with KMS; single-use.
identity.user.registered.v1triggers communication-service email dispatch.
Definition of Done:
- Integration test: token replay returns 422.
identity.user.email_verified.v1schema test green.
IDENT-US-005 — Device registration and trust promotion
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | Register device, promote to trusted, issue offline binding certificate |
| Epic link | IDENT-EPIC-02 |
| Status | To Do |
| Priority | Must |
| Story points | 8 |
| Labels | service:identity, type:backend, type:api, slice:S1 |
| Components | device-module |
| FR references | FR-IDENT-DEV-001..004 |
| Legacy FR refs | FR-IAM-AUTH-007 |
| Dependencies | IDENT-US-001 |
User story: As a clinical user in a low-connectivity setting, when I register my device and it is trusted by an admin, I want an offline binding certificate so that patient data can be securely encrypted for offline access.
Acceptance criteria (Gherkin):
- Given an authenticated user, when
POST /api/v1/me/devicesis called with fingerprint + public key, then device is created withuntrustedstatus;identity.device.registered.v1emitted. - Given a trusted device, when
POST /api/v1/me/devices/:id/bind-offlineis called, then a binding certificate (≤ 30 days expiry) is returned;identity.device.bound_for_offline.v1emitted. - Given an untrusted device, when bind-offline is called, then
422 IDENT_DEVICE_NOT_TRUSTEDreturned. - Given admin revokes device, when
DELETE /api/v1/me/devices/:idis called, then certificate is revoked;identity.device.revoked.v1emitted.
Technical notes:
- Binding cert signed via KMS device cert key (
IDENTITY_DEVICE_CERT_KEY_ARN). identity.device.bound_for_offline.v1consumed by patient-chart-service and document-service.- Cert max lifetime: 30 days (INV-05).
Definition of Done:
- Device state-machine integration test passes all transitions.
identity.device.bound_for_offline.v1schema test green.- Telemetry:
identity.device.bind.total.
IDENT-US-006 — Device revocation
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | Revoke device and invalidate offline binding certificate |
| Epic link | IDENT-EPIC-02 |
| Status | To Do |
| Priority | Must |
| Story points | 3 |
| Labels | service:identity, type:backend, type:api, slice:S1 |
| Components | device-module |
| FR references | FR-IDENT-DEV-005 |
| Legacy FR refs | — |
| Dependencies | IDENT-US-005 |
User story: As a security operator, when a device is lost or compromised, I want to revoke it immediately so that offline access to patient data is disabled.
Acceptance criteria (Gherkin):
- Given an
offline_bounddevice, whenDELETE /api/v1/me/devices/:idis called, then device transitions torevoked; certrevoked_atset;identity.device.revoked.v1emitted. - Given
POST /api/v1/auth/logout-all, when called, then all associated devices transition torevoked. - Given a revoked device, when bind-offline is called, then
422is returned.
Technical notes:
- Revocation event consumed by patient-chart-service to invalidate offline encryption key.
Definition of Done:
- Revocation event schema test green.
- Device-binding integration test passes revoke path.
IDENT-US-007 — API key and service account lifecycle
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | Create, rotate, and revoke tenant API keys and service accounts |
| Epic link | IDENT-EPIC-03 |
| Status | To Do |
| Priority | Must |
| Story points | 5 |
| Labels | service:identity, type:backend, type:api, slice:S1 |
| Components | api-key-module, service-account-module |
| FR references | FR-IDENT-SVC-001..003 |
| Legacy FR refs | FR-IAM-SVC-001..003 |
| Dependencies | IDENT-US-001 |
User story: As a platform administrator, when managing machine-to-machine integrations, I want to create scoped API keys and service accounts with rotation and revocation controls so that M2M identities stay governed.
Acceptance criteria (Gherkin):
- Given valid create payload, when
POST /api/v1/api-keysis called, then API key is created; secret is returned exactly once;identity.api_key.issued.v1emitted. - Given a rotation request, when
POST /api/v1/api-keys/:id/rotateis called, then new secret is returned; old secret remains valid for overlap window (default 24 h). - Given revocation, when
DELETE /api/v1/api-keys/:idis called, then key transitions to revoked state; subsequent use returns 401. - Given SUPER_ADMIN, when service account created via
POST /api/v1/service-accounts, thenclientIdis globally unique;identity.service_account.created.v1emitted.
Technical notes:
- API key secret stored as HMAC-SHA256 using
IDENTITY_APIKEY_HMAC_SECRET. - Scopes drawn from platform vocabulary
tenant:{resource}:{action}.
Definition of Done:
- Rotation overlap-window integration test passes.
identity.api_key.issued.v1schema test green.
IDENT-US-008 — TOTP MFA enrollment and verification
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | Enroll TOTP second factor and use during login challenge |
| Epic link | IDENT-EPIC-04 |
| Status | To Do |
| Priority | Must |
| Story points | 5 |
| Labels | service:identity, type:backend, type:api, slice:S0 |
| Components | mfa-module |
| FR references | FR-IDENT-MFA-001..003 |
| Legacy FR refs | FR-IAM-AUTH-005 |
| Dependencies | IDENT-US-001 |
User story: As a clinical user required to use MFA, when I enroll TOTP, I want to scan a QR code and verify my authenticator app so that subsequent logins require my second factor.
Acceptance criteria (Gherkin):
- Given an authenticated user, when
POST /api/v1/me/mfa/totp/enrollis called, then a TOTP provisioning URI is returned (RFC 6238 compatible);verifiedAtis null. - Given enrollment started, when
POST /api/v1/me/mfa/totp/confirmis called with valid 6-digit code, then factorverifiedAtis set;identity.user.mfa_enrolled.v1emitted. - Given MFA policy
mfa_required=truefor tenant, when user logs in, then login returns401 IDENT_MFA_REQUIREDwithchallengeId;POST /api/v1/auth/mfa/verifycompletes login. - Given invalid TOTP code (3 consecutive failures), when verify is called, then
401 IDENT_MFA_FAILED;identity.user.mfa_challenge_failed.v1emitted.
Technical notes:
- TOTP seed stored KMS-wrapped in
mfa_factors.secret. challengeIdis short-lived JWT (5 min) signed with KMS.
Definition of Done:
- MFA enrollment + login integration test passes.
identity.user.mfa_enrolled.v1schema test green.
IDENT-US-009 — WebAuthn second factor
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | Register and use WebAuthn platform authenticator as MFA |
| Epic link | IDENT-EPIC-04 |
| Status | To Do |
| Priority | Should |
| Story points | 8 |
| Labels | service:identity, type:backend, type:api, slice:S0 |
| Components | mfa-module |
| FR references | FR-IDENT-MFA-004..005 |
| Legacy FR refs | — |
| Dependencies | IDENT-US-008 |
User story: As a clinical user with a biometric device, when I register my WebAuthn authenticator, I want to use fingerprint or face recognition as my second factor so that login is both secure and fast.
Acceptance criteria (Gherkin):
- Given an authenticated user, when
POST /api/v1/me/mfa/webauthn/register/optionsis called, then registration options are returned per FIDO2 spec. - Given registration options, when
POST /api/v1/me/mfa/webauthn/registeris called with authenticator response, then credential is stored; factorverifiedAtset. - Given a WebAuthn-enrolled user with MFA challenge, when authenticator assertion is provided via
POST /api/v1/auth/mfa/verify, then login is completed;amrincludeswebauthn. - Given up to 5 WebAuthn credentials per user (INV — MFA invariant), when 6th registration is attempted, then
422is returned.
Technical notes:
- RP ID pinned to
identity.ghasi-ehealth.af(configurable via env). - Max 5 WebAuthn credentials per user per domain model invariant.
Definition of Done:
- WebAuthn registration + assertion integration test passes (using virtual authenticator).
- Contract test updated.
IDENT-US-010 — OIDC federated login with JIT provisioning
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | OIDC Authorization Code + PKCE flow with JIT user provisioning |
| Epic link | IDENT-EPIC-05 |
| Status | To Do |
| Priority | Should |
| Story points | 8 |
| Labels | service:identity, type:backend, type:api, slice:S2 |
| Components | federation-module |
| FR references | FR-IDENT-FED-001..003 |
| Legacy FR refs | FR-IAM-AUTH-008 |
| Dependencies | IDENT-US-001 |
User story: As a user from an enterprise tenant using Google Workspace or Azure AD, when I click "Sign in with SSO", I want to authenticate via my corporate IdP and be provisioned a Ghasi account automatically so that I don't need a separate password.
Acceptance criteria (Gherkin):
- Given an OIDC provider configured for a tenant, when
GET /api/v1/auth/oidc/:provider/startis called, then redirect to IdP authorization endpoint with PKCE parameters. - Given IdP callback with authorization code, when
GET /api/v1/auth/oidc/:provider/callbackis called, then token exchange completes; user is JIT-provisioned or linked; Ghasi JWT is issued. - Given
(issuer, subject)already linked to a different tenant user, when JIT provisioning is attempted, then409 IDENT_EXT_IDENTITY_MISMATCHreturned. - Given IdP unavailable (circuit open), when OIDC flow is initiated, then
503 IDENT_FEDERATION_UNAVAILABLEreturned to client.
Technical notes:
identity.external_identity.linked.v1emitted on first JIT provision.- Circuit breaker per provider: 5 failures → open; half-open retry 30 s.
Definition of Done:
- OIDC flow integration test with Wiremock stub passes.
identity.external_identity.linked.v1schema test green.
IDENT-US-011 — Keycloak broker token exchange
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | RFC 8693 token exchange for Keycloak broker mode tenants |
| Epic link | IDENT-EPIC-05 |
| Status | To Do |
| Priority | Should |
| Story points | 5 |
| Labels | service:identity, type:backend, type:api, slice:S2 |
| Components | federation-module |
| FR references | FR-IDENT-FED-004 |
| Legacy FR refs | — |
| Dependencies | IDENT-US-010 |
User story: As a tenant using Keycloak as their central IAM, when a Keycloak access token is presented, I want identity-service to exchange it for a Ghasi JWT so that all downstream services use consistent claim shapes.
Acceptance criteria (Gherkin):
- Given a valid Keycloak access token, when
POST /api/v1/auth/keycloak/token-exchangeis called, then a Ghasi JWT with F01 claim shape is returned;keycloak_user_idis backfilled on user row. - Given an invalid or expired upstream token, when exchange is called, then
401 IDENT_INVALID_CREDENTIALSreturned.
Technical notes:
- RFC 8693 token exchange; Keycloak confidential client required.
- All paths re-mint Ghasi JWT — frozen F01/F03.
Definition of Done:
- Token exchange integration test passes.
- Pact consumer test green.
IDENT-US-012 — Access context aggregation endpoint
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | GET /me/access-context returns roles + memberships + effective modules |
| Epic link | IDENT-EPIC-06 |
| Status | To Do |
| Priority | Must |
| Story points | 5 |
| Labels | service:identity, type:backend, type:api, slice:S0 |
| Components | access-context-module |
| FR references | FR-IDENT-CTX-001..003 |
| Legacy FR refs | FR-IAM-CTX-001, FR-IAM-PROV-003 |
| Dependencies | IDENT-US-001, cross-service: TENANT-US-001 |
User story: As a client application, when the user is authenticated, I want to call a single endpoint that returns the user's roles, memberships, and effective licensed modules so that UI gating is consistent and based on a single source of truth.
Acceptance criteria (Gherkin):
- Given an authenticated user, when
GET /api/v1/me/access-contextis called, then response contains{ roles, memberships, effectiveModules }; data sourced from tenant-service (roles/memberships) and identity licensing resolver (modules). - Given repeated calls within 5-minute TTL, when cache is warm, then response latency < 50 ms (cache hit observable in trace).
- Given a role change event from tenant-service, when event arrives, then access-context cache for affected user is invalidated within 30 s.
Technical notes:
- Calls
GET /internal/identity/providers/:id/identityin tenant-service. - Redis cache key:
access-context:{userId}:{nodeId}.
Definition of Done:
- Cache invalidation integration test passes.
- Telemetry:
identity.access_context.cache_hit_ratio.
IDENT-US-013 — Module catalogue management
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | Super admin creates and updates the platform module catalogue |
| Epic link | IDENT-EPIC-07 |
| Status | To Do |
| Priority | Must |
| Story points | 3 |
| Labels | service:identity, type:backend, type:api, slice:S1 |
| Components | licensing-module |
| FR references | FR-IDENT-LIC-001..003 |
| Legacy FR refs | FR-LICN-001..003 |
| Dependencies | — |
User story: As a Super Admin, when managing platform capabilities, I want to create and update module catalogue entries so that the full set of licensable modules is defined before assignment.
Acceptance criteria (Gherkin):
- Given a unique module code, when
POST /api/v1/licensing/modulesis called, then module is persisted;identity.license.module.created.v1emitted. - Given an existing module with
is_always_on=true, when delete or terminate is attempted, then422 IDENT_LIC_ALWAYS_ON_IMMUTABLEreturned. - Given a module update (description change), when
PATCH /api/v1/licensing/modules/:codeis called, thencoderemains unchanged (immutable per F05);identity.license.module.updated.v1emitted.
Technical notes:
- Module
codeis frozen at F05 — globally unique, immutable once created. - Seed modules
platform.iam,platform.audit,ehr.coreare always-on.
Definition of Done:
- Immutability invariant test passes (code update blocked).
identity.license.module.created.v1schema test green.
IDENT-US-014 — License assignment lifecycle
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | Assign, update status, and terminate license assignments at hierarchy nodes |
| Epic link | IDENT-EPIC-07 |
| Status | To Do |
| Priority | Must |
| Story points | 8 |
| Labels | service:identity, type:backend, type:api, slice:S1 |
| Components | licensing-module |
| FR references | FR-IDENT-LIC-004..009 |
| Legacy FR refs | FR-LICN-004..009 |
| Dependencies | IDENT-US-013, cross-service: TENANT-US-002 |
User story: As a Super Admin, when licensing a tenant's facility for a specific module, I want to assign a license at a hierarchy node with a scope and status lifecycle so that module access is governed and auditable.
Acceptance criteria (Gherkin):
- Given a module with satisfied dependency chain, when
POST /api/v1/licensing/assignmentsis called, then assignment is created;identity.license.assignment.created.v1emitted. - Given an unsatisfied dependency (parent module not licensed at same/ancestor node), when assignment is attempted, then
422 IDENT_LIC_DEPENDENCY_NOT_LICENSEDreturned. - Given an active assignment, when
PATCH /api/v1/licensing/assignments/:id/statuschanges tosuspended, then status updated; history row appended;identity.license.assignment.status_changed.v1emitted. - Given a
terminatedassignment, when any status mutation is attempted, then422 IDENT_LIC_TERMINATEDreturned. - Given a trial assignment without
effective_to, when create is attempted, then422returned (INV-09).
Technical notes:
- Append-only
license_assignment_historytable. - Effective resolver must invalidate cache on status change event.
Definition of Done:
- Dependency chain invariant integration test passes.
- History append integration test passes.
identity.license.assignment.status_changed.v1schema test green.
IDENT-US-015 — Effective license resolver with inheritance
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | Compute effective license set via ancestor hierarchy walk with caching |
| Epic link | IDENT-EPIC-07 |
| Status | To Do |
| Priority | Must |
| Story points | 5 |
| Labels | service:identity, type:backend, type:api, slice:S1 |
| Components | licensing-module |
| FR references | FR-IDENT-LIC-010..012 |
| Legacy FR refs | FR-LICN-EFF-001..005 |
| Dependencies | IDENT-US-014, cross-service: TENANT-US-002 |
User story: As a platform service, when evaluating module access for a provider at a node, I want to call an internal endpoint that returns the effective set of licensed modules after ancestor inheritance so that module gating is consistent across the platform.
Acceptance criteria (Gherkin):
- Given a node with
inherit-downassignment at its grandparent, whenGET /internal/identity/licensing/effective?nodeId=Xis called, then module appears in effective set. - Given a direct
exactassignment at node that overrides a suspended ancestor assignment, when effective set is resolved, then deeper-wins rule returns the exact assignment status. - Given cache warm for
(tenantId, providerId, nodeId), when repeated calls within 5 min, then ancestor walk is not re-executed (trace shows cache hit). - Given
identity.license.assignment.status_changed.v1event arrives, when processed, then affected cache entries are invalidated within 30 s.
Technical notes:
- Ancestor hierarchy fetched from
GET /internal/hierarchy/nodes/:id/ancestors(tenant-service). - Redis cache TTL 5 min; invalidation on status change event (inbox pattern).
Definition of Done:
- Resolver unit tests cover all scope/status combinations.
- Cache invalidation integration test passes.
- Resolver p99 latency < 200 ms under test load.
IDENT-US-016 — JWKS publication and rotation
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | Publish JWKS and automate 90-day key rotation with overlap |
| Epic link | IDENT-EPIC-08 |
| Status | To Do |
| Priority | Must |
| Story points | 5 |
| Labels | service:identity, type:backend, type:api, slice:S0 |
| Components | jwks-module |
| FR references | FR-IDENT-AUTH-010 |
| Legacy FR refs | FR-IAM-AUTH-010 |
| Dependencies | IDENT-US-001 |
User story: As every platform service, when I validate a JWT, I want to fetch JWKS from a well-known endpoint so that I can verify tokens without direct identity-service coupling; and I want rotation to happen transparently without causing 401 cascades.
Acceptance criteria (Gherkin):
- Given a running identity-service, when
GET /.well-known/jwks.jsonis called, then current RS256 public key(s) are returned; responseCache-Control: max-age=3600. - Given key rotation is triggered, when new key is promoted, then old key remains in JWKS for 7-day overlap;
identity.jwks.rotated.v1emitted; downstream 401 rate does not spike. - Given KMS unreachable, when JWKS endpoint is called, then last-cached key set is returned from in-memory cache (15 min TTL).
Technical notes:
- Freeze F01:
iss,aud,kidheader shape frozen. - Rotation cron:
0 0 1 */3 *(quarterly).
Definition of Done:
- Rotation overlap integration test passes (old + new kid both valid).
identity.jwks.rotated.v1schema test green.
IDENT-US-017 — Platform quality gates (coverage + observability + GDPR)
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | Coverage gates, OTel instrumentation, GDPR erasure propagation verified |
| Epic link | IDENT-EPIC-08 |
| Status | To Do |
| Priority | Must |
| Story points | 5 |
| Labels | service:identity, type:backend, type:ops, slice:S0 |
| Components | cross-cutting |
| FR references | FR-IDENT-NFR-001..008 |
| Legacy FR refs | ENH-IAM-005..007 |
| Dependencies | All IDENT-US |
User story: As an engineering lead, when releasing identity-service, I want measurable quality gates, observability, and compliance evidence so that release risk is controlled and auditors have the data they need.
Acceptance criteria (Gherkin):
- Given CI runs
pnpm test:cov, when pipeline completes, then zero failed suites; statements ≥ 80%; lines ≥ 80%. - Given a login request in staging, when Grafana Tempo is queried, then trace spans include
identity.auth.login,identity.session.create,identity.outbox.publish. - Given a GDPR erasure (
DELETE /api/v1/users/:id), when audit-service is queried, then erasure record is visible with actor, timestamp, and affected fields. - Given login p99 alert threshold (> 300 ms), when exceeded for 2 min, then on-call is paged via PagerDuty.
Technical notes:
@ghasi/telemetryinitialized beforeNestFactoryinmain.ts.- GDPR erasure propagated via
identity.user.deactivated.v1event.
Definition of Done:
- Coverage gate green in CI.
- OTel traces visible in staging Grafana.
- GDPR erasure integration test passes.
- SLO alerts configured with runbook links.