Skip to main content

Numbering Service — Jira-Ready Epics & User Stories

Status: populated Owner: Commerce Engineering Last updated: 2026-04-20 Service prefix: NUM Scope: MSISDN/short-code/alpha-ID inventory + lifecycle, reservation/hold/release workflow, and per-tenant pool management.


Epic Summary

Epic IDTitleStoriesPoints
EP-NUM-01MSISDN/Short-Code Inventory + Lifecycle (lease, recall, expiry)US-NUM-001 – US-NUM-00631
EP-NUM-02Number Reservation, Hold, and Release WorkflowUS-NUM-007 – US-NUM-01015
EP-NUM-03Per-Tenant Number Pool ManagementUS-NUM-011 – US-NUM-01418

Totals: 3 epics · 14 stories · 64 points


EP-NUM-01 · MSISDN/Short-Code Inventory + Lifecycle

Context: The canonical inventory ledger for every sendable identity (MSISDN, short code, alpha ID) with explicit lifecycle states and audit trail. Hot-path lease validation, MNO block import, recall flows, auto-renewal, and post-recall quarantine cool-down.

US-NUM-001 · Bulk Import MSISDN Block from MNO

Type: Feature | Points: 8

Description: As the numbering operations engineer, I need to import a signed MSISDN block CSV from a Mobile Network Operator so that the new block is added to the AVAILABLE inventory for tenant assignment.

Acceptance Criteria:

  • POST /v1/admin/numbering/blocks/import with {operatorId, signature, csvFile} verifies signature against numbering.mno_signing_keys
  • Invalid signature → 422 SIGNATURE_INVALID + audit event
  • E.164 validation (+93[0-9]{9}) and prefix-vs-operator check
  • Duplicate MSISDNs reported as duplicate (skipped, not errored)
  • Summary {inserted, duplicates, invalid} returned + numbering.block.imported{operatorId, count} published
  • Inserted MSISDNs start in AVAILABLE with originatingBlockId set
  • Future validFrom excludes from ListAvailable() until date passes

US-NUM-002 · Validate Sender Identity Lease (Hot Path)

Type: Feature | Points: 5

Description: As the sms-orchestrator, I need to validate that a tenant currently holds an active lease on a sender identifier so that I can reject outbound messages using unleased or recalled identifiers.

Acceptance Criteria:

  • gRPC ValidateLease({identifier, tenantId}) → P95 ≤ 20 ms cache-hit
  • Cache miss → PG fallback P95 ≤ 50 ms; 60 s cache TTL
  • LEASED to caller + not expired → valid: true
  • LEASED to other tenant → valid: false, reason: "WRONG_TENANT"
  • SUSPENDED → valid: false, reason: "LEASE_SUSPENDED"
  • Unknown identifier → valid: false, reason: "NOT_REGISTERED"
  • Redis down → PG fallback P95 ≤ 80 ms + alert

US-NUM-003 · Lease Identifier to Tenant

Type: Feature | Points: 5

Description: As a tenant via customer-portal, I need to lease an identifier to my tenant for a defined term so that I can begin using it for outbound messaging.

Acceptance Criteria:

  • POST /v1/numbers/{identifier}/lease with {tenantId, term, autoRenew} transitions RESERVED|HELD (own) → LEASED
  • AVAILABLE direct lease only with bypass permission
  • HELD by other tenant → 409 HELD_BY_OTHER_TENANT
  • effectiveFrom = now(), effectiveUntil = now() + term; numbering.lease.created published
  • At quota → 403 QUOTA_EXCEEDED with currentCount + quota
  • In QUARANTINE → 409 QUARANTINE_ACTIVE with availableAt

US-NUM-004 · Recall Identifier (Regulator / Abuse / Tenant-Initiated)

Type: Feature | Points: 5

Description: As a numbering admin or system actor, I need to recall a leased identifier and put it into post-recall quarantine so that the identifier is removed from the tenant and held off the available pool to prevent abuse rotation.

Acceptance Criteria:

  • POST /v1/numbers/{identifier}/recall with {reason, actorId, ticketId?} → RECALLED + event
  • REGULATOR_ORDER and ABUSE require ticketId; missing → 422
  • MSISDN → 90-day QUARANTINE; short code → 30 days; alpha → 0 days
  • Cached ValidateLease invalidated within 1 s
  • compliance.tenant.suspended triggers cascade recall of all tenant leases under one ticket

US-NUM-005 · Lease Auto-Renewal

Type: Feature | Points: 5

Description: As a tenant with autoRenew enabled, I need to have my lease automatically renewed before expiry so that my active sender identities do not lapse and disrupt campaigns.

Acceptance Criteria:

  • Daily renewal job extends leases with autoRenew=true whose effectiveUntil is within 7 days
  • Billing rejection → no renewal + numbering.lease.renewal_failed{reason: "BILLING_REJECTED"}
  • Suspended tenant → skip with TENANT_SUSPENDED
  • New row in numbering.lease_history referencing prior leaseId
  • Renewal vs current quota: existing leases grandfathered; warning event emitted

US-NUM-006 · Quarantine Cool-Down Enforcement

Type: Feature | Points: 3

Description: As the platform integrity guard, I need to prevent re-leasing of recalled identifiers until the quarantine period elapses so that bad actors cannot rotate phishing numbers across tenant identities.

Acceptance Criteria:

  • Lease attempt on QUARANTINE identifier → 409 QUARANTINE_ACTIVE with availableAt
  • Sweep job (5 min) transitions to AVAILABLE on cool-down end + numbering.identifier.released event
  • Self-bypass forbidden (same tenant who recalled cannot re-lease)
  • Admin override via POST /v1/admin/numbers/{id}/quarantine/release with justification + audit
  • Daily override report surfaced in compliance dashboard

EP-NUM-02 · Number Reservation, Hold, and Release Workflow

Context: Two-phase pinning model. RESERVED (15 min) for browsing; HELD (24h) for build-phase. Concurrent attempts resolved by partial unique index. TTL auto-release driven by Redis keyspace notifications with PG sweep safety net.

US-NUM-007 · Reserve Identifier (15-min TTL)

Type: Feature | Points: 5

Description: As a tenant browsing the customer portal, I need to reserve a candidate identifier for 15 minutes while I evaluate it so that it does not disappear from the pool while I am deciding.

Acceptance Criteria:

  • POST /v1/numbers/{identifier}/reserve → AVAILABLE → RESERVED + Redis num:reserve:{id} TTL 900s
  • Non-AVAILABLE → 409 with current state
  • Concurrent attempts → exactly one wins via partial unique index (identifier) WHERE state IN ('RESERVED','HELD','LEASED')
  • TTL expiry → AVAILABLE + numbering.reservation.released{reason: "TTL_EXPIRED"}
  • Tenant exceeds maxActiveReservations → 403 RESERVATION_QUOTA
  • numbering.reservation.held{kind: "RESERVE"} event published

US-NUM-008 · Hold Identifier (24-h TTL)

Type: Feature | Points: 3

Description: As a tenant in a build-phase workflow, I need to promote a reservation to a 24-hour hold so that I have a longer window to complete KYC, billing, or campaign config before commit.

Acceptance Criteria:

  • POST /v1/numbers/{identifier}/hold → RESERVED (own) → HELD with TTL 86400s
  • Wrong tenant or state → 409
  • HELD expiry → AVAILABLE + event
  • Starter plan limit > 5 holds → 403 HOLD_QUOTA
  • PG + Redis updated atomically; Redis-only failure → PG row remains, sweep handles release

US-NUM-009 · Release Reservation or Hold

Type: Feature | Points: 2

Description: As a tenant, I need to release a reservation or hold I no longer need so that the identifier returns to the available pool and I free a quota slot.

Acceptance Criteria:

  • POST /v1/numbers/{identifier}/release → AVAILABLE + Redis key delete
  • LEASED → 409 USE_RECALL_FOR_LEASES
  • Held by other tenant → 403
  • numbering.reservation.released{reason: "TENANT_RELEASE"} published
  • Releasing alpha-ID reservation does not cascade to leased numbers

US-NUM-010 · Auto-Release on TTL Expiry

Type: Feature | Points: 5

Description: As the pool integrity guard, I need to automatically release expired reservations and holds within 2 seconds of TTL so that the pool stays current and tenants do not see stale unavailable identifiers.

Acceptance Criteria:

  • Redis keyspace expiry → worker transitions PG row within 2 s
  • Fallback sweep job every 60 s for missed notifications
  • numbering.reservation.released{reason: "TTL_EXPIRED"} published
  • Race-safe via UPDATE … WHERE state = 'RESERVED'
  • Metrics: num_reservations_auto_released_total, num_reservations_active

EP-NUM-03 · Per-Tenant Number Pool Management

Context: Tenant-scoped quotas (MSISDN, short code, alpha, active reservations). Vanity short-code long-term reservation with auto-renew and extended quarantine. Pool capacity monitoring with exhaustion alerts. Tenant browse with filters and pricing quotes.

US-NUM-011 · Per-Tenant Pool Quota Enforcement

Type: Feature | Points: 5

Description: As the platform, I need to enforce per-tenant quotas on leased MSISDNs, short codes, and alpha IDs so that tenant plans and billing tiers are honored.

Acceptance Criteria:

  • At quota → 403 QUOTA_EXCEEDED {quota, current, identifierClass}
  • numbering.tenant_quotas cached in Redis 5 min TTL
  • Admin update emits numbering.quota.changed + invalidates cache immediately
  • Plan upgrade via billing-service applies within 60 s
  • Reservation quota independent: exceed → RESERVATION_QUOTA

US-NUM-012 · Vanity Short-Code Long-Term Reservation

Type: Feature | Points: 5

Description: As a premium tenant, I need to reserve a vanity short code for a multi-year term with auto-renew so that my brand short code is protected from competitors.

Acceptance Criteria:

  • POST /v1/numbers/{shortCode}/lease with {vanityFlag: true, term: "1y"|"3y", autoRenew} creates lease
  • Non-vanity-eligible short code → 422 NOT_VANITY_ELIGIBLE
  • numbering.leases.vanity = true; vanity tier pricing applied
  • Recall → 365-day quarantine (configurable)
  • Renewal failure → 14-day grace + daily reminder events

US-NUM-013 · Pool Capacity Monitoring

Type: Feature | Points: 3

Description: As a platform operator, I need alerts when an MSISDN block or short-code range is approaching exhaustion so that I can request a new block from the MNO before tenants are blocked.

Acceptance Criteria:

  • numbering_block_capacity_remaining per (operatorId, blockPrefix) exported
  • Remaining < 5 % → numbering.pool.exhausted event
  • Remaining < 1 % → SEV1 numbering_block_critical + page
  • GET /v1/admin/numbering/pools/capacity returns utilisation + projected exhaustion
  • New block import recalculates + auto-resolves alerts
  • Short-code range alert references manual ATRA-request runbook

US-NUM-014 · Tenant Pool Browse with Filters

Type: Feature | Points: 5

Description: As a tenant via customer portal, I need to browse the available identifier pool with filters so that I can choose identifiers fitting my campaign budget and routing needs.

Acceptance Criteria:

  • GET /v1/numbers/available?identifierClass=&operatorId=&prefix=&vanity=&page= paginated with per-tenant pricing
  • Cursor-based pagination, stable sort
  • Per-tenant operator allowlist filters results
  • Rate limit 60 req/min per tenant → 429 with Retry-After
  • Wildcard alpha search ?identifierClass=ALPHA&pattern=BANK*

End of report — 3 epics, 14 stories, 64 points