Skip to content

feat: 006 bounded accepted-finding evidence 및 reconstruction-risk 구현 - #281

Merged
goodtu02 merged 5 commits into
devfrom
feat/280-006-bounded-evidence
Aug 10, 2026
Merged

feat: 006 bounded accepted-finding evidence 및 reconstruction-risk 구현#281
goodtu02 merged 5 commits into
devfrom
feat/280-006-bounded-evidence

Conversation

@goodtu02

@goodtu02 goodtu02 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

🎋 작업 중인 브랜치 및 이슈

🔎 주요 변경 사항

  • sast-accepted-finding-evidence-v1 shared 계약에 durable finding scope, fragment request/candidate, reconstruction decision, pack, build decision과 canonical digest·exact-shape 검증을 추가했습니다.
  • T040 freshness, T039 complete coverage, T038 correlation source와 T037 occurrence·observation·normalized finding·lineage·fingerprint를 durable state에서 다시 결속해 caller-owned authority를 거부합니다.
  • 내부 source authority는 기본 UNAVAILABLE로 두고, scanner-redacted·attested source만 8 KiB 이내에서 메모리로 읽도록 했습니다.
  • known-format 및 platform secret 재검사를 적용하고 multiline redaction에서도 line coordinate를 보존하며 raw source와 secret value는 저장하지 않습니다.
  • fragment당 8 KiB, pack당 5개·32 KiB, anchor 양쪽 5줄, 최대 7일 expiry를 canonical policy로 강제합니다.
  • full-file, 파일당 2개 초과, overlap, adjacency, 파일 line coverage 25% 이상을 deterministic reconstruction risk로 거부합니다.
  • pack·decision·fragment digest와 reconstruction reference를 교차 검증하고 changed replay·context drift를 serializable transaction에서 fail closed 처리합니다.
  • Prisma에 tenant/repository/scan/attempt 범위를 composite FK로 결박한 build decision, evidence pack, fragment ledger와 DB-level zero-authority·byte·expiry 제약을 추가했습니다.
  • dashboardSafe, aiSafe, policy/publication/lifecycle authority를 false로 유지하고 classification/deletion reference는 T042 전까지 null로 고정했습니다.
  • ScanPlaneModule은 T041 service만 다음 단계로 export하고 freshness/coverage/correlation/lineage provider와 source authority는 내부에 유지했습니다.
  • 006 contracts/data-model/research/threat-model/quality-gates/spec/plan/quickstart/tasks 및 active-feature guard를 T041 완료와 T042 다음 진입점으로 동기화했습니다.

✅ 컨벤션 확인

  • 브랜치명이 type/issue-number-short-feature 형식을 따르나요?
  • 이슈 제목과 PR 제목을 동일하게 작성했나요?
  • 커밋 메시지가 <type>: <description> 형식을 따르나요?

Check List

  • Assignees 등록을 하였나요?
  • 라벨(Label) 등록을 하였나요?
  • PR 머지 전 반드시 CI가 정상적으로 작동하는지 확인했나요?

검증

  • shared/API TypeScript typecheck 통과
  • shared/API lint 통과
  • API·AI·Web production build 통과
  • Prisma schema format 및 validate 통과
  • shared 97/97 tests 통과
  • API 98 suites / 623 tests 통과
  • focused T041 API 17/17 tests 통과
  • AI runtime 17/17 tests 통과
  • GitHub Actions contract/active-feature/ontology 32/32 tests 통과
  • git diff --check
  • GitHub Actions CI run 31359997880 성공 (clean database migration 및 online-schema 검증 포함)
  • Codex/CodeRabbit inline review thread 5/5 해결, CodeRabbit status 성공

006 진행 상태

  • T041 완료
  • 다음 작업: T042 dashboard/AI classification, second-pass secret redaction, seven-day expiry enforcement 및 deletion proof

Closes #280

Summary by CodeRabbit

  • New Features
    • Added secure SAST evidence construction with validated, scoped, and redacted source fragments.
    • Added strict limits for evidence size, fragment count, coverage, and reconstruction risk.
    • Added immutable evidence decisions and packs with deterministic replay handling.
    • Evidence remains unavailable for dashboard, AI, publication, and other downstream actions until the next workflow stage.
  • Bug Fixes
    • Prevented secret exposure, malformed source data, forged evidence, overlapping fragments, and invalid reconstructions.
  • Documentation
    • Updated SAST runtime specifications, workflow guidance, quality gates, and threat-model documentation.

@goodtu02 goodtu02 self-assigned this Aug 10, 2026
@goodtu02 goodtu02 added the ✨ feat 새로운 기능 추가 label Aug 10, 2026 — with ChatGPT Codex Connector
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Accepted SAST evidence

Layer / File(s) Summary
Evidence contracts and persistence model
packages/shared/src/types/sast-accepted-evidence.ts, apps/api/prisma/schema.prisma, apps/api/prisma/migrations/..., specs/006-production-sast-runtime-design/*
Adds deterministic evidence contracts, reconstruction limits, redaction rules, durable decision, pack, and fragment models, plus T041 design requirements.
Evidence build and redaction
apps/api/src/scan-plane/sast-accepted-evidence.service.ts, apps/api/src/scan-plane/sast-accepted-evidence-source.authority.ts, packages/shared/test/sast-accepted-evidence.test.mjs, apps/api/test/scan-plane/sast-accepted-evidence.e2e-spec.ts
Validates source candidates, reapplies secret redaction, rejects unsafe reconstruction, builds bounded outcomes, and tests unavailable sources, tampering, replay, and rejection cases.
Context binding and replay persistence
apps/api/src/scan-plane/prisma-sast-accepted-evidence.store.ts, apps/api/src/scan-plane/sast-accepted-evidence.store.ts, apps/api/src/scan-plane/scan-plane.module.ts, apps/api/test/scan-plane/*, test/github-actions/*
Revalidates freshness, coverage, occurrence, and lineage context. Persists decisions transactionally, handles serializable retries and replay conflicts, and exports SastAcceptedEvidenceService as the T041 handoff.

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
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes implement T041 requirements for bounded evidence, redaction, reconstruction-risk rejection, durable binding, replay handling, persistence, authority limits, and tests [#280].
Out of Scope Changes check ✅ Passed The code, tests, migrations, module wiring, and synchronized specifications directly support the linked T041 objectives without unrelated changes.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the T041 implementation of bounded accepted-finding evidence and reconstruction-risk handling.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread apps/api/src/scan-plane/prisma-sast-accepted-evidence.store.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (14)
specs/006-production-sast-runtime-design/threat-model.md (1)

85-86: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add 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 value

Remove the identity map call.

.map((candidate) => candidate) returns the same array contents. stableJson already 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 win

Precompile the contract-ID patterns.

isContractId constructs a new RegExp on 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 Map keyed by prefix, or narrow prefix to 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 win

Accept a policy parameter in isSastEvidenceBuildDecisionShapeValid.

isSastAcceptedEvidencePackShapeValid and isSastAcceptedEvidenceBuildResultShapeValid take a policy argument. This validator instead reads the module-level SAST_ACCEPTED_EVIDENCE_POLICY at Line 952. If a caller passes a custom policy to isSastAcceptedEvidenceBuildResultShapeValid, the decision check still uses the default limit. The two checks can then disagree.

Add an optional policy parameter with the same default and forward it from isSastAcceptedEvidenceBuildResultShapeValid.

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 value

Add an index for coverageDecisionId.

coverageDecisionId is a durable scope binding, but it has no relation, no foreign key, and no index. freshnessDecisionId and occurrenceId both 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 win

Add 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 cover EVIDENCE_PRIMARY_FRAGMENT_INVALID. Both are stated T041 rules in specs/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 sourceFileLineCount works.

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 win

Assert that the risk cases produce no pack.

The overlap, adjacency, and coverage cases only check reasonCodes. They do not check pack or the reconstruction status. The stated T041 rule is that a RISK decision 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 adjacent and substantial.

🤖 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 win

Add 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, and endLine = 800 satisfies every listed condition. The shared contract rejects that case through contextLinesBefore and contextLinesAfter, 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 win

Evidence policy limits are duplicated as SQL literals in three check constraints. The shared contract derives every bound from SAST_ACCEPTED_EVIDENCE_POLICY, which aliases DEFAULT_SAST_EVIDENCE_POLICY. The migration restates the same numbers. A change to the shared policy turns a deterministic EVIDENCE_* rejection into an insert-time constraint violation.

  • apps/api/prisma/migrations/20260810043000_sast_accepted_evidence/migration.sql#L35-L87: Line 47 caps selectedFragmentCount at 5; confirm it matches policy.maxFragmentCount and 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 encode maxTotalBytes, maxFragmentCount, and maxRetentionSeconds; 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 and maxFragmentBytes; 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

SastAcceptedEvidencePack declares four composite unique constraints that the primary key and the existing buildDecisionId unique 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: keep SastAcceptedEvidencePack_fragment_scope_key, which the fragment foreign key at Lines 261-262 references, and drop SastAcceptedEvidencePack_scope_key, SastAcceptedEvidencePack_decision_key, and SastAcceptedEvidencePack_decision_scope_key.
  • apps/api/prisma/schema.prisma#L1486-L1489: remove the three matching @@unique declarations and keep only SastAcceptedEvidencePack_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 value

Align the durable field names with the Prisma model.

This section documents the durable SastAcceptedEvidencePack. It names evidencePackId and reconstructionRiskDecisionRef. The Prisma model in apps/api/prisma/schema.prisma Lines 1455 and 1472 names the same columns id and reconstructionDecisionId. The shared contract type uses the evidencePackId and reconstructionRiskDecisionRef names.

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 win

Add 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.md lines 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 win

Narrow the P2002 retry and add backoff.

P2002 is 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 the replayExisting path. P2002 raised by any other unique constraint, for example the deterministic sastAcceptedEvidencePack.id or a fragment id, is permanent. The current code retries it twice more, and each attempt can hold a serializable transaction for SERIALIZABLE_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 P2002 to 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 win

The typed persistence reason never reaches the caller. SastAcceptedEvidencePersistenceError carries CONTEXT_DRIFT, REPLAY_CONFLICT, and OUTPUT_INVALID, but the service discards that value, and the error class does not restore its prototype for the instanceof branch 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 on error.reason in the catch block and map each reason to a distinct SastEvidenceReasonCode instead of returning EVIDENCE_PERSISTENCE_CONFLICT for every failure.
  • apps/api/src/scan-plane/sast-accepted-evidence.store.ts#L20-L30: add Object.setPrototypeOf(this, new.target.prototype) in the constructor, or confirm that the API tsconfig target is ES2015 or later so that instanceof resolves 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

📥 Commits

Reviewing files that changed from the base of the PR and between 9c018f5 and 6418ebe.

📒 Files selected for processing (27)
  • apps/api/prisma/migrations/20260810043000_sast_accepted_evidence/migration.sql
  • apps/api/prisma/schema.prisma
  • apps/api/src/scan-plane/prisma-sast-accepted-evidence.store.ts
  • apps/api/src/scan-plane/sast-accepted-evidence-source.authority.ts
  • apps/api/src/scan-plane/sast-accepted-evidence.service.ts
  • apps/api/src/scan-plane/sast-accepted-evidence.store.ts
  • apps/api/src/scan-plane/scan-plane.module.ts
  • apps/api/test/scan-plane/sast-accepted-evidence-persistence.e2e-spec.ts
  • apps/api/test/scan-plane/sast-accepted-evidence.e2e-spec.ts
  • apps/api/test/scan-plane/sast-finding-correlation-persistence.e2e-spec.ts
  • apps/api/test/scan-plane/sast-finding-lineage-persistence.e2e-spec.ts
  • apps/api/test/scan-plane/sast-scan-coverage-persistence.e2e-spec.ts
  • apps/api/test/scan-plane/sast-scan-freshness-persistence.e2e-spec.ts
  • packages/shared/src/index.ts
  • packages/shared/src/types/sast-accepted-evidence.ts
  • packages/shared/test/sast-accepted-evidence.test.mjs
  • specs/006-production-sast-runtime-design/contracts/sast-runtime.md
  • specs/006-production-sast-runtime-design/data-model.md
  • specs/006-production-sast-runtime-design/plan.md
  • specs/006-production-sast-runtime-design/quality-gates.md
  • specs/006-production-sast-runtime-design/quickstart.md
  • specs/006-production-sast-runtime-design/research.md
  • specs/006-production-sast-runtime-design/spec.md
  • specs/006-production-sast-runtime-design/tasks.md
  • specs/006-production-sast-runtime-design/threat-model.md
  • test/github-actions/active-feature.test.mjs
  • test/github-actions/ontology.test.mjs

Comment thread packages/shared/src/types/sast-accepted-evidence.ts
Comment thread packages/shared/test/sast-accepted-evidence.test.mjs Outdated
Comment thread specs/006-production-sast-runtime-design/quickstart.md Outdated
@goodtu02
goodtu02 merged commit 063f9bb into dev Aug 10, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ feat 새로운 기능 추가

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 006 bounded accepted-finding evidence 및 reconstruction-risk 구현

1 participant