feat(entitlement): preview(tier) + GET /api/entitlement/preview#3221
Merged
Conversation
upgrade_diff() answers "what changes"; preview() answers "what does the
resulting Entitlement look like" -- the full denormalised to_dict() shape
rendered for an arbitrary purchasable tier so the upgrade-CTA card can
render concrete numbers ("Cloud Pro: 90-day retention, unlimited channels,
claude_code + codex + ... unlocked") without re-deriving per-tier capacity
tables in JS.
The previewed Entitlement is built with grace=False so the per-tier limits
(channel_limit, retention_days) actually surface -- a grace-mode preview
zeroes those out and defeats the purpose. source="preview" tags the
synthetic shape so the UI can never mistake it for a live entitlement.
GET /api/entitlement/preview?tier=cloud_pro -> the to_dict() body, with
400 on missing tier and 404 on unknown tier. preview() itself returns
None for unknown / empty / non-string input and never raises.
No behaviour change: this is a pure-read primitive; nothing in the
existing gates calls it.
19 new tests pin the shape, per-tier limits (OSS/Starter/Pro/Enterprise),
case-insensitivity, fallback, and that calling preview never mutates the
live cached entitlement.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
Visual diffComparing No flagged differences (running with ALWAYS_POST=1).
Folder: pr/3221/d770df140132. Full PNGs also attached as a workflow artefact. Generated by visual-diff bot. Pixel diffs >1% flagged; eyeball the table before merging. This check is non-blocking — fail = bot bug, not a code problem. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
upgrade_diff()answers "what changes";preview()answers "what does the resulting Entitlement look like" — the full denormalisedto_dict()shape rendered for an arbitrary purchasable tier so the upgrade-CTA card can render concrete numbers ("Cloud Pro: 90-day retention, unlimited channels, claude_code + codex + … unlocked") without re-deriving per-tier capacity tables in JS.Entitlementis built withgrace=Falseso per-tier limits (channel_limit,retention_days) actually surface — a grace-mode preview zeroes those out and defeats the purpose.source="preview"tags the synthetic shape so the UI can never mistake it for a live entitlement.GET /api/entitlement/preview?tier=cloud_pro→ theto_dict()body.400on missing tier,404on unknown tier.preview()itself returnsNonefor unknown / empty / non-string input and never raises.Behaviour change
None. This is a pure-read primitive; nothing in the existing gates calls it. It composes with
upgrade_diff/downgrade_diff(delta surface) andtier_catalog(per-tier static metadata) —previewis the third leg: "the resolved shape at tier X".Files
clawmetry/entitlements.py—preview(target_tier)module-level helper (~40 lines, follows the_build()pattern).routes/entitlement.py—GET /api/entitlement/previewonbp_entitlement+ docstring update.tests/test_entitlement_preview.py— 19 tests covering shape, per-tier limits (OSS / Starter / Pro / Enterprise), case-insensitivity,source="preview"invariant, grace-independence, mutation-free, and the four HTTP status paths.Tests
Local operator verification checklist
Since this PR is opened from a remote container, the local-daemon + live-cloud verification has to happen on your box:
~/.clawmetry/lib/python3.11/site-packages/clawmetry/(androutes/):-
clawmetry/entitlements.py-
routes/entitlement.py__pycache__under the installedclawmetry/+routes/trees.launchctl kickstart -k gui/$(id -u)/com.clawmetry.syncto restart the daemon.curl -s http://localhost:8900/api/entitlement/preview?tier=cloud_pro | jq .tier,.source,.retention_days,.channel_limit— expect"cloud_pro","preview",90,null.curl -i http://localhost:8900/api/entitlement/preview— expect400.curl -i http://localhost:8900/api/entitlement/preview?tier=nope— expect404./api/entitlementis unchanged.🤖 Generated with Claude Code
Generated by Claude Code