feat(ai): expose durable proposal audit and decision API - #105
Conversation
|
Warning Review limit reached
Next review available in: 42 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (8)
📝 WalkthroughWalkthroughAI 제안 감사 기능을 추가했습니다. 제안을 PostgreSQL에 저장하고 테넌트 범위로 조회합니다. 승인·거부 결정을 멱등적으로 기록합니다. Production 런타임, HTTP 오류 매핑, 통합 테스트와 운영 문서를 추가했습니다. ChangesAI 제안 감사 API
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant AiProposalAuditController
participant ProposalAuditApplication
participant PostgresProposalAuditRepository
Client->>AiProposalAuditController: 제안 생성 또는 결정 요청
AiProposalAuditController->>ProposalAuditApplication: workspace/actor 범위와 요청 전달
ProposalAuditApplication->>PostgresProposalAuditRepository: 제안 또는 결정 감사 레코드 저장
PostgresProposalAuditRepository-->>ProposalAuditApplication: 저장 결과 반환
ProposalAuditApplication-->>AiProposalAuditController: 감사된 제안 또는 결정 반환
AiProposalAuditController-->>Client: JSON 응답 반환
Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/ai-service/src/main.ts (1)
118-134: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win생성 경로에서 감사 검증 오류가 503으로 매핑됩니다.
createProposal의 catch는ProposalValidationError와ProposalAuditPersistenceError만 구분합니다.ProposalAuditApplication.generateProposal은createProposalAuditRecord와now(this.clock)을 통해ProposalAuditValidationError를 던질 수 있습니다. 그 오류는 라인 129의 기본 분기로 떨어져503 proposal_unavailable이 됩니다.
docs/superpowers/plans/2026-08-04-ai-proposal-audit-api-slice.md라인 54는 "malformed headers, identifiers, bodies, or model/audit evidence"를400 invalid_request로 문서화합니다. 따라서 현재 매핑은 문서화된 실패 계약과 어긋납니다.mapAuditError가 이미 모든 분기를 처리하므로 재사용하면 두 경로의 매핑이 일치합니다.🐛 제안 수정: 감사 오류 매퍼를 재사용합니다
} catch (error) { if (error instanceof ProposalValidationError) { throw problem(400, 'Proposal request is invalid', 'invalid_request'); } - if (error instanceof ProposalAuditPersistenceError) { - throw problem( - 503, - 'Proposal audit is unavailable', - 'audit_unavailable', - ); - } + if ( + error instanceof ProposalAuditValidationError || + error instanceof ProposalAuditPersistenceError + ) { + return mapAuditError(error); + } throw problem( 503, 'Proposal generation is unavailable', 'proposal_unavailable', ); }🤖 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/main.ts` around lines 118 - 134, Update the createProposal catch path to route ProposalAuditValidationError through the existing mapAuditError helper before the generic 503 proposal_unavailable fallback. Preserve the current ProposalValidationError and ProposalAuditPersistenceError handling, and ensure audit validation failures map to the same 400 invalid_request contract as the other audit path.
🧹 Nitpick comments (7)
apps/ai-service/src/main.ts (2)
228-245: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value두 토큰이 같은 인스턴스를 제공합니다.
PROPOSAL_SERVICE와PROPOSAL_AUDIT_APPLICATION은 모두runtime.application을 반환합니다. 팩토리 두 개가 동일한 객체를 노출합니다.AiProposalController는ProposalGenerator계약만 필요하므로 토큰 분리는 의도된 계약 축소로 보입니다. 그 의도를 남기려면 한 줄 주석을 추가하십시오. 그렇지 않으면 토큰 하나로 통합하는 편이 배선이 단순합니다.또한
PROPOSAL_SERVICE팩토리의 반환 타입은ProposalAuditApplication으로 선언되어 있습니다. 좁힌 계약을 드러내려면ProposalGenerator로 선언하십시오.🤖 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/main.ts` around lines 228 - 245, Clarify the intentional shared instance in the provider configuration by adding a brief comment explaining that PROPOSAL_SERVICE exposes the narrowed ProposalGenerator contract while both tokens reuse runtime.application. Update the PROPOSAL_SERVICE factory return type from ProposalAuditApplication to ProposalGenerator, leaving PROPOSAL_AUDIT_APPLICATION unchanged.
67-91: 🩺 Stability & Availability | 🔵 Trivial알 수 없는 오류가 관측 가능성 없이 소실됩니다.
라인 90은 분류되지 않은 모든 오류를
503 audit_unavailable로 변환합니다. 자격 증명 없는 응답 본문은 올바른 설계입니다. 그러나 원래 오류는 어디에도 기록되지 않습니다. 프로그래밍 결함과 실제 데이터베이스 장애가 운영자에게 동일하게 보입니다.기본 분기에서 구조화된 로그를 남기고
code라벨별 카운터를 노출하십시오. 그러면 감사 원장 장애를 경보로 연결할 수 있습니다.🤖 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/main.ts` around lines 67 - 91, Update the fallback branch of mapAuditError to record the original unknown error through the existing structured logging mechanism and increment/expose a counter labeled with the relevant error code, while preserving the current sanitized 503 response. Use the project’s established logger and metrics symbols rather than introducing ad hoc observability APIs.package.json (1)
16-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
format:check가 명시적 파일 목록으로 계속 커집니다.새 AI 파일 경로는 정확히 추가되었습니다. 그러나 이 스크립트는 모든 대상 파일을 하나의 인수 목록으로 열거합니다. 목록은 PR마다 늘어나고, 경로를 빠뜨리면 포맷 검사가 조용히 건너뜁니다. 글롭과
.prettierignore로 바꾸면 새 파일이 자동으로 포함됩니다.예:
prettier --single-quote --check "**/*.{ts,tsx,mjs,cjs,json,yaml,yml,md}"와.prettierignore에node_modules,dist,.turbo등록.🤖 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 16, Update the format:check script in package.json to use Prettier glob patterns for supported file extensions instead of maintaining a manually enumerated file list. Add or update .prettierignore to exclude generated and dependency directories such as node_modules, dist, and .turbo, ensuring newly added files are included automatically.apps/ai-service/src/proposal-audit-application.ts (2)
17-17: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win애플리케이션 계층이 PostgreSQL 모듈에 의존합니다.
ProposalDigestMismatchError를postgres-proposal-audit-repository에서 가져옵니다. 그 결과 애플리케이션 계층이 저장소 구현 모듈에 의존합니다. 이 오류는 저장 기술과 무관한 도메인 규칙입니다.proposal-audit-domain으로 옮기고 저장소가 도메인에서 재수출하도록 하면 의존 방향이 정리됩니다.
main.ts도 같은 모듈에서 이 오류를 가져오므로, 이동 시 두 임포트를 함께 수정해야 합니다.🤖 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-application.ts` at line 17, Move ProposalDigestMismatchError from postgres-proposal-audit-repository into proposal-audit-domain, then update proposal-audit-application.ts and main.ts to import it from the domain module. Re-export the error from the repository module to preserve its existing public surface while keeping application dependencies independent of the PostgreSQL implementation.
218-244: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value결정 요청을 두 번 검증합니다.
HTTP 경계(
main.ts라인 204)가 이미validateProposalDecisionRequest로 본문을 정규화합니다. 라인 224는 같은 검증을 다시 수행합니다. 현재 검증은 멱등이므로 동작은 정확합니다. 방어적 재검증을 유지하려면 그대로 두어도 됩니다. 단일 검증 지점을 원한다면 경계에서만 검증하고 애플리케이션은 이미 검증된 타입만 받도록 계약을 좁히십시오.🤖 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-application.ts` around lines 218 - 244, Remove the duplicate validateProposalDecisionRequest call from appendDecision and narrow its request contract to the already-validated request type produced by the HTTP boundary, preserving all subsequent field usage and decision-event creation.apps/ai-service/src/proposal-audit-http.integration.test.ts (1)
191-200: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value존재 확인이 단정 뒤에 있습니다.
라인 197은
audit를 사용하고, 라인 198의if (!audit)보호 구문은 그 뒤에 옵니다. 옵셔널 체이닝 때문에 런타임 오류는 발생하지 않습니다. 그러나audit가 없을 때 실패 메시지가 명확하지 않습니다. 보호 구문을 단정보다 앞으로 옮기십시오.♻️ 제안 리팩터: 보호 구문을 먼저 실행합니다
- expect(audit?.proposal.proposalId).toBe(proposalId); if (!audit) { throw new Error('Expected persisted proposal audit evidence'); } + expect(audit.proposal.proposalId).toBe(proposalId);🤖 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-http.integration.test.ts` around lines 191 - 200, Move the audit existence guard before the proposalId assertion in the integration test, so the missing-audit error is reported first. Keep the existing “Expected persisted proposal audit evidence” failure and the audit.proposal.proposalId assertion unchanged after the guard.apps/ai-service/src/ai-runtime.ts (1)
140-146: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value
end()실패 후 재시도가 불가능합니다.라인 144는
await this.pool.end()전에closed를true로 설정합니다.end()가 거부되면 플래그는 이미 설정되어 있습니다. 이후onApplicationShutdown이 다시 호출되어도 정리는 시도되지 않고 조용히 반환됩니다. 정확히 한 번 종료를 유지하면서 실패를 표면화하려면 진행 중인 종료 프로미스를 저장해 재사용하십시오.♻️ 제안 리팩터: 종료 프로미스를 재사용합니다
- private closed = false; + private closing?: Promise<void>; constructor( private readonly pool: AiPool, readonly application: ProposalAuditApplication, ) {} async close(): Promise<void> { - if (this.closed) { - return; - } - this.closed = true; - await this.pool.end(); + this.closing ??= this.pool.end(); + await this.closing; }🤖 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/ai-runtime.ts` around lines 140 - 146, Update the close() method to store and reuse the in-progress pool.end() promise instead of setting closed before awaiting it. Return the existing promise for repeated calls, ensure a rejected end() remains observable and can be retried on a later shutdown attempt, and preserve exactly-once successful cleanup.
🤖 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/ai-service/src/ai-runtime.ts`:
- Around line 127-129: Update defaultPoolFactory to store the new Pool instance,
register a pool.on('error', ...) listener that records the error without
terminating the process, then pass that configured pool to NodePostgresAiPool.
In `@apps/ai-service/src/proposal-audit-http.integration.test.ts`:
- Around line 109-122: Update the database setup in beforeAll and afterAll to
use a dedicated disposable test database connection, such as
AI_TEST_DATABASE_URL, instead of the configured application database URL.
Require that variable for this integration test and ensure both Pool creation
and schema cleanup use it, preventing DROP SCHEMA from running against a shared
or production database.
---
Outside diff comments:
In `@apps/ai-service/src/main.ts`:
- Around line 118-134: Update the createProposal catch path to route
ProposalAuditValidationError through the existing mapAuditError helper before
the generic 503 proposal_unavailable fallback. Preserve the current
ProposalValidationError and ProposalAuditPersistenceError handling, and ensure
audit validation failures map to the same 400 invalid_request contract as the
other audit path.
---
Nitpick comments:
In `@apps/ai-service/src/ai-runtime.ts`:
- Around line 140-146: Update the close() method to store and reuse the
in-progress pool.end() promise instead of setting closed before awaiting it.
Return the existing promise for repeated calls, ensure a rejected end() remains
observable and can be retried on a later shutdown attempt, and preserve
exactly-once successful cleanup.
In `@apps/ai-service/src/main.ts`:
- Around line 228-245: Clarify the intentional shared instance in the provider
configuration by adding a brief comment explaining that PROPOSAL_SERVICE exposes
the narrowed ProposalGenerator contract while both tokens reuse
runtime.application. Update the PROPOSAL_SERVICE factory return type from
ProposalAuditApplication to ProposalGenerator, leaving
PROPOSAL_AUDIT_APPLICATION unchanged.
- Around line 67-91: Update the fallback branch of mapAuditError to record the
original unknown error through the existing structured logging mechanism and
increment/expose a counter labeled with the relevant error code, while
preserving the current sanitized 503 response. Use the project’s established
logger and metrics symbols rather than introducing ad hoc observability APIs.
In `@apps/ai-service/src/proposal-audit-application.ts`:
- Line 17: Move ProposalDigestMismatchError from
postgres-proposal-audit-repository into proposal-audit-domain, then update
proposal-audit-application.ts and main.ts to import it from the domain module.
Re-export the error from the repository module to preserve its existing public
surface while keeping application dependencies independent of the PostgreSQL
implementation.
- Around line 218-244: Remove the duplicate validateProposalDecisionRequest call
from appendDecision and narrow its request contract to the already-validated
request type produced by the HTTP boundary, preserving all subsequent field
usage and decision-event creation.
In `@apps/ai-service/src/proposal-audit-http.integration.test.ts`:
- Around line 191-200: Move the audit existence guard before the proposalId
assertion in the integration test, so the missing-audit error is reported first.
Keep the existing “Expected persisted proposal audit evidence” failure and the
audit.proposal.proposalId assertion unchanged after the guard.
In `@package.json`:
- Line 16: Update the format:check script in package.json to use Prettier glob
patterns for supported file extensions instead of maintaining a manually
enumerated file list. Add or update .prettierignore to exclude generated and
dependency directories such as node_modules, dist, and .turbo, ensuring newly
added files are included automatically.
🪄 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: fbbbaa59-4040-4857-b92e-36adf0a59fab
📒 Files selected for processing (11)
.env.exampleCHANGELOG.mdapps/ai-service/migrations/README.mdapps/ai-service/src/ai-runtime.test.tsapps/ai-service/src/ai-runtime.tsapps/ai-service/src/main.tsapps/ai-service/src/proposal-audit-application.test.tsapps/ai-service/src/proposal-audit-application.tsapps/ai-service/src/proposal-audit-http.integration.test.tsdocs/superpowers/plans/2026-08-04-ai-proposal-audit-api-slice.mdpackage.json
|
Review disposition for the exact head
|
Summary
Implements the next reviewable
ai.auditable-proposalsslice as a production PostgreSQL-backed boundary.AI_DATABASE_URLpool configuration with a dedicated application name and exactly-once NestJS shutdownCapability boundary
The production AI module receives no planning, calendar, habit, identity, notification, event-bus, command-bus, or generic user-data mutation dependency. Proposal operations remain inert evidence. Workspace and actor headers must be derived and authorized by a private authenticated gateway before external exposure.
Verification gate
Merge only when formatting, lint, type checking, complete tests, build, Compose validation, AppGuardrail, SAST Semgrep, Security Scan, Commercial Readiness, CodeRabbit, and every human/security review finding pass on the exact current head.
Closes #56.
Refs #46 and #21.
Summary by CodeRabbit
새 기능
문서
테스트