{"record":{"id":"eae47fa35eb22c0d","repo":"can1357/oh-my-pi","slug":"finding-finding-id-belongs-to-finding-scanid","errorCode":null,"errorMessage":"Finding ${finding.id} belongs to ${finding.scanId}, expected ${bundle.scan.id}","messagePattern":"Finding (.+?) belongs to (.+?), expected (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/security/contracts/validation.ts","lineNumber":50,"sourceCode":"\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}\n\t\t\t}\n\t\t}\n\t}\n\treturn bundle;","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/security/contracts/validation.ts#L32-L68","documentation":"parseSecurityScanBundle validates referential integrity of a security scan bundle after schema validation. Every finding in bundle.findings must declare the same scanId as the bundle's scan record; a mismatch means the findings array and the scan manifest describe different scans. The library throws to prevent mixing findings across scans when loading, importing (Sarif/Codex), or reading bundles from disk.","triggerScenarios":"Calling bundle(), readBundle(), importSarif(), or importCodexSecurityBundle() with a bundle JSON where one or more findings have scanId different from bundle.scan.id — e.g. hand-merged scan files, a copied scan record paired with another scan's findings, or an importer that misassigns scanId.","commonSituations":"Manually editing or concatenating scan output files under the security store; post-processing scripts that rewrite scan.id but not finding.scanId; importing SARIF produced by external tooling where run-to-finding linkage was reconstructed incorrectly.","solutions":["Open the bundle JSON and compare each finding's scanId with scan.id; make them consistent (usually the finding's scanId is stale — update it to match bundle.scan.id).","Regenerate the bundle from the original scan run instead of hand-editing it.","If merging scans, merge at the scan level: keep each finding with its own scan record rather than cross-assigning findings.","Check the importer (Sarif/Codex) invocation — ensure the source file corresponds to a single scan and wasn't corrupted in transit."],"exampleFix":"// before (hand-edited bundle.json)\n{ \"scan\": { \"id\": \"scan-2\" }, \"findings\": [{ \"id\": \"f1\", \"scanId\": \"scan-1\" }] }\n// after\n{ \"scan\": { \"id\": \"scan-2\" }, \"findings\": [{ \"id\": \"f1\", \"scanId\": \"scan-2\" }] }","handlingStrategy":"validation","validationCode":"const mismatched = bundle.findings.filter(f => f.scanId !== bundle.scan.id);\nif (mismatched.length) throw new Error(`findings with foreign scanId: ${mismatched.map(f => f.id).join(\", \")}`);","typeGuard":"function hasConsistentScanIds(bundle: SecurityScanBundle): boolean {\n  return bundle.findings.every(f => f.scanId === bundle.scan.id);\n}","tryCatchPattern":"try {\n  const bundle = parseSecurityScanBundle(raw);\n  // use bundle\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"belongs to\")) {\n    logger.warn(\"bundle contains findings from another scan\", { cause: err.message });\n  } else throw err;\n}","preventionTips":["Never hand-edit scan bundle files; treat them as immutable scan output.","When merging scans, keep findings paired with their own scan record.","Run bundle validation as a post-processing step after any importer or transform."],"tags":["validation","referential-integrity","security-scan"],"backgroundTag":"scan-finding-scanid-mismatch","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}