Static Documentation

Service Standards and Readiness

Version latest · Updated 2026-06-28
Interactive docs View on GitHub

Service Standards, Readiness and Catalog Events#

This document describes the Service Catalog readiness model in IncidentRelay: service standards, readiness checks, the in-process catalog event adapter, and the service timeline API.

Goals#

Service Standards define what a service must have before it is considered operationally ready. A standard is applied to services by applies_to selectors. Readiness evaluates all standards that match a service and stores an aggregated service readiness state.

Catalog Events provide one internal entry point for service catalog changes. A view or service layer emits a domain event, and the adapter writes timeline events and runs readiness reconciliation when needed.

Service/standard action
└── emit_service_catalog_event(...) or emit_group_service_catalog_event(...)
    ├── writes ServiceEvent when the event is service-scoped
    └── runs readiness reconciliation

ServiceEvent is intentionally service-scoped. Group-level changes such as standards/checks do not create a direct timeline row for every service. Instead, they trigger readiness reconciliation for the group. If a service readiness batch is created or changed, the readiness evaluator writes service timeline events.

Main concepts#

Service Standard#

A standard is a named set of readiness checks for a group.

Important fields:

FieldDescription
group_idGroup that owns the standard.
slugStable unique identifier inside the group.
nameHuman-readable name.
descriptionOptional explanation.
applies_toSelector object that decides which services the standard applies to.
enabledDisabled standards are ignored by readiness evaluation unless explicitly listed with include_disabled=1.

applies_to#

applies_to is an object. Empty object means the standard applies to all services in the group.

Supported selector keys:

KeyValues
kindstechnical, business
lifecyclesexperimental, development, production, deprecated, retired
tierstier_1, tier_2, tier_3, tier_4
criticalitieslow, medium, high, critical
environmentsproduction, staging, development, testing, shared
service_typesapi, web, database, queue, cache, worker, cron, network, storage, infrastructure, external, other

Example:

{
  "kinds": ["technical"],
  "lifecycles": ["production"],
  "tiers": ["tier_1", "tier_2"],
  "criticalities": ["critical", "high"]
}

Standard Check#

A check is one requirement inside a standard.

Important fields:

FieldDescription
standard_idParent standard.
slugStable identifier inside the standard.
check_typeBuilt-in evaluator type.
configurationType-specific settings.
weightPoints contributed to the standard score, from 1 to 100.
severityinfo, warning, critical.
requiredRequired failures are tracked separately.
positionSort order in UI/evaluation output.
enabledDisabled checks are ignored.

Supported check types:

TypePurposeCommon configuration
field_presentService field must be non-empty.{ "field": "metadata.owner" }
field_equalsService field must match an expected value.{ "field": "environment", "value": "production" }
owner_existsService must have at least one active default stakeholder.{}
active_rotation_existsService must have a default rotation with active members.{}
escalation_policy_existsService must have a default escalation policy.{ "require_rules": true }
notification_policy_existsService must have a notification policy.{ "require_rules": true, "require_channels": true }
service_channel_existsService/team must have at least one usable notification channel.{}
route_existsService must be targeted by at least one route or service match rule.{}
match_rule_existsService must have at least one enabled service match rule.{}
runbook_existsService must have runbooks.{ "minimum": 1 }
link_type_existsService must have a link of a type.{ "link_type": "dashboard" }
dependency_existsService must have at least one dependency.{ "direction": "upstream" }
dependency_cycle_absentService dependency graph must not contain a cycle.{}
metadata_valueService metadata key must equal a value.{ "key": "pci", "value": true }

Readiness State#

Readiness state is the current aggregate for one service.

Statuses:

StatusMeaning
readyApplicable checks passed.
warningSome non-critical/non-required checks failed.
not_readyRequired or critical checks failed.
not_applicableNo enabled standards apply to this service.
unknownReadiness was not evaluated yet or evaluation failed unexpectedly.

The state contains score and failure counters:

{
  "status": "not_ready",
  "score": 72,
  "standards_count": 1,
  "checks_count": 6,
  "failed_count": 2,
  "failed_required_count": 1,
  "failed_critical_count": 1,
  "batch_uid": "...",
  "evaluated_at": "2026-06-28T09:32:10Z"
}

Built-in preset#

Endpoint:

POST /api/services/standards/presets/basic-operational

The preset creates/restores basic-operational-readiness for a group. It applies to production technical services and checks core operational requirements:

  • active owner/default stakeholder;
  • escalation policy with rules;
  • notification policy with rules and channels;
  • route or matching coverage;
  • at least one runbook;
  • dependency graph without cycles.

The preset emits a group catalog event and reconciles readiness for the group.

Catalog event adapter#

The adapter lives in:

app/services/service_catalog/events.py

Use it instead of calling timeline and readiness functions directly from views.

Service-scoped events#

emit_service_catalog_event(
    service,
    category="configuration",
    event_type="service_runbook.created",
    title="Service runbook created",
    summary=runbook.title,
    source_ref=f"service_runbook:{runbook.id}",
    external_url=runbook.url,
    actor_user=current_user(),
    payload={"runbook": service_runbook_snapshot(runbook)},
    readiness_trigger="service_runbook_created",
)

This writes a ServiceEvent row and reconciles readiness by default.

Group-scoped events#

emit_group_service_catalog_event(
    group_id,
    category="readiness",
    event_type="service_standard.updated",
    title="Service standard updated",
    actor_user=current_user(),
    before=before_snapshot,
    after=after_snapshot,
    readiness_trigger="standard_updated",
)

This does not write a direct timeline row because ServiceEvent is service-scoped. It reconciles group readiness.

Manual readiness reconciliation#

reconcile_service_catalog_readiness(
    service,
    trigger="manual_evaluate",
    actor_user=current_user(),
)

Use this for explicit Evaluate readiness API/UI actions when no extra domain event should be created.

Readiness scopes#

ScopeUse case
noneTimeline event only, no readiness update.
serviceReconcile the changed service.
servicesReconcile the changed service plus explicit affected service IDs.
groupReconcile every readable/active service in a group.
dependency_componentReconcile services in a dependency component.

Event naming conventions#

Use stable dotted names:

service.created
service.updated
service.deleted
service_owner.created
service_owner.updated
service_owner.deleted
service_link.created
service_link.updated
service_link.deleted
service_runbook.created
service_runbook.updated
service_runbook.deleted
service_dependency.created
service_dependency.updated
service_dependency.deleted
service_dependency.downstream_created
service_dependency.downstream_updated
service_dependency.downstream_deleted
service_match_rule.created
service_match_rule.updated
service_match_rule.deleted
service_standard.created
service_standard.updated
service_standard.deleted
service_standard_check.created
service_standard_check.updated
service_standard_check.deleted
service_standard_preset.applied
readiness.evaluated
readiness.score_changed

Recommended categories:

CategoryEvents
configurationservice, owner, link, runbook, dependency changes
routingservice match rule changes
readinessstandards, checks, readiness evaluation
statusservice status updates
alertingalert-driven future service catalog events

Timeline API#

Endpoint:

GET /api/services/{service_id}/timeline

Query parameters:

ParameterDescription
limitNumber of events to return, 1..200, default 50.
categoryOptional category filter.
event_typeOptional event type filter.
beforeCursor timestamp from next_cursor.before.
before_idCursor ID from next_cursor.before_id.

Example response:

{
  "items": [
    {
      "id": 123,
      "uid": "...",
      "service_id": 10,
      "group_id": 1,
      "team_id": 2,
      "category": "configuration",
      "event_type": "service_runbook.created",
      "title": "Service runbook created",
      "summary": "RabbitMQ cluster partition",
      "source": "incidentrelay",
      "source_ref": "service_runbook:42",
      "external_url": "https://docs.example.com/runbooks/rabbitmq",
      "actor": {
        "type": "user",
        "user_id": 7,
        "display_name": "Alice",
        "email": "alice@example.com",
        "label": null
      },
      "severity": null,
      "status": null,
      "occurred_at": "2026-06-28T09:32:10Z",
      "recorded_at": "2026-06-28T09:32:10Z",
      "schema_version": 1,
      "payload": {
        "runbook": {
          "id": 42,
          "title": "RabbitMQ cluster partition"
        }
      }
    }
  ],
  "next_cursor": {
    "before": "2026-06-28T09:32:10Z",
    "before_id": 123
  }
}

API summary#

Standards#

MethodPathDescription
GET/api/services/standardsList standards visible to the user.
POST/api/services/standardsCreate a standard.
GET/api/services/standards/{standard_id}Get one standard with checks.
PUT/api/services/standards/{standard_id}Update a standard.
DELETE/api/services/standards/{standard_id}Soft-delete a standard.
POST/api/services/standards/presets/basic-operationalCreate/restore the built-in preset.

Checks#

MethodPathDescription
GET/api/services/standards/{standard_id}/checksList checks.
POST/api/services/standards/{standard_id}/checksCreate a check.
GET/api/services/standards/{standard_id}/checks/{check_id}Get one check.
PUT/api/services/standards/{standard_id}/checks/{check_id}Update a check.
DELETE/api/services/standards/{standard_id}/checks/{check_id}Soft-delete a check.

Readiness#

MethodPathDescription
GET/api/services/{service_id}/readinessGet current readiness batch.
POST/api/services/{service_id}/readiness/evaluateForce readiness evaluation.

Events#

MethodPathDescription
GET/api/services/{service_id}/timelineList service timeline events.

Access control#

  • Standards are group-scoped.
  • Listing standards requires group read access.
  • Creating/updating/deleting standards and checks requires group write access.
  • Service readiness and timeline require read access to the service team.
  • Manual readiness evaluation requires write access to the service team.

Error responses#

Common error codes:

ErrorHTTPMeaning
validation_error400Request body did not pass schema validation.
service_standard_invalid400Standard domain validation failed.
service_standard_check_invalid400Check domain validation failed.
service_standard_not_found404Standard was not found.
service_standard_check_not_found404Check was not found.
service_not_found404Service was not found.
group_not_found404Group was not found.
timeline_before_invalid400Timeline cursor timestamp is not valid ISO 8601.

Implementation checklist#

  • Use emit_service_catalog_event() for service-scoped changes.
  • Use emit_group_service_catalog_event() for standard/check/preset changes.
  • Use reconcile_service_catalog_readiness() for manual evaluation without a separate domain event.
  • Keep write_audit(...) in views as the security/API audit log.
  • Do not write group-level standard events directly into every service timeline.
  • Add snapshots to app/services/service_catalog/snapshots.py when introducing a new catalog object type.