feat: 006 bounded accepted-finding evidence 및 reconstruction-risk 구현 - #281
Conversation
📝 WalkthroughWalkthroughChangesAccepted SAST evidence
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Sequence Diagram(s)sequenceDiagram
participant ScanPlaneCaller
participant SastAcceptedEvidenceService
participant SastAcceptedEvidenceSourceAuthority
participant PrismaSastAcceptedEvidenceStore
participant PrismaDatabase
ScanPlaneCaller->>SastAcceptedEvidenceService: build evidence request
SastAcceptedEvidenceService->>PrismaSastAcceptedEvidenceStore: loadContext freshness and occurrence
PrismaSastAcceptedEvidenceStore->>PrismaDatabase: read verified context
PrismaDatabase-->>PrismaSastAcceptedEvidenceStore: freshness, coverage, and finding records
SastAcceptedEvidenceService->>SastAcceptedEvidenceSourceAuthority: read source fragments
SastAcceptedEvidenceSourceAuthority-->>SastAcceptedEvidenceService: verified redacted source result
SastAcceptedEvidenceService->>PrismaSastAcceptedEvidenceStore: persist accepted or rejected result
PrismaSastAcceptedEvidenceStore->>PrismaDatabase: transactional decision, pack, and fragment writes
PrismaDatabase-->>PrismaSastAcceptedEvidenceStore: persisted identifiers or replay result
PrismaSastAcceptedEvidenceStore-->>SastAcceptedEvidenceService: persisted evidence outcome
SastAcceptedEvidenceService-->>ScanPlaneCaller: build outcome
🚥 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: 6418ebe6f0
ℹ️ 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: 4
🧹 Nitpick comments (14)
specs/006-production-sast-runtime-design/threat-model.md (1)
85-86: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a matching entry to the Security Invariants list.
The threat matrix now covers evidence source forgery and evidence reconstruction. The numbered Security Invariants list ends at item 16 and covers T039 and T040. Each earlier gate added one invariant. T041 adds none, so the invariant list no longer states the bounded-evidence guarantee.
Add an item such as: an evidence pack exists only for a durable accepted occurrence under a verified, fresh, comparable decision; it never stores raw source or a secret value; and it grants no dashboard, AI, policy, publication, or lifecycle 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 `@specs/006-production-sast-runtime-design/threat-model.md` around lines 85 - 86, Add a new item to the numbered Security Invariants list after item 16 covering T041’s bounded-evidence guarantee: evidence packs require a durable accepted occurrence and a verified, fresh, comparable decision, must exclude raw source and secrets, and must grant no dashboard, AI, policy, publication, or lifecycle authority.packages/shared/src/types/sast-accepted-evidence.ts (3)
258-266: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove the identity
mapcall.
.map((candidate) => candidate)returns the same array contents.stableJsonalready canonicalizes each entry. The call adds an allocation with no effect.♻️ Proposed simplification
return stableJson( - [...candidates] - .sort(compareSastEvidenceCandidates) - .map((candidate) => candidate) + [...candidates].sort(compareSastEvidenceCandidates) );🤖 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-accepted-evidence.ts` around lines 258 - 266, Remove the identity map callback from canonicalizeSastEvidenceCandidateSet and pass the sorted candidates directly to stableJson, preserving the existing compareSastEvidenceCandidates ordering and canonical output.
1475-1483: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winPrecompile the contract-ID patterns.
isContractIdconstructs a newRegExpon every call. The validators call it for every scope, fragment, and pack, so the cost repeats across large candidate sets. All current call sites pass string literals, so the static-analysis warning about a non-literal pattern is not exploitable here.Cache the compiled patterns in a
Mapkeyed by prefix, or narrowprefixto a literal union type and use a frozen lookup table.🤖 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-accepted-evidence.ts` around lines 1475 - 1483, Update isContractId to reuse precompiled regular expressions instead of constructing a RegExp on every call. Cache patterns by prefix or use a frozen lookup keyed by the literal prefixes used by current callers, while preserving the existing contract-ID validation behavior.Source: Linters/SAST tools
893-896: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAccept a
policyparameter inisSastEvidenceBuildDecisionShapeValid.
isSastAcceptedEvidencePackShapeValidandisSastAcceptedEvidenceBuildResultShapeValidtake apolicyargument. This validator instead reads the module-levelSAST_ACCEPTED_EVIDENCE_POLICYat Line 952. If a caller passes a custom policy toisSastAcceptedEvidenceBuildResultShapeValid, the decision check still uses the default limit. The two checks can then disagree.Add an optional
policyparameter with the same default and forward it fromisSastAcceptedEvidenceBuildResultShapeValid.Also applies to: 948-952
🤖 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-accepted-evidence.ts` around lines 893 - 896, Update isSastEvidenceBuildDecisionShapeValid to accept an optional policy parameter using the same default as the surrounding validators, and use that parameter instead of the module-level SAST_ACCEPTED_EVIDENCE_POLICY for decision validation. In isSastAcceptedEvidenceBuildResultShapeValid, forward its policy argument to the decision validator so custom policies are applied consistently.apps/api/prisma/schema.prisma (1)
1410-1452: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueAdd an index for
coverageDecisionId.
coverageDecisionIdis a durable scope binding, but it has no relation, no foreign key, and no index.freshnessDecisionIdandoccurrenceIdboth have one. A lookup or an integrity audit by coverage decision performs a sequential scan on a table that grows with every accepted finding.Add
@@index([coverageDecisionId])and the matching migration index, or state why coverage lookups are not required.🤖 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/prisma/schema.prisma` around lines 1410 - 1452, Add a Prisma index for coverageDecisionId in SastEvidenceBuildDecision using @@index([coverageDecisionId]), and create the corresponding database migration index. Keep the existing schema fields and relations unchanged.packages/shared/test/sast-accepted-evidence.test.mjs (2)
338-369: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd coverage for the per-file fragment-count limit and the primary-fragment rule.
The suite covers full-file, overlap, adjacency, and coverage. It does not cover
EVIDENCE_RECONSTRUCTION_FRAGMENT_COUNT, which rejects more than two fragments from one file, and it does not coverEVIDENCE_PRIMARY_FRAGMENT_INVALID. Both are stated T041 rules inspecs/006-production-sast-runtime-design/contracts/sast-runtime.md.A three-fragment single-file case needs non-overlapping, non-adjacent ranges and a total coverage below 2500 basis points, so a large
sourceFileLineCountworks.Do you want me to generate both test 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-accepted-evidence.test.mjs` around lines 338 - 369, Extend the SAST evidence tests around evidenceScope to cover T041’s per-file fragment-count and primary-fragment rules. Add a three-fragment single-file case using non-overlapping, non-adjacent ranges and a sufficiently large sourceFileLineCount so coverage remains below 2500 basis points, asserting EVIDENCE_RECONSTRUCTION_FRAGMENT_COUNT; also add a case with an invalid primary fragment asserting EVIDENCE_PRIMARY_FRAGMENT_INVALID.
165-239: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssert that the risk cases produce no pack.
The overlap, adjacency, and coverage cases only check
reasonCodes. They do not checkpackor the reconstruction status. The stated T041 rule is that aRISKdecision rejects the complete pack. A regression that keeps the reason code but still emits a pack would pass this test.💚 Proposed additional assertions
assert.ok( overlapping.decision.reasonCodes.includes( 'EVIDENCE_RECONSTRUCTION_OVERLAP' ) ); + assert.equal(overlapping.pack, null); + assert.equal(overlapping.decision.outcome, 'REJECTED'); + assert.equal( + overlapping.decision.reconstruction.status, + 'RISK' + );Apply the same three assertions to
adjacentandsubstantial.🤖 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-accepted-evidence.test.mjs` around lines 165 - 239, Extend the overlap, adjacent, and substantial assertions in the test to verify each result emits no pack and has reconstruction status indicating rejection. Keep the existing reasonCodes checks, and apply the same pack/status assertions to all three RISK cases.apps/api/prisma/migrations/20260810043000_sast_accepted_evidence/migration.sql (3)
183-196: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winAdd database checks for the context window and the normalized path.
The check constraint validates the anchor inside the fragment range and rejects a full-file span. It does not bound the context window. A row with
startLine = 1,anchorStartLine = 400, andendLine = 800satisfies every listed condition. The shared contract rejects that case throughcontextLinesBeforeandcontextLinesAfter, but the database does not.The constraint also accepts any
normalizedPath, while the shared contract requires a relative, NFC, traversal-free path.Add both checks so the durable layer enforces the reconstruction bound independently of the service.
🛡️ Proposed additional checks
AND "sourceFileLineCount" >= "endLine" AND NOT ("startLine" = 1 AND "endLine" = "sourceFileLineCount") + AND "anchorStartLine" - "startLine" <= 5 + AND "endLine" - "anchorEndLine" <= 5 + AND "normalizedPath" !~ '(^/|/$|\\|(^|/)\.\.?(/|$)|//)' AND "byteSize" BETWEEN 1 AND 8192🤖 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/prisma/migrations/20260810043000_sast_accepted_evidence/migration.sql` around lines 183 - 196, Add checks to the migration’s evidence-row constraint for the context window, enforcing that contextLinesBefore and contextLinesAfter keep the fragment within the shared reconstruction bounds relative to the anchor. Also validate normalizedPath as a relative, NFC, traversal-free path, reusing the project’s established path-validation contract or equivalent database predicates.
35-87: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winEvidence policy limits are duplicated as SQL literals in three check constraints. The shared contract derives every bound from
SAST_ACCEPTED_EVIDENCE_POLICY, which aliasesDEFAULT_SAST_EVIDENCE_POLICY. The migration restates the same numbers. A change to the shared policy turns a deterministicEVIDENCE_*rejection into an insert-time constraint violation.
apps/api/prisma/migrations/20260810043000_sast_accepted_evidence/migration.sql#L35-L87: Line 47 capsselectedFragmentCountat 5; confirm it matchespolicy.maxFragmentCountand add a comment that names the source constant.apps/api/prisma/migrations/20260810043000_sast_accepted_evidence/migration.sql#L119-L141: Lines 126-127 and Line 140 encodemaxTotalBytes,maxFragmentCount, andmaxRetentionSeconds; add the same source comment.apps/api/prisma/migrations/20260810043000_sast_accepted_evidence/migration.sql#L176-L197: Line 181 and Line 189 encode the ordinal ceiling andmaxFragmentBytes; add the same source comment and add a shared test that asserts the policy values equal these literals.🤖 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/prisma/migrations/20260810043000_sast_accepted_evidence/migration.sql` around lines 35 - 87, Keep the SQL policy literals synchronized with SAST_ACCEPTED_EVIDENCE_POLICY (aliased to DEFAULT_SAST_EVIDENCE_POLICY) and document that source. In apps/api/prisma/migrations/20260810043000_sast_accepted_evidence/migration.sql lines 35-87, annotate and verify the selectedFragmentCount ceiling; lines 119-141, annotate maxTotalBytes, maxFragmentCount, and maxRetentionSeconds; and lines 176-197, annotate the ordinal ceiling and maxFragmentBytes. Add a shared test asserting every referenced policy value equals its corresponding migration literal.
217-224: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win
SastAcceptedEvidencePackdeclares four composite unique constraints that the primary key and the existingbuildDecisionIdunique constraint already imply. Only the six-column list backs a foreign key. The rest add write cost and storage with no added guarantee, and the migration and the Prisma model must stay in sync.
apps/api/prisma/migrations/20260810043000_sast_accepted_evidence/migration.sql#L217-L224: keepSastAcceptedEvidencePack_fragment_scope_key, which the fragment foreign key at Lines 261-262 references, and dropSastAcceptedEvidencePack_scope_key,SastAcceptedEvidencePack_decision_key, andSastAcceptedEvidencePack_decision_scope_key.apps/api/prisma/schema.prisma#L1486-L1489: remove the three matching@@uniquedeclarations and keep onlySastAcceptedEvidencePack_fragment_scope_key.🤖 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/prisma/migrations/20260810043000_sast_accepted_evidence/migration.sql` around lines 217 - 224, Remove the redundant unique constraints while preserving the fragment foreign-key constraint: in apps/api/prisma/migrations/20260810043000_sast_accepted_evidence/migration.sql lines 217-224, drop SastAcceptedEvidencePack_scope_key, SastAcceptedEvidencePack_decision_key, and SastAcceptedEvidencePack_decision_scope_key, keeping SastAcceptedEvidencePack_fragment_scope_key; make the matching change in apps/api/prisma/schema.prisma lines 1486-1489 by removing the three corresponding @@unique declarations and retaining only SastAcceptedEvidencePack_fragment_scope_key.specs/006-production-sast-runtime-design/data-model.md (1)
759-768: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAlign the durable field names with the Prisma model.
This section documents the durable
SastAcceptedEvidencePack. It namesevidencePackIdandreconstructionRiskDecisionRef. The Prisma model inapps/api/prisma/schema.prismaLines 1455 and 1472 names the same columnsidandreconstructionDecisionId. The shared contract type uses theevidencePackIdandreconstructionRiskDecisionRefnames.State which layer each name belongs to, or use the column names in the data-model document.
🤖 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 `@specs/006-production-sast-runtime-design/data-model.md` around lines 759 - 768, Align the SastAcceptedEvidencePack field names with the Prisma model by using id and reconstructionDecisionId in the durable data-model section, or explicitly distinguish those database column names from the shared contract names evidencePackId and reconstructionRiskDecisionRef. Apply the same naming clarification to the listed invariant and attribution fields without changing their semantics.apps/api/test/scan-plane/sast-accepted-evidence.e2e-spec.ts (1)
133-202: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winAdd tests for adjacency, combined coverage, and the per-file fragment cap.
This test covers full-file and overlap rejection. Three other reconstruction invariants stay untested: adjacent intervals (for example 10-12 then 13-15), combined coverage at or above 25% of
sourceFileLineCount, and more than two fragments from one file.quality-gates.mdlines 296-298 lists all five as release-blocking. These rules are the reconstruction defense, so a regression in any of them is silent today.Do you want me to generate the three additional 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 `@apps/api/test/scan-plane/sast-accepted-evidence.e2e-spec.ts` around lines 133 - 202, Add three cases to the test around SastAcceptedEvidenceService.build: verify adjacent intervals such as 10-12 and 13-15 are rejected, verify combined fragment coverage at or above 25% of sourceFileLineCount is rejected, and verify more than two fragments from one file is rejected. Assert each result is REJECTED and checks the corresponding reconstruction reason code, while preserving the existing full-file and overlap assertions.apps/api/src/scan-plane/prisma-sast-accepted-evidence.store.ts (1)
715-720: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winNarrow the
P2002retry and add backoff.
P2002is a unique-constraint violation. It is retryable here only for the find-then-create race on the build-decision key at lines 78-98, where the retry re-reads and takes thereplayExistingpath.P2002raised by any other unique constraint, for example the deterministicsastAcceptedEvidencePack.idor a fragmentid, is permanent. The current code retries it twice more, and each attempt can hold a serializable transaction forSERIALIZABLE_TIMEOUT_MILLISECONDS(120 s). The caller then waits up to six minutes for the same failure.The loop also retries immediately with no delay, which increases write-conflict contention under load.
Restrict
P2002to the build-decision constraint, and add a short bounded backoff.♻️ Proposed retry narrowing
function isRetryableTransactionError(error: unknown): boolean { - return ( - error instanceof Prisma.PrismaClientKnownRequestError && - (error.code === 'P2034' || error.code === 'P2002') - ); + if ( + !(error instanceof Prisma.PrismaClientKnownRequestError) + ) { + return false; + } + if (error.code === 'P2034') return true; + if (error.code !== 'P2002') return false; + const target = error.meta?.target; + const fields = Array.isArray(target) + ? target.map(String) + : typeof target === 'string' + ? [target] + : []; + return fields.includes('candidateSetDigest'); }🤖 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-accepted-evidence.store.ts` around lines 715 - 720, Update isRetryableTransactionError and its call sites to retry P2002 only when it represents the build-decision unique-key find-then-create race; continue treating P2034 as retryable generally, while allowing pack or fragment ID violations to fail permanently. Add a short bounded delay between retry attempts in the surrounding transaction retry loop, without delaying the initial attempt or changing the existing retry limit.apps/api/src/scan-plane/sast-accepted-evidence.service.ts (1)
224-262: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winThe typed persistence reason never reaches the caller.
SastAcceptedEvidencePersistenceErrorcarriesCONTEXT_DRIFT,REPLAY_CONFLICT, andOUTPUT_INVALID, but the service discards that value, and the error class does not restore its prototype for theinstanceofbranch that would read it. The shared root cause is that the error contract is defined but not consumed.
apps/api/src/scan-plane/sast-accepted-evidence.service.ts#L224-L262: branch onerror.reasonin thecatchblock and map each reason to a distinctSastEvidenceReasonCodeinstead of returningEVIDENCE_PERSISTENCE_CONFLICTfor every failure.apps/api/src/scan-plane/sast-accepted-evidence.store.ts#L20-L30: addObject.setPrototypeOf(this, new.target.prototype)in the constructor, or confirm that the APItsconfigtarget is ES2015 or later so thatinstanceofresolves correctly.🤖 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-accepted-evidence.service.ts` around lines 224 - 262, Consume the typed persistence failure in the service catch block: in apps/api/src/scan-plane/sast-accepted-evidence.service.ts lines 224-262, use SastAcceptedEvidencePersistenceError and its error.reason to map CONTEXT_DRIFT, REPLAY_CONFLICT, and OUTPUT_INVALID to their distinct SastEvidenceReasonCode values instead of always returning EVIDENCE_PERSISTENCE_CONFLICT. In apps/api/src/scan-plane/sast-accepted-evidence.store.ts lines 20-30, restore the error prototype in SastAcceptedEvidencePersistenceError’s constructor, or confirm the API TypeScript target is ES2015+ so the instanceof check works.
🤖 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/20260810043000_sast_accepted_evidence/migration.sql`:
- Around line 241-263: Add migration-level database guards that reject UPDATE
operations on SastEvidenceBuildDecision, SastAcceptedEvidencePack, and
SastAcceptedEvidenceFragment, using the project’s established trigger or rule
pattern if available. Place the guards alongside the existing scoped foreign-key
constraints and ensure inserts and deletes retain their current behavior.
In `@packages/shared/src/types/sast-accepted-evidence.ts`:
- Around line 401-404: Update buildSastAcceptedEvidence around the
createdAtMilliseconds calculation to validate input.decidedAt as a canonical
timestamp before parsing or calling toISOString. Return the existing rejected
result shape with EVIDENCE_INPUT_INVALID for invalid timestamps, while
preserving the current expiry calculation for valid input.
In `@packages/shared/test/sast-accepted-evidence.test.mjs`:
- Around line 290-321: Update the destructuring bindings in the test setup
around isSastAcceptedEvidencePackShapeValid and
canonicalizeSastEvidenceBuildDecision so packDigest and decisionDigest use the
project’s ignored-variable convention, such as an underscore-prefixed name.
Preserve the existing packCore and decisionCore objects and forged-object
assertions unchanged.
In `@specs/006-production-sast-runtime-design/quickstart.md`:
- Around line 480-482: Update the ScanPlaneModule export description in the
quickstart to acknowledge all five exported symbols, while retaining that
SastAcceptedEvidenceService is the sole sequential T041 handoff to T042. Remove
the claim that earlier coverage, correlation, lineage, identity, and redaction
providers remain internal.
---
Nitpick comments:
In
`@apps/api/prisma/migrations/20260810043000_sast_accepted_evidence/migration.sql`:
- Around line 183-196: Add checks to the migration’s evidence-row constraint for
the context window, enforcing that contextLinesBefore and contextLinesAfter keep
the fragment within the shared reconstruction bounds relative to the anchor.
Also validate normalizedPath as a relative, NFC, traversal-free path, reusing
the project’s established path-validation contract or equivalent database
predicates.
- Around line 35-87: Keep the SQL policy literals synchronized with
SAST_ACCEPTED_EVIDENCE_POLICY (aliased to DEFAULT_SAST_EVIDENCE_POLICY) and
document that source. In
apps/api/prisma/migrations/20260810043000_sast_accepted_evidence/migration.sql
lines 35-87, annotate and verify the selectedFragmentCount ceiling; lines
119-141, annotate maxTotalBytes, maxFragmentCount, and maxRetentionSeconds; and
lines 176-197, annotate the ordinal ceiling and maxFragmentBytes. Add a shared
test asserting every referenced policy value equals its corresponding migration
literal.
- Around line 217-224: Remove the redundant unique constraints while preserving
the fragment foreign-key constraint: in
apps/api/prisma/migrations/20260810043000_sast_accepted_evidence/migration.sql
lines 217-224, drop SastAcceptedEvidencePack_scope_key,
SastAcceptedEvidencePack_decision_key, and
SastAcceptedEvidencePack_decision_scope_key, keeping
SastAcceptedEvidencePack_fragment_scope_key; make the matching change in
apps/api/prisma/schema.prisma lines 1486-1489 by removing the three
corresponding @@unique declarations and retaining only
SastAcceptedEvidencePack_fragment_scope_key.
In `@apps/api/prisma/schema.prisma`:
- Around line 1410-1452: Add a Prisma index for coverageDecisionId in
SastEvidenceBuildDecision using @@index([coverageDecisionId]), and create the
corresponding database migration index. Keep the existing schema fields and
relations unchanged.
In `@apps/api/src/scan-plane/prisma-sast-accepted-evidence.store.ts`:
- Around line 715-720: Update isRetryableTransactionError and its call sites to
retry P2002 only when it represents the build-decision unique-key
find-then-create race; continue treating P2034 as retryable generally, while
allowing pack or fragment ID violations to fail permanently. Add a short bounded
delay between retry attempts in the surrounding transaction retry loop, without
delaying the initial attempt or changing the existing retry limit.
In `@apps/api/src/scan-plane/sast-accepted-evidence.service.ts`:
- Around line 224-262: Consume the typed persistence failure in the service
catch block: in apps/api/src/scan-plane/sast-accepted-evidence.service.ts lines
224-262, use SastAcceptedEvidencePersistenceError and its error.reason to map
CONTEXT_DRIFT, REPLAY_CONFLICT, and OUTPUT_INVALID to their distinct
SastEvidenceReasonCode values instead of always returning
EVIDENCE_PERSISTENCE_CONFLICT. In
apps/api/src/scan-plane/sast-accepted-evidence.store.ts lines 20-30, restore the
error prototype in SastAcceptedEvidencePersistenceError’s constructor, or
confirm the API TypeScript target is ES2015+ so the instanceof check works.
In `@apps/api/test/scan-plane/sast-accepted-evidence.e2e-spec.ts`:
- Around line 133-202: Add three cases to the test around
SastAcceptedEvidenceService.build: verify adjacent intervals such as 10-12 and
13-15 are rejected, verify combined fragment coverage at or above 25% of
sourceFileLineCount is rejected, and verify more than two fragments from one
file is rejected. Assert each result is REJECTED and checks the corresponding
reconstruction reason code, while preserving the existing full-file and overlap
assertions.
In `@packages/shared/src/types/sast-accepted-evidence.ts`:
- Around line 258-266: Remove the identity map callback from
canonicalizeSastEvidenceCandidateSet and pass the sorted candidates directly to
stableJson, preserving the existing compareSastEvidenceCandidates ordering and
canonical output.
- Around line 1475-1483: Update isContractId to reuse precompiled regular
expressions instead of constructing a RegExp on every call. Cache patterns by
prefix or use a frozen lookup keyed by the literal prefixes used by current
callers, while preserving the existing contract-ID validation behavior.
- Around line 893-896: Update isSastEvidenceBuildDecisionShapeValid to accept an
optional policy parameter using the same default as the surrounding validators,
and use that parameter instead of the module-level SAST_ACCEPTED_EVIDENCE_POLICY
for decision validation. In isSastAcceptedEvidenceBuildResultShapeValid, forward
its policy argument to the decision validator so custom policies are applied
consistently.
In `@packages/shared/test/sast-accepted-evidence.test.mjs`:
- Around line 338-369: Extend the SAST evidence tests around evidenceScope to
cover T041’s per-file fragment-count and primary-fragment rules. Add a
three-fragment single-file case using non-overlapping, non-adjacent ranges and a
sufficiently large sourceFileLineCount so coverage remains below 2500 basis
points, asserting EVIDENCE_RECONSTRUCTION_FRAGMENT_COUNT; also add a case with
an invalid primary fragment asserting EVIDENCE_PRIMARY_FRAGMENT_INVALID.
- Around line 165-239: Extend the overlap, adjacent, and substantial assertions
in the test to verify each result emits no pack and has reconstruction status
indicating rejection. Keep the existing reasonCodes checks, and apply the same
pack/status assertions to all three RISK cases.
In `@specs/006-production-sast-runtime-design/data-model.md`:
- Around line 759-768: Align the SastAcceptedEvidencePack field names with the
Prisma model by using id and reconstructionDecisionId in the durable data-model
section, or explicitly distinguish those database column names from the shared
contract names evidencePackId and reconstructionRiskDecisionRef. Apply the same
naming clarification to the listed invariant and attribution fields without
changing their semantics.
In `@specs/006-production-sast-runtime-design/threat-model.md`:
- Around line 85-86: Add a new item to the numbered Security Invariants list
after item 16 covering T041’s bounded-evidence guarantee: evidence packs require
a durable accepted occurrence and a verified, fresh, comparable decision, must
exclude raw source and secrets, and must grant no dashboard, AI, policy,
publication, or lifecycle authority.
🪄 Autofix
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: eef4ff51-5d13-4c3e-a2c5-46678123c9a3
📒 Files selected for processing (27)
apps/api/prisma/migrations/20260810043000_sast_accepted_evidence/migration.sqlapps/api/prisma/schema.prismaapps/api/src/scan-plane/prisma-sast-accepted-evidence.store.tsapps/api/src/scan-plane/sast-accepted-evidence-source.authority.tsapps/api/src/scan-plane/sast-accepted-evidence.service.tsapps/api/src/scan-plane/sast-accepted-evidence.store.tsapps/api/src/scan-plane/scan-plane.module.tsapps/api/test/scan-plane/sast-accepted-evidence-persistence.e2e-spec.tsapps/api/test/scan-plane/sast-accepted-evidence.e2e-spec.tsapps/api/test/scan-plane/sast-finding-correlation-persistence.e2e-spec.tsapps/api/test/scan-plane/sast-finding-lineage-persistence.e2e-spec.tsapps/api/test/scan-plane/sast-scan-coverage-persistence.e2e-spec.tsapps/api/test/scan-plane/sast-scan-freshness-persistence.e2e-spec.tspackages/shared/src/index.tspackages/shared/src/types/sast-accepted-evidence.tspackages/shared/test/sast-accepted-evidence.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.mjstest/github-actions/ontology.test.mjs
🎋 작업 중인 브랜치 및 이슈
feat/280-006-bounded-evidence🔎 주요 변경 사항
sast-accepted-finding-evidence-v1shared 계약에 durable finding scope, fragment request/candidate, reconstruction decision, pack, build decision과 canonical digest·exact-shape 검증을 추가했습니다.UNAVAILABLE로 두고, scanner-redacted·attested source만 8 KiB 이내에서 메모리로 읽도록 했습니다.dashboardSafe,aiSafe, policy/publication/lifecycle authority를 false로 유지하고 classification/deletion reference는 T042 전까지 null로 고정했습니다.ScanPlaneModule은 T041 service만 다음 단계로 export하고 freshness/coverage/correlation/lineage provider와 source authority는 내부에 유지했습니다.✅ 컨벤션 확인
type/issue-number-short-feature형식을 따르나요?<type>: <description>형식을 따르나요?Check List
검증
git diff --check31359997880성공 (clean database migration 및 online-schema 검증 포함)006 진행 상태
Closes #280
Summary by CodeRabbit