Skip to content

feat: createEntitlement accepts optional quota overrides - #1914

Open
cwjwisse wants to merge 2 commits into
mainfrom
fix-tier-client-quota-override
Open

cwjwisse wants to merge 2 commits into
mainfrom
fix-tier-client-quota-override

Conversation

@cwjwisse

@cwjwisse cwjwisse commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

1. Abstract

Adds an optional quotaOverrides parameter to TierClient.createEntitlement so a caller can store a non-default prompt quota when a new entitlement is created.

2. Reasoning

createEntitlement(tier) hardcodes quotas.llmo_trial_prompts: 200 on every new entitlement, so consumers can never persist the real contracted quota. Downstream, the ABV Semrush provisioning message and the stored LLMO entitlement therefore always read 200, even for a 1000-prompt offer. This change unblocks the fulfillment-worker sourcing the real contracted cap from the UPP event (companion PR).

3. High-level overview of the changes

  • createEntitlement(tier)createEntitlement(tier, quotaOverrides = {}).
  • On the new-entitlement path, the overrides are merged over the default quotas so a provided key (e.g. llmo_trial_prompts) wins.
  • llmo_trial_prompts_consumed is pinned to 0 after the merge — a brand-new entitlement has consumed nothing, so it is never caller-overridable.
  • A non-object quotaOverrides is ignored (defends the shared-library boundary).
  • The existing-entitlement path is unchanged: it updates the tier and returns without touching quotas, so overrides are ignored there (documented in the JSDoc; see the follow-up note in the companion PR).
  • Fully backward-compatible: every current caller passes only tier → default {} → byte-for-byte identical behavior.

4. Required information

7. Test plan

  • Unit tests cover: override applied on create; default preserved when omitted; llmo_trial_prompts_consumed not overridable; a non-object override ignored; the existing-entitlement path ignores overrides. Ran the package unit tests + lint locally.
  • No per-environment verification — this is a library. Correctness is exercised by the consumer (the worker PR) after this releases.

8. Deployment & merge order

🤖 Generated with Claude Code

@cwjwisse
cwjwisse force-pushed the fix-tier-client-quota-override branch from 0659ace to d5dc53b Compare September 7, 2026 13:02
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

This PR will trigger a minor release when merged.

@MysticatBot MysticatBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey @cwjwisse,

⚠ Degraded review - no spec document was found for this change (searched the PR links, the touched repos' docs, the architecture/guidelines docs, and linked Jira). This review covers code-level quality but could not validate the change against an agreed design, so confidence is reduced. Add a spec link (PR template section 4) and re-request review for a full-confidence pass.

Verdict: Request changes - one input-validation gap in the type guard.
Complexity: LOW - small diff, single package.
Changes: Adds an optional quotaOverrides parameter to TierClient.createEntitlement so callers can persist a non-default prompt quota on new entitlements (2 files).

Must fix before merge

  1. [Important] Array passes the typeof === 'object' type guard and spreads numeric keys into quotas - packages/spacecat-shared-tier-client/src/tier-client.js:188 (details inline)
Non-blocking (3): minor issues and suggestions
  • nit: No test for null passed explicitly as quotaOverrides (bypasses the default parameter, exercises the falsy branch differently from strings) - packages/spacecat-shared-tier-client/test/tier-client.test.js
  • suggestion: Consider an allowlist of recognized quota keys rather than open spread, so typos and unexpected keys do not silently persist garbage into the data layer. Today the only overridable key is llmo_trial_prompts; a tight contract is cheaper to enforce now than later when callers depend on the loose one.
  • suggestion: Narrow the JSDoc @param {object} to @param {{llmo_trial_prompts?: number}} so IDE consumers see which keys are valid.

Skill: pr-review | Model: us.anthropic.claude-opus-4-6-v1[1m] | Duration: 2m 28s | Cost: $3.93 | Commit: 9455e5645ce884511c2127a1001f49b3f170d1bb
If this code review was useful, please react with 👍. Otherwise, react with 👎.

tier,
quotas: {
llmo_trial_prompts: 200,
// Only merge a real object; a non-object (string/null) would spread garbage keys.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (blocking): Array passes the typeof === 'object' type guard.

typeof [] === 'object' is true in JavaScript. Spreading an array like [500] into the quotas object produces { 0: 500, llmo_trial_prompts: 200, llmo_trial_prompts_consumed: 0 } - silently corrupt data written to the persistence layer.

The existing non-object test covers strings, but arrays slip through.

Fix: Tighten the guard to also reject arrays:

...(quotaOverrides && typeof quotaOverrides === 'object' && !Array.isArray(quotaOverrides) ? quotaOverrides : {}),

Add a test case passing [500] as quotaOverrides and asserting it falls back to the defaults.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 7d6858b — tightened the guard with !Array.isArray(quotaOverrides) so arrays (and null) fall back to the defaults instead of spreading numeric keys, and added tests passing [500] and explicit null. Also narrowed the JSDoc to {llmo_trial_prompts?: number}.

@MysticatBot MysticatBot added ai-reviewed Reviewed by AI complexity:low AI-assessed PR complexity: LOW labels Sep 10, 2026
cwjwisse and others added 2 commits September 10, 2026 23:26
createEntitlement(tier) hardcoded quotas.llmo_trial_prompts to 200 on every new
entitlement, so callers could not store the real contracted prompt quota. Add an
optional createEntitlement(tier, quotaOverrides = {}) that merges the overrides over
the default quotas on new-entitlement creation only. llmo_trial_prompts_consumed is
pinned to 0 (not caller-overridable) and a non-object override is ignored. Existing
callers passing only tier are byte-for-byte unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cwjwisse
cwjwisse force-pushed the fix-tier-client-quota-override branch from 9455e56 to 7d6858b Compare September 10, 2026 21:27

@MysticatBot MysticatBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey @cwjwisse,

⚠ Degraded review - no spec document was found for this change (searched the PR links, the touched repos' docs, the architecture/guidelines docs, and linked Jira). This review covers code-level quality but could not validate the change against an agreed design, so confidence is reduced. Add a spec link (PR template section 4) and re-request review for a full-confidence pass.

Verdict: Approve - prior blocking finding addressed, no new issues.
Complexity: LOW - small diff, single package.
Changes: Adds an optional quotaOverrides parameter to TierClient.createEntitlement so callers can persist a non-default prompt quota on new entitlements (2 files).

Non-blocking (3): minor issues and suggestions
  • nit: The inline comment block (tier-client.js:189-192) is four lines explaining a three-line expression that the JSDoc already documents thoroughly. Consider trimming to a single line. - packages/spacecat-shared-tier-client/src/tier-client.js:189
  • suggestion: No test for an explicitly empty object {} as quotaOverrides. It is a no-op under the current spread logic, but an explicit test would close the last interesting equivalence class and guard against future regressions. - packages/spacecat-shared-tier-client/test/tier-client.test.js
  • suggestion: Consider narrowing accepted keys to a recognized set (allowlist) as the feature matures and additional callers adopt quotaOverrides. Today the spread accepts any key from the caller-supplied object. For an internal library with a single trusted caller this is acceptable, but an allowlist would prevent unrecognized properties from persisting silently if the caller surface grows.

Previously flagged, now resolved

  • Array passes typeof === 'object' type guard - fixed with !Array.isArray(quotaOverrides) guard and tests for [500] and null.
  • No test for explicit null - test added.
  • JSDoc narrowed from {object} to {{llmo_trial_prompts?: number}}.

Skill: pr-review | Model: us.anthropic.claude-opus-4-6-v1[1m] | Duration: 1m 40s | Cost: $2.78 | Commit: 7d6858bc2eaaed6f5b666b8ebe0427d6a91863d0
If this code review was useful, please react with 👍. Otherwise, react with 👎.

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

Labels

ai-reviewed Reviewed by AI complexity:low AI-assessed PR complexity: LOW

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants