feat: SITES-40623 - token system in Spacecat#1414
Conversation
Made-with: Cursor # Conflicts: # packages/spacecat-shared-data-access/src/models/entitlement/entitlement.collection.js # packages/spacecat-shared-data-access/test/unit/models/entitlement/entitlement.collection.test.js
|
This PR will trigger a minor release when merged. |
…ES-40623-spacecat-shared-utils PR) Made-with: Cursor
|
verview Adds two new entities (Token, SuggestionGrant) to implement a capped suggestion grant system. Tokens track per-site, per-cycle allocation; SuggestionGrant records Critical Issues
"@adobe/spacecat-shared-utils": "https://gist.githubusercontent.com/sandsinh/e6c7d5895ece4f0ad3b32a64701faf3b/raw/..." This is the most significant blocker. The Gist URL:
This PR must not be merged until #1420 is published to npm and the dependency is updated to a proper semver reference.
// suggestion-grant.collection.js This only checks that at least 1 token remains. If suggestionIds.length > token.getRemaining(), the RPC is still called with all IDs. The check should be: if (token.getRemaining() >= suggestionIds.length) { ... } Unless the intent is that the entire batch consumes exactly 1 token (not 1 per suggestion), which is not documented anywhere in the code or PR description. Design & Architecture Issues
SuggestionGrantCollection.grantSuggestions reaches into entityRegistry to resolve TokenCollection: const tokenCollection = this.entityRegistry.getCollection('Token'); This is unusual for this codebase — collections typically don't resolve sibling collections. It creates hidden coupling and makes the method harder to test in
These two methods bypass the standard entity-level error handling the rest of the layer uses (DataAccessError). If PostgREST returns an error, callers of
It's a raw implementation detail exposed on the collection. It should be private (rename to #invokeGrantSuggestionsRpc or document as internal-only). Exposing it Code-Level Issues
getRemaining() { this.getTotal?.() with ?. implies getTotal might not exist. In this codebase, BaseModel generates getters from the schema — they'll always exist. The fallback to return Math.max(0, this.getTotal() - this.getUsed());
readOnly on total prevents updates, which is intentional — total is set at creation time. But the name is slightly misleading. A comment clarifying "set at creation,
pull_policy: ${MYSTICAT_DATA_SERVICE_PULL_POLICY:-always} Defaults to always, meaning every local npm run test:it run re-pulls the Docker image even if it's already present. This significantly slows local dev. The default Test Coverage Strengths:
Minor Issues
|
| .from(this.tableName) | ||
| .select('suggestion_id,grant_id') | ||
| .in('suggestion_id', suggestionIds); | ||
| return { data: data ?? [], error }; |
There was a problem hiding this comment.
We can throw DataAccessError in case of failure
| throw new DataAccessError('grantSuggestions: tokenType is required', this); | ||
| } | ||
|
|
||
| const tokenCollection = this.entityRegistry.getCollection('TokenCollection'); |
There was a problem hiding this comment.
Design concern: Collections typically don't call into other collections via entityRegistry. This cross-entity coupling is atypical for this codebase orchestration usually lives in the service layer.
There was a problem hiding this comment.
We are using it in some collections. So it should be good.
| const tokenCollection = this.entityRegistry.getCollection('TokenCollection'); | ||
| const token = await tokenCollection.findBySiteIdAndTokenType(siteId, tokenType); | ||
|
|
||
| if (!token || token.getRemaining() < 1) { |
There was a problem hiding this comment.
Critical bug: token.getRemaining() >= 1 only checks if at least one token remains. If you pass 5 suggestionIds but only 2 tokens remain, it still proceeds. Should be
= suggestionIds.length (assuming 1 token per suggestion) or the semantics need documenting.
There was a problem hiding this comment.
1 token could grant multiple suggestions in some cases, no need for check on suggestionIds.
packages/spacecat-shared-data-access/src/models/token/token.model.js
Outdated
Show resolved
Hide resolved
## [@adobe/spacecat-shared-data-access-v3.24.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.23.0...@adobe/spacecat-shared-data-access-v3.24.0) (2026-03-19) ### Features * SITES-40623 - token system in Spacecat ([#1414](#1414)) ([9c540ba](9c540ba))
|
🎉 This PR is included in version @adobe/spacecat-shared-data-access-v3.24.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Summary
grantSuggestions, and findBySuggestionIds — centralizing all grant-related data access previously spread across the Suggestion collection.
Token.getRemaining() computes available tokens (total - used).
Changes
New: SuggestionGrant (src/models/suggestion-grant/)
(resolves token, calls grant_suggestions RPC), invokeGrantSuggestionsRpc (raw RPC call).
New: Token (src/models/token/)
with total = min(options.total, config.tokensPerCycle).
Modified
Tests
suggestionKey).
Test plan
Related Issue
Motivation and Context
Sites need a capped number of granted suggestions per opportunity type and cycle. The shared library adds:
grantsso UI/API can show which suggestions were granted and with which cycle/token.grantSuggestionso callers can grant by IDs without touching RPC or token creation directly; token creation for the current cycle happens on first use.How Has This Been Tested?
token-grant-config.test.js—TOKEN_GRANT_CONFIGshape,tokensPerCycle/cycle/cycleFormatper type,getTokenGrantConfigreturns correct values andcurrentCycle, unknown type returnsundefined, and that config is immutable (e.g. copy, not reference).token.schema.test.js,token.model.test.js,token.collection.test.js(e.g.findBySiteIdAndTokenTypewith create/upsert and config).suggestion.collection.test.js—grantSuggestioncallsfindBySiteIdAndTokenType, checks remaining, invokes RPC with correct params; returnsno_tokenswhen remaining < count and does not call RPC; handles RPC error and RPCgranted: false.test/it/token/token.test.jsand suggestion ITs where applicable.Please ensure your pull request adheres to the following guidelines:
Related Issues
Thanks for contributing!