Blocker
There is no way to see what the service is doing
PR-01 · SaaS
The observability stack is a single dependency, spring-boot-starter-actuator, and three exposed endpoints. There is no metrics registry, so /actuator/metrics has nothing scraping it. There is no tracing library. There are no correlation identifiers anywhere — a search for MDC, traceId, correlationId and requestId across the whole backend returns nothing, so the 123 log statements cannot be tied to a request, a tenant, or each other. There is no logging configuration file, so output is Spring's default console pattern rather than structured JSON.
There are also no custom health indicators. Health is the framework default, which checks the database and nothing else — not Keycloak, not Accountly, not Kill Bill, not the KMS provider, not the blockchain anchors. Every one of those can fail while the load balancer still sees a healthy instance.
server/build.gradle.kts actuator only — no micrometer-registry-prometheus, no OpenTelemetry
application.yml:79-86 exposure: health, info, metrics; no probes group configured
grep MDC|traceId|correlationId|requestId → 0 hits
grep HealthIndicator → 0 implementations
server/src/main/resources/ no logback-spring.xml or log4j2.xml
OncePerRequestFilter only DevAuthFilter, which is @Profile("dev")
Fix: add micrometer-registry-prometheus and expose the scrape endpoint on the platform-admin path. Add a OncePerRequestFilter that puts a request id and the resolved organization id into the MDC, and a logback-spring.xml that emits JSON including both. Add health indicators for Keycloak and the billing provider, and enable the liveness/readiness probe groups so orchestration can tell starting from broken.
Blocker
No infrastructure definition, and the documented deploy ships default credentials
PR-02 · SaaS
There is no infrastructure-as-code of any kind — no Terraform, no CDK, no Kubernetes manifests, no CloudFormation. The only deployment artifact is docker-compose.yml, and DEPLOYMENT.md presents it as the recommended path. That file hardcodes POSTGRES_PASSWORD: postgres, KEYCLOAK_ADMIN_PASSWORD: admin, DATABASE_PASSWORD: postgres and an empty KEYCLOAK_CLIENT_SECRET.
This is the same class of defect that SA-R10-06 and SA-R11-02 fixed inside application.yml. Those fixes made the application refuse to start without real credentials; the deployment file then supplies the weak ones from outside, which puts the default back.
Fix: treat docker-compose.yml as a local development file, say so in DEPLOYMENT.md, and source every credential from .env with no fallback. Then write the real target as code. Per the AWS staging design, that means a managed Postgres, a secret manager, and Vault on Fargate for the Ed25519 KMS — none of which exists yet in any committed form.
Blocker
The service is built against SNAPSHOT dependencies
PR-03 · SaaS
Eight dependencies resolve to com.geoknoesis.trustweave:*:1.0.0-SNAPSHOT, including trustweave-all, the KMS providers and the status-list implementations. A SNAPSHOT is mutable by definition: the same build, run twice, can produce different binaries. That removes reproducibility, auditability and the ability to roll back to a known artifact — which for a service whose product is verifiable trust is a particularly awkward place to be. The coordinate also names a version, 1.0.0, that the library has never released; the library is at 0.7.0.
The mitigation in place is genuinely clever and worth keeping: CI pins the library by git revision and verifies a source hash before building. But that guarantees the inputs were reviewed, not that the resolved artifact is immutable or reproducible from a coordinate.
server/build.gradle.kts 8 × com.geoknoesis.trustweave:*:1.0.0-SNAPSHOT
trustweave/build.gradle.kts:22 version = "0.7.0"
.github/workflows/ci.yml verify-sdk-source.py --revision (pins inputs, not artifacts)
Fix: cut a real library release and depend on the fixed version. This is the same work as TW-R10-06 — once the API surface is validated and frozen, the SaaS gets a version it can pin, and the source-hash check becomes a second line of defence rather than the only one.
Blocker
Nothing survives running more than one instance
PR-04 · SaaS
Two mechanisms assume a single process. The rate limiter is an in-process HashMap behind a single @Synchronized lock, so the effective limit multiplies by instance count — and it is the only control protecting the public verification and claim endpoints. Five of the six @Scheduled jobs have no coordination at all; only the OID4VP verification path takes a database work lease.
The usage-outbox drain is the one to look at first. It selects PENDING and FAILED rows with a plain derived query — no SKIP LOCKED, no pessimistic lock, no claim before the HTTP call — so two instances would post the same usage events concurrently. Each event does carry an idempotency key, so billing correctness rests on Accountly honouring it rather than on anything local. That is a reasonable belt, but there is no braces.
RateLimiter.kt:9-21 private val windows = HashMap<String, Window>(), @Synchronized, per-JVM
UsageReporter.kt:30,39 @Scheduled 15s → findByStatusInAndAttemptsLessThan… (no claim)
VerificationSession.kt:155 releaseWork() — the one job that is coordinated
fly.toml min_machines_running = 1, auto_start_machines = true
Fix: add SKIP LOCKED and a claimed status to the outbox drain — that is the highest-consequence one and a small change. Introduce ShedLock (it only needs the database you already have) for the remaining scheduled jobs, and move the rate limiter behind a shared store before scaling past one machine.
Blocker
The integration suite cannot currently be run
PR-05 · SaaS
The round-11 remediation pass reports that Docker stalled during PostgreSQL container startup and the test worker was killed after five minutes, leaving the integration tests compiled but unverified — and notes that prior full-backend instability has not been cleared. That matters more than usual here, because the strongest recent addition, FlywayMigrationTest, is a Testcontainers test. The check that proves the schema builds from empty is exactly the check that currently cannot run.
To the team's credit this was reported plainly rather than papered over, and the remediation explicitly declined to self-score because of it. But a green pipeline you cannot reproduce locally is not a signal you can deploy on.
Fix: get the Docker environment stable and run the full backend suite end to end before any deploy. Until that happens, treat the migration and boot coverage as unverified regardless of what CI reports.
Blocker
No published artifact can be verified by whoever consumes it
PR-06 · Library
The library publishes to Maven with no GPG signature, no SBOM and no build attestation, and CI runs no SAST. Carried forward unchanged from round 10, but it reads differently under a readiness rubric: this is a security library whose entire proposition is that recipients can verify provenance, distributed by a channel that offers its own recipients none. Signing is required for Maven Central regardless, so it has to happen before any real release anyway.
Fix: GPG signing and cyclonedx-gradle-plugin in the release workflow, plus GitHub artifact attestation. Pairs naturally with PR-03, since both are prerequisites for cutting the version the SaaS needs to pin.