feat(proxy): entitlements storage factory module (PR 1/5 — premium tier)#48
Draft
DocNR wants to merge 1 commit into
Draft
feat(proxy): entitlements storage factory module (PR 1/5 — premium tier)#48DocNR wants to merge 1 commit into
DocNR wants to merge 1 commit into
Conversation
Introduces the data layer for the premium-tier entitlement system
(plan: ~/.claude/plans/i-d-like-to-create-eventual-moon.md). Pure
addition — proxy.js does not import this yet, so behavior is
unchanged in production.
`relay-proxy/entitlements.js` mirrors the `clients.js` factory
pattern with one key shape difference: storage is keyed by hex
pubkey (object) rather than an array of pairs.
Schema per entry:
{ tier: "free"|"premium", granted_at, granted_by?, expires_at,
note?, devices_seen[] }
Methods:
- loadAll, getByPubkey, setEntitlement (upsert), revoke
- tierForPubkey — defaults unknown to "free"; downgrades expired
premium to "free" so Phase 2 time-bounded grants are forward-compat
- recordDevice — abuse-tripwire device tracking, dedupes by
token_prefix, no-ops for unknown pubkeys (free-tier doesn't
accumulate device data)
- auditMultiDevice — flags pubkeys exceeding N devices in a window
- listByTier — for the grant CLI's `list` subcommand
- maxAccountsForTier / maxClientsForTier — locked caps:
free 4 accts / 5 clients → premium 10 / 30. Matches shipped
baseline so premium is purely additive (no migration risk).
Atomic temp+rename writes; defensive load tolerates missing /
malformed / schema-drifted files. Pubkey keys validated against
`/^[0-9a-f]{64}$/`.
Tests: 32 new (`relay-proxy/test/entitlements.test.js`), full
proxy suite green at 111/111 (79 existing + 32 new).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 10, 2026
Owner
Author
|
Paused — queued for Phase 2. This is part of a server-side entitlement series (#48 → #49 → #50, plus an uncommitted iOS service layer on Preserved as draft because:
Architecture decision (Apple IAP vs Lightning device-bound vs Lightning npub-bound) deferred until there's a real monetization timeline. No action needed here. Will revisit when payment work begins. Plan: |
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
First PR in the premium-tier entitlement series. Introduces the data layer (
relay-proxy/entitlements.js+ tests). Pure addition —proxy.jsdoes not import this yet, so production behavior is unchanged.Plan:
~/.claude/plans/i-d-like-to-create-eventual-moon.md(local). Sequence:GET /entitlementendpoint + tier-aware/pair-clientcap.tools/grant.jsadmin CLI.Entitlementmodel +EntitlementService+ tests.What this adds
relay-proxy/entitlements.jsmirrors theclients.jsfactory pattern with one key shape difference: storage is keyed by hex pubkey (object) rather than an array of pairs.Schema per entry:
{ "<pubkey_hex>": { "tier": "premium", "granted_at": 1714000000, "granted_by": "admin:<admin_npub_hex>", "expires_at": null, "note": "tester @bfgreen", "devices_seen": [ { "token_prefix": "abc12345", "first_seen_at": ..., "last_seen_at": ... } ] } }Methods:
loadAll,getByPubkey,setEntitlement(upsert, preservesgranted_at+devices_seen),revoketierForPubkey— defaults unknown to"free"; downgrades expired premium to"free"so Phase 2 time-bounded grants are forward-compatiblerecordDevice— abuse-tripwire device tracking, dedupes bytoken_prefix, no-ops for unknown pubkeys (free-tier doesn't accumulate device data)auditMultiDevice— flags pubkeys exceeding N distinct devices in a windowlistByTier— for the grant CLI'slistsubcommandmaxAccountsForTier/maxClientsForTier— locked caps:Design notes
clients.jsandtokens.json.{})./^[0-9a-f]{64}$/— invalid keys filtered on load (won't crash on legacy data).recordDevicedeliberately won't auto-create entries for unknown pubkeys. Free-tier shouldn't accumulate device data, and entitlement creation is grant-driven only (CLI in PR 3, Lightning later).Test plan
relay-proxy/test/entitlements.test.js)proxy.jschanges — zero production behavior change/entitlementendpoint +/pair-clientcap-check integrationTest coverage
expires_at: numberRisk
Low. Pure additive module, not yet imported. PR 2 is the first PR with observable production behavior change.
🤖 Generated with Claude Code