Make premium claim windows manual close by default

This commit is contained in:
thePR0M3TH3AN committed 2026-07-04 16:34:23 -04:00
1 parent e0e7755cf5
commit 37d871431b
5 files changed
+83 -33

No files matched your search

+23 -13
View File
@@ -359,8 +359,8 @@ Premium 2.0 changes the post-payment path:
1. Artstr fetches and verifies the signed admin premium policy event.
2. Creator publishes a premium event as usual. Artstr automatically stamps
policy fields such as `softgate-epoch`, `claim-epoch`, `claim-until`,
and `post-purchase-action`.
policy fields such as `softgate-epoch`, `claim-epoch`, optional
`claim-until`, and `post-purchase-action`.
3. Buyer unlocks and pays split zaps as today.
4. Artstr verifies all required receipts.
5. Browser decrypts the public premium payload once.
@@ -410,9 +410,10 @@ Content:
"v": 1,
"activeSoftgateEpoch": "2026-07",
"activeClaimEpoch": "2026-07",
"defaultClaimDays": 90,
"minClaimDays": 7,
"maxClaimDays": 180,
"defaultClaimDays": 0,
"claimWindow": "manual",
"minClaimDays": 0,
"maxClaimDays": 3650,
"privateCopies": "required",
"firstUnlockDefault": "allowed",
"issuedAt": 1783200000,
@@ -477,7 +478,7 @@ Artstr-controlled automatic fields:
["premium-mode", "softgate-v1.5"],
["softgate-epoch", "<policy.activeSoftgateEpoch>"],
["claim-epoch", "<policy.activeClaimEpoch>"],
["claim-until", "<now + policy.defaultClaimDays>"],
["claim-until", "<now + policy.defaultClaimDays, omitted when manual>"],
["claim-policy", "private-snapshot-required"],
["post-purchase-action", "private-snapshot-required"]
]
@@ -494,7 +495,7 @@ Envelope additions:
"claimEpoch": "2026-07",
"publicDesignSalt": "<base64-random-32-bytes>",
"purchaseScope": "address",
"claimUntil": 1796083200,
"claimUntil": 0,
"postPurchaseAction": "private-snapshot-required"
},
"license": {
@@ -507,8 +508,8 @@ Envelope additions:
Creator UI should stay simple:
```text
Premium purchases are claimable for 90 days. Buyers keep a private
encrypted copy after purchase.
Premium purchases stay claimable until Artstr closes the epoch. Buyers keep
a private encrypted copy after purchase.
```
Do not expose `epoch`, `KDF`, or `policy manifest` language in normal
@@ -522,9 +523,12 @@ Claim windows are honest-client policy.
Rules:
- `claim-until` controls first-time public-premium unlock attempts;
- after `claim-until`, honest clients stop offering a new first-time
unlock from that public event;
- `claim-until` is optional. If it is absent or `0`, the public listing stays
claimable until the admin manually closes the epoch;
- when `claim-until` is present, it controls first-time public-premium
unlock attempts;
- after `claim-until`, honest clients stop offering a new first-time unlock
from that public event;
- already-claimed private purchase copies keep opening;
- self-authored premium events can keep auto-unlocking for the creator;
- expired source events remain browseable as previews;
@@ -533,7 +537,7 @@ Rules:
Recommended defaults:
- default claim window: 90 days;
- default claim window: unlimited/manual close (`defaultClaimDays: 0`);
- creator does not configure it in v2.0;
- admin can later expose creator choice within `minClaimDays` and
`maxClaimDays` if there is real demand;
@@ -542,6 +546,12 @@ Recommended defaults:
Claim windows should never be described as cryptographic expiry.
If an epoch is manually closed and a creator wants an old public listing to
be purchasable again, the creator should update/republish the listing under
the current active policy. The new public event gets the new active epoch and
becomes a fresh storefront. Existing buyers keep their older private purchase
copies as snapshots of what they bought.
---
## 9. Buyer-Private Purchase Copy
+13 -7
View File
@@ -5633,11 +5633,11 @@
</details>
<details id="premiumPolicyAdminDetails" style="margin-top:12px;border-top:1px solid var(--line);padding-top:10px;display:none">
<summary style="font-weight:700;font-size:.82rem;cursor:pointer;color:var(--muted)">Premium policy admin</summary>
<p class="hint" style="margin:8px 0 6px">Publish the signed Premium 2.0 policy event used for epoch and claim-window stamping.</p>
<p class="hint" style="margin:8px 0 6px">Publish the signed Premium 2.0 policy event used for epoch and claim-window stamping. Leave claim days blank/0 to keep listings claimable until you manually close the epoch.</p>
<div style="display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px">
<div><label for="premiumPolicySoftgateEpoch">Softgate epoch</label><input id="premiumPolicySoftgateEpoch" type="text" value="2026-07" /></div>
<div><label for="premiumPolicyClaimEpoch">Claim epoch</label><input id="premiumPolicyClaimEpoch" type="text" value="2026-07" /></div>
<div><label for="premiumPolicyDefaultDays">Claim days</label><input id="premiumPolicyDefaultDays" type="number" min="7" max="180" step="1" value="90" /></div>
<div><label for="premiumPolicyDefaultDays">Claim days</label><input id="premiumPolicyDefaultDays" type="number" min="0" max="3650" step="1" value="0" placeholder="0 = manual close" /></div>
<div><label for="premiumPolicyEpochStatus">Epoch status</label><select id="premiumPolicyEpochStatus"><option value="active">Active</option><option value="legacy">Legacy</option><option value="closed">Closed</option></select></div>
</div>
<div class="buttonRow" style="margin:8px 0 0">
@@ -26300,15 +26300,20 @@
function buildPremiumPolicyFromAdminForm() {
const softgateEpoch = ($('premiumPolicySoftgateEpoch')?.value || '2026-07').trim();
const claimEpoch = ($('premiumPolicyClaimEpoch')?.value || softgateEpoch).trim();
const defaultClaimDays = Math.max(7, Math.min(180, Number($('premiumPolicyDefaultDays')?.value) || 90));
const rawClaimDays = ($('premiumPolicyDefaultDays')?.value || '').trim();
const parsedClaimDays = Number(rawClaimDays);
const defaultClaimDays = rawClaimDays === '' || parsedClaimDays === 0
? 0
: Math.max(1, Math.min(3650, parsedClaimDays || 0));
const status = ($('premiumPolicyEpochStatus')?.value || 'active').trim();
return {
v: 1,
activeSoftgateEpoch: softgateEpoch,
activeClaimEpoch: claimEpoch,
defaultClaimDays,
minClaimDays: 7,
maxClaimDays: 180,
claimWindow: defaultClaimDays === 0 ? 'manual' : 'fixed-days',
minClaimDays: defaultClaimDays === 0 ? 0 : 1,
maxClaimDays: 3650,
privateCopies: 'required',
firstUnlockDefault: 'allowed',
issuedAt: Math.floor(Date.now() / 1000),
@@ -26340,9 +26345,10 @@
const policy = loaded.policy || {};
if ($('premiumPolicySoftgateEpoch')) $('premiumPolicySoftgateEpoch').value = policy.activeSoftgateEpoch || '2026-07';
if ($('premiumPolicyClaimEpoch')) $('premiumPolicyClaimEpoch').value = policy.activeClaimEpoch || policy.activeSoftgateEpoch || '2026-07';
if ($('premiumPolicyDefaultDays')) $('premiumPolicyDefaultDays').value = String(policy.defaultClaimDays || 90);
if ($('premiumPolicyDefaultDays')) $('premiumPolicyDefaultDays').value = Number(policy.defaultClaimDays || 0) > 0 ? String(policy.defaultClaimDays) : '0';
if ($('premiumPolicyEpochStatus')) $('premiumPolicyEpochStatus').value = policy.epochs?.[policy.activeSoftgateEpoch]?.status || 'active';
statusEl.textContent = `Active policy: ${policy.activeSoftgateEpoch || 'unknown'} / claim ${policy.activeClaimEpoch || 'unknown'}.`;
const claimWindow = Number(policy.defaultClaimDays || 0) > 0 ? `${policy.defaultClaimDays} days` : 'manual close';
statusEl.textContent = `Active policy: ${policy.activeSoftgateEpoch || 'unknown'} / claim ${policy.activeClaimEpoch || 'unknown'} / ${claimWindow}.`;
} catch (err) {
statusEl.textContent = err?.message || 'No valid premium policy found.';
}
+15 -13
View File
@@ -55,13 +55,22 @@ export function buildPremiumPublishStamp(policy, {
if (supportedSoftgateEpochs.length && !supportedSoftgateEpochs.includes(softgateEpoch)) {
throw new Error(`Premium policy epoch ${softgateEpoch} is not supported by this client.`);
}
const minDays = Math.max(1, Number(policy.minClaimDays) || 1);
const maxDays = Math.max(minDays, Number(policy.maxClaimDays) || 180);
const requestedDays = Number(policy.defaultClaimDays) || 90;
const claimDays = Math.min(maxDays, Math.max(minDays, requestedDays));
const claimUntil = Number(now) + (claimDays * 86400);
const requestedDays = Number(policy.defaultClaimDays);
const manualClaimClose = requestedDays === 0 || policy.claimWindow === 'manual';
const minDays = manualClaimClose ? 0 : Math.max(1, Number(policy.minClaimDays) || 1);
const maxDays = manualClaimClose ? 0 : Math.max(minDays, Number(policy.maxClaimDays) || 180);
const claimDays = manualClaimClose ? 0 : Math.min(maxDays, Math.max(minDays, requestedDays || 90));
const claimUntil = claimDays > 0 ? Number(now) + (claimDays * 86400) : 0;
const premiumMode = PREMIUM_MODE_V15;
const postPurchaseAction = PRIVATE_SNAPSHOT_ACTION;
const tags = [
['premium-mode', premiumMode],
['softgate-epoch', softgateEpoch],
['claim-epoch', claimEpoch],
['claim-policy', postPurchaseAction],
['post-purchase-action', postPurchaseAction],
];
if (claimUntil) tags.splice(3, 0, ['claim-until', String(claimUntil)]);
return {
premiumMode,
softgateEpoch,
@@ -70,14 +79,7 @@ export function buildPremiumPublishStamp(policy, {
claimDays,
postPurchaseAction,
encryptedTag: epochConfig.softgateKdf || 'artstr-softgate-v1.5',
tags: [
['premium-mode', premiumMode],
['softgate-epoch', softgateEpoch],
['claim-epoch', claimEpoch],
['claim-until', String(claimUntil)],
['claim-policy', postPurchaseAction],
['post-purchase-action', postPurchaseAction],
],
tags,
envelopeSoftgate: {
claimEpoch,
claimUntil,
+29
View File
@@ -68,6 +68,35 @@ test('builds automatic premium publish stamps from admin policy', async () => {
]);
});
test('builds unlimited premium publish stamps for manual-close policies', async () => {
const valid = await fixture('policy-valid-active.json');
const { policy } = validatePremiumPolicyEvent(valid, { platformPubkey });
const stamp = buildPremiumPublishStamp({
...policy,
defaultClaimDays: 0,
claimWindow: 'manual',
minClaimDays: 0,
}, {
now: 1783200000,
supportedSoftgateEpochs: ['2026-05', '2026-07'],
});
assert.equal(stamp.claimDays, 0);
assert.equal(stamp.claimUntil, 0);
assert.deepEqual(stamp.envelopeSoftgate, {
claimEpoch: '2026-07',
claimUntil: 0,
postPurchaseAction: 'private-snapshot-required',
});
assert.deepEqual(stamp.tags, [
['premium-mode', 'softgate-v1.5'],
['softgate-epoch', '2026-07'],
['claim-epoch', '2026-07'],
['claim-policy', 'private-snapshot-required'],
['post-purchase-action', 'private-snapshot-required'],
]);
});
test('rejects premium publish stamps for unsupported or closed policy epochs', async () => {
const valid = await fixture('policy-valid-active.json');
const { policy } = validatePremiumPolicyEvent(valid, { platformPubkey });
@@ -52,6 +52,9 @@ test('index page exposes admin-only premium policy publishing controls', async (
assert.match(html, /async function publishPremiumPolicyFromSettings\(\)/);
assert.match(html, /helpers\.buildPremiumPolicyEventDraft\(policy\)/);
assert.match(html, /helpers\.validatePremiumPolicyEvent\(signed, \{ platformPubkey: platform\.getPubkeyHex\(\) \}\)/);
assert.match(html, /placeholder="0 = manual close"/);
assert.match(html, /claimWindow: defaultClaimDays === 0 \? 'manual' : 'fixed-days'/);
assert.match(html, /statusEl\.textContent = `Active policy: \$\{policy\.activeSoftgateEpoch \|\| 'unknown'\} \/ claim \$\{policy\.activeClaimEpoch \|\| 'unknown'\} \/ \$\{claimWindow\}\.`/);
assert.match(html, /on\('premiumPolicyPublishBtn', 'click'/);
assert.match(html, /Premium policy published/);
});