{"record":{"id":"c0f2aac1a7a13171","repo":"floci-io/floci","slug":"failed-to-write-self-signed-tls-certificate","errorCode":null,"errorMessage":"Failed to write self-signed TLS certificate","messagePattern":"Failed to write self-signed TLS certificate","errorType":"console","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"src/main/java/io/github/hectorvent/floci/config/TlsConfigSource.java","lineNumber":205,"sourceCode":"            List<String> allSans = new ArrayList<>();\n            allSans.addAll(DEFAULT_SAN_HOSTNAMES);\n            allSans.addAll(customHostnames);\n\n            CertificateGenerator gen = new CertificateGenerator();\n            CertificateGenerator.GeneratedCertificate generated = gen.generateSelfSignedCertificate(\n                    \"localhost\",\n                    allSans,\n                    KeyAlgorithm.RSA_2048);\n\n            Files.writeString(certFile, generated.certificatePem());\n            Files.writeString(keyFile, generated.privateKeyPem());\n\n            LOG.infov(\"TLS: generated self-signed certificate: {0}\", certFile);\n\n            // Persist metadata for change detection on restart\n            persistMetadata(tlsDir, allSans);\n        } catch (IOException e) {\n            throw new IllegalStateException(\"Failed to write self-signed TLS certificate\", e);\n        }\n    }\n\n    private static void validateFileExists(String path, String description) {\n        if (!Files.isReadable(Path.of(path))) {\n            throw new IllegalStateException(\n                    description + \" file not found or not readable: \" + path);\n        }\n    }\n\n    /**\n     * Returns {@code true} if the certificate at {@code certFile} is genuinely self-signed\n     * (issuer == subject) and therefore usable as a trust anchor. Legacy Floci certs carried a\n     * cosmetic Amazon issuer DN and return {@code false} here, triggering regeneration on upgrade.\n     */\n    private boolean isSelfSigned(Path certFile) {\n        try {\n            X509Certificate cert = new CertificateGenerator().parseCertificate(Files.readString(certFile));","sourceCodeStart":187,"sourceCodeEnd":223,"githubUrl":"https://github.com/floci-io/floci/blob/62ff490619e7bd3554597c28c704081b4c15add5/src/main/java/io/github/hectorvent/floci/config/TlsConfigSource.java#L187-L223","documentation":"Thrown by AcknowledgeJob when the supplied nonce does not match the nonce stored on the job. Every job returned by PollForJobs carries a one-time nonce; AcknowledgeJob must echo it back verbatim to prove the caller received the job from a poll. A mismatch means the nonce is stale, mistyped, or belongs to a different job.","triggerScenarios":"Calling AcknowledgeJob with a nonce from a different jobId; re-acknowledging after the job's nonce was rotated; manually constructing the nonce instead of reading job.nonce from the polled Job object; string truncation or whitespace when copying the nonce.","commonSituations":"Worker restarts that replay an old poll response; multiple worker processes consuming the same queue and mixing up job/nonce pairs; logging frameworks that trim or mask the nonce before it is reused.","solutions":["Always take the nonce from job.nonce of the exact job object returned by PollForJobs, keyed by the same jobId","If the job may already be acknowledged, expect InvalidNonce/InvalidJobState on retry and treat it as idempotent success","Verify no other worker instance has already consumed and acknowledged the polled job"],"exampleFix":"// before\nclient.acknowledgeJob(r -> r.jobId(jobId).nonce(nonceFromConfig));\n\n// after\nJob job = polledJobs.get(0);\nclient.acknowledgeJob(r -> r.jobId(job.getId()).nonce(job.getNonce()));","handlingStrategy":"validation","validationCode":"Job job = polled.getJobs().get(0);\nif (job.getNonce() == null || job.getNonce().isBlank()) {\n    throw new IllegalStateException(\"polled job missing nonce; do not acknowledge\");\n}\nclient.acknowledgeJob(r -> r.jobId(job.getId()).nonce(job.getNonce()));","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never hand-build nonces; always pass through job.nonce from the same poll response","Keep jobId and nonce as one immutable pair in worker state to avoid cross-wiring"],"tags":["codepipeline","acknowledgejob","nonce","job-worker"],"backgroundTag":null,"analyzedSha":"62ff490619e7bd3554597c28c704081b4c15add5","analyzedAt":"2026-08-14T14:25:23.764Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}