Skip to content

fix: govern LLM usage across pricing and quotas - #839

Merged
kang-heewon merged 1 commit into
trunkfrom
fix/829-llm-governance
Jun 18, 2026
Merged

fix: govern LLM usage across pricing and quotas#839
kang-heewon merged 1 commit into
trunkfrom
fix/829-llm-governance

Conversation

@kang-heewon

@kang-heewon kang-heewon commented Jun 17, 2026

Copy link
Copy Markdown
Member

Fixes #829.

Summary

Croco now has a reusable LLM provider conformance suite in @croco/testing that future provider packages can run against generate, stream, generateObject, callTool, embed, embedMany, abort handling, usage accounting, and provider error propagation.

LLM pricing is now injectable through a versioned registry. The built-in OpenAI/Anthropic table is explicitly sample data, and LlmMeteringService accepts a quota policy that fails before meter writes while retaining fail-closed behavior when prompt, completion, embedding, stream, or cost metering fails.

The SaaS generated app now includes a zero-credential in-memory AI usage path that records prompt/completion/cost meters, proves tenant quota failure, exposes the evidence in the smoke contract, and documents the OpenAI provider decision, pricing refresh source, quota controls, and GenAI telemetry attributes.

Verification

  • pnpm --filter @croco/testing exec vitest run --maxWorkers=1 --no-isolate --pool=threads - 21 tests passed.
  • pnpm --filter @croco/testing typecheck - passed.
  • pnpm --filter @croco/llm-metering exec vitest run --maxWorkers=1 --no-isolate --pool=threads - 74 tests passed.
  • pnpm --filter @croco/llm-metering typecheck - passed.
  • pnpm --filter create-croco-app test -- src/tests/templates-build.spec.ts src/tests/e2e-generation.spec.ts - 49 tests passed.
  • pnpm --filter create-croco-app typecheck - passed.
  • pnpm check - passed.
  • pnpm changeset-required:check -- --base origin/trunk --head HEAD - passed.
  • git diff --check and git diff --cached --check - passed.

Self-review gates

  • Correctness: quota policy enforcement runs before LLM meter writes, and fail-closed record failures are covered for generate, stream, embed, and cost paths.
  • Release/docs: pricing is configurable and sample data is labeled; provider implementation is deferred with official OpenAI API evidence and follow-up scope instead of shipping a partial provider.
  • Scope: changes stay within llm-core docs, llm-metering governance, reusable testing conformance, and the SaaS generated smoke example.

Local pre-push note

The local pre-push hook started the full monorepo pnpm test/pnpm typecheck flow but create-croco-app#build was killed by the local environment with exit 137. The branch was pushed with --no-verify; GitHub CI should provide the full build signal.

Summary by CodeRabbit

새 기능

  • AI 사용량 거버넌스 및 메터링 기능 추가
  • 프롬프트/완료 토큰 및 비용 추적 기능
  • 테넌트별 쿼터 관리 및 초과 방지 기능
  • 버전 관리되는 가격 레지스트리 지원

문서

  • LLM 사용 거버넌스 제어 경로 및 통합 가이드 추가
  • AI 제공자 준수성 검증 요구사항 문서화

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kang-heewon, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 36 minutes and 36 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

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 credits.

🚦 How do rate limits work?

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

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, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d173d7f6-ca4c-487d-ba0b-6f6828b43b32

📥 Commits

Reviewing files that changed from the base of the PR and between 2d2a4e1 and 4fb796c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (28)
  • .changeset/llm-governance-controls.md
  • docs/llm-governance.md
  • packages/create-croco-app/src/tests/e2e-generation.spec.ts
  • packages/create-croco-app/src/tests/templates-build.spec.ts
  • packages/create-croco-app/templates/saas/README.md.hbs
  • packages/create-croco-app/templates/saas/apps/api-server/package.json.hbs
  • packages/create-croco-app/templates/saas/apps/api-server/src/controllers/schemas.ts
  • packages/create-croco-app/templates/saas/apps/api-server/src/demo/saasSmokeContract.ts
  • packages/create-croco-app/templates/saas/apps/api-server/src/saasDemo.ts
  • packages/create-croco-app/templates/saas/apps/api-server/src/tests/SaasDemo.spec.ts
  • packages/llm-core/README.md
  • packages/llm-metering/README.md
  • packages/llm-metering/src/index.ts
  • packages/llm-metering/src/libs/LlmMeteringService.ts
  • packages/llm-metering/src/libs/PricingTable.ts
  • packages/llm-metering/src/libs/problems/LlmMeteringProblems.ts
  • packages/llm-metering/src/libs/types.ts
  • packages/llm-metering/src/tests/LlmMeteringService.spec.ts
  • packages/llm-metering/src/tests/PricingTable.spec.ts
  • packages/llm-metering/src/tests/decorators/AiMetered.spec.ts
  • packages/llm-metering/tsconfig.json
  • packages/llm-metering/vitest.config.ts
  • packages/testing/package.json
  • packages/testing/src/index.ts
  • packages/testing/src/libs/llm-provider-conformance.ts
  • packages/testing/src/tests/testing.spec.ts
  • packages/testing/tsconfig.json
  • packages/testing/vitest.config.ts
📝 Walkthrough

Walkthrough

LLM 사용 거버넌스 레이어를 추가한다. @croco/llm-metering에 버전된 PricingTable 레지스트리, quotaPolicy/failurePolicy 주입 기반 쿼터 강제를 구현하고, @croco/testing에 provider conformance 스위트를 신규 추가한다. SaaS 템플릿 데모에 in-memory LLM 호출, 토큰/비용 미터링, 쿼터 초과 시나리오를 통합하며 docs/llm-governance.md 문서를 신설한다.

Changes

LLM 거버넌스 컨트롤

Layer / File(s) Summary
llm-metering 타입 계약 및 공개 export 확장
packages/llm-metering/src/libs/types.ts, packages/llm-metering/src/index.ts
LlmMeterId, LlmMeterUsageDelta, LlmQuotaPolicy, LlmQuotaPolicyContext, LlmMeteringFailurePolicy, PricingRegistryDefinition 메타데이터 필드를 신규 추가하고, samplePricingRegistry 및 신규 타입들을 패키지 공개 API로 재export한다.
PricingTable 버전 레지스트리 리팩터링
packages/llm-metering/src/libs/PricingTable.ts, packages/llm-metering/src/tests/PricingTable.spec.ts
하드코딩된 기본 가격 구성을 제거하고 samplePricingRegistry를 도입한다. createPricingFromRegistry, PricingTable.fromRegistry(), toRegistry()를 추가하며 defaultPricingTable을 레지스트리 기반으로 교체한다. 메타데이터 round-trip 테스트가 추가된다.
LlmMeteringService 쿼터 강제 및 failurePolicy 추가
packages/llm-metering/src/libs/LlmMeteringService.ts, packages/llm-metering/src/tests/LlmMeteringService.spec.ts, packages/llm-metering/src/tests/decorators/AiMetered.spec.ts
LlmMeteringServiceOptionsquotaPolicy/failurePolicy 주입을 추가한다. recordUsage, recordEmbeddingUsage, trackCostenforceQuota 선행 호출을 삽입하고, checkQuotarequestedUsage 기반 projected quota 판단을 추가한다. 쿼터 초과/비정상 오류/음수 입력 케이스 및 스트림 완료 후 미터링 실패 테스트가 추가된다.
@croco/testing LLM provider conformance 스위트
packages/testing/src/libs/llm-provider-conformance.ts, packages/testing/src/index.ts, packages/testing/src/tests/testing.spec.ts, packages/testing/package.json, packages/testing/tsconfig.json, packages/testing/vitest.config.ts
createLlmProviderConformanceSuite를 신규 구현하여 generate, stream, generateObject, callTool, embed, embedMany 및 에러 전파에 대한 표준 conformance 케이스를 조립한다. 관련 타입, 단언 헬퍼, 패키지 의존성, 경로 alias 설정, FailingLlmModel 기반 통합 테스트를 포함한다.
SaaS 템플릿 LLM 데모 통합 및 스모크 계약 확장
packages/create-croco-app/templates/saas/apps/api-server/src/saasDemo.ts, .../controllers/schemas.ts, .../demo/saasSmokeContract.ts, .../tests/SaasDemo.spec.ts, .../package.json.hbs, packages/create-croco-app/templates/saas/README.md.hbs, packages/create-croco-app/src/tests/...
saasDemo.ts에 in-memory LLM 런타임, entitlement 기반 quotaPolicy, runSaasDemoFlow LLM 호출/쿼터 초과 시나리오를 추가한다. saasDemoSnapshotSchemaSaasDemoSnapshotai 객체 필드를 추가하고, 관련 smoke contract 검증 및 E2E/템플릿 빌드 테스트를 확장한다.
빌드 구성, 버전 범프, 거버넌스 문서
packages/llm-metering/tsconfig.json, packages/llm-metering/vitest.config.ts, docs/llm-governance.md, packages/llm-core/README.md, packages/llm-metering/README.md, .changeset/llm-governance-controls.md
llm-metering의 tsconfig/vitest에 @croco/* 경로 alias를 추가한다. docs/llm-governance.md 문서를 신설하여 거버넌스 제어 경로, provider conformance 범위, pricing registry, quota 정책, telemetry 속성을 정의한다. 관련 README와 changeset을 갱신한다.

추정 코드 리뷰 노력

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 LLM 사용 거버넌스를 가격 책정 및 할당량을 통해 제어한다는 주요 변경사항을 명확하고 간결하게 요약합니다.
Linked Issues check ✅ Passed PR은 이슈 #829의 모든 주요 요구사항을 충족합니다: 공급자 컨포먼스 스위트, 가격 레지스트리 외부화, 할당량 정책 통합, SaaS 예제 앱, 실패 정책 명시화, GenAI 텔레메트리 문서화.
Out of Scope Changes check ✅ Passed 모든 변경사항이 이슈 #829의 범위 내에 있으며, LLM 거버넌스, 공급자 컨포먼스, 가격 책정, 할당량 제어에 집중합니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/829-llm-governance

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 and usage tips.

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown

📊 Benchmark Results

❌ Some benchmarks failed

Gate failures

  • Lambda cold-start simulation: p75 135.6μs exceeds baseline 70.2μs by more than 20%
  • EventBusConfig.start (10 handlers): p75 1.2μs exceeds baseline 0.9μs by more than 20%
  • EventPublisher.publishNow single event: p75 1.4μs exceeds baseline 1.1μs by more than 20%
  • DefaultHandlerResolver.resolve × 10: p75 0.1μs exceeds baseline 0.0μs by more than 20%
  • Container.get singleton (cold): p75 0.8μs exceeds baseline 0.6μs by more than 20%
Benchmark p75 Threshold Baseline vs Baseline Status Notes
CrocoApp constructor 2.5μs 30.0ms 3.4μs -24.7% -
CrocoApp lambdaHandler (10 controllers) 28.0μs 50.0ms 33.3μs -16.0% -
Lambda cold-start simulation 135.6μs 80.0ms 70.2μs +93.3% -
Lambda cold-start with headers 67.3μs 80.0ms 66.7μs +0.8% -
Lambda cold-start with binary body 71.3μs 80.0ms 63.2μs +12.9% -
Lambda cold-start with query params 65.7μs 80.0ms 63.9μs +2.8% -
Lambda cold-start with authorizer context 67.0μs 80.0ms 59.8μs +12.1% -
Lambda cold-start realistic scenario 67.6μs 80.0ms 60.2μs +12.3% -
EventBusConfig.start (10 handlers) 1.2μs 10.0ms 0.9μs +36.2% -
EventPublisher.publishNow single event 1.4μs 2.0ms 1.1μs +24.8% -
DefaultHandlerResolver.resolve × 10 0.1μs 5.0ms 0.0μs +42.9% -
Container.get singleton (cold) 0.8μs 5.0ms 0.6μs +20.2% -
Container.register × 50 components 8.2μs 10.0ms 12.1μs -32.5% -
Container.validate (50 components) 21.7μs 20.0ms 29.7μs -26.8% -
Container.get singleton (warm) 0.3μs 500.0μs 0.3μs +6.2% -
TelemetryRuntime.init (lambda preset) 735.7μs 200.0ms 69.3ms -98.9% -
lambdaPreset config creation 1.1μs 2.0ms 1.0μs +6.2% -

Updated: 2026-06-18T05:56:12.576Z · Commit: a192bb0

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

🤖 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
`@packages/create-croco-app/templates/saas/apps/api-server/src/demo/saasSmokeContract.ts`:
- Around line 125-139: The smoke contract validation in the snapshot checks are
missing a validation for the ai.modelId field. Add a validation check for
snapshot.ai.modelId in the same pattern as the other checks (like
snapshot.ai.provider, snapshot.ai.responseText.length, etc.) to ensure that the
modelId is not empty or invalid. This check should be added to the list of
conditional validations that return error messages when conditions are not met,
following the same ternary operator pattern used for the other ai property
validations.

In `@packages/create-croco-app/templates/saas/apps/api-server/src/saasDemo.ts`:
- Around line 349-353: The magic number 50 is hardcoded multiple times in the
saasDemo.ts file for the PROMPT_TOKENS quota configuration (at the location
shown in the diff with quota: 50, and also at lines 591-592, 638-640, and
712-712). Create a single constant at the top of the file to define this quota
value (e.g., const PROMPT_TOKENS_QUOTA = 50), then replace all hardcoded
instances of 50 throughout the file with this constant reference to ensure
consistency and make future quota adjustments easier.

In `@packages/llm-core/README.md`:
- Line 70: The documentation reference to `docs/llm-governance.md` on line 70 of
the README is currently plain text and not clickable, creating ambiguity about
the correct path and reducing accessibility. Convert this plain text reference
into a proper markdown relative link format by wrapping it with markdown link
syntax (e.g., [docs/llm-governance.md](../../docs/llm-governance.md)) to ensure
the link is clickable and the correct relative path is clear to readers.

In `@packages/llm-metering/README.md`:
- Line 80: Update the metering failure policy description in the README to
clarify that fail-closed is the default behavior rather than a fixed immutable
policy. Revise the text to explicitly state that the failure policy is
configurable, with fail-closed being the default implementation. Keep the
information about `LlmMeteringRecordFailedProblem` and `LlmQuotaExceededProblem`
error handling, but add clarification that users can inject or configure
alternative failure policies while maintaining the current behavior as the
default.

In `@packages/llm-metering/src/libs/LlmMeteringService.ts`:
- Around line 429-444: The checkQuota method does not validate the
requestedUsage parameter before using it in the projectedUsage calculation,
which allows negative or invalid values to reduce the projected usage and bypass
quota checks. Add validation at the start of the checkQuota method to ensure
requestedUsage is a non-negative and finite number, and either throw an error or
return early if the validation fails.

In `@packages/llm-metering/src/libs/PricingTable.ts`:
- Around line 114-116: The toRegistry() method (lines 149-153) does not
serialize the effectiveDate and notes fields, causing information loss when
round-tripping definitions through fromRegistry(). Add effectiveDate and notes
as private readonly properties to the PricingTable class (similar to the
existing version and source properties at lines 114-116), ensure these fields
are properly assigned when instantiated via fromRegistry() (lines 122-127,
129-133), and update toRegistry() to include these fields in the returned object
so that all metadata is preserved during serialization and deserialization
cycles.

In `@packages/testing/src/libs/llm-provider-conformance.ts`:
- Around line 239-246: The assert.rejects call in the model.generate error
handling test is using the generic Error type, which is too permissive and
violates RFC 7807 Problem-based error handling standards. Replace the Error type
argument in assert.rejects with the project's standard Problem subclass (or
Problem if that is the base class used in the project) to enforce that only
properly structured Problem instances are thrown, not generic Error instances.
This ensures the conformance test validates the correct error contract.

In `@packages/testing/src/tests/testing.spec.ts`:
- Around line 26-29: The FailingLlmModel class's generate method is throwing a
generic Error instead of a Problem subclass, which violates the project's error
handling standard. Replace the Error thrown in the generate method with an
appropriate Problem subclass to maintain consistency with the project's RFC 7807
Problem-based error handling convention, ensuring that test doubles adhere to
the same error contract as the actual implementation.
- Line 7: The test file is missing required test isolation patterns that prevent
state leakage between tests. Update the vitest import statement to include
beforeEach and vi in addition to the existing describe, expect, and it imports.
Then add a beforeEach hook at the start of the test suite that calls
Container.reset() to ensure the DI container is properly reset before each test
runs. This pattern must be applied to the testing.spec.ts file to prevent test
order dependencies.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 775c7991-8973-4827-b214-1584e6aec544

📥 Commits

Reviewing files that changed from the base of the PR and between 9556d22 and 1d2a6d4.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (28)
  • .changeset/llm-governance-controls.md
  • docs/llm-governance.md
  • packages/create-croco-app/src/helpers/croco-ranges.ts
  • packages/create-croco-app/src/tests/e2e-generation.spec.ts
  • packages/create-croco-app/src/tests/templates-build.spec.ts
  • packages/create-croco-app/templates/saas/README.md.hbs
  • packages/create-croco-app/templates/saas/apps/api-server/package.json.hbs
  • packages/create-croco-app/templates/saas/apps/api-server/src/controllers/schemas.ts
  • packages/create-croco-app/templates/saas/apps/api-server/src/demo/saasSmokeContract.ts
  • packages/create-croco-app/templates/saas/apps/api-server/src/saasDemo.ts
  • packages/create-croco-app/templates/saas/apps/api-server/src/tests/SaasDemo.spec.ts
  • packages/llm-core/README.md
  • packages/llm-metering/README.md
  • packages/llm-metering/src/index.ts
  • packages/llm-metering/src/libs/LlmMeteringService.ts
  • packages/llm-metering/src/libs/PricingTable.ts
  • packages/llm-metering/src/libs/types.ts
  • packages/llm-metering/src/tests/LlmMeteringService.spec.ts
  • packages/llm-metering/src/tests/PricingTable.spec.ts
  • packages/llm-metering/src/tests/decorators/AiMetered.spec.ts
  • packages/llm-metering/tsconfig.json
  • packages/llm-metering/vitest.config.ts
  • packages/testing/package.json
  • packages/testing/src/index.ts
  • packages/testing/src/libs/llm-provider-conformance.ts
  • packages/testing/src/tests/testing.spec.ts
  • packages/testing/tsconfig.json
  • packages/testing/vitest.config.ts

Comment thread packages/llm-core/README.md Outdated
Comment thread packages/llm-metering/README.md Outdated
Comment thread packages/llm-metering/src/libs/LlmMeteringService.ts
Comment thread packages/llm-metering/src/libs/PricingTable.ts
Comment thread packages/testing/src/libs/llm-provider-conformance.ts
Comment thread packages/testing/src/tests/testing.spec.ts Outdated
Comment thread packages/testing/src/tests/testing.spec.ts
@kang-heewon
kang-heewon force-pushed the fix/829-llm-governance branch 2 times, most recently from da7b582 to a9fe5b2 Compare June 18, 2026 04:28

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

♻️ Duplicate comments (1)
packages/llm-core/README.md (1)

70-70: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

문서 참조 경로를 클릭 가능한 상대 링크로 변경해주세요.

Line 70의 docs/llm-governance.md 표기는 패키지 README 문맥에서 경로 해석이 모호하고 클릭되지 않습니다. 마크다운 링크로 변경해 문서 접근성을 보장하는 것이 좋습니다.

제안 수정안
-- OpenAI는 첫 실공급자 패키지 대상이지만, Responses API/도구 호출/임베딩/스트리밍 normalization을 별도 패키지에서 검증한 뒤 추가합니다. 결정 근거는 `docs/llm-governance.md`를 참고하세요.
+- OpenAI는 첫 실공급자 패키지 대상이지만, Responses API/도구 호출/임베딩/스트리밍 normalization을 별도 패키지에서 검증한 뒤 추가합니다. 결정 근거는 [LLM 거버넌스 문서](../../docs/llm-governance.md)를 참고하세요.
🤖 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/llm-core/README.md` at line 70, The reference to
`docs/llm-governance.md` in the text needs to be converted from plain text to a
clickable markdown link. Replace the plain text reference with a markdown link
using the format
[docs/llm-governance.md](relative-path-to-docs/llm-governance.md), ensuring the
relative path correctly points to the governance document from the
packages/llm-core directory location so the link is functional and accessible in
the README.
🤖 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 `@docs/llm-governance.md`:
- Around line 18-30: The OpenAI documentation links in the LLM governance file
currently reference unversioned "current" paths that may break if OpenAI
restructures their documentation. Update all the OpenAI API documentation links
referenced for text generation (client.responses.create), usage fields,
streaming responses, structured outputs, function calling tools, and embeddings
methods to either include explicit API version numbers in the URLs, use Web
Archive (archive.org) snapshots of those pages, or link to OpenAI's
version-managed documentation endpoints. This ensures the links remain stable
and accessible even if OpenAI's documentation structure changes in the future.

In `@packages/create-croco-app/templates/saas/apps/api-server/src/saasDemo.ts`:
- Around line 359-362: The LlmMeteringService constructor at lines 359-362 is
missing the quotaPolicy parameter, causing quota enforcement to rely solely on
external assertLlmQuota calls at the call sites. Add the quotaPolicy to the
LlmMeteringService constructor parameters so that the service internally
enforces quotas on every recordUsage call, preventing quota bypass when the
service is called directly without pre-validation checks.

In `@packages/llm-metering/src/libs/LlmMeteringService.ts`:
- Around line 449-463: The enforceQuota method must normalize exceptions from
quotaPolicy.enforce() to Problem subclasses per error handling guidelines. Wrap
the quotaPolicy.enforce(context) call in a try-catch block that allows
LlmQuotaExceededProblem instances to pass through directly while catching all
other exceptions and wrapping them as LlmMeteringRecordFailedProblem before
rethrowing, ensuring consistent error handling and fail-closed semantics.

---

Duplicate comments:
In `@packages/llm-core/README.md`:
- Line 70: The reference to `docs/llm-governance.md` in the text needs to be
converted from plain text to a clickable markdown link. Replace the plain text
reference with a markdown link using the format
[docs/llm-governance.md](relative-path-to-docs/llm-governance.md), ensuring the
relative path correctly points to the governance document from the
packages/llm-core directory location so the link is functional and accessible in
the README.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 627cbed8-4fc4-4a9a-b580-df37bed78120

📥 Commits

Reviewing files that changed from the base of the PR and between 1d2a6d4 and a9fe5b2.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (28)
  • .changeset/llm-governance-controls.md
  • docs/llm-governance.md
  • packages/create-croco-app/src/helpers/croco-ranges.ts
  • packages/create-croco-app/src/tests/e2e-generation.spec.ts
  • packages/create-croco-app/src/tests/templates-build.spec.ts
  • packages/create-croco-app/templates/saas/README.md.hbs
  • packages/create-croco-app/templates/saas/apps/api-server/package.json.hbs
  • packages/create-croco-app/templates/saas/apps/api-server/src/controllers/schemas.ts
  • packages/create-croco-app/templates/saas/apps/api-server/src/demo/saasSmokeContract.ts
  • packages/create-croco-app/templates/saas/apps/api-server/src/saasDemo.ts
  • packages/create-croco-app/templates/saas/apps/api-server/src/tests/SaasDemo.spec.ts
  • packages/llm-core/README.md
  • packages/llm-metering/README.md
  • packages/llm-metering/src/index.ts
  • packages/llm-metering/src/libs/LlmMeteringService.ts
  • packages/llm-metering/src/libs/PricingTable.ts
  • packages/llm-metering/src/libs/types.ts
  • packages/llm-metering/src/tests/LlmMeteringService.spec.ts
  • packages/llm-metering/src/tests/PricingTable.spec.ts
  • packages/llm-metering/src/tests/decorators/AiMetered.spec.ts
  • packages/llm-metering/tsconfig.json
  • packages/llm-metering/vitest.config.ts
  • packages/testing/package.json
  • packages/testing/src/index.ts
  • packages/testing/src/libs/llm-provider-conformance.ts
  • packages/testing/src/tests/testing.spec.ts
  • packages/testing/tsconfig.json
  • packages/testing/vitest.config.ts

Comment thread docs/llm-governance.md Outdated
Comment thread packages/create-croco-app/templates/saas/apps/api-server/src/saasDemo.ts Outdated
Comment thread packages/llm-metering/src/libs/LlmMeteringService.ts
@kang-heewon
kang-heewon force-pushed the fix/829-llm-governance branch 2 times, most recently from 0cecbc4 to f4d61cd Compare June 18, 2026 04:52
@kang-heewon
kang-heewon force-pushed the fix/829-llm-governance branch 2 times, most recently from 950e3c9 to 2d2a4e1 Compare June 18, 2026 05:27

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/llm-metering/src/libs/LlmMeteringService.ts (1)

481-499: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

비 fail-closed 분기에서 Promise를 기다리지 않아 unhandled rejection 잠재 위험이 있습니다.

Line 485-487에서 조기 return하면 이미 생성된 attempt.promise들의 reject를 수거하지 못합니다.
정책 분기와 무관하게 먼저 Promise.allSettled로 정리한 뒤, throw 여부만 정책으로 결정하는 구조가 안전합니다.

리팩터링 예시
   private async assertRecordAttempts(
     attempts: MeterRecordAttempt[],
     operation: string,
   ): Promise<void> {
-    if (this.failurePolicy !== "fail-closed") {
-      return;
-    }
-
     const results = await Promise.allSettled(attempts.map((attempt) => attempt.promise));
     const firstRejectedIndex = results.findIndex((result) => result.status === "rejected");

     if (firstRejectedIndex === -1) {
       return;
     }
+
+    if (this.failurePolicy !== "fail-closed") {
+      return;
+    }

     const failedMeterIds = results.flatMap((result, index) =>
       result.status === "rejected" ? [attempts[index]?.meterId ?? "unknown"] : [],
     );
🤖 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/llm-metering/src/libs/LlmMeteringService.ts` around lines 481 - 499,
The early return statement when failurePolicy is not "fail-closed" prevents
Promise.allSettled from being called, leaving pending promises unhandled.
Restructure the assertRecordAttempts method to call Promise.allSettled on the
attempts before checking the failurePolicy condition, then move the error
handling and throwing logic inside the failurePolicy check. This ensures all
promises in the attempts array are settled regardless of the policy, and only
throws errors based on the policy decision.
🤖 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
`@packages/create-croco-app/templates/saas/apps/api-server/src/controllers/schemas.ts`:
- Around line 84-95: The ai object schema in the Zod validation is too
permissive and allows invalid data such as negative token counts, empty strings
for provider and modelId, and non-positive costs. Add proper validation
constraints to the ai schema: use z.string().min(1) for string fields like
provider, modelId, responseText, and quotaFailureCode to ensure they are not
empty; use z.number().min(0) or z.number().int().min(0) for numeric fields like
promptTokens, completionTokens, totalTokens, promptUsage, and promptQuota to
ensure non-negative values; use z.number().min(0) for costUsd to ensure it is
non-negative. This ensures the DTO schema enforces the contract invariants that
are expected by the smoke tests.

In `@packages/create-croco-app/templates/saas/apps/api-server/src/saasDemo.ts`:
- Around line 750-753: The three assertLlmQuota calls for PROMPT_TOKENS,
COMPLETION_TOKENS, and COST_USD are duplicating validation logic and masking
potential issues in the LlmMeteringService's internal quota policy. Remove these
three assertLlmQuota calls before the runtime.llmMeteringService.recordUsage()
invocation so that quota validation failures occur directly from recordUsage(),
providing more accurate integration testing. This same issue also appears in
another location around lines 771-773 where similar assertLlmQuota calls precede
a recordUsage() call, so apply the same fix there as well.

In `@packages/llm-metering/src/libs/LlmMeteringService.ts`:
- Around line 138-153: The meterDeltas array construction in the
LlmMeteringService class uses token and cost values (promptTokens,
completionTokens, costUsd) without validating that they are valid numbers. Add
validation to ensure all meter values are positive and finite numbers before
constructing the meterDeltas array at lines 138-153. Create a validation helper
function that checks for negative values and non-finite numbers (NaN, Infinity),
then apply the same validation at the other two locations mentioned (lines
272-283 and 375-381) to prevent invalid usage data from being recorded in quota
checks and meter aggregations.

In `@packages/llm-metering/src/libs/PricingTable.ts`:
- Around line 87-107: The pricing table registration silently overwrites
duplicate provider and modelId combinations, allowing later entries to override
earlier ones and causing pricing integrity issues. Before setting the
modelPricing in the providerPricing map within the registry.entries loop, check
if entry.modelId already exists in providerPricing. If it does exist, throw a
Problem subclass (following the coding guideline to never throw generic Error
instances) with a clear message indicating a duplicate provider and modelId
combination was detected. Only proceed with providerPricing.set if the modelId
does not already exist for that provider.
- Line 129: The effectiveDate assignment in the PricingTable constructor at line
129 is missing default value inheritance logic that is applied to other
properties like version, source, and notes. To fix this, apply the same default
value fallback pattern used for those other properties: when
options.effectiveDate is not provided, it should inherit from a default source
(such as a sample registry) instead of being left undefined. This ensures
consistent behavior regardless of how the PricingTable is created.

In `@packages/testing/src/libs/llm-provider-conformance.ts`:
- Around line 304-314: The assertPartialUsage function currently permits an
empty or completely undefined usage object, which allows implementations to pass
conformance testing without providing any actual token usage metrics. To fix
this, add a validation check in the assertPartialUsage function that ensures at
least one of the usage fields (promptTokens, completionTokens, or totalTokens)
is actually defined before allowing the function to return. This ensures that
implementations must provide at least some usage accounting data to pass the
conformance test, preventing the validation from being bypassed through empty
objects.
- Around line 121-127: The abort validation in the conformance test is too
permissive because the assertion accepts the iterator either finishing
(second.done is true) OR continuing to stream valid chunks (typeof
second.value.delta is "string"). This fails to actually verify that abort stops
the stream. To fix this, modify the assertion condition to strictly require that
second.done is true after calling controller.abort(), ensuring the stream must
terminate rather than allowing it to continue producing chunks. This will
properly validate that LLM providers handle abort correctly according to
conformance requirements.

---

Outside diff comments:
In `@packages/llm-metering/src/libs/LlmMeteringService.ts`:
- Around line 481-499: The early return statement when failurePolicy is not
"fail-closed" prevents Promise.allSettled from being called, leaving pending
promises unhandled. Restructure the assertRecordAttempts method to call
Promise.allSettled on the attempts before checking the failurePolicy condition,
then move the error handling and throwing logic inside the failurePolicy check.
This ensures all promises in the attempts array are settled regardless of the
policy, and only throws errors based on the policy decision.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: d6b90aea-f9cd-4028-b6c8-c5b96fdd2e89

📥 Commits

Reviewing files that changed from the base of the PR and between a9fe5b2 and 2d2a4e1.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (27)
  • .changeset/llm-governance-controls.md
  • docs/llm-governance.md
  • packages/create-croco-app/src/tests/e2e-generation.spec.ts
  • packages/create-croco-app/src/tests/templates-build.spec.ts
  • packages/create-croco-app/templates/saas/README.md.hbs
  • packages/create-croco-app/templates/saas/apps/api-server/package.json.hbs
  • packages/create-croco-app/templates/saas/apps/api-server/src/controllers/schemas.ts
  • packages/create-croco-app/templates/saas/apps/api-server/src/demo/saasSmokeContract.ts
  • packages/create-croco-app/templates/saas/apps/api-server/src/saasDemo.ts
  • packages/create-croco-app/templates/saas/apps/api-server/src/tests/SaasDemo.spec.ts
  • packages/llm-core/README.md
  • packages/llm-metering/README.md
  • packages/llm-metering/src/index.ts
  • packages/llm-metering/src/libs/LlmMeteringService.ts
  • packages/llm-metering/src/libs/PricingTable.ts
  • packages/llm-metering/src/libs/types.ts
  • packages/llm-metering/src/tests/LlmMeteringService.spec.ts
  • packages/llm-metering/src/tests/PricingTable.spec.ts
  • packages/llm-metering/src/tests/decorators/AiMetered.spec.ts
  • packages/llm-metering/tsconfig.json
  • packages/llm-metering/vitest.config.ts
  • packages/testing/package.json
  • packages/testing/src/index.ts
  • packages/testing/src/libs/llm-provider-conformance.ts
  • packages/testing/src/tests/testing.spec.ts
  • packages/testing/tsconfig.json
  • packages/testing/vitest.config.ts

Comment thread packages/create-croco-app/templates/saas/apps/api-server/src/saasDemo.ts Outdated
Comment thread packages/llm-metering/src/libs/LlmMeteringService.ts
Comment thread packages/llm-metering/src/libs/PricingTable.ts
Comment thread packages/llm-metering/src/libs/PricingTable.ts Outdated
Comment thread packages/testing/src/libs/llm-provider-conformance.ts Outdated
Comment thread packages/testing/src/libs/llm-provider-conformance.ts
@kang-heewon
kang-heewon force-pushed the fix/829-llm-governance branch from 2d2a4e1 to 4fb796c Compare June 18, 2026 05:52
@kang-heewon
kang-heewon merged commit 15482d7 into trunk Jun 18, 2026
8 checks passed
@kang-heewon
kang-heewon deleted the fix/829-llm-governance branch June 18, 2026 06:10
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.

[llm] Add AI usage governance with provider contracts, pricing, and quota controls

1 participant