Build an in-memory REST API for managing service deployments across environments: register services, create deployments with environment promotion rules, claim and execute them, handle failures with retries and backoff, track rollbacks, and maintain deployment history.
- GET /api/health ->
200with{ "status": "ok" }
- POST /api/services
- Body:
name: non-empty stringrepository: non-empty stringenvironments: non-empty array of environment name strings (ordered as the promotion chain)maxAttempts: positive integerbackoffSeconds: positive integer
- Deduplicate
environmentswhile preserving first-occurrence order. - Returns
201.
- Body:
- GET /api/services
- Returns all services in insertion order.
- Response:
{ services: [...] }
- POST /api/deployments
- Body:
{ serviceId, environment, commitHash, now? } serviceIdmust reference an existing service.environmentmust be in the service's environments list.commitHash: non-empty string.- Promotion rule: if the environment is not the first in the chain, there must be a
LIVEdeployment for the same(serviceId, commitHash)in the immediately preceding environment. - Idempotent: on duplicate
(serviceId, environment, commitHash), return the existing deployment with status200. - On first create: status
PENDING,nextAttemptAt = now, returns201. nowdefaults to current time if omitted.
- Body:
- POST /api/deployments/claim
- Body:
{ now? } - Claim the next due deployment:
- Only
PENDINGdeployments whosenextAttemptAt <= now - Sort by
nextAttemptAtascending - Tie-break by
createdAtascending
- Only
- When claimed: status ->
DEPLOYING,attempts += 1,claimedAt = now - Returns
200with{ deployment } - Returns
204if nothing is due.
- Body:
- POST /api/deployments/:id/complete
- Body:
{ now? } - Only valid from
DEPLOYING. - Sets status ->
LIVE,completedAt = now,nextAttemptAt = null. - Any previous
LIVEdeployment for the same(serviceId, environment)is set toSUPERSEDED. - Returns
200with the updated deployment.
- Body:
- POST /api/deployments/:id/fail
- Body:
{ error, now? } - Only valid from
DEPLOYING. - Stores
lastError = error. - If
attempts < maxAttempts(from the service):- status ->
PENDING, clearclaimedAt nextAttemptAt = now + attempts * backoffSeconds(in seconds)
- status ->
- Otherwise:
- status ->
DEAD,completedAt = now,nextAttemptAt = null
- status ->
- Returns
200with the updated deployment.
- Body:
- POST /api/deployments/:id/rollback
- Body:
{ now? } - Only valid when the deployment status is
LIVE. - Find the most recent
SUPERSEDEDdeployment for the same(serviceId, environment). - If found: set it to
LIVE, set the current deployment toROLLED_BACK. - If not found: return
400(nothing to roll back to). - Returns
200with{ rolledBack, revived }.
- Body:
- GET /api/deployments/:id
- Return the current deployment snapshot.
- Return
404if missing.
- GET /api/services/:id/deployments
- Optional query:
environment,status - Returns deployments for that service, sorted by creation order.
- Response:
{ deployments: [...] } - Return
404for missing service.
- Optional query:
- GET /api/deployments/:id/history
- Response:
{ history: [...] } - Sort by
atascending. - Each entry:
{ type, at, attempt } - History entry types:
CREATED(when deployment is first created, attempt = 0)CLAIMED(when claimed by a worker)DEPLOYED(when completed successfully)FAILED(when failed, either retrying or going dead)DEAD(when all retries exhausted)SUPERSEDED(when replaced by a newer deployment)ROLLED_BACK(when rolled back)REVIVED(when restored to LIVE from a rollback)
- Response:
- Use in-memory storage only.
- IDs can be any unique string format.
- Timestamps in responses must be ISO strings.
- Any supplied
nowmust be a full ISO datetime string with both time and timezone. Date-only values like2026-03-20are invalid. - Return
400when a suppliednowoverride is not a valid ISO datetime. - Return
404for missing service/deployment IDs.
npm install && npm start