Skip to content
This repository was archived by the owner on Apr 7, 2026. It is now read-only.

feat: DB-driven gateway margin + mount in core#175

Merged
TSavo merged 2 commits intomainfrom
feat/db-driven-gateway
Mar 29, 2026
Merged

feat: DB-driven gateway margin + mount in core#175
TSavo merged 2 commits intomainfrom
feat/db-driven-gateway

Conversation

@TSavo
Copy link
Copy Markdown
Contributor

@TSavo TSavo commented Mar 29, 2026

Summary

  • Gateway mounted in core mountRoutes — all products get it automatically
  • Margin read from product_billing_config.margin_config.default
  • Fails hard if billing config not seeded (no hardcoded fallback)
  • resolveMargin getter for runtime DB changes without restart
  • MeterEmitter + BudgetChecker constructed in core container

Test plan

  • CI passes
  • Deploy → verify gateway uses 4x margin from DB

🤖 Generated with Claude Code

Note

Mount DB-driven gateway routes with live margin resolution in core server

  • Adds meter (MeterEmitter) and budgetChecker (DrizzleBudgetChecker) to PlatformContainer.gateway in container.ts, initialized with Drizzle-backed repositories and cache/flush intervals.
  • Makes mount-routes.ts async and mounts the gateway when container.gateway is present, wiring in live margin resolution via resolveMargin() that reads product_billing_config.margin_config.default per request.
  • Renames DEFAULT_MARGIN to TEST_ONLY_MARGIN in proxy.ts and adds resolveMargin?: () => number to GatewayConfig so production always uses a live resolver instead of the static fallback.
  • Fixes a race condition in server/index.ts by awaiting the now-async mountRoutes call.
  • Risk: server throws at startup if product_billing_config.margin_config.default is missing when the gateway feature is enabled.
📊 Macroscope summarized 45979ab. 1 file reviewed, 1 issue evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

Summary by CodeRabbit

  • New Features

    • Dynamic per-request margin resolution for gateway pricing, with a static fallback for tests
    • Integrated metering and budget-tracking into gateway services, enabling metered inference
  • Documentation

    • Clarified margin configuration behavior in comments
  • Tests

    • Updated test fixtures to include new gateway service dependencies and reflect runtime changes

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Sorry @TSavo, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 29, 2026

Warning

Rate limit exceeded

@TSavo has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 12 minutes and 58 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 12 minutes and 58 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4da699cd-422c-4f97-ba52-294e79bcacc9

📥 Commits

Reviewing files that changed from the base of the PR and between c33c17b and a69f3ff.

📒 Files selected for processing (8)
  • src/gateway/proxy.ts
  • src/gateway/types.ts
  • src/index.ts
  • src/server/__tests__/container.test.ts
  • src/server/container.ts
  • src/server/index.ts
  • src/server/mount-routes.ts
  • src/server/routes/__tests__/admin.test.ts
📝 Walkthrough

Walkthrough

Dynamic per-request margin resolution is introduced for the gateway proxy via a new resolveMargin callback; defaultMargin becomes a test-only fallback. Gateway services are extended to include metering and budget checking. Route mounting is made asynchronous to support dynamic gateway initialization. Tests updated to include new gateway deps.

Changes

Cohort / File(s) Summary
Gateway Margin Resolution
src/gateway/proxy.ts, src/gateway/types.ts
defaultMargin is now accessed via a getter that calls config.resolveMargin() when present; otherwise falls back to config.defaultMargin ?? TEST_ONLY_MARGIN. GatewayConfig adds optional resolveMargin?: () => number and documents defaultMargin as test-only. Review: dynamic accessor behavior and fallback semantics.
Gateway Services Initialization
src/server/container.ts
GatewayServices now includes meter (MeterEmitter) and budgetChecker (IBudgetChecker). buildContainer dynamically imports and instantiates metering and budget-checker implementations and injects them into container.gateway. Review: DI timing, import side-effects, and lifecycle config values.
Async Route Mounting and Gateway Wiring
src/server/index.ts, src/server/mount-routes.ts
mountRoutes converted to async and awaited in boot. When container.gateway exists, mount-routes now validates margin config, constructs a resolveMargin callback, dynamically imports ../gateway/index.js, and mounts a metered inference gateway with meter, budgetChecker, credit ledger, OpenRouter provider config, and a resolveServiceKey callback. Review: async flow, error propagation, and config validation.
Tests Updated for New Gateway Deps
src/server/__tests__/container.test.ts, src/server/routes/__tests__/admin.test.ts
Test fixtures and assertions updated to include gateway.meter and gateway.budgetChecker in overrides alongside serviceKeyRepo. Review: mock shapes and test coverage for new behavior.
Non-functional comment addition
src/index.ts
Four comment lines appended after export * from "./trpc/index.js"; — no runtime or API changes.

Sequence Diagram(s)

sequenceDiagram
    participant Server as Server (bootPlatformServer)
    participant Router as RouteMounter (mountRoutes)
    participant Gateway as Gateway Module
    participant Meter as Metering System
    participant Budget as Budget Checker
    participant Provider as OpenRouter Provider

    Server->>Router: await mountRoutes(container)
    Router->>Router: if container.gateway present
    Router->>Router: validate productConfig.billing?.marginConfig.default
    Router->>Router: create resolveMargin() callback
    Router->>Provider: configure using OPENROUTER_API_KEY / base URL
    Router->>Gateway: dynamic import ../gateway/index.js
    Router->>Gateway: mountGateway(app, { meter, budgetChecker, creditLedger, resolveMargin, resolveServiceKey })
    Gateway->>Meter: initialize MeterEmitter (flushIntervalMs: 5000)
    Gateway->>Budget: initialize BudgetChecker (cacheTtlMs: 30000)
    Router->>Server: return (mountRoutes done)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A margin once fixed now hops free and wide,
Per-request it nudges, no longer to hide.
Meters tick softly, budgets keep track,
Async routes mount and never look back—
A rabbit cheers on this gateway ride!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: DB-driven gateway margin + mount in core' accurately captures the main changes: adding DB-driven margin resolution and mounting the gateway in core mountRoutes.

✏️ 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 feat/db-driven-gateway

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
Copy link
Copy Markdown

github-actions bot commented Mar 29, 2026

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 87.43% 2080 / 2379
🔵 Statements 87.07% 2197 / 2523
🔵 Functions 89.13% 517 / 580
🔵 Branches 73.63% 1075 / 1460
File CoverageNo changed files found.
Generated in workflow #546 for commit a69f3ff by the Vitest Coverage Report Action

- Gateway mounted in core mountRoutes (all products get it)
- Margin from product_billing_config.margin_config.default (no fallback)
- resolveMargin getter for runtime DB changes
- MeterEmitter + BudgetChecker in core container

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@TSavo TSavo force-pushed the feat/db-driven-gateway branch from c33c17b to b37f567 Compare March 29, 2026 03:00
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 29, 2026

Greptile Summary

This PR mounts the metered inference gateway centrally in mountRoutes so all products get it automatically, and shifts the margin multiplier from a hardcoded constant to a value read from product_billing_config.margin_config.default in the database. MeterEmitter and DrizzleBudgetChecker are now constructed inside buildContainer as part of the gateway feature slice.

Key findings:

  • resolveMargin does not deliver live DB changes — the closure reads container.productConfig.billing, which is a plain object set once at boot and never refreshed. The ProductConfigService TTL cache is not consulted per-request, so the margin is effectively static until restart, contradicting the PR description's claim of "runtime DB changes without restart."
  • Margin config convention violated — all WOPR codebase rules require margin to live in the MARGIN_CONFIG_JSON env var (loaded via loadMarginConfig()), not in the database. This deliberate departure should be explicitly discussed and the convention rules updated in the same PR if the migration is intentional.
  • GatewayServices.meter is typed as the concrete MeterEmitter/DrizzleMeterEmitter class; IMeterEmitter exists and should be used to match the interface-first convention followed by budgetChecker.
  • Four CI breadcrumb comments (// monorepo e2e cutover test, etc.) were accidentally committed to src/index.ts and should be removed.
  • The !marginConfig?.default guard falsely throws when default is 0; == null is the correct check.
  • Step numbering in mount-routes.ts has a duplicate 7 after the gateway block was inserted.

Confidence Score: 3/5

Not safe to merge — the advertised live margin feature is a no-op, and the change departs from a team-wide convention without updating it.

Two P1 findings block merge: the resolveMargin closure silently reads a stale boot-time snapshot (operators who update the DB margin expecting a live effect will see no change), and the DB-driven margin approach directly contradicts the MARGIN_CONFIG_JSON env-var convention codified in every WOPR codebase rule. The remaining findings are P2 style issues.

src/server/mount-routes.ts requires the most attention — both P1 issues originate there.

Important Files Changed

Filename Overview
src/server/mount-routes.ts Core of this PR — adds gateway mounting with DB-driven margin; resolveMargin reads a stale boot-time snapshot (P1) and uses the DB rather than MARGIN_CONFIG_JSON env var per convention (P1); minor falsy-guard and comment numbering issues.
src/server/container.ts Constructs MeterEmitter and DrizzleBudgetChecker in the gateway block — correct Drizzle repository wiring; meter typed as concrete class instead of IMeterEmitter interface (P2 style).
src/gateway/proxy.ts Replaces static DEFAULT_MARGIN with a getter-based defaultMargin that calls resolveMargin when provided; the proxy layer change itself is clean.
src/gateway/types.ts Adds optional resolveMargin to GatewayConfig; straightforward type extension with clear doc comments.
src/index.ts Four leftover CI breadcrumb comments appended after the tRPC export; no functional change but should be removed before merge.
src/server/index.ts Adds await to mountRoutes call — necessary now that the function is async; straightforward and correct.
src/server/tests/container.test.ts Updates test container stubs to include the new meter and budgetChecker fields; no logic issues.
src/server/routes/tests/admin.test.ts Adds meter and budgetChecker stubs to existing gateway fixture objects; routine test housekeeping.

Sequence Diagram

sequenceDiagram
    participant Boot as bootPlatformServer
    participant BC as buildContainer
    participant MR as mountRoutes
    participant GW as mountGateway
    participant Req as Inbound Request

    Boot->>BC: await buildContainer(config)
    BC->>BC: new DrizzleMeterEventRepository(db)
    BC->>BC: new MeterEmitter(repo, {flushIntervalMs:5000})
    BC->>BC: new DrizzleBudgetChecker(db, {cacheTtlMs:30000})
    BC-->>Boot: container (productConfig snapshot frozen here)

    Boot->>MR: await mountRoutes(app, container, config)
    MR->>MR: read container.productConfig.billing.marginConfig.default
    MR->>MR: capture initialMargin (boot-time value)
    MR->>MR: define resolveMargin() → reads same frozen snapshot
    MR->>GW: mountGateway(app, { resolveMargin, meter, budgetChecker })

    Req->>GW: POST /v1/chat/completions
    GW->>GW: resolveMargin() → returns boot-time margin (not live DB)
    GW->>GW: budget check via DrizzleBudgetChecker
    GW->>GW: proxy to upstream provider
    GW->>GW: MeterEmitter.emit(event)
Loading

Comments Outside Diff (1)

  1. src/server/mount-routes.ts, line 135-141 (link)

    P2 Duplicate step number 7 in comments; docblock also out of date

    After inserting the gateway as step 6, both "Product-specific route plugins" and "Tenant proxy middleware" are labelled // 7.. The top-of-function docblock still lists the old numbering (only 7 items, gateway missing). Suggest:

    And update the docblock to include the new step 6 (gateway) and renumber 7 → 8 for plugins and tenant proxy.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/server/mount-routes.ts
    Line: 135-141
    
    Comment:
    **Duplicate step number `7` in comments; docblock also out of date**
    
    After inserting the gateway as step 6, both "Product-specific route plugins" and "Tenant proxy middleware" are labelled `// 7.`. The top-of-function docblock still lists the old numbering (only 7 items, gateway missing). Suggest:
    
    
    
    And update the docblock to include the new step 6 (gateway) and renumber 7 → 8 for plugins and tenant proxy.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/server/mount-routes.ts
Line: 110-115

Comment:
**`resolveMargin` reads a stale boot-time snapshot, not live DB values**

`container.productConfig` is a plain `ProductConfig` object set once when `buildContainer` runs and is **never mutated or reassigned** afterward. The closure therefore always returns the margin captured at startup — identical to `initialMargin`. The `ProductConfigService` (with its TTL cache) is available as `container.productConfigService`, but it is not used here.

The PR description claims "resolveMargin getter for runtime DB changes without restart", but since `container.productConfig` is a frozen snapshot, this does not work. An operator who updates `margin_config.default` in the DB mid-run will see no effect until the server restarts.

To make the margin truly live the resolver must call `container.productConfigService.getBySlug(slug)` and await it; but since `resolveMargin` is typed as `() => number` (sync), this requires either making the type `() => Promise<number>` throughout the gateway, or adding a separate background-refresh loop that pushes the cached value into a mutable local variable. As-is, the "live margin" feature is a no-op.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/server/mount-routes.ts
Line: 103-108

Comment:
**Margin config convention violated — must use `MARGIN_CONFIG_JSON` env var**

Every WOPR codebase convention rule explicitly states: *"Margin config is stored in `MARGIN_CONFIG_JSON` env var, never in source"* (with `loadMarginConfig()` in `src/monetization/adapters/margin-config.ts` as the canonical loader). This PR moves the margin to `product_billing_config.margin_config.default` in the database instead, which is an intentional but undiscussed departure from the team-wide convention.

If the intent is to permanently migrate to DB-driven margin, the custom instruction rules and `loadMarginConfig()` path should be updated in the same PR so no future author is misled. If this is a product-specific override on top of the env-var baseline, the env-var path should still be respected as a fallback.

**Rule Used:** WOPR codebase conventions:
- Repository pattern is... ([source](https://app.greptile.com/review/custom-context?memory=b986c674-3c9f-4f81-9c16-19366bd8a16f))

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/server/container.ts
Line: 54-55

Comment:
**`meter` typed as concrete class instead of its interface**

`IMeterEmitter` is exported from `src/metering/index.ts` and matches the convention that sub-container fields take repository/emitter *interfaces*, not implementations. `budgetChecker` correctly uses `IBudgetChecker`; `meter` should be consistent:

```suggestion
  meter: import("../metering/emitter.js").IMeterEmitter;
  budgetChecker: import("../monetization/budget/budget-checker.js").IBudgetChecker;
```

**Rule Used:** WOPR codebase conventions:
- Repository pattern is... ([source](https://app.greptile.com/review/custom-context?memory=158cf84c-c9a3-4390-b104-b5df2c2423d6))

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/index.ts
Line: 51-54

Comment:
**Leftover CI breadcrumb comments in public barrel**

These four lines look like temporary markers left over from debugging the build pipeline and should not be committed to the public export barrel:

```suggestion
// tRPC
export * from "./trpc/index.js";
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/server/mount-routes.ts
Line: 135-141

Comment:
**Duplicate step number `7` in comments; docblock also out of date**

After inserting the gateway as step 6, both "Product-specific route plugins" and "Tenant proxy middleware" are labelled `// 7.`. The top-of-function docblock still lists the old numbering (only 7 items, gateway missing). Suggest:

```suggestion
  // 7. Product-specific route plugins
  for (const plugin of plugins) {
    app.route(plugin.path, plugin.handler(container));
  }

  // 8. Tenant proxy middleware (catch-all — MUST be last)
```

And update the docblock to include the new step 6 (gateway) and renumber 7 → 8 for plugins and tenant proxy.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/server/mount-routes.ts
Line: 103-108

Comment:
**Falsy guard rejects a `0` margin**

`!marginConfig?.default` is `true` when `default` is `0`, which would throw even though `0` is a valid (if degenerate) numeric value. Prefer an explicit null/undefined check:

```suggestion
    if (marginConfig?.default == null) {
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "feat: DB-driven gateway margin + mount i..." | Re-trigger Greptile

Comment on lines +110 to +115
// Live margin — reads from productConfig per-request (DB-cached with TTL)
const initialMargin = marginConfig.default;
const resolveMargin = (): number => {
const cfg = container.productConfig.billing?.marginConfig as { default?: number } | null;
return cfg?.default ?? initialMargin;
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 resolveMargin reads a stale boot-time snapshot, not live DB values

container.productConfig is a plain ProductConfig object set once when buildContainer runs and is never mutated or reassigned afterward. The closure therefore always returns the margin captured at startup — identical to initialMargin. The ProductConfigService (with its TTL cache) is available as container.productConfigService, but it is not used here.

The PR description claims "resolveMargin getter for runtime DB changes without restart", but since container.productConfig is a frozen snapshot, this does not work. An operator who updates margin_config.default in the DB mid-run will see no effect until the server restarts.

To make the margin truly live the resolver must call container.productConfigService.getBySlug(slug) and await it; but since resolveMargin is typed as () => number (sync), this requires either making the type () => Promise<number> throughout the gateway, or adding a separate background-refresh loop that pushes the cached value into a mutable local variable. As-is, the "live margin" feature is a no-op.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/server/mount-routes.ts
Line: 110-115

Comment:
**`resolveMargin` reads a stale boot-time snapshot, not live DB values**

`container.productConfig` is a plain `ProductConfig` object set once when `buildContainer` runs and is **never mutated or reassigned** afterward. The closure therefore always returns the margin captured at startup — identical to `initialMargin`. The `ProductConfigService` (with its TTL cache) is available as `container.productConfigService`, but it is not used here.

The PR description claims "resolveMargin getter for runtime DB changes without restart", but since `container.productConfig` is a frozen snapshot, this does not work. An operator who updates `margin_config.default` in the DB mid-run will see no effect until the server restarts.

To make the margin truly live the resolver must call `container.productConfigService.getBySlug(slug)` and await it; but since `resolveMargin` is typed as `() => number` (sync), this requires either making the type `() => Promise<number>` throughout the gateway, or adding a separate background-refresh loop that pushes the cached value into a mutable local variable. As-is, the "live margin" feature is a no-op.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +103 to +108
if (!marginConfig?.default) {
throw new Error(
"Gateway enabled but product_billing_config.margin_config.default is not set. " +
"Seed the DB: INSERT INTO product_billing_config (product_id, margin_config) VALUES ('<id>', '{\"default\": 4.0}')",
);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Margin config convention violated — must use MARGIN_CONFIG_JSON env var

Every WOPR codebase convention rule explicitly states: "Margin config is stored in MARGIN_CONFIG_JSON env var, never in source" (with loadMarginConfig() in src/monetization/adapters/margin-config.ts as the canonical loader). This PR moves the margin to product_billing_config.margin_config.default in the database instead, which is an intentional but undiscussed departure from the team-wide convention.

If the intent is to permanently migrate to DB-driven margin, the custom instruction rules and loadMarginConfig() path should be updated in the same PR so no future author is misled. If this is a product-specific override on top of the env-var baseline, the env-var path should still be respected as a fallback.

Rule Used: WOPR codebase conventions:

  • Repository pattern is... (source)
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/server/mount-routes.ts
Line: 103-108

Comment:
**Margin config convention violated — must use `MARGIN_CONFIG_JSON` env var**

Every WOPR codebase convention rule explicitly states: *"Margin config is stored in `MARGIN_CONFIG_JSON` env var, never in source"* (with `loadMarginConfig()` in `src/monetization/adapters/margin-config.ts` as the canonical loader). This PR moves the margin to `product_billing_config.margin_config.default` in the database instead, which is an intentional but undiscussed departure from the team-wide convention.

If the intent is to permanently migrate to DB-driven margin, the custom instruction rules and `loadMarginConfig()` path should be updated in the same PR so no future author is misled. If this is a product-specific override on top of the env-var baseline, the env-var path should still be respected as a fallback.

**Rule Used:** WOPR codebase conventions:
- Repository pattern is... ([source](https://app.greptile.com/review/custom-context?memory=b986c674-3c9f-4f81-9c16-19366bd8a16f))

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread src/server/container.ts
Comment on lines +54 to +55
meter: import("../metering/emitter.js").MeterEmitter;
budgetChecker: import("../monetization/budget/budget-checker.js").IBudgetChecker;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 meter typed as concrete class instead of its interface

IMeterEmitter is exported from src/metering/index.ts and matches the convention that sub-container fields take repository/emitter interfaces, not implementations. budgetChecker correctly uses IBudgetChecker; meter should be consistent:

Suggested change
meter: import("../metering/emitter.js").MeterEmitter;
budgetChecker: import("../monetization/budget/budget-checker.js").IBudgetChecker;
meter: import("../metering/emitter.js").IMeterEmitter;
budgetChecker: import("../monetization/budget/budget-checker.js").IBudgetChecker;

Rule Used: WOPR codebase conventions:

  • Repository pattern is... (source)
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/server/container.ts
Line: 54-55

Comment:
**`meter` typed as concrete class instead of its interface**

`IMeterEmitter` is exported from `src/metering/index.ts` and matches the convention that sub-container fields take repository/emitter *interfaces*, not implementations. `budgetChecker` correctly uses `IBudgetChecker`; `meter` should be consistent:

```suggestion
  meter: import("../metering/emitter.js").IMeterEmitter;
  budgetChecker: import("../monetization/budget/budget-checker.js").IBudgetChecker;
```

**Rule Used:** WOPR codebase conventions:
- Repository pattern is... ([source](https://app.greptile.com/review/custom-context?memory=158cf84c-c9a3-4390-b104-b5df2c2423d6))

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread src/index.ts
Comment on lines +51 to +54
// monorepo e2e cutover test
// hybrid dockerfile e2e
// sequential build test
// lockfile build
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Leftover CI breadcrumb comments in public barrel

These four lines look like temporary markers left over from debugging the build pipeline and should not be committed to the public export barrel:

Suggested change
// monorepo e2e cutover test
// hybrid dockerfile e2e
// sequential build test
// lockfile build
// tRPC
export * from "./trpc/index.js";
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/index.ts
Line: 51-54

Comment:
**Leftover CI breadcrumb comments in public barrel**

These four lines look like temporary markers left over from debugging the build pipeline and should not be committed to the public export barrel:

```suggestion
// tRPC
export * from "./trpc/index.js";
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +103 to +108
if (!marginConfig?.default) {
throw new Error(
"Gateway enabled but product_billing_config.margin_config.default is not set. " +
"Seed the DB: INSERT INTO product_billing_config (product_id, margin_config) VALUES ('<id>', '{\"default\": 4.0}')",
);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Falsy guard rejects a 0 margin

!marginConfig?.default is true when default is 0, which would throw even though 0 is a valid (if degenerate) numeric value. Prefer an explicit null/undefined check:

Suggested change
if (!marginConfig?.default) {
throw new Error(
"Gateway enabled but product_billing_config.margin_config.default is not set. " +
"Seed the DB: INSERT INTO product_billing_config (product_id, margin_config) VALUES ('<id>', '{\"default\": 4.0}')",
);
}
if (marginConfig?.default == null) {
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/server/mount-routes.ts
Line: 103-108

Comment:
**Falsy guard rejects a `0` margin**

`!marginConfig?.default` is `true` when `default` is `0`, which would throw even though `0` is a valid (if degenerate) numeric value. Prefer an explicit null/undefined check:

```suggestion
    if (marginConfig?.default == null) {
```

How can I resolve this? If you propose a fix, please make it concise.

@TSavo TSavo force-pushed the feat/db-driven-gateway branch from 45979ab to a69f3ff Compare March 29, 2026 03:04
);
}

// Live margin — reads from productConfig per-request (DB-cached with TTL)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium server/mount-routes.ts:110

The resolveMargin closure reads from container.productConfig.billing?.marginConfig, which is a static snapshot captured at startup. Because the comment explicitly claims "Live margin — reads from productConfig per-request (DB-cached with TTL)", the implementation contradicts its own documentation: margin changes in the database will never be reflected during the process lifetime. Consider reading from container.productConfigService.getBySlug() instead to actually leverage the TTL-cached service.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file src/server/mount-routes.ts around line 110:

The `resolveMargin` closure reads from `container.productConfig.billing?.marginConfig`, which is a static snapshot captured at startup. Because the comment explicitly claims "Live margin — reads from productConfig per-request (DB-cached with TTL)", the implementation contradicts its own documentation: margin changes in the database will never be reflected during the process lifetime. Consider reading from `container.productConfigService.getBySlug()` instead to actually leverage the TTL-cached service.

Evidence trail:
src/server/mount-routes.ts lines 110-115 (REVIEWED_COMMIT): comment claims 'Live margin — reads from productConfig per-request (DB-cached with TTL)' but resolveMargin reads `container.productConfig.billing?.marginConfig`. src/product-config/boot.ts lines 39-45: platformBoot returns `{ service, config }` where config is `await service.getBySlug(slug)` - a one-time fetch. src/server/container.ts line 144: `const { config: productConfig, service: productConfigService } = await platformBoot(...)`. src/product-config/service.ts lines 37-49: ProductConfigService.getBySlug() has TTL-cached DB reads (default 60s). container.productConfig is static; container.productConfigService.getBySlug() is TTL-cached.

Comment on lines +102 to +103
const marginConfig = billingConfig?.marginConfig as { default?: number } | null;
if (!marginConfig?.default) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Low server/mount-routes.ts:102

The check !marginConfig?.default at line 103 treats a margin of 0 as "not set" and throws an error, even though 0 is a valid configuration value (e.g., for testing or promotional pricing). Consider using typeof marginConfig?.default !== 'number' to properly distinguish between unset and zero values.

    const marginConfig = billingConfig?.marginConfig as { default?: number } | null;
-    if (!marginConfig?.default) {
+    if (typeof marginConfig?.default !== 'number') {
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file src/server/mount-routes.ts around lines 102-103:

The check `!marginConfig?.default` at line 103 treats a margin of `0` as "not set" and throws an error, even though `0` is a valid configuration value (e.g., for testing or promotional pricing). Consider using `typeof marginConfig?.default !== 'number'` to properly distinguish between unset and zero values.

Evidence trail:
src/server/mount-routes.ts lines 102-107 at REVIEWED_COMMIT: The check `if (!marginConfig?.default)` on line 103 uses JavaScript falsy check which treats 0 as falsy, causing a valid margin value of 0 to trigger the error. The type annotation `{ default?: number }` on line 102 confirms that `default` is expected to be a number, and 0 is a valid number value.

@TSavo TSavo added this pull request to the merge queue Mar 29, 2026
Merged via the queue into main with commit c42d48a Mar 29, 2026
9 checks passed
@TSavo TSavo deleted the feat/db-driven-gateway branch March 29, 2026 03:09
@github-actions
Copy link
Copy Markdown

🎉 This PR is included in version 1.75.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant