Skip to content

feat: 006 dashboard/AI evidence classification 및 deletion proof 구현 - #283

Merged
goodtu02 merged 4 commits into
devfrom
feat/282-006-evidence-access
Aug 11, 2026
Merged

feat: 006 dashboard/AI evidence classification 및 deletion proof 구현#283
goodtu02 merged 4 commits into
devfrom
feat/282-006-evidence-access

Conversation

@goodtu02

@goodtu02 goodtu02 commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

🎋 작업 중인 브랜치 및 이슈

🔎 주요 변경 사항

  • sast-evidence-access-decision-v1 shared 계약에 dashboard와 AI advisory를 분리한 exact-shape classification, canonical digest 및 zero downstream-authority 검증을 추가했습니다.
  • T041 pack과 T040/T039/T037 durable state를 tenant/repository/scan/attempt/occurrence/fingerprint 범위로 다시 결속하고 caller-owned classification authority를 거부합니다.
  • dashboard 접근은 opt-in, active retention, current secret registry, projection digest와 access claim을 응답 직전까지 재검증하며 실패를 generic 404로 처리합니다.
  • AI 경로는 별도 opt-in과 최대 24시간 eligibility를 요구하고, 원문 fragment 대신 bounded reduced evidence reference만 반환합니다.
  • known-format, registered platform secret value, secret-like/high-entropy path를 access-time에 다시 검사하며 registry가 unavailable 또는 drift 상태이면 fail closed 처리합니다.
  • accepted evidence pack에 최대 7일 retention schedule을 동일 serializable transaction으로 생성하고, 기존 pack은 bounded lazy backfill 대상으로 제한했습니다.
  • lease/fencing 기반 deletion claim, deterministic provider operation, retry 및 exact receipt-bound deletion proof를 구현했습니다.
  • deletion worker는 pack·fragment를 제거하면서 T041 build decision, retention schedule, access decision 및 immutable deletion proof ledger를 보존합니다.
  • Prisma에 repository opt-in, access decision, deletion schedule/claim/proof 모델과 DB-level scope·expiry·immutability 제약을 추가했습니다.
  • authenticated dashboard evidence route와 bounded deletion task를 wiring하고, ScanPlaneModule은 T042 access service만 T043 handoff로 export합니다.
  • 006 contracts/data-model/research/threat-model/quality-gates/spec/plan/quickstart/tasks 및 active-feature/ontology guard를 T042 완료와 T043 다음 진입점으로 동기화했습니다.

✅ 컨벤션 확인

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

Check List

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

검증

  • Prisma format, validate 및 client generate 통과
  • shared/API/AI/Web TypeScript typecheck 통과
  • 전체 lint 통과
  • shared/API/AI/Web production build 통과
  • shared 102/102 tests 통과
  • API 100 suites / 642 tests 통과
  • AI runtime 17/17 tests 통과
  • GitHub Actions contract/runtime guards 34/34 tests 통과
  • ontology Python 21/21 tests 통과
  • git diff --check
  • GitHub Actions CI 31455491247 성공
  • Codex/CodeRabbit 인라인 리뷰 11/11 반영 및 스레드 해결

006 진행 상태

  • T042 완료
  • 다음 작업: T043 normalized finding 및 reduced evidence reference만 advisory AI Plane에 전달

Closes #282

Summary by CodeRabbit

  • New Features

    • Added secure, tenant- and repository-scoped SAST evidence access for dashboards.
    • Added purpose-specific authorization, redaction, expiration checks, and limited AI evidence references.
    • Added configurable SAST AI-advisory opt-in controls.
    • Added scheduled, auditable evidence deletion with retry and replay protection.
  • Security

    • Added secret detection and redaction for sensitive evidence content.
    • Enforced fail-closed behavior for invalid, expired, conflicting, or unverifiable requests.
  • Documentation & Tests

    • Documented evidence access, retention, deletion, and security requirements.
    • Added comprehensive coverage for authorization, redaction, expiry, concurrency, and deletion workflows.

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

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This change adds T042 SAST evidence governance. It defines purpose-bound dashboard and AI-advisory access, second-pass redaction, seven-day deletion scheduling, leased deletion processing, immutable deletion proofs, dashboard routing, Prisma persistence, validation contracts, and T043 handoff updates.

Changes

SAST evidence governance

Layer / File(s) Summary
Access and deletion contracts
packages/shared/src/types/sast-evidence-access.ts, apps/api/prisma/schema.prisma, apps/api/prisma/migrations/...
Adds shared access, retention, redaction, deletion, digest, and proof contracts. Adds tenant and repository opt-in flags and four lifecycle ledgers with constraints, indexes, foreign keys, and immutable triggers.
Access classification and dashboard boundary
apps/api/src/scan-plane/sast-evidence-access.service.ts, apps/api/src/scan-plane/sast-evidence-access.store.ts, apps/api/src/dashboard/..., apps/api/src/scan-plane/scan-plane.module.ts
Adds dashboard and AI-advisory classification, secret-registry checks, path validation, second-pass redaction, restricted projections, persistence confirmation, and an authenticated dashboard evidence endpoint.
Deletion scheduling and proof execution
apps/api/src/scan-plane/prisma-sast-accepted-evidence.store.ts, apps/api/src/scan-plane/prisma-sast-evidence-access.store.ts, apps/api/src/scan-plane/sast-evidence-deletion*.ts
Schedules deletion during evidence persistence. Adds serializable claiming, lease fencing, provider receipt validation, immutable proof persistence, evidence removal, retry handling, and bounded background processing.
Access and deletion validation
packages/shared/test/*, apps/api/test/scan-plane/sast-evidence-access*.ts, apps/api/test/scan-plane/*persistence.e2e-spec.ts
Adds contract, persistence, integration, replay, concurrency, tenant-isolation, redaction, expiration, authority, and deletion-proof coverage.
T042 handoff and repository guards
specs/006-production-sast-runtime-design/*, test/github-actions/*
Synchronizes T042 requirements, threat controls, planning state, task completion, module-boundary expectations, and T043 progression checks.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

  • AigisAI/AegisAI_v2#281 — Provides the SastEvidenceBuildDecision and accepted evidence-pack models consumed by this change.
  • AigisAI/AegisAI_v2#247 — Defines the SAST runtime contracts and specifications implemented by the new access and deletion boundary.
  • AigisAI/AegisAI_v2#267 — Introduces secret detection and redaction logic extended by the evidence-access service.
🚥 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 address the linked issue’s access classification, redaction, retention, deletion proof, boundary, schema, test, and documentation requirements [#282].
Out of Scope Changes check ✅ Passed The code, schema, tests, documentation, and feature-guard changes remain within the T042 objectives defined by the linked issue [#282].
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: implementing dashboard and AI evidence classification with deletion proofs.
✨ 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: 204105e1bd

ℹ️ 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/sast-evidence-deletion.task.ts Outdated
Comment thread apps/api/src/scan-plane/sast-evidence-deletion.service.ts Outdated
Comment thread apps/api/src/scan-plane/sast-evidence-access.service.ts

@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: 8

🧹 Nitpick comments (9)
apps/api/test/scan-plane/sast-evidence-access-persistence.e2e-spec.ts (2)

61-79: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

These assertions couple the test to source formatting.

Lines 62-67 assert an exact line break and indentation inside the shared source file. A formatter change, such as a different print width, breaks these assertions without any functional regression. The same applies to lines 73-78, which assert exact expression text in the service and store.

Prefer importing the constants from @aegisai/shared and asserting their numeric values. Keep source-text matching only for the SQL constraints, which have no runtime accessor here.

♻️ Proposed direction
-    expect(shared).toContain(
-      'SAST_EVIDENCE_MAX_RETENTION_SECONDS =\n  7 * 24 * 60 * 60'
-    );
-    expect(shared).toContain(
-      'SAST_AI_PAYLOAD_MAX_RETENTION_SECONDS =\n  24 * 60 * 60'
-    );
+    expect(SAST_EVIDENCE_MAX_RETENTION_SECONDS).toBe(7 * 24 * 60 * 60);
+    expect(SAST_AI_PAYLOAD_MAX_RETENTION_SECONDS).toBe(24 * 60 * 60);
🤖 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-evidence-access-persistence.e2e-spec.ts` around
lines 61 - 79, Refactor the test case `pins seven-day evidence and 24-hour AI
payload retention in code and SQL` to import the retention constants from
`@aegisai/shared` and assert their numeric seven-day and 24-hour values instead
of matching formatted source text. Remove the exact service and store expression
assertions, while retaining source-text checks only for the SQL interval and
constraint expressions.

132-138: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Four specs parse the ScanPlaneModule exports array with duplicated and divergent regexes. The shared root cause is the absence of one helper that extracts the exports block. Two different patterns are now in use, so a formatting change to scan-plane.module.ts can break one group of specs while the other group still matches, which produces inconsistent module-boundary enforcement.

Add one helper, for example readScanPlaneExports(), in a shared test utility and call it from every spec.

  • apps/api/test/scan-plane/sast-evidence-access-persistence.e2e-spec.ts#L132-L138: replace the /exports:\s*\[([\s\S]*?)\]\s*\}\)\s*export class/ match with the shared helper.
  • apps/api/test/scan-plane/sast-accepted-evidence-persistence.e2e-spec.ts#L254-L259: replace the local regex and the ?? '' fallback with the shared helper.
  • apps/api/test/scan-plane/sast-scan-coverage-persistence.e2e-spec.ts#L117-L121: replace the local regex and the ?? '' fallback with the shared helper.
  • apps/api/test/scan-plane/sast-scan-freshness-persistence.e2e-spec.ts#L173-L177: replace the local regex and the ?? '' fallback with the shared helper.
🤖 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-evidence-access-persistence.e2e-spec.ts` around
lines 132 - 138, Add a shared test utility helper such as readScanPlaneExports()
to extract the ScanPlaneModule exports block, then replace each local regex
extraction and fallback with that helper. Update
apps/api/test/scan-plane/sast-evidence-access-persistence.e2e-spec.ts#L132-L138,
apps/api/test/scan-plane/sast-accepted-evidence-persistence.e2e-spec.ts#L254-L259,
apps/api/test/scan-plane/sast-scan-coverage-persistence.e2e-spec.ts#L117-L121,
and
apps/api/test/scan-plane/sast-scan-freshness-persistence.e2e-spec.ts#L173-L177;
each site requires the shared helper call and no duplicated regex.
apps/api/test/scan-plane/sast-evidence-access.e2e-spec.ts (1)

506-512: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert a rejection instead of a synchronous throw.

finalizeDeletion declares Promise<...> as its return type. This assertion requires a synchronous throw. It passes only because MemoryAccessStore.finalizeDeletion throws before returning a promise at line 628. The Prisma-backed store performs await work and will reject instead. The double therefore diverges from the production contract, and this fencing check does not exercise the real failure mode.

Make the double reject, and assert with rejects, which also matches lines 490-495.

♻️ Proposed change
-    expect(() =>
+    await expect(
       replayStore.finalizeDeletion({
         candidate,
         receipt: changedReceipt,
         proof: changedProof
       })
-    ).toThrow(SastEvidenceAccessPersistenceError);
+    ).rejects.toBeInstanceOf(SastEvidenceAccessPersistenceError);

Change the two throw statements in MemoryAccessStore.finalizeDeletion and the one in releaseDeletion to Promise.reject(...) so the double matches the async 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/scan-plane/sast-evidence-access.e2e-spec.ts` around lines 506 -
512, Update MemoryAccessStore.finalizeDeletion and releaseDeletion to return
Promise.reject(...) for their failure paths instead of throwing synchronously,
matching the async contract. Change the finalizeDeletion assertion in the test
to await expect(...).rejects.toThrow(SastEvidenceAccessPersistenceError),
consistent with the existing rejection assertions around lines 490-495.
packages/shared/src/types/sast-evidence-access.ts (1)

838-843: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Precompile the contract identifier patterns.

isContractId builds a new RegExp on every call. The validators call it many times per decision, and isSastEvidenceAccessScopeValid alone calls it five times. Cache the compiled patterns in a Map keyed by prefix, or export one frozen record of prefix-to-pattern entries.

The static analysis hint about regex from variable input is not exploitable here, because every caller passes a module-local literal prefix. The change is a performance and clarity improvement only.

🤖 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-evidence-access.ts` around lines 838 - 843,
Update isContractId to reuse precompiled regular expressions instead of
constructing a new RegExp on every call. Cache patterns by prefix in a
module-level Map or frozen prefix-to-pattern record, while preserving the
existing contract-ID validation behavior for all callers.

Source: Linters/SAST tools

apps/api/src/scan-plane/prisma-sast-evidence-access.store.ts (3)

271-302: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

backfillDeletionSchedules opens one serializable transaction per row.

The raw query selects up to 128 packs. The loop then calls this.load for each pack, and every load opens its own serializable transaction that re-reads the schedule, reads the pack with all fragments, revalidates the build result, and writes the schedule. With the default limit of 32 from SastEvidenceDeletionTask, this is 32 sequential serializable transactions per tick.

Batch the work into one transaction, or lower the per-tick limit. Add a comment that states the intended bound if the sequential shape is deliberate.

🤖 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-evidence-access.store.ts` around lines
271 - 302, Update backfillDeletionSchedules so the selected packs are processed
within a single transaction rather than calling this.load separately for each
row and opening one serializable transaction per pack. Reuse the existing load
validation and schedule-writing behavior through a transaction-aware helper or
equivalent batch path, while preserving the limit and scheduled count semantics.

589-619: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Add jitter to the serializable retry delay.

The backoff is 10ms * attempt, which is deterministic. Concurrent workers that abort on the same serialization failure retry at the same instants and collide again. Add a random component to each delay.

♻️ Proposed change
         await delay(
-          SERIALIZABLE_RETRY_BASE_DELAY_MILLISECONDS * attempt
+          SERIALIZABLE_RETRY_BASE_DELAY_MILLISECONDS *
+            attempt *
+            (1 + Math.random())
         );
🤖 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-evidence-access.store.ts` around lines
589 - 619, Update the retry delay in runSerializable to add a random jitter
component to the existing SERIALIZABLE_RETRY_BASE_DELAY_MILLISECONDS * attempt
backoff. Keep the linear attempt-based delay and retry conditions unchanged,
ensuring each retry waits a different randomized duration to reduce concurrent
collisions.

33-36: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Reduce the serializable transaction timeout on the request path.

load runs inside runSerializable with SERIALIZABLE_TIMEOUT_MILLISECONDS = 120_000. load also writes, because it calls createSchedule when no schedule row exists. The dashboard read path therefore holds a serializable read-write transaction for up to two minutes and holds a connection from the pool for that time.

Two concurrent dashboard reads of the same unscheduled evidence pack also contend on SastEvidenceDeletionSchedule_evidencePackId_key, which isRetryableTransactionError handles, but each retry repeats the same long window.

Use a short timeout for the interactive request path, for example five to ten seconds, and keep the long timeout only for the background deletion task. Consider letting SastEvidenceDeletionTask own all schedule creation so load becomes read-only.

Also applies to: 91-108

🤖 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-evidence-access.store.ts` around lines 33
- 36, The request-path transaction in load currently uses the two-minute
SERIALIZABLE_TIMEOUT_MILLISECONDS, including when createSchedule writes a
missing schedule. Reduce this timeout to a short interactive value of roughly
five to ten seconds, while preserving the longer timeout exclusively for the
background SastEvidenceDeletionTask; if schedule creation is moved, update load
and createSchedule accordingly so load remains read-only.
apps/api/prisma/migrations/20260810070000_sast_evidence_access_deletion/migration.sql (1)

232-243: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Add indexes that support the new composite foreign keys.

SastEvidenceAccessDecision declares foreign keys on ("scanRequestId","tenantId","repositoryBindingId") and on ("buildDecisionId","tenantId","repositoryBindingId","scanRequestId","attemptId"). No index has these columns as a leading prefix. SastEvidenceAccessDecision_lookup_idx starts with tenantId, repositoryBindingId, evidencePackId, so PostgreSQL cannot use it for the referencing side. Every cascade delete of a ScanRequest or SastEvidenceBuildDecision then triggers a sequential scan of the access ledger. SastEvidenceDeletionSchedule has the same gap for scan_scope_fkey.

⚡ Proposed indexes
 CREATE INDEX "SastEvidenceAccessDecision_deletionScheduleId_idx"
   ON "SastEvidenceAccessDecision"("deletionScheduleId");
+CREATE INDEX "SastEvidenceAccessDecision_scan_scope_idx"
+  ON "SastEvidenceAccessDecision"("scanRequestId", "tenantId", "repositoryBindingId");
+CREATE INDEX "SastEvidenceAccessDecision_build_scope_idx"
+  ON "SastEvidenceAccessDecision"("buildDecisionId", "tenantId", "repositoryBindingId", "scanRequestId", "attemptId");
+CREATE INDEX "SastEvidenceDeletionSchedule_scan_scope_idx"
+  ON "SastEvidenceDeletionSchedule"("scanRequestId", "tenantId", "repositoryBindingId");

Add the matching @@index entries to apps/api/prisma/schema.prisma so the schema and the migration stay in sync.

🤖 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/20260810070000_sast_evidence_access_deletion/migration.sql`
around lines 232 - 243, Add indexes for the composite foreign-key columns on
SastEvidenceAccessDecision, with leading prefixes matching (scanRequestId,
tenantId, repositoryBindingId) and (buildDecisionId, tenantId,
repositoryBindingId, scanRequestId, attemptId). Also add the corresponding
scan-scope foreign-key index to SastEvidenceDeletionSchedule, keeping
schema.prisma consistent with the migration.
apps/api/src/scan-plane/sast-evidence-deletion.service.ts (1)

80-89: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the dead branch and log the deletion authority failure.

Both paths in this catch block return 'RETRY_SCHEDULED', so the instanceof test has no effect. The error itself is discarded. A persistent provider failure then produces retries every tick with no operator signal, and the seven-day retention guarantee degrades silently.

Inject a Logger and record the error name and the operationId at warn level. Keep the payload out of the log.

♻️ Proposed change
     } catch (error) {
       await this.safeRelease(candidate, referenceTime);
-      if (
-        error instanceof
-        SastEvidenceDeletionAuthorityUnavailableError
-      ) {
-        return 'RETRY_SCHEDULED';
-      }
+      this.logger.warn(
+        `Evidence deletion authority failed for operation ${candidate.schedule.operationId}: ${
+          error instanceof Error ? error.name : 'UnknownError'
+        }`
+      );
       return 'RETRY_SCHEDULED';
     }

Remove the now unused SastEvidenceDeletionAuthorityUnavailableError import if no other reference remains.

🤖 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-evidence-deletion.service.ts` around lines 80 -
89, Update the catch block in the deletion flow to remove the redundant
SastEvidenceDeletionAuthorityUnavailableError check and retain a single
RETRY_SCHEDULED return after safeRelease. Inject and use Logger to warn with the
caught error name and operationId, without logging the payload; remove the
unused error import if no other references remain.
🤖 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/20260810070000_sast_evidence_access_deletion/migration.sql`:
- Around line 320-324: Define the tenant-offboarding behavior for the
SastEvidenceDeletionProof relationship by changing the
SastEvidenceDeletionProof_schedule_scope_fkey deletion policy to detach proofs
from SastEvidenceDeletionSchedule cascades, or document the required procedure
for removing existing proofs before deleting a tenant and its dependent records.
Ensure deletions of Tenant, RepositoryBinding, ScanRequest, or
SastEvidenceBuildDecision do not fail unexpectedly.

In `@apps/api/src/scan-plane/prisma-sast-evidence-access.store.ts`:
- Around line 336-348: The CONTEXT_DRIFT path must fence drifted claims before
failing so one claim cannot block the deletion queue. In
apps/api/src/scan-plane/prisma-sast-evidence-access.store.ts lines 336-348,
update claimDeletion to use a separate transaction that advances nextAttemptAt
and increments attemptCount for the drifted scheduleId, quarantining it after
the existing bounded attempt limit before raising the persistence error; in
apps/api/src/scan-plane/sast-evidence-deletion.service.ts lines 57-64, wrap
this.store.claimDeletion in try/catch, log the error, and return
'RETRY_SCHEDULED' so processBatch continues.

In `@apps/api/src/scan-plane/sast-evidence-access.service.ts`:
- Around line 834-843: Update reducedReference to return a denial result instead
of throwing when reducedEvidenceRef, redactedProjectionDigest, or
aiPayloadExpiresAt is missing. Adjust classifyForAi to handle this denied result
at its call site outside the try block, preserving the existing
denied('EVIDENCE_ACCESS_OUTPUT_INVALID', classified.decision) fail-closed
behavior and result contract.

In `@apps/api/src/scan-plane/sast-evidence-deletion.service.ts`:
- Around line 57-64: Update processNext around the claimDeletion call to catch
SastEvidenceAccessPersistenceError with code CONTEXT_DRIFT and handle it as an
idle/no-work result, preventing the batch from aborting; preserve propagation of
other errors and the existing candidate-processing flow.

In `@apps/api/src/scan-plane/sast-evidence-deletion.task.ts`:
- Around line 59-78: Update processBatch so each processNext deletion attempt
receives a freshly created Date rather than the batch-level referenceTime. Keep
the initial referenceTime for service.backfill, while preserving the existing
loop limits, IDLE handling, and processed counting.

In `@apps/api/test/scan-plane/sast-evidence-access.e2e-spec.ts`:
- Around line 423-424: Update the assertion for unavailableContext.result in the
SAST evidence access test so it fails when the result is either null or
undefined, rather than asserting against the optional pack property. Preserve
the existing deletionState assertion and verify the result object directly with
an appropriate non-nullish matcher.

In `@packages/shared/src/types/sast-evidence-access.ts`:
- Around line 329-336: Update buildSastEvidenceAccessDecision so evidenceExpiry,
decidedAt, and payloadExpiry are computed only within the allowed && purpose ===
'AI_ADVISORY' path. Use the resulting aiPayloadExpiresAt value directly where
the AI advisory response is built, while leaving dashboard and denied paths free
of date parsing.
- Around line 600-628: Update the decision validator’s `value.purpose` checks so
a denied decision requires `secondPassRedactionDecisionRef`,
`reducedEvidenceRef`, and `aiPayloadExpiresAt` to be exactly `null`, rather than
validating only their formats. Ensure the `aiPayloadExpiresAt` retention-window
parsing and comparisons run only for allowed decisions, preventing malformed
denied timestamps from passing validation.

---

Nitpick comments:
In
`@apps/api/prisma/migrations/20260810070000_sast_evidence_access_deletion/migration.sql`:
- Around line 232-243: Add indexes for the composite foreign-key columns on
SastEvidenceAccessDecision, with leading prefixes matching (scanRequestId,
tenantId, repositoryBindingId) and (buildDecisionId, tenantId,
repositoryBindingId, scanRequestId, attemptId). Also add the corresponding
scan-scope foreign-key index to SastEvidenceDeletionSchedule, keeping
schema.prisma consistent with the migration.

In `@apps/api/src/scan-plane/prisma-sast-evidence-access.store.ts`:
- Around line 271-302: Update backfillDeletionSchedules so the selected packs
are processed within a single transaction rather than calling this.load
separately for each row and opening one serializable transaction per pack. Reuse
the existing load validation and schedule-writing behavior through a
transaction-aware helper or equivalent batch path, while preserving the limit
and scheduled count semantics.
- Around line 589-619: Update the retry delay in runSerializable to add a random
jitter component to the existing SERIALIZABLE_RETRY_BASE_DELAY_MILLISECONDS *
attempt backoff. Keep the linear attempt-based delay and retry conditions
unchanged, ensuring each retry waits a different randomized duration to reduce
concurrent collisions.
- Around line 33-36: The request-path transaction in load currently uses the
two-minute SERIALIZABLE_TIMEOUT_MILLISECONDS, including when createSchedule
writes a missing schedule. Reduce this timeout to a short interactive value of
roughly five to ten seconds, while preserving the longer timeout exclusively for
the background SastEvidenceDeletionTask; if schedule creation is moved, update
load and createSchedule accordingly so load remains read-only.

In `@apps/api/src/scan-plane/sast-evidence-deletion.service.ts`:
- Around line 80-89: Update the catch block in the deletion flow to remove the
redundant SastEvidenceDeletionAuthorityUnavailableError check and retain a
single RETRY_SCHEDULED return after safeRelease. Inject and use Logger to warn
with the caught error name and operationId, without logging the payload; remove
the unused error import if no other references remain.

In `@apps/api/test/scan-plane/sast-evidence-access-persistence.e2e-spec.ts`:
- Around line 61-79: Refactor the test case `pins seven-day evidence and 24-hour
AI payload retention in code and SQL` to import the retention constants from
`@aegisai/shared` and assert their numeric seven-day and 24-hour values instead
of matching formatted source text. Remove the exact service and store expression
assertions, while retaining source-text checks only for the SQL interval and
constraint expressions.
- Around line 132-138: Add a shared test utility helper such as
readScanPlaneExports() to extract the ScanPlaneModule exports block, then
replace each local regex extraction and fallback with that helper. Update
apps/api/test/scan-plane/sast-evidence-access-persistence.e2e-spec.ts#L132-L138,
apps/api/test/scan-plane/sast-accepted-evidence-persistence.e2e-spec.ts#L254-L259,
apps/api/test/scan-plane/sast-scan-coverage-persistence.e2e-spec.ts#L117-L121,
and
apps/api/test/scan-plane/sast-scan-freshness-persistence.e2e-spec.ts#L173-L177;
each site requires the shared helper call and no duplicated regex.

In `@apps/api/test/scan-plane/sast-evidence-access.e2e-spec.ts`:
- Around line 506-512: Update MemoryAccessStore.finalizeDeletion and
releaseDeletion to return Promise.reject(...) for their failure paths instead of
throwing synchronously, matching the async contract. Change the finalizeDeletion
assertion in the test to await
expect(...).rejects.toThrow(SastEvidenceAccessPersistenceError), consistent with
the existing rejection assertions around lines 490-495.

In `@packages/shared/src/types/sast-evidence-access.ts`:
- Around line 838-843: Update isContractId to reuse precompiled regular
expressions instead of constructing a new RegExp on every call. Cache patterns
by prefix in a module-level Map or frozen prefix-to-pattern record, while
preserving the existing contract-ID validation behavior for all callers.
🪄 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: 31b02e21-f746-4b48-b1ec-bdc2fea07ed2

📥 Commits

Reviewing files that changed from the base of the PR and between 063f9bb and 204105e.

📒 Files selected for processing (34)
  • apps/api/prisma/migrations/20260810070000_sast_evidence_access_deletion/migration.sql
  • apps/api/prisma/schema.prisma
  • apps/api/src/dashboard/dashboard-evidence.controller.ts
  • apps/api/src/dashboard/dashboard.module.ts
  • apps/api/src/scan-plane/prisma-sast-accepted-evidence.store.ts
  • apps/api/src/scan-plane/prisma-sast-evidence-access.store.ts
  • apps/api/src/scan-plane/sast-evidence-access.service.ts
  • apps/api/src/scan-plane/sast-evidence-access.store.ts
  • apps/api/src/scan-plane/sast-evidence-deletion.authority.ts
  • apps/api/src/scan-plane/sast-evidence-deletion.service.ts
  • apps/api/src/scan-plane/sast-evidence-deletion.task.ts
  • apps/api/src/scan-plane/sast-evidence-secret-registry.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-evidence-access-persistence.e2e-spec.ts
  • apps/api/test/scan-plane/sast-evidence-access.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-evidence-access.ts
  • packages/shared/test/sast-evidence-access.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 apps/api/src/scan-plane/prisma-sast-evidence-access.store.ts Outdated
Comment thread apps/api/src/scan-plane/sast-evidence-access.service.ts
Comment thread apps/api/src/scan-plane/sast-evidence-deletion.service.ts Outdated
Comment thread apps/api/src/scan-plane/sast-evidence-deletion.task.ts Outdated
Comment thread apps/api/test/scan-plane/sast-evidence-access.e2e-spec.ts Outdated
Comment thread packages/shared/src/types/sast-evidence-access.ts Outdated
Comment thread packages/shared/src/types/sast-evidence-access.ts
@goodtu02
goodtu02 merged commit dcbca91 into dev Aug 11, 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 dashboard/AI evidence classification 및 deletion proof 구현

1 participant