Skip to content

feat(us3): implement the todos-api operational contract - #15

Merged
EstebanGZam merged 7 commits into
mainfrom
feat/us3-operational-contract
Sep 1, 2026
Merged

feat(us3): implement the todos-api operational contract#15
EstebanGZam merged 7 commits into
mainfrom
feat/us3-operational-contract

Conversation

@EstebanGZam

@EstebanGZam EstebanGZam commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

What changes

Implements the todos-api operational contract for full-platform User Story 3:

  • unauthenticated startup, readiness, and liveness probes plus Prometheus metrics;
  • end-to-end X-Request-Id correlation, including the Redis audit event;
  • default-off, non-secret runtime configuration;
  • bounded, best-effort Redis audit publication with an error listener and circuit breaker;
  • a non-root distroless runtime that contains every module loaded by server.js;
  • a patched, digest-pinned runtime base without HIGH or CRITICAL findings.

Why

Redis carries the audit log, not todo state. Previously an unhandled Redis error event could terminate the Node process, and a publish that never settled could couple request behavior to a logging dependency. Health endpoints mounted behind JWT would also answer 401 and cause Kubernetes to restart a healthy process.

Runtime verification found one additional packaging defect: server.js required ./operational, but the final image did not copy operational.js, so the container could not start. The added packaging contract captures that failure before the Dockerfile correction.

The previous runtime digest also contained CVE-2026-14456 in libssl3t64. The current pinned distroless digest contains the patched package; no vulnerability suppression was added.

Tasks

  • microservice-app-gitops/specs/009-full-platform-rollout T075
  • microservice-app-gitops/specs/009-full-platform-rollout T080
  • The task register is updated in this pull request — the authoritative register is owned by another repository, so the paired update is microservice-app-gitops#85.
  • Every task ticked in the paired register update was verified by locating its named artifact.

How it is verified

  • Commit 590b2e8 introduced the operational contract tests red before commit 87fd274 implemented them.
  • Commit 515fbda introduced the runtime packaging test red with: server.js requires ./operational, but the runtime image does not copy operational.js. Commit 48627b2 made that test pass by packaging the module.
  • npm test: 17 tests passed, including the real Redis Testcontainers integration; 0 failed.
  • npm audit --omit=dev --audit-level=high: found 0 vulnerabilities.
  • npx --yes @stoplight/spectral-cli@6 lint contracts/openapi.yaml contracts/asyncapi.yaml --ruleset .spectral.yaml: no errors.
  • docker build --no-cache --progress=plain -t microtodosuite/todos-api-us3:local .: succeeded and copied operational.js into the final stage.
  • Runtime smoke test: all three health endpoints returned 200, the image ran as 65532:65532, and X-Request-Id: smoke-correlation-id was echoed unchanged.
  • Trivy v0.72.0 against the exact rebuilt image, with the CI policy --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1: Debian findings 0, Node package findings 0, exit 0.
  • Mutation checks previously exercised the independent failure paths: removing the Redis error listener, restoring direct publish, or mounting probes after JWT each made its corresponding test fail.

Risk and rollback

The additive audit correlationId field and Redis failure handling are the main behavior changes. A bad timeout or breaker threshold could reduce audit delivery during an outage, but cannot fail the todo request. Roll back by reverting the implementation commits; revert the digest commit separately if the upstream image causes a runtime regression.

What this PR does not do

It does not deploy the service, change a GitOps overlay, activate a full-profile environment, suppress a vulnerability, or create an approval/acceptance artifact. Deployment remains owned by microservice-app-gitops and ArgoCD.

EstebanGZam and others added 3 commits August 26, 2026 14:17
T075. The Redis cases are why this file exists.

todos-api publishes an audit line to Redis on every create and delete, but
Redis is a *logging* dependency: losing it must degrade the audit trail, not the
API. Today it does neither safely. `publish` is called with no callback, and
node-redis surfaces those failures as an 'error' event on the client, which
terminates the Node process when nothing is listening. A Redis restart takes the
whole todo API down with it.

Three separate failure shapes are covered because they fail differently: an
error callback, a client 'error' event, and a publish that never settles. The
last would otherwise hold every create open indefinitely.

The health tests assert the probes answer without a JWT. /todos sits behind
expressjwt, and a probe mounted after that middleware answers 401, which
Kubernetes reads as unhealthy — restarting every pod forever while the
application is fine.

Fails until T080.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NLzNfeedbe2xFTjwrBPPBg
T080. Health probes, correlation, non-secret runtime configuration, and a Redis
publish that cannot take the API down with it. Makes T075 pass.

The Redis client now has an 'error' listener. Without one, node-redis's error
event terminates the process, so a Redis restart was an API outage — for a
dependency that only carries the audit log.

publishAudit contains all three failure shapes: an error callback, a synchronous
throw, and a call that never settles. It always resolves rather than rejecting,
by design — the caller is a todo write that has already succeeded, and a
rejected promise would invite someone to await it and put Redis latency back on
the request path. A circuit breaker stops publishing to a Redis that is clearly
down, so a logging outage does not become a latency incident.

Probes and /metrics are mounted before expressjwt. A probe behind it answers
401, which Kubernetes reads as unhealthy, restarting every pod forever.

The audit payload gains correlationId, so an audit line can be tied back to the
request that caused it without going through Zipkin. This is additive: the
consumer reads only zipkinSpan and prints the rest, and the AsyncAPI schema sets
no additionalProperties restriction. The existing contract test asserts the new
field is present rather than ignoring it, so a regression that stops emitting it
still fails.

Verified by mutation, each caught by its own test: removing the error listener,
publishing directly again, and mounting the probes after expressjwt.

15 tests pass. The Docker-backed integration suite was not run locally
(testcontainers is not installed here); it is unchanged by this work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NLzNfeedbe2xFTjwrBPPBg
Moves the pinned gcr.io/distroless/nodejs24-debian13:nonroot digest to the
current rebuild.

This does NOT fix CVE-2026-14456, and an earlier version of this message claimed
it did. The current nonroot rebuild still ships libssl3t64 3.5.6-1~deb13u2; the
fix is in 3.5.7-1~deb13u2 and upstream has not published a rebuild carrying it
yet. Verified by re-running the scan against this exact digest: same finding,
same installed version.

The refresh is kept because the newer rebuild carries other package updates and
the pin stays a digest rather than a floating tag, but the OpenSSL denial of
service remains open until upstream rebuilds.

This is a pre-existing condition, not a regression from this branch: no
dependency changed here, and main fails the same scan today for the same reason.
Suppressing a HIGH finding is a team security decision rather than something to
slip into a feature branch, so no .trivyignore is added here.

Among the five services only todos-api is affected. auth-api uses
static-debian13, which ships no openssl. users-api runs java21-debian13 and will
likely report the same CVE when its scan next runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NLzNfeedbe2xFTjwrBPPBg
Require the final image to include the operational module loaded by server.js. The test fails against the current image source list, preserving the red SDD evidence before the packaging fix.
Copy operational.js into the final distroless image so server.js can load the health, correlation, runtime configuration, and Redis resilience contract at startup.
Pin the current distroless Node.js 24 Debian 13 nonroot digest, which contains the patched libssl3t64 for CVE-2026-14456. The exact rebuilt image reports zero HIGH or CRITICAL findings with Trivy 0.72.0.
@EstebanGZam EstebanGZam changed the title feat(us3): todos-api health, correlation, and Redis resilience contract feat(us3): implement the todos-api operational contract Sep 1, 2026
@EstebanGZam
EstebanGZam merged commit 85b6944 into main Sep 1, 2026
6 checks passed
@EstebanGZam
EstebanGZam deleted the feat/us3-operational-contract branch September 1, 2026 03:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant