Microservice cross-cutting

Cross-cutting service rules: idempotency, timeouts, queues, observability.

What this is

Cross-cutting service rules that apply regardless of language — idempotency, timeouts, contract versioning, at-least-once delivery. Its concerns are distributed-systems failure modes any service can hit: calls without timeouts, non-idempotent message consumers, breaking API changes shipped without a version bump, dual writes with no outbox, PII leaking into logs and traces. This is the stack every service-* profile pulls in alongside its language-specific rules.

How to onboard it

A repository picks up this stack by onboarding on a profile that includes it. redline init detects the profile from what is in the repository, so in most cases this is automatic:

terminal
$ npx redlinegate init  # detects the profile
$ npx redlinegate init --profile fullstack-node  # or name one

7 profiles pull these rules in: fullstack-node, service-dotnet, service-express, service-go, service-java, service-node, service-python.

Once onboarded, your files match this stack when they fit any of these globs:

  • **/*.java
  • **/*.go
  • **/*.py
  • **/*.cs
  • src/**/*.ts
  • cmd/**
  • internal/**
  • services/**

How to use it — 17 rules

Nothing to run. Once your profile includes Microservice cross-cutting, redline init renders these rules into your repository's AI tooling and the reviewer applies them on every pull request. When a review comment cites one of these ids, this table is where to look up what it catches and why.

Rule idSeverityCatches
microservices/missing-timeoutBLOCKEROutbound calls without timeouts. Every HTTP/gRPC/DB/queue call must have an explicit timeout; no infinite defaults.
microservices/unversioned-breaking-changeBLOCKERBreaking API changes without versioning. Removing/renaming fields, changing types, or tightening validation on a published API contract requires a version bump or an approved deprecation path. Additive changes only on existing versions.
microservices/pii-in-telemetryBLOCKERPII in logs, traces, or metrics labels — MSISDN, email, account IDs, tokens. Log opaque IDs only.
microservices/secrets-in-configBLOCKERSecrets in code, Dockerfiles, compose files, Helm values, or CI YAML — vault/secret-manager references only.
microservices/non-idempotent-consumerBLOCKERNon-idempotent consumers/handlers for at-least-once delivery (Kafka, SQS, retries on POST). Duplicate delivery must not double-apply.
microservices/swallowed-errorsBLOCKERSwallowed errors — catch-and-ignore, error logged then treated as success, or error branch returning 200.
microservices/retry-without-backoffHIGHRetries without backoff + jitter, or retrying non-idempotent operations.
microservices/missing-circuit-breakerHIGHMissing circuit breaker / bulkhead on dependencies known to degrade (peer services, third parties).
microservices/unauthenticated-endpointHIGHNew endpoint without authn/authz middleware — even "internal" services; do not trust the network.
microservices/dual-write-no-outboxHIGHDB work and message publish in one logical step without outbox or equivalent — dual-write inconsistency.
microservices/unbounded-workHIGHUnbounded work from external input: no pagination limits, unbounded batch sizes, unbounded payload accepted.
microservices/readiness-liveness-conflatedHIGHMissing health/readiness distinction: readiness must fail when dependencies are down; liveness must not.
microservices/missing-trace-propagationHIGHNew external call without propagating trace context / correlation ID.
microservices/incompatible-migrationHIGHMigration not backward-compatible with the currently deployed version (rename/drop column while old pods still run).
microservices/undocumented-configSUGGESTIONNew config value without a sane default and documentation in the service README/values file.
microservices/missing-metricsSUGGESTIONMetrics for new critical paths (rate, errors, duration) missing.
microservices/missing-dlq-handlingSUGGESTIONConsumer lag / DLQ handling absent on new consumers.

Expected output

A finding from this file, and every finding Redline produces, opens with a machine-readable first line — severity, then the rule id in brackets, then the problem in one line:

a finding from this file
Redline/BLOCKER [microservices/missing-timeout]: <one-line problem>

Ids are aggregated per rule, which is how the organisation finds out which rules earn their place and which only generate noise — so a finding without a valid id cannot be measured and counts as untagged. Of the 17 rules here, 6 BLOCKER, 8 HIGH and 3 SUGGESTION. Only a BLOCKER must not merge; a SUGGESTION may be dismissed without justification, and is never upgraded to get attention.

If a rule here fires constantly on code your team has deliberately decided to allow, that is the signal to raise with the standards owner — the rule id is what makes that conversation measurable — not to argue it away comment by comment.

How to edit it

Rules are edited in standards/stacks/microservices.md and nowhere else. The rendered copies in AGENTS.md, .github/copilot-instructions.md and .github/instructions/ are generated and are overwritten by the next render. A change here propagates to every onboarded repository as a pull request, so treat it as a production change.

  1. Edit the markdownstandards/ is the only place a human edits a rule. Everything under AGENTS.md, .github/copilot-instructions.md and .github/instructions/ is rendered from it and is overwritten by the next render.
  2. node scripts/assign-rule-ids.mjsAssigns a permanent <stack>/<slug> id to any new rule bullet and rewrites the file in place. Do not invent an id by hand. CI runs the same script with --check and fails if a rule is missing one.
  3. node scripts/render-self.mjsRe-renders this repository's own artifacts from the edited source. CI runs it with --check, so stale checked-in output fails the build.
  4. Bump standards/manifest.json → versionRequired in the same pull request as the rule change. Sync pull requests quote the version, so a repository's rendered artifacts always name where they came from.
  5. Add a CHANGELOG.md entryAlso in the same pull request. A standards change with no measurement is an opinion — record the seed score alongside it.
  6. node scripts/validate.mjsThe bundle self-check CI runs: manifest integrity, well-formed rule ids, the severity output contract surviving your edit, glob portability.

Rule ids are permanent. Rewording a rule is fine and keeps its id; renaming or removing an id orphans every historical telemetry record that cited it.

The full file

standards/stacks/microservices.md · 38 lines · 3.0 KB
# Microservice Cross-Cutting Review Rules

**Scope:** backend service code in any language. Applies in addition to the
language-specific rules, never instead of them.

Apply to all backend services regardless of language, in addition to the language-specific rules.

## BLOCKER — request changes

- `microservices/missing-timeout` — **Outbound calls without timeouts.** Every HTTP/gRPC/DB/queue call must have an explicit timeout; no infinite defaults.
- `microservices/unversioned-breaking-change` — **Breaking API changes without versioning.** Removing/renaming fields, changing types, or tightening validation on a published API contract requires a version bump or an approved deprecation path. Additive changes only on existing versions.
- `microservices/pii-in-telemetry` — **PII in logs, traces, or metrics labels** — MSISDN, email, account IDs, tokens. Log opaque IDs only.
- `microservices/secrets-in-config` — **Secrets in code, Dockerfiles, compose files, Helm values, or CI YAML** — vault/secret-manager references only.
- `microservices/non-idempotent-consumer` — **Non-idempotent consumers/handlers for at-least-once delivery** (Kafka, SQS, retries on POST). Duplicate delivery must not double-apply.
- `microservices/swallowed-errors` — **Swallowed errors** — catch-and-ignore, error logged then treated as success, or error branch returning 200.

## HIGH

- `microservices/retry-without-backoff` — Retries without backoff + jitter, or retrying non-idempotent operations.
- `microservices/missing-circuit-breaker` — Missing circuit breaker / bulkhead on dependencies known to degrade (peer services, third parties).
- `microservices/unauthenticated-endpoint` — New endpoint without authn/authz middleware — even "internal" services; do not trust the network.
- `microservices/dual-write-no-outbox` — DB work and message publish in one logical step without outbox or equivalent — dual-write inconsistency.
- `microservices/unbounded-work` — Unbounded work from external input: no pagination limits, unbounded batch sizes, unbounded payload accepted.
- `microservices/readiness-liveness-conflated` — Missing health/readiness distinction: readiness must fail when dependencies are down; liveness must not.
- `microservices/missing-trace-propagation` — New external call without propagating trace context / correlation ID.
- `microservices/incompatible-migration` — Migration not backward-compatible with the currently deployed version (rename/drop column while old pods still run).

## SUGGESTION

- `microservices/undocumented-config` — New config value without a sane default and documentation in the service README/values file.
- `microservices/missing-metrics` — Metrics for new critical paths (rate, errors, duration) missing.
- `microservices/missing-dlq-handling` — Consumer lag / DLQ handling absent on new consumers.

## What NOT to flag

- Existing contract shapes the PR does not modify.
- Infrastructure choices (broker, DB engine) already established in the service.