feat(us3): implement the todos-api operational contract - #15
Merged
Conversation
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
EstebanGZam
force-pushed
the
feat/us3-operational-contract
branch
from
August 26, 2026 19:49
6584d1c to
62399ae
Compare
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.
Open
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes
Implements the
todos-apioperational contract for full-platform User Story 3:X-Request-Idcorrelation, including the Redis audit event;server.js;Why
Redis carries the audit log, not todo state. Previously an unhandled Redis
errorevent 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 answer401and cause Kubernetes to restart a healthy process.Runtime verification found one additional packaging defect:
server.jsrequired./operational, but the final image did not copyoperational.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-14456inlibssl3t64. The current pinned distroless digest contains the patched package; no vulnerability suppression was added.Tasks
microservice-app-gitops/specs/009-full-platform-rolloutT075microservice-app-gitops/specs/009-full-platform-rolloutT080How it is verified
590b2e8introduced the operational contract tests red before commit87fd274implemented them.515fbdaintroduced the runtime packaging test red with:server.js requires ./operational, but the runtime image does not copy operational.js. Commit48627b2made 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 copiedoperational.jsinto the final stage.200, the image ran as65532:65532, andX-Request-Id: smoke-correlation-idwas echoed unchanged.v0.72.0against the exact rebuilt image, with the CI policy--severity HIGH,CRITICAL --ignore-unfixed --exit-code 1: Debian findings0, Node package findings0, exit0.Risk and rollback
The additive audit
correlationIdfield 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-gitopsand ArgoCD.