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:
$ 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**/*.cssrc/**/*.tscmd/**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 id | Severity | Catches |
|---|---|---|
microservices/missing-timeout | BLOCKER | Outbound calls without timeouts. Every HTTP/gRPC/DB/queue call must have an explicit timeout; no infinite defaults. |
microservices/unversioned-breaking-change | BLOCKER | 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 | BLOCKER | PII in logs, traces, or metrics labels — MSISDN, email, account IDs, tokens. Log opaque IDs only. |
microservices/secrets-in-config | BLOCKER | Secrets in code, Dockerfiles, compose files, Helm values, or CI YAML — vault/secret-manager references only. |
microservices/non-idempotent-consumer | BLOCKER | Non-idempotent consumers/handlers for at-least-once delivery (Kafka, SQS, retries on POST). Duplicate delivery must not double-apply. |
microservices/swallowed-errors | BLOCKER | Swallowed errors — catch-and-ignore, error logged then treated as success, or error branch returning 200. |
microservices/retry-without-backoff | HIGH | Retries without backoff + jitter, or retrying non-idempotent operations. |
microservices/missing-circuit-breaker | HIGH | Missing circuit breaker / bulkhead on dependencies known to degrade (peer services, third parties). |
microservices/unauthenticated-endpoint | HIGH | New endpoint without authn/authz middleware — even "internal" services; do not trust the network. |
microservices/dual-write-no-outbox | HIGH | DB work and message publish in one logical step without outbox or equivalent — dual-write inconsistency. |
microservices/unbounded-work | HIGH | Unbounded work from external input: no pagination limits, unbounded batch sizes, unbounded payload accepted. |
microservices/readiness-liveness-conflated | HIGH | Missing health/readiness distinction: readiness must fail when dependencies are down; liveness must not. |
microservices/missing-trace-propagation | HIGH | New external call without propagating trace context / correlation ID. |
microservices/incompatible-migration | HIGH | Migration not backward-compatible with the currently deployed version (rename/drop column while old pods still run). |
microservices/undocumented-config | SUGGESTION | New config value without a sane default and documentation in the service README/values file. |
microservices/missing-metrics | SUGGESTION | Metrics for new critical paths (rate, errors, duration) missing. |
microservices/missing-dlq-handling | SUGGESTION | Consumer 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:
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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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
# 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.