Skip to content

feat(ai): persist tenant-safe proposal audit history - #54

Merged
seonghobae merged 30 commits into
mainfrom
feat/ai-proposal-audit-repository
Aug 3, 2026
Merged

feat(ai): persist tenant-safe proposal audit history#54
seonghobae merged 30 commits into
mainfrom
feat/ai-proposal-audit-repository

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements #49 as the next reviewable slice of #46 after #48.

  • adds a forward-only PostgreSQL migration for immutable proposal evidence and append-only accept/reject decisions
  • introduces an audit-only asynchronous repository contract with no planning, calendar, habit, identity, notification, or user-data mutation dependency
  • persists bounded normalized request evidence, model identity, rationale, inert operations, mandatory-confirmation state, and timestamps
  • computes canonical SHA-256 request and proposal-content digests and verifies both on every persistence read
  • carries UUIDv4 workspace, proposal, actor, event, and idempotency identifiers through tenant-scoped ownership paths
  • requires decisions to reference the exact persisted content digest through a composite foreign key
  • recovers exact idempotent decision replays and rejects stale digests or conflicting reuse
  • parameterizes every SQL value, scopes every read by workspace_id, validates stored JSON/scalars, and returns deterministic ordering
  • rejects UPDATE, DELETE, and TRUNCATE across the proposal and decision ledger
  • adds unit and pooled PostgreSQL integration evidence for tamper detection, restart durability, tenant isolation, concurrent replay serialization, stale-digest rejection, conflicting replay rejection, and append-only enforcement
  • documents runtime privileges, retention and erasure constraints, rollback guidance, and the no-user-data-mutation trust boundary

Deferred

Validated production pool configuration, NestJS repository wiring, proposal retrieval and decision HTTP routes, authenticated actor derivation, external model transport, and separately authorized proposal execution remain subsequent reviewable slices.

Validation gate

Merge only when CI, AppGuardrail, Semgrep, Security Scan, Commercial Readiness, CodeRabbit, and all human/security review requirements pass on the exact head with no unresolved actionable findings.

Closes #49
Refs #46

Summary by CodeRabbit

  • 새 기능

    • AI 제안과 승인·거부 결정 이벤트를 안전하게 저장하고 조회할 수 있습니다.
    • 제안 내용의 무결성을 검증하고 변경·변조를 감지합니다.
    • 테넌트별 데이터 격리와 중복 결정 요청의 멱등 처리를 지원합니다.
    • 감사 기록은 생성 후 수정·삭제할 수 없도록 보호됩니다.
  • 문서

    • 마이그레이션 적용 순서, 보안 경계, 운영 및 롤백 절차를 문서화했습니다.
  • 테스트 및 품질

    • 데이터 검증, 권한 보호, 동시 요청, 오류 처리에 대한 자동 검증을 강화했습니다.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@seonghobae, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 13 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: af97c278-4f3d-4fc4-be45-8308d2f4b925

📥 Commits

Reviewing files that changed from the base of the PR and between 98e59aa and c8f2161.

📒 Files selected for processing (6)
  • .github/workflows/ci.yml
  • apps/ai-service/src/postgres-proposal-audit-repository.integration.test.ts
  • apps/ai-service/src/postgres-proposal-audit-repository.test.ts
  • apps/ai-service/src/postgres-proposal-audit-repository.ts
  • apps/ai-service/src/proposal-audit-domain.test.ts
  • apps/ai-service/src/proposal-audit-domain.ts
📝 Walkthrough

Walkthrough

AI 제안 감사 도메인과 PostgreSQL 저장소를 추가했습니다. 제안 및 결정 이벤트를 테넌트 범위로 저장합니다. SHA-256 다이제스트, 멱등성, append-only 제약, 통합 검증, CI PostgreSQL 설정을 추가했습니다.

Changes

AI 제안 감사 저장

Layer / File(s) Summary
감사 도메인 계약과 다이제스트
apps/ai-service/src/proposal-audit-domain.ts, apps/ai-service/src/proposal-audit-domain.test.ts
제안 감사 레코드와 결정 이벤트의 타입, 입력 정규화, 불변 객체, UUID·시간·다이제스트 검증, 결정적 SHA-256 계산을 추가했습니다.
감사 스키마와 불변성 보호
apps/ai-service/migrations/0001_proposal_audit.sql, apps/ai-service/migrations/README.md
감사 기록과 결정 이벤트 테이블, 복합 제약조건, 조회 인덱스, UPDATE·DELETE·TRUNCATE 거부 트리거를 추가했습니다. 적용 및 운영 절차를 문서화했습니다.
PostgreSQL 저장소 동작
apps/ai-service/src/postgres-proposal-audit-repository.ts, apps/ai-service/src/postgres-proposal-audit-repository.test.ts
parameterized SQL 기반 저장소를 추가했습니다. 테넌트 범위 조회, 행 검증, 오류 매핑, stale digest 거부, 동일 결정 재사용, 충돌 재생 거부를 구현했습니다.
통합 검증과 CI 연결
apps/ai-service/src/postgres-proposal-audit-repository.integration.test.ts, apps/ai-service/package.json, .github/workflows/ci.yml, package.json, turbo.json, docs/superpowers/plans/*
PostgreSQL 통합 테스트와 동시성·격리·append-only 검증을 추가했습니다. AI_DATABASE_URL을 CI와 Turbo 환경에 연결하고 관련 파일의 포맷 검사를 확장했습니다.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant PostgresProposalAuditRepository
  participant PostgreSQL
  Caller->>PostgresProposalAuditRepository: appendDecision()
  PostgresProposalAuditRepository->>PostgreSQL: proposal digest와 decision INSERT
  PostgreSQL-->>PostgresProposalAuditRepository: 성공 또는 제약조건 오류
  PostgresProposalAuditRepository->>PostgreSQL: 동일 idempotency key 조회
  PostgreSQL-->>PostgresProposalAuditRepository: 기존 decision payload
  PostgresProposalAuditRepository-->>Caller: 기존 이벤트 재사용 또는 충돌 오류
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.79% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 테넌트 안전 AI 제안 감사 이력의 영속화라는 주요 변경을 정확하고 간결하게 설명합니다.
Linked Issues check ✅ Passed 변경 사항은 이슈 #49의 테넌트 범위 저장소, 멱등 결정, 무결성 검증, append-only 보호, 테스트와 문서 요구사항을 포함합니다.
Out of Scope Changes check ✅ Passed 모든 변경은 이슈 #49의 저장소 구현, 데이터베이스 보호, 검증, 테스트, 문서화 또는 CI 검증 범위에 직접 관련됩니다.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ai-proposal-audit-repository

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

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

🧹 Nitpick comments (4)
apps/ai-service/migrations/0001_proposal_audit.sql (1)

1-5: 🗄️ Data Integrity & Integration | 🔵 Trivial

이 마이그레이션은 재적용하면 CREATE TABLE, CREATE FUNCTION, CREATE TRIGGER에서 실패합니다. 적용 이력을 기록하는 테이블이나 마이그레이션 러너가 없으면 운영자가 부분 적용 상태를 판별할 수 없습니다. 이력 테이블 또는 러너 도입을 후속 작업으로 계획하십시오.

Also applies to: 118-132

🤖 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/ai-service/migrations/0001_proposal_audit.sql` around lines 1 - 5,
마이그레이션에 재적용 가능한 `CREATE TABLE`, `CREATE FUNCTION`, `CREATE TRIGGER` 처리를 추가하고, 부분
적용 상태를 확인할 수 있도록 적용 이력을 기록하는 테이블 또는 마이그레이션 러너 도입을 후속 작업으로 계획하십시오.
package.json (1)

15-15: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

format:check가 경로를 명시적으로 열거합니다. 파일이 추가될 때마다 이 목록을 갱신해야 하고, 누락 시 포맷 검사가 조용히 건너뜁니다. prettier --single-quote --check ..prettierignore 조합으로 전환하면 목록 관리가 사라집니다. 변경 범위가 넓으므로 후속 작업으로 진행해도 됩니다.

🤖 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 `@package.json` at line 15, Update the format:check script in package.json to
run Prettier against the repository root instead of explicitly enumerating
files, and add or reuse a .prettierignore configuration to exclude paths that
should not be checked. Preserve the existing single-quote option while ensuring
newly added files are included automatically.
apps/ai-service/src/proposal-audit-domain.test.ts (1)

153-167: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

경계 조건 테스트가 없습니다. 도메인은 requiresConfirmation !== true 거부, 텍스트 1000자 상한, rationale/operations 20개 상한을 강제합니다. 이 세 경계에 대한 케이스를 추가하면 상한 값 변경 시 회귀를 잡을 수 있습니다.

🤖 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/ai-service/src/proposal-audit-domain.test.ts` around lines 153 - 167,
Extend the tests around createProposalDecisionEvent with boundary cases for the
domain constraints: reject decisions where requiresConfirmation is not true,
accept text at the 1000-character limit while rejecting longer text, and accept
rationale and operations at 20 items while rejecting 21. Use the existing test
fixtures and ProposalAuditValidationError assertions, covering each boundary
explicitly.
apps/ai-service/src/proposal-audit-domain.ts (1)

426-443: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

validateProposalDecisionEvent는 각 필드를 requireUuidV4, requireDigest, requireString, requireTimestamp로 검증한 뒤 createProposalDecisionEvent에 넘깁니다. createProposalDecisionEvent는 같은 검증을 다시 수행합니다. 키 존재 검사만 이 함수에서 하고 값 검증은 createProposalDecisionEvent에 위임하면 중복이 사라집니다. 동작은 동일합니다.

🤖 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/ai-service/src/proposal-audit-domain.ts` around lines 426 - 443, Update
validateProposalDecisionEvent to retain only key-presence checks and delegate
all field value validation to createProposalDecisionEvent; remove the
requireUuidV4, requireDigest, requireString, and requireTimestamp calls from
this function while preserving the existing decision and optional reason
behavior.
🤖 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 @.github/workflows/ci.yml:
- Around line 54-67: Remove the “Render proposal audit formatting diff” step
from the workflow so validate proceeds to the formatting, lint, typecheck, test,
build, and compose validation stages. Do not run Prettier with --write in CI;
formatting should be applied locally via pnpm format and checked by the existing
format-check stage.

In `@apps/ai-service/src/postgres-proposal-audit-repository.ts`:
- Around line 259-279: Update the INSERT parameter list in saveProposal to
JSON.stringify safe.proposal.rationale and safe.proposal.operations before
binding them to the jsonb columns, while leaving safe.request unchanged; update
the related unit-test expectations to assert the serialized JSON strings.

---

Nitpick comments:
In `@apps/ai-service/migrations/0001_proposal_audit.sql`:
- Around line 1-5: 마이그레이션에 재적용 가능한 `CREATE TABLE`, `CREATE FUNCTION`, `CREATE
TRIGGER` 처리를 추가하고, 부분 적용 상태를 확인할 수 있도록 적용 이력을 기록하는 테이블 또는 마이그레이션 러너 도입을 후속 작업으로
계획하십시오.

In `@apps/ai-service/src/proposal-audit-domain.test.ts`:
- Around line 153-167: Extend the tests around createProposalDecisionEvent with
boundary cases for the domain constraints: reject decisions where
requiresConfirmation is not true, accept text at the 1000-character limit while
rejecting longer text, and accept rationale and operations at 20 items while
rejecting 21. Use the existing test fixtures and ProposalAuditValidationError
assertions, covering each boundary explicitly.

In `@apps/ai-service/src/proposal-audit-domain.ts`:
- Around line 426-443: Update validateProposalDecisionEvent to retain only
key-presence checks and delegate all field value validation to
createProposalDecisionEvent; remove the requireUuidV4, requireDigest,
requireString, and requireTimestamp calls from this function while preserving
the existing decision and optional reason behavior.

In `@package.json`:
- Line 15: Update the format:check script in package.json to run Prettier
against the repository root instead of explicitly enumerating files, and add or
reuse a .prettierignore configuration to exclude paths that should not be
checked. Preserve the existing single-quote option while ensuring newly added
files are included automatically.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: dc77fac4-1656-44d2-a3e5-d67e16cd42e5

📥 Commits

Reviewing files that changed from the base of the PR and between c5c84fc and 98e59aa.

📒 Files selected for processing (12)
  • .github/workflows/ci.yml
  • apps/ai-service/migrations/0001_proposal_audit.sql
  • apps/ai-service/migrations/README.md
  • apps/ai-service/package.json
  • apps/ai-service/src/postgres-proposal-audit-repository.integration.test.ts
  • apps/ai-service/src/postgres-proposal-audit-repository.test.ts
  • apps/ai-service/src/postgres-proposal-audit-repository.ts
  • apps/ai-service/src/proposal-audit-domain.test.ts
  • apps/ai-service/src/proposal-audit-domain.ts
  • docs/superpowers/plans/2026-08-04-ai-proposal-audit-repository-slice.md
  • package.json
  • turbo.json

Comment thread .github/workflows/ci.yml Outdated
Comment thread apps/ai-service/src/postgres-proposal-audit-repository.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Persist tenant-safe AI proposal audit history

1 participant