{"record":{"id":"a0bbfc872ecb6693","repo":"can1357/oh-my-pi","slug":"security-scan-omits-finding-from-manifest-findi","errorCode":null,"errorMessage":"Security scan omits finding from manifest: ${findingId}","messagePattern":"Security scan omits finding from manifest: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/security/contracts/validation.ts","lineNumber":46,"sourceCode":"}\n\nexport function parseSecurityScanBundle(value: unknown): SecurityScanBundle {\n\tconst { securityScanBundleSchema } = getSecurityContractSchemas();\n\tconst result = securityScanBundleSchema(value);\n\tif (result instanceof type.errors) throw schemaError(\"Security scan bundle\", result);\n\tconst bundle = result as SecurityScanBundle;\n\tconst findingIds = new Set(bundle.findings.map(finding => finding.id));\n\tif (findingIds.size !== bundle.findings.length) throw new Error(\"Security scan contains duplicate finding ids\");\n\tconst referencedFindingIds = new Set(bundle.scan.findingIds);\n\tif (referencedFindingIds.size !== bundle.scan.findingIds.length) {\n\t\tthrow new Error(\"Security scan manifest contains duplicate finding references\");\n\t}\n\tfor (const findingId of referencedFindingIds) {\n\t\tif (!findingIds.has(findingId)) throw new Error(`Security scan references missing finding: ${findingId}`);\n\t}\n\tfor (const findingId of findingIds) {\n\t\tif (!referencedFindingIds.has(findingId))\n\t\t\tthrow new Error(`Security scan omits finding from manifest: ${findingId}`);\n\t}\n\tfor (const finding of bundle.findings) {\n\t\tif (finding.scanId !== bundle.scan.id) {\n\t\t\tthrow new Error(`Finding ${finding.id} belongs to ${finding.scanId}, expected ${bundle.scan.id}`);\n\t\t}\n\t\tconst evidenceIds = new Set(finding.evidence.map(evidence => evidence.id));\n\t\tif (evidenceIds.size !== finding.evidence.length) {\n\t\t\tthrow new Error(`Finding ${finding.id} contains duplicate evidence ids`);\n\t\t}\n\t\tconst occurrenceIds = new Set(finding.occurrences.map(occurrence => occurrence.id));\n\t\tif (occurrenceIds.size !== finding.occurrences.length) {\n\t\t\tthrow new Error(`Finding ${finding.id} contains duplicate occurrence ids`);\n\t\t}\n\t\tfor (const occurrence of finding.occurrences) {\n\t\t\tfor (const evidenceId of occurrence.evidenceIds) {\n\t\t\t\tif (!evidenceIds.has(evidenceId)) {\n\t\t\t\t\tthrow new Error(`Occurrence ${occurrence.id} references missing evidence: ${evidenceId}`);\n\t\t\t\t}","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/security/contracts/validation.ts#L28-L64","documentation":"The inverse check: every finding in the findings array must be listed in scan.findingIds. A finding omitted from the manifest would be invisible to consumers that trust the manifest index, so parseSecurityScanBundle throws with the orphaned finding id.","triggerScenarios":"Parsing a bundle where a finding exists in bundle.findings but its id is missing from bundle.scan.findingIds.","commonSituations":"Appending new findings to an export without updating the manifest; a generator bug that snapshots the manifest before all findings are collected; hand-merging findings from another scan into the array.","solutions":["Rebuild the manifest to include every finding: scan.findingIds = [...new Set(findings.map(f => f.id))]\nRe-export the scan from the tool so manifest and findings are generated together\nIf the extra finding is unintended, remove it from findings instead of adding it to the manifest","Fix the exporting pipeline to append to the manifest when adding findings"],"exampleFix":"// before\nparseSecurityScanBundle(raw); // finding orphaned from manifest\n// after\nraw.scan.findingIds = [...new Set(raw.findings.map(f => f.id))];\nparseSecurityScanBundle(raw);","handlingStrategy":"validation","validationCode":"const manifest = new Set(raw.scan.findingIds);\nconst orphaned = raw.findings.filter((f: { id: string }) => !manifest.has(f.id));\nif (orphaned.length) throw new Error(`Findings missing from manifest: ${orphaned.map(f => f.id).join(\", \")}`);","typeGuard":null,"tryCatchPattern":"try {\n\tbundle = parseSecurityScanBundle(value);\n} catch (err) {\n\tif (err instanceof Error && err.message.startsWith(\"Security scan omits finding from manifest:\")) {\n\t\traw.scan.findingIds = [...new Set(raw.findings.map(f => f.id))];\n\t\tbundle = parseSecurityScanBundle(raw);\n\t} else throw err;\n}","preventionTips":["Derive the manifest from the findings array, never maintain it by hand","Regenerate exports after adding findings rather than patching JSON","Round-trip generated bundles through parseSecurityScanBundle as a build check"],"tags":["validation","manifest","integrity"],"backgroundTag":"finding-missing-from-manifest","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}