Skip to main content

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

FieldValue
Issue typeStory
SummaryPlatform user logs in with email + password and receives JWT
Epic linkIDENT-EPIC-01
StatusTo Do
PriorityMust
Story points5
Labelsservice:identity, type:backend, type:api, slice:S0
Componentsauth-module, session-module
FR referencesFR-IDENT-AUTH-001, FR-IDENT-AUTH-002, FR-IDENT-AUTH-003
Legacy FR refsFR-IAM-AUTH-001, FR-IAM-AUTH-002, FR-IAM-AUTH-003
DependenciesIDENT-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 active status and valid argon2id password hash, when POST /api/v1/auth/login is called with correct email + password, then response contains accessToken (15 min), refreshToken (8 h), and user identity.
  • Given valid credentials and MFA not required, when login succeeds, then SessionCreated and UserLoggedIn domain events are emitted via outbox.
  • Given a suspended or deactivated user, when login is attempted, then 401 IDENT_INVALID_CREDENTIALS is returned (timing-safe; no user existence disclosure).
  • Given 6 failed attempts within 5 minutes from same IP, when 7th attempt arrives, then 429 with 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.v1 and identity.user.logged_in.v1 schema 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

FieldValue
Issue typeStory
SummaryRotate refresh token and revoke sessions on logout
Epic linkIDENT-EPIC-01
StatusTo Do
PriorityMust
Story points3
Labelsservice:identity, type:backend, type:api, slice:S0
Componentssession-module
FR referencesFR-IDENT-AUTH-004, FR-IDENT-AUTH-005
Legacy FR refsFR-IAM-AUTH-004
DependenciesIDENT-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/refresh is 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/refresh is called, then 401 IDENT_REFRESH_REPLAY is returned; the entire session is revoked; identity.session.revoked.v1 is emitted.
  • Given an authenticated user, when POST /api/v1/auth/logout is called, then the session is marked revoked_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.v1 schema conformance test green.
  • Telemetry: identity.session.revoke.total counter.
  • API_CONTRACTS updated.

IDENT-US-003 — User lifecycle (create, suspend, reactivate, deactivate)

FieldValue
Issue typeStory
SummaryTenant admin manages user status transitions with audit events
Epic linkIDENT-EPIC-01
StatusTo Do
PriorityMust
Story points5
Labelsservice:identity, type:backend, type:api, slice:S0
Componentsuser-lifecycle
FR referencesFR-IDENT-USR-001..006
Legacy FR refsFR-IAM-USR-001..006
DependenciesIDENT-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/users is called, then user is created with pending_verification status; identity.user.registered.v1 is emitted.
  • Given an active user, when POST /api/v1/users/:id/suspend is called, then status becomes suspended; all sessions revoked; identity.user.suspended.v1 emitted.
  • Given a suspended user, when POST /api/v1/users/:id/reactivate is called, then status becomes active; identity.user.reactivated.v1 emitted.
  • Given a GDPR erasure request, when DELETE /api/v1/users/:id is called, then user transitions to deactivated; PII fields zeroed; identity.user.deactivated.v1 emitted.
  • Given a cross-tenant request (tenant admin in tenant A targeting user in tenant B), when endpoint is called, then 403 IDENT_CROSS_TENANT returned.

Technical notes:

  • Duplicate email check via UNIQUE(tenant_id, email) constraint — 409 IDENT_EMAIL_DUPLICATE.
  • GDPR erasure: first_name, last_name, email zeroed; deactivated_at set.
  • 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.v1 schema test green.

IDENT-US-004 — Email verification flow

FieldValue
Issue typeStory
SummarySend and confirm email verification token to activate user
Epic linkIDENT-EPIC-01
StatusTo Do
PriorityMust
Story points3
Labelsservice:identity, type:backend, type:api, slice:S0
Componentsuser-lifecycle
FR referencesFR-IDENT-USR-002
Legacy FR refsFR-IAM-USR-002
DependenciesIDENT-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_verification user, when POST /api/v1/auth/verify-email is called with valid token, then user status changes to active; identity.user.email_verified.v1 emitted.
  • Given an expired or already-used verification token, when confirm is called, then 422 error is returned.
  • Given POST /api/v1/auth/register for 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.v1 triggers communication-service email dispatch.

Definition of Done:

  • Integration test: token replay returns 422.
  • identity.user.email_verified.v1 schema test green.

IDENT-US-005 — Device registration and trust promotion

FieldValue
Issue typeStory
SummaryRegister device, promote to trusted, issue offline binding certificate
Epic linkIDENT-EPIC-02
StatusTo Do
PriorityMust
Story points8
Labelsservice:identity, type:backend, type:api, slice:S1
Componentsdevice-module
FR referencesFR-IDENT-DEV-001..004
Legacy FR refsFR-IAM-AUTH-007
DependenciesIDENT-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/devices is called with fingerprint + public key, then device is created with untrusted status; identity.device.registered.v1 emitted.
  • Given a trusted device, when POST /api/v1/me/devices/:id/bind-offline is called, then a binding certificate (≤ 30 days expiry) is returned; identity.device.bound_for_offline.v1 emitted.
  • Given an untrusted device, when bind-offline is called, then 422 IDENT_DEVICE_NOT_TRUSTED returned.
  • Given admin revokes device, when DELETE /api/v1/me/devices/:id is called, then certificate is revoked; identity.device.revoked.v1 emitted.

Technical notes:

  • Binding cert signed via KMS device cert key (IDENTITY_DEVICE_CERT_KEY_ARN).
  • identity.device.bound_for_offline.v1 consumed 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.v1 schema test green.
  • Telemetry: identity.device.bind.total.

IDENT-US-006 — Device revocation

FieldValue
Issue typeStory
SummaryRevoke device and invalidate offline binding certificate
Epic linkIDENT-EPIC-02
StatusTo Do
PriorityMust
Story points3
Labelsservice:identity, type:backend, type:api, slice:S1
Componentsdevice-module
FR referencesFR-IDENT-DEV-005
Legacy FR refs
DependenciesIDENT-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_bound device, when DELETE /api/v1/me/devices/:id is called, then device transitions to revoked; cert revoked_at set; identity.device.revoked.v1 emitted.
  • Given POST /api/v1/auth/logout-all, when called, then all associated devices transition to revoked.
  • Given a revoked device, when bind-offline is called, then 422 is 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

FieldValue
Issue typeStory
SummaryCreate, rotate, and revoke tenant API keys and service accounts
Epic linkIDENT-EPIC-03
StatusTo Do
PriorityMust
Story points5
Labelsservice:identity, type:backend, type:api, slice:S1
Componentsapi-key-module, service-account-module
FR referencesFR-IDENT-SVC-001..003
Legacy FR refsFR-IAM-SVC-001..003
DependenciesIDENT-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-keys is called, then API key is created; secret is returned exactly once; identity.api_key.issued.v1 emitted.
  • Given a rotation request, when POST /api/v1/api-keys/:id/rotate is called, then new secret is returned; old secret remains valid for overlap window (default 24 h).
  • Given revocation, when DELETE /api/v1/api-keys/:id is called, then key transitions to revoked state; subsequent use returns 401.
  • Given SUPER_ADMIN, when service account created via POST /api/v1/service-accounts, then clientId is globally unique; identity.service_account.created.v1 emitted.

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.v1 schema test green.

IDENT-US-008 — TOTP MFA enrollment and verification

FieldValue
Issue typeStory
SummaryEnroll TOTP second factor and use during login challenge
Epic linkIDENT-EPIC-04
StatusTo Do
PriorityMust
Story points5
Labelsservice:identity, type:backend, type:api, slice:S0
Componentsmfa-module
FR referencesFR-IDENT-MFA-001..003
Legacy FR refsFR-IAM-AUTH-005
DependenciesIDENT-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/enroll is called, then a TOTP provisioning URI is returned (RFC 6238 compatible); verifiedAt is null.
  • Given enrollment started, when POST /api/v1/me/mfa/totp/confirm is called with valid 6-digit code, then factor verifiedAt is set; identity.user.mfa_enrolled.v1 emitted.
  • Given MFA policy mfa_required=true for tenant, when user logs in, then login returns 401 IDENT_MFA_REQUIRED with challengeId; POST /api/v1/auth/mfa/verify completes login.
  • Given invalid TOTP code (3 consecutive failures), when verify is called, then 401 IDENT_MFA_FAILED; identity.user.mfa_challenge_failed.v1 emitted.

Technical notes:

  • TOTP seed stored KMS-wrapped in mfa_factors.secret.
  • challengeId is short-lived JWT (5 min) signed with KMS.

Definition of Done:

  • MFA enrollment + login integration test passes.
  • identity.user.mfa_enrolled.v1 schema test green.

IDENT-US-009 — WebAuthn second factor

FieldValue
Issue typeStory
SummaryRegister and use WebAuthn platform authenticator as MFA
Epic linkIDENT-EPIC-04
StatusTo Do
PriorityShould
Story points8
Labelsservice:identity, type:backend, type:api, slice:S0
Componentsmfa-module
FR referencesFR-IDENT-MFA-004..005
Legacy FR refs
DependenciesIDENT-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/options is called, then registration options are returned per FIDO2 spec.
  • Given registration options, when POST /api/v1/me/mfa/webauthn/register is called with authenticator response, then credential is stored; factor verifiedAt set.
  • Given a WebAuthn-enrolled user with MFA challenge, when authenticator assertion is provided via POST /api/v1/auth/mfa/verify, then login is completed; amr includes webauthn.
  • Given up to 5 WebAuthn credentials per user (INV — MFA invariant), when 6th registration is attempted, then 422 is 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

FieldValue
Issue typeStory
SummaryOIDC Authorization Code + PKCE flow with JIT user provisioning
Epic linkIDENT-EPIC-05
StatusTo Do
PriorityShould
Story points8
Labelsservice:identity, type:backend, type:api, slice:S2
Componentsfederation-module
FR referencesFR-IDENT-FED-001..003
Legacy FR refsFR-IAM-AUTH-008
DependenciesIDENT-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/start is called, then redirect to IdP authorization endpoint with PKCE parameters.
  • Given IdP callback with authorization code, when GET /api/v1/auth/oidc/:provider/callback is 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, then 409 IDENT_EXT_IDENTITY_MISMATCH returned.
  • Given IdP unavailable (circuit open), when OIDC flow is initiated, then 503 IDENT_FEDERATION_UNAVAILABLE returned to client.

Technical notes:

  • identity.external_identity.linked.v1 emitted 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.v1 schema test green.

IDENT-US-011 — Keycloak broker token exchange

FieldValue
Issue typeStory
SummaryRFC 8693 token exchange for Keycloak broker mode tenants
Epic linkIDENT-EPIC-05
StatusTo Do
PriorityShould
Story points5
Labelsservice:identity, type:backend, type:api, slice:S2
Componentsfederation-module
FR referencesFR-IDENT-FED-004
Legacy FR refs
DependenciesIDENT-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-exchange is called, then a Ghasi JWT with F01 claim shape is returned; keycloak_user_id is backfilled on user row.
  • Given an invalid or expired upstream token, when exchange is called, then 401 IDENT_INVALID_CREDENTIALS returned.

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

FieldValue
Issue typeStory
SummaryGET /me/access-context returns roles + memberships + effective modules
Epic linkIDENT-EPIC-06
StatusTo Do
PriorityMust
Story points5
Labelsservice:identity, type:backend, type:api, slice:S0
Componentsaccess-context-module
FR referencesFR-IDENT-CTX-001..003
Legacy FR refsFR-IAM-CTX-001, FR-IAM-PROV-003
DependenciesIDENT-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-context is 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/identity in 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

FieldValue
Issue typeStory
SummarySuper admin creates and updates the platform module catalogue
Epic linkIDENT-EPIC-07
StatusTo Do
PriorityMust
Story points3
Labelsservice:identity, type:backend, type:api, slice:S1
Componentslicensing-module
FR referencesFR-IDENT-LIC-001..003
Legacy FR refsFR-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/modules is called, then module is persisted; identity.license.module.created.v1 emitted.
  • Given an existing module with is_always_on=true, when delete or terminate is attempted, then 422 IDENT_LIC_ALWAYS_ON_IMMUTABLE returned.
  • Given a module update (description change), when PATCH /api/v1/licensing/modules/:code is called, then code remains unchanged (immutable per F05); identity.license.module.updated.v1 emitted.

Technical notes:

  • Module code is frozen at F05 — globally unique, immutable once created.
  • Seed modules platform.iam, platform.audit, ehr.core are always-on.

Definition of Done:

  • Immutability invariant test passes (code update blocked).
  • identity.license.module.created.v1 schema test green.

IDENT-US-014 — License assignment lifecycle

FieldValue
Issue typeStory
SummaryAssign, update status, and terminate license assignments at hierarchy nodes
Epic linkIDENT-EPIC-07
StatusTo Do
PriorityMust
Story points8
Labelsservice:identity, type:backend, type:api, slice:S1
Componentslicensing-module
FR referencesFR-IDENT-LIC-004..009
Legacy FR refsFR-LICN-004..009
DependenciesIDENT-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/assignments is called, then assignment is created; identity.license.assignment.created.v1 emitted.
  • Given an unsatisfied dependency (parent module not licensed at same/ancestor node), when assignment is attempted, then 422 IDENT_LIC_DEPENDENCY_NOT_LICENSED returned.
  • Given an active assignment, when PATCH /api/v1/licensing/assignments/:id/status changes to suspended, then status updated; history row appended; identity.license.assignment.status_changed.v1 emitted.
  • Given a terminated assignment, when any status mutation is attempted, then 422 IDENT_LIC_TERMINATED returned.
  • Given a trial assignment without effective_to, when create is attempted, then 422 returned (INV-09).

Technical notes:

  • Append-only license_assignment_history table.
  • 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.v1 schema test green.

IDENT-US-015 — Effective license resolver with inheritance

FieldValue
Issue typeStory
SummaryCompute effective license set via ancestor hierarchy walk with caching
Epic linkIDENT-EPIC-07
StatusTo Do
PriorityMust
Story points5
Labelsservice:identity, type:backend, type:api, slice:S1
Componentslicensing-module
FR referencesFR-IDENT-LIC-010..012
Legacy FR refsFR-LICN-EFF-001..005
DependenciesIDENT-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-down assignment at its grandparent, when GET /internal/identity/licensing/effective?nodeId=X is called, then module appears in effective set.
  • Given a direct exact assignment 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.v1 event 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

FieldValue
Issue typeStory
SummaryPublish JWKS and automate 90-day key rotation with overlap
Epic linkIDENT-EPIC-08
StatusTo Do
PriorityMust
Story points5
Labelsservice:identity, type:backend, type:api, slice:S0
Componentsjwks-module
FR referencesFR-IDENT-AUTH-010
Legacy FR refsFR-IAM-AUTH-010
DependenciesIDENT-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.json is called, then current RS256 public key(s) are returned; response Cache-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.v1 emitted; 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, kid header 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.v1 schema test green.

IDENT-US-017 — Platform quality gates (coverage + observability + GDPR)

FieldValue
Issue typeStory
SummaryCoverage gates, OTel instrumentation, GDPR erasure propagation verified
Epic linkIDENT-EPIC-08
StatusTo Do
PriorityMust
Story points5
Labelsservice:identity, type:backend, type:ops, slice:S0
Componentscross-cutting
FR referencesFR-IDENT-NFR-001..008
Legacy FR refsENH-IAM-005..007
DependenciesAll 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/telemetry initialized before NestFactory in main.ts.
  • GDPR erasure propagated via identity.user.deactivated.v1 event.

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.