feat: 006 finding occurrence and lineage lifecycle 구현 - #271
Conversation
📝 WalkthroughWalkthroughThis change implements T037 SAST finding lineage and lifecycle persistence. It adds shared contracts, Prisma models and constraints, serializable observation/reconciliation flows, rename verification, fail-closed coverage gates, module wiring, tests, and synchronized production-runtime design documentation. ChangesSAST finding lineage lifecycle
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant ScannerHandoff
participant SastFindingLineageService
participant PrismaSastFindingLineageStore
participant PrismaDatabase
participant LifecycleCoverageGate
ScannerHandoff->>SastFindingLineageService: submit fingerprinted finding batch
SastFindingLineageService->>PrismaSastFindingLineageStore: validate scope and persist observation
PrismaSastFindingLineageStore->>PrismaDatabase: serializable lineage, occurrence, and event writes
LifecycleCoverageGate-->>SastFindingLineageService: verify complete comparable coverage
SastFindingLineageService->>PrismaSastFindingLineageStore: reconcile lifecycle state
PrismaSastFindingLineageStore->>PrismaDatabase: append FIXED or REOPENED transition
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fd839544f3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (13)
packages/shared/src/types/sast-finding-lineage.ts (2)
963-976: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDuplicate authority literal.
canonicalAuthority()re-declares the exact object already frozen inAUTHORITY(Lines 288-300). Derive it instead so the two can't drift.♻️ Derive from the frozen constant
function canonicalAuthority(): SastFindingLineageAuthority { - return { - normalizedFindingPersistenceAuthority: true, - occurrenceAuthority: true, - lifecycleAuthority: true, - renameAuthority: true, - correlationAuthority: false, - coverageCalculationAuthority: false, - evidenceAuthority: false, - policyAuthority: false, - publicationAuthority: false, - aiPayloadEligible: false - }; + return { ...AUTHORITY }; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/shared/src/types/sast-finding-lineage.ts` around lines 963 - 976, Update canonicalAuthority() to derive its return value from the existing frozen AUTHORITY constant instead of redeclaring the authority object literal. Preserve the same SastFindingLineageAuthority shape and values while ensuring future changes to AUTHORITY are reflected automatically.
1156-1158: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueHoist the
TextEncoder.
utf8ByteLengthis on the hot path (every reference, path, and canonical field) and allocates a new encoder per call.♻️ Module-level encoder
+const UTF8_ENCODER = new TextEncoder(); + function utf8ByteLength(value: string): number { - return new TextEncoder().encode(value).byteLength; + return UTF8_ENCODER.encode(value).byteLength; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/shared/src/types/sast-finding-lineage.ts` around lines 1156 - 1158, Hoist the TextEncoder allocation out of utf8ByteLength by creating a module-level encoder and reusing it for each call. Update utf8ByteLength to encode with that shared instance while preserving its byte-length result.packages/shared/test/sast-finding-lineage.test.mjs (1)
5-25: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winNo coverage for
isSastFindingLineageObservationResultShapeValidortoSastFindingLineageAuditMetadata.The import list omits both. The observation-result validator is the primary ingestion gate for this contract (occurrence/finding count equality, the
created + exact + renamed === distinctalgebra, authority validation), andtoSastFindingLineageAuditMetadatais the fail-closed dispatcher that throws on invalid outcomes. Neither is exercised anywhere in this suite.Want me to draft the missing cases?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/shared/test/sast-finding-lineage.test.mjs` around lines 5 - 25, Extend the test imports and suite to cover isSastFindingLineageObservationResultShapeValid and toSastFindingLineageAuditMetadata. Add cases verifying the observation validator’s occurrence/finding count equality, created + exact + renamed === distinct algebra, and authority validation, plus dispatcher cases confirming toSastFindingLineageAuditMetadata returns valid metadata and throws for invalid outcomes.apps/api/test/scan-plane/prisma-sast-finding-lineage.store.e2e-spec.ts (3)
1117-1123: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDuplicate digest helper.
batchIndependentDigestis byte-identical tofixtureDigestalready exported from../support/sast-finding-lineage-fixtures(imported in this file's sibling specs). Reuse the shared helper to keep fixture digests consistent across the suite.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/test/scan-plane/prisma-sast-finding-lineage.store.e2e-spec.ts` around lines 1117 - 1123, Remove the duplicate batchIndependentDigest helper and import and reuse fixtureDigest from ../support/sast-finding-lineage-fixtures in the affected test code. Update references to batchIndependentDigest to use fixtureDigest, preserving the existing digest behavior.
682-682: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winThis assertion is tautological.
reconciliationTransactionnever definesnormalizedFinding, so'normalizedFinding' in transactionisfalseregardless of what the store does. The intended invariant (reconciliation never touches policy status) isn't actually exercised. Adding anormalizedFindingmock to the transaction and asserting its methods were not called would test the store instead of the fixture.♻️ Suggested change
- expect('normalizedFinding' in transaction).toBe(false); + expect( + transaction.normalizedFinding.updateMany + ).not.toHaveBeenCalled();Add to
reconciliationTransaction:normalizedFinding: { updateMany: jest.fn(), createMany: jest.fn() },🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/test/scan-plane/prisma-sast-finding-lineage.store.e2e-spec.ts` at line 682, Update the reconciliationTransaction fixture to include a normalizedFinding object with mocked updateMany and createMany methods, then replace the tautological property-existence assertion with expectations that both methods were not called. Keep the test focused on verifying reconciliation does not modify policy status.
212-217: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueGuard the mock-call destructure.
calls[0]?.[0].datastill throws aTypeErrorwhencreateManywas never invoked, masking the real assertion failure with a confusing stack. Assert the call count first (or use?.[0]?.data).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/test/scan-plane/prisma-sast-finding-lineage.store.e2e-spec.ts` around lines 212 - 217, Update the occurrenceRows setup around the mocked sastFindingOccurrence.createMany call to safely handle an uninvoked mock. Assert that the mock was called before accessing its arguments, or make the argument access fully optional with ?. [0]?.data, so missing calls produce the intended assertion failure rather than a TypeError.apps/api/test/scan-plane/sast-finding-lineage-persistence.e2e-spec.ts (2)
76-81: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winWhitespace-exact schema assertion will break on
prisma format.
'lastObservedAt DateTime?'hardcodes the column-alignment padding thatprisma formatrecomputes whenever a longer field name is added to the model. Match on a whitespace-tolerant regex instead.♻️ Suggested change
- expect(schema).toContain( - 'lastObservedAt DateTime?' - ); + expect(schema).toMatch(/lastObservedAt\s+DateTime\?/);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/test/scan-plane/sast-finding-lineage-persistence.e2e-spec.ts` around lines 76 - 81, Update the schema assertion in the SAST finding lineage persistence test to match the lastObservedAt field with a whitespace-tolerant regular expression instead of hardcoded alignment spaces. Keep the migration assertion unchanged and continue validating the DateTime? type.
174-176: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueNegative regex is broader than intended.
/\b(calculate|publish|override)\s*\(/also matches these words inside comments, string literals, or unrelated local helpers, so an innocuous edit to the gate file can fail this gate while a method named e.g.computeCoverageslips through. Anchor on the declaration form you actually forbid (e.g. method definitions in the class body).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/test/scan-plane/sast-finding-lineage-persistence.e2e-spec.ts` around lines 174 - 176, Refine the coverageGate assertion in the SAST lineage persistence test to match only forbidden method declarations in the class body, rather than occurrences in comments, strings, or unrelated helpers. Preserve detection of calculate, publish, and override declarations while avoiding false positives such as call sites or textual references.apps/api/test/support/sast-finding-lineage-fixtures.ts (1)
244-322: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
as OpenGrepRedactedFindingdisables the type check this fixture most needs.The cast means a drift in the shared
SastSecretRedactedFindingCandidatecontract (renamed/added required field) compiles cleanly here and surfaces only as a confusing runtime fixture rejection infingerprintedFindingBatch. Building the base object withsatisfies OpenGrepRedactedFindingbefore applying overrides would keep compile-time coverage of the contract.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/test/support/sast-finding-lineage-fixtures.ts` around lines 244 - 322, Replace the direct `as OpenGrepRedactedFinding` cast in the finding fixture with a `satisfies OpenGrepRedactedFinding` validation on the base object before applying `overrides`. Preserve the existing override and redaction decision-digest calculation flow while ensuring required contract changes are caught at compile time.apps/api/src/scan-plane/prisma-sast-finding-lineage.store.ts (3)
2446-2458: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDerive scanner capabilities from
SAST_SCANNER_RESPONSIBILITIESinstead of re-hardcoding them.
SAST_SCANNER_RESPONSIBILITIESis already imported and used at Line 1567. This local copy of the OPENGREP/TRIVY capability lists will silently diverge if the shared contract changes, causingreadCapabilitiesto reject legitimately persisted batches.♻️ Proposed refactor
function authoritativeScannerCapabilities( scanner: string ): FindingCapability[] { - if (scanner === 'OPENGREP') return ['SAST']; - if (scanner === 'TRIVY') { - return [ - 'DEPENDENCY_VULNERABILITY', - 'SECRET_DETECTION', - 'IAC_MISCONFIGURATION' - ]; - } - throw new SastFindingLineageDurableScopeError(); + if (!isFindingScannerKind(scanner)) { + throw new SastFindingLineageDurableScopeError(); + } + return FINDING_CAPABILITIES.filter((capability) => + ( + SAST_SCANNER_RESPONSIBILITIES[scanner] + .authoritativeCapabilities as readonly string[] + ).includes(capability) + ); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/src/scan-plane/prisma-sast-finding-lineage.store.ts` around lines 2446 - 2458, Update authoritativeScannerCapabilities to derive each scanner’s capabilities from the imported SAST_SCANNER_RESPONSIBILITIES contract instead of hardcoded OPENGREP and TRIVY lists. Preserve the existing unknown-scanner error behavior and return the responsibility-defined capability values used by readCapabilities.
2574-2586: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
JSON.stringifyequality makes context comparison key-order sensitive.
sameObservationContext/sameReconciliationContextcompare serialized objects. Both sides are currently produced byreadObservationContext/readReconciliationContext, so key order matches today — butSastFindingLineageScanContext.sourcehas optionalruleBundleDigest/vulnerabilityDatabaseDigestthat are conditionally spread (Lines 1667-1675), so any future reordering or a context that round-trips through another producer will fail-closed with a confusingDurableScopeError. A field-wise comparison (you already haveobservationBatchMatchesContextas a template) is more robust.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/src/scan-plane/prisma-sast-finding-lineage.store.ts` around lines 2574 - 2586, The sameObservationContext and sameReconciliationContext helpers are sensitive to object key order because they compare JSON strings. Replace both with explicit field-wise comparisons, including all required nested source fields and optional ruleBundleDigest/vulnerabilityDatabaseDigest values, following observationBatchMatchesContext as the comparison pattern.
1774-1792: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueSerializable retries have no backoff.
On
P2034/P2002the loop immediately re-enters a 120s-timeout serializable transaction. Under concurrent ingestion for the same scanner run this maximises contention and repeated full re-reads. A small jittered delay between attempts would shed contention cheaply.Also note
throw lastErrorafter exhausting attempts is typedunknown; that's fine at runtime but the caller only distinguishes the domain error classes, so exhausted retries surface asFINDING_LINEAGE_PERSISTENCE_FAILED.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/src/scan-plane/prisma-sast-finding-lineage.store.ts` around lines 1774 - 1792, Add a small jittered backoff delay between retryable failures in the serializable transaction retry loop, before the next iteration of the operation guarded by SERIALIZABLE_ATTEMPTS. Keep the existing immediate rethrow for non-retryable errors and preserve the final throw of lastError so exhausted retries retain their current caller behavior.apps/api/src/scan-plane/sast-finding-lineage.store.ts (1)
177-182: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDrop the unused durable SAST type aliases.
DurableSastFingerprintedFinding,DurableSastFindingObservationResult, andDurableSastFindingReconciliationResultare only imported and exist as aliases, so callers can import the shared types directly instead.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/src/scan-plane/sast-finding-lineage.store.ts` around lines 177 - 182, Remove the unused type aliases DurableSastFingerprintedFinding, DurableSastFindingObservationResult, and DurableSastFindingReconciliationResult from the SAST lineage store. Update any imports or references to use SastFingerprintedFinding, SastFindingLineageObservationResult, and SastFindingLifecycleReconciliationResult directly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@apps/api/prisma/migrations/20260730160000_sast_finding_lineage_lifecycle/migration.sql`:
- Around line 23-32: Update the migration and occurrence projection around
createOccurrences so SAST non-FILE findings cannot expose null filePath,
lineStart, or lineEnd to existing consumers before making those columns
nullable. Preserve the existing non-null location contract for AI inference and
report PDF consumers, or guard and exclude unsupported occurrences before
applying the ALTER TABLE changes.
In `@apps/api/src/scan-plane/prisma-sast-finding-lineage.store.ts`:
- Around line 1957-1999: The rename-candidate derivation and comparison logic is
duplicated between the persistence store and service, risking mismatched
validation. Extract the shared derivation and ordering into a helper in
packages/shared, then update the store validation block and service function
prepareRenameCandidates to use it. Replace localeCompare-based ordering with
deterministic plain key < and > comparisons so both boundaries produce identical
JSON ordering.
- Around line 634-671: Scope both lineage lookup sites to the current tenant and
repository binding. In the sastFindingLineage.findMany existence probe around
lines 634-671, add tenantId and repositoryBindingId to the where clause; in the
OR branches around lines 1073-1094, add tenantId and repositoryBindingId to the
id and coverageDecisionDigest branches to match the existing third branch.
In `@apps/api/src/scan-plane/sast-finding-lineage.service.ts`:
- Line 115: Remove the upfront yieldForFindingBatch(batch.findings.length) call
from the batch loop and move yielding into the per-finding iteration used by
prepareRenameCandidates, yielding every yieldFindingInterval processed findings.
Ensure the heavy context, rename-derivation, and persistence work is interleaved
with event-loop yields, and apply the same adjustment to the additional
finding-processing path.
- Around line 102-112: Add a Number.isFinite(retentionExpiresAt) guard
immediately after parsing it in the retention validation flow, alongside the
existing firstReferenceTime check, and reject with the appropriate
invalid-retention result when parsing fails. Ensure both retention gates fail
closed for unparseable expiration values.
In `@packages/shared/src/types/sast-finding-lineage.ts`:
- Around line 848-876: Update the rejection payload validation around
reasonCodes to reject arrays longer than the 16 supported rejection codes, using
the existing canonical-code collection for the cap. Compute
orderSastFindingLineageRejectionReasons(reasonCodes) once before validating
ordering, then compare the original array against that hoisted result; use the
resulting length equality to detect unknown or duplicate codes and remove the
redundant per-element membership check.
- Around line 715-727: Update the validation chain for the lineage result around
findingCount and distinctFingerprintCount to require distinctFingerprintCount to
be positive whenever findingCount is greater than zero. Preserve the existing
zero-count behavior and all current upper-bound and lineage-sum invariants.
In `@packages/shared/test/sast-finding-lineage.test.mjs`:
- Around line 263-290: Update the negative-case mutations in
packages/shared/test/sast-finding-lineage.test.mjs at lines 263-290 and 129-154
to spread the undigested core object, then recompute decisionDigest with
canonicalizeSastFindingLifecycleCoverageDecision or attestationDigest with
canonicalizeSastFindingRenameAttestation before validation. Preserve each
mutation’s intended structural violation so the tests independently exercise the
relevant shape rules rather than failing only on stale digests.
---
Nitpick comments:
In `@apps/api/src/scan-plane/prisma-sast-finding-lineage.store.ts`:
- Around line 2446-2458: Update authoritativeScannerCapabilities to derive each
scanner’s capabilities from the imported SAST_SCANNER_RESPONSIBILITIES contract
instead of hardcoded OPENGREP and TRIVY lists. Preserve the existing
unknown-scanner error behavior and return the responsibility-defined capability
values used by readCapabilities.
- Around line 2574-2586: The sameObservationContext and
sameReconciliationContext helpers are sensitive to object key order because they
compare JSON strings. Replace both with explicit field-wise comparisons,
including all required nested source fields and optional
ruleBundleDigest/vulnerabilityDatabaseDigest values, following
observationBatchMatchesContext as the comparison pattern.
- Around line 1774-1792: Add a small jittered backoff delay between retryable
failures in the serializable transaction retry loop, before the next iteration
of the operation guarded by SERIALIZABLE_ATTEMPTS. Keep the existing immediate
rethrow for non-retryable errors and preserve the final throw of lastError so
exhausted retries retain their current caller behavior.
In `@apps/api/src/scan-plane/sast-finding-lineage.store.ts`:
- Around line 177-182: Remove the unused type aliases
DurableSastFingerprintedFinding, DurableSastFindingObservationResult, and
DurableSastFindingReconciliationResult from the SAST lineage store. Update any
imports or references to use SastFingerprintedFinding,
SastFindingLineageObservationResult, and
SastFindingLifecycleReconciliationResult directly.
In `@apps/api/test/scan-plane/prisma-sast-finding-lineage.store.e2e-spec.ts`:
- Around line 1117-1123: Remove the duplicate batchIndependentDigest helper and
import and reuse fixtureDigest from ../support/sast-finding-lineage-fixtures in
the affected test code. Update references to batchIndependentDigest to use
fixtureDigest, preserving the existing digest behavior.
- Line 682: Update the reconciliationTransaction fixture to include a
normalizedFinding object with mocked updateMany and createMany methods, then
replace the tautological property-existence assertion with expectations that
both methods were not called. Keep the test focused on verifying reconciliation
does not modify policy status.
- Around line 212-217: Update the occurrenceRows setup around the mocked
sastFindingOccurrence.createMany call to safely handle an uninvoked mock. Assert
that the mock was called before accessing its arguments, or make the argument
access fully optional with ?. [0]?.data, so missing calls produce the intended
assertion failure rather than a TypeError.
In `@apps/api/test/scan-plane/sast-finding-lineage-persistence.e2e-spec.ts`:
- Around line 76-81: Update the schema assertion in the SAST finding lineage
persistence test to match the lastObservedAt field with a whitespace-tolerant
regular expression instead of hardcoded alignment spaces. Keep the migration
assertion unchanged and continue validating the DateTime? type.
- Around line 174-176: Refine the coverageGate assertion in the SAST lineage
persistence test to match only forbidden method declarations in the class body,
rather than occurrences in comments, strings, or unrelated helpers. Preserve
detection of calculate, publish, and override declarations while avoiding false
positives such as call sites or textual references.
In `@apps/api/test/support/sast-finding-lineage-fixtures.ts`:
- Around line 244-322: Replace the direct `as OpenGrepRedactedFinding` cast in
the finding fixture with a `satisfies OpenGrepRedactedFinding` validation on the
base object before applying `overrides`. Preserve the existing override and
redaction decision-digest calculation flow while ensuring required contract
changes are caught at compile time.
In `@packages/shared/src/types/sast-finding-lineage.ts`:
- Around line 963-976: Update canonicalAuthority() to derive its return value
from the existing frozen AUTHORITY constant instead of redeclaring the authority
object literal. Preserve the same SastFindingLineageAuthority shape and values
while ensuring future changes to AUTHORITY are reflected automatically.
- Around line 1156-1158: Hoist the TextEncoder allocation out of utf8ByteLength
by creating a module-level encoder and reusing it for each call. Update
utf8ByteLength to encode with that shared instance while preserving its
byte-length result.
In `@packages/shared/test/sast-finding-lineage.test.mjs`:
- Around line 5-25: Extend the test imports and suite to cover
isSastFindingLineageObservationResultShapeValid and
toSastFindingLineageAuditMetadata. Add cases verifying the observation
validator’s occurrence/finding count equality, created + exact + renamed ===
distinct algebra, and authority validation, plus dispatcher cases confirming
toSastFindingLineageAuditMetadata returns valid metadata and throws for invalid
outcomes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: d7fdf574-3f69-4aed-8851-99d3fb329855
📒 Files selected for processing (28)
README.mdapps/api/prisma/migrations/20260730160000_sast_finding_lineage_lifecycle/migration.sqlapps/api/prisma/schema.prismaapps/api/scripts/apply-online-sast-runtime-schema.mjsapps/api/src/scan-plane/prisma-sast-finding-lineage.store.tsapps/api/src/scan-plane/sast-finding-lifecycle-coverage.gate.tsapps/api/src/scan-plane/sast-finding-lineage.service.tsapps/api/src/scan-plane/sast-finding-lineage.store.tsapps/api/src/scan-plane/sast-finding-rename-attestation.verifier.tsapps/api/src/scan-plane/scan-plane.module.tsapps/api/test/scan-plane/prisma-sast-finding-lineage.store.e2e-spec.tsapps/api/test/scan-plane/sast-finding-lineage-persistence.e2e-spec.tsapps/api/test/scan-plane/sast-finding-lineage.e2e-spec.tsapps/api/test/support/sast-finding-lineage-fixtures.tspackages/shared/src/index.tspackages/shared/src/types/sast-finding-lineage.tspackages/shared/test/sast-finding-lineage.test.mjspackages/shared/test/shared-contract-exports.test.mjsspecs/006-production-sast-runtime-design/contracts/sast-runtime.mdspecs/006-production-sast-runtime-design/data-model.mdspecs/006-production-sast-runtime-design/plan.mdspecs/006-production-sast-runtime-design/quality-gates.mdspecs/006-production-sast-runtime-design/quickstart.mdspecs/006-production-sast-runtime-design/research.mdspecs/006-production-sast-runtime-design/spec.mdspecs/006-production-sast-runtime-design/tasks.mdspecs/006-production-sast-runtime-design/threat-model.mdtest/github-actions/active-feature.test.mjs
🎋 작업 중인 브랜치 및 이슈 - 브랜치:
feat/270-006-finding-lineage- 이슈: #270 - 선행 PR: #269 ## 🔎 주요 변경 사항 -sast-finding-lineage-v1, target-context, rename-attestation, T039-compatible lifecycle coverage 계약과 canonical digest/shape validator를 shared package에 추가했습니다. - T036 fingerprinted handoff와 tenant/repository/scan/attempt/scanner/plan/artifact/profile/retention binding을 service와 Prisma receiving boundary에서 재검증합니다. - tenant·repository·capability·fingerprint-version·stable-fingerprint 단위로 하나의 durable lineage를 유지하고, 동일 fingerprint의 반복 관측도 producer ordinal별 별도 occurrence와 normalized-finding row로 모두 보존합니다. - observation batch/source scanner-run replay는 전체 immutable batch와 ordered occurrence ledger가 정확히 일치할 때만 idempotent하게 반환하며 변경·누락·추가·cross-scope replay를 거부합니다. - rename은 검증된 fixed-commit/fixed-target one-to-one canonical attestation과 기존 predecessor alias가 있을 때만 continuity를 부여하고, rename-back도 alias 중복 없이 append-onlyRENAMEDevent로 기록합니다. - lifecycle을 tenant/repository/target context별OPEN | FIXED상태로 분리하고 legacy policy/triage status와 독립시켰습니다. -FIXED/REOPENED는 T039 호환 gate가 검증한COMPLETE, non-stale, comparable decision과 현재 scan의 zero-finding batch를 포함한 전체 observation digest set이 정확히 일치할 때만 적용합니다. - global reconciliation sequence는 연속적으로 유지하면서 새로 생성되거나 다시 eligible해진 state가 non-future fence에서 안전하게 따라잡도록 처리했습니다. - serializable transaction, 5초 acquisition wait, 120초 deadline, 최대 3회 P2034/P2002 retry로 concurrent lineage/alias/lifecycle race를 결정적으로 처리합니다. - rolling-safe Prisma migration에 lineage/alias/batch/occurrence/state/reconciliation/event 모델과 tenant·repository·target-context composite FK, unique, check, index를 추가했습니다. - exact/repeated/replay/tamper/concurrency/rename/rename-back/ambiguous/UNKNOWN/context isolation/fixed/reopen/partial/stale/zero-batch/zero-leak 회귀 테스트를 추가했습니다. -ScanPlaneModule은 T037 lineage service만 다음 내부 stage에 export하고 correlation, coverage calculation, evidence, policy, publication, AI authority와 사용자 route는 계속 차단했습니다. - 006 contracts/data-model/research/threat-model/quality-gates/spec/plan/quickstart/tasks 및 active-feature guard를 T037 완료와 T038 다음 진입점으로 동기화했습니다. ## ✅ 컨벤션 확인 - [x] 브랜치명이type/issue-number-short-feature형식을 따르나요? - [x] 이슈 제목과 PR 제목을 동일하게 작성했나요? - [x] 커밋 메시지가<type>: <description>형식을 따르나요? ## Check List - [x] Assignees 등록을 하였나요? - [x] 라벨(Label) 등록을 하였나요? - [x] PR 머지 전 반드시 CI가 정상적으로 작동하는지 확인했나요? ## 검증 -corepack pnpm lint-corepack pnpm test(API 88 suites / 540 tests, Web 15 files / 54 tests, Shared 75 tests, GitHub/runtime contract tests 통과) -corepack pnpm typecheck-corepack pnpm build-corepack pnpm --filter @aegisai/api prisma:validate-node --test test/runtime/*.test.mjs(1 test) - focused T037 finding-lineage API 38 tests - focused T037 shared lineage 9 tests -git diff --check