Harden bundled checkout disclosure and launcher-only commerce policy
This commit is contained in:
parent
a215a7d0f0
commit
2b313db814
@ -36,6 +36,7 @@ docs/
|
||||
membership-pricing-policy.md
|
||||
membership-tier-extension.md
|
||||
wallet-ownership-payment-model.md
|
||||
catalog-distribution-policy.md
|
||||
failure-state-matrix.md
|
||||
legal-copy-matrix.md
|
||||
localization-qa-matrix.md
|
||||
@ -63,6 +64,7 @@ docs/
|
||||
governance-install-vectors.md
|
||||
deployment/
|
||||
README.md
|
||||
public-surface-checklist.md
|
||||
chain-config.template.json
|
||||
contract-addresses.template.json
|
||||
environment-invariants.md
|
||||
|
||||
@ -51,9 +51,30 @@ Success (`200`):
|
||||
"payer_wallet": "0x2299547f6fA9A8f9b6d9aEA9F9D8A4B53C8A0e11",
|
||||
"offer_id": "edut.governance.core",
|
||||
"currency": "USDC",
|
||||
"amount": "499.00",
|
||||
"amount_atomic": "499000000",
|
||||
"amount": "500.00",
|
||||
"amount_atomic": "500000000",
|
||||
"total_amount": "505.00",
|
||||
"total_amount_atomic": "505000000",
|
||||
"decimals": 6,
|
||||
"membership_activation_included": true,
|
||||
"line_items": [
|
||||
{
|
||||
"kind": "license",
|
||||
"label": "Governance Core License",
|
||||
"amount": "500.00",
|
||||
"amount_atomic": "500000000",
|
||||
"decimals": 6,
|
||||
"currency": "USDC"
|
||||
},
|
||||
{
|
||||
"kind": "membership",
|
||||
"label": "EDUT Membership Activation",
|
||||
"amount": "5.00",
|
||||
"amount_atomic": "5000000",
|
||||
"decimals": 6,
|
||||
"currency": "USDC"
|
||||
}
|
||||
],
|
||||
"policy_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"expires_at": "2026-02-17T07:44:30Z",
|
||||
"tx": {
|
||||
@ -64,6 +85,12 @@ Success (`200`):
|
||||
}
|
||||
```
|
||||
|
||||
### Notes
|
||||
|
||||
1. `amount`/`amount_atomic` represent the license component.
|
||||
2. `total_amount`/`total_amount_atomic` represent the actual payable quote total.
|
||||
3. First checkout can include membership activation as a separate line item.
|
||||
|
||||
Error (`403`):
|
||||
|
||||
```json
|
||||
|
||||
@ -5,10 +5,12 @@ info:
|
||||
description: Membership-gated offer and entitlement commerce endpoints.
|
||||
servers:
|
||||
- url: https://api.edut.ai
|
||||
security:
|
||||
- AppSession: []
|
||||
paths:
|
||||
/marketplace/offers:
|
||||
get:
|
||||
summary: List active offers
|
||||
summary: List active offers (launcher/app surface)
|
||||
responses:
|
||||
'200':
|
||||
description: Offer list
|
||||
@ -98,6 +100,11 @@ paths:
|
||||
items:
|
||||
$ref: '#/components/schemas/Entitlement'
|
||||
components:
|
||||
securitySchemes:
|
||||
AppSession:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: EDUT-APP-SESSION
|
||||
schemas:
|
||||
Offer:
|
||||
type: object
|
||||
@ -156,9 +163,13 @@ components:
|
||||
ownership_proof:
|
||||
type: string
|
||||
description: Optional ownership-wallet signature proving entitlement recipient approval when payer differs.
|
||||
include_membership_if_missing:
|
||||
type: boolean
|
||||
default: true
|
||||
description: If true, quote may bundle first-time membership fee into total.
|
||||
CheckoutQuoteResponse:
|
||||
type: object
|
||||
required: [quote_id, wallet, offer_id, currency, amount_atomic, policy_hash, expires_at]
|
||||
required: [quote_id, wallet, offer_id, currency, amount_atomic, total_amount_atomic, policy_hash, expires_at]
|
||||
properties:
|
||||
quote_id:
|
||||
type: string
|
||||
@ -174,8 +185,20 @@ components:
|
||||
type: string
|
||||
amount_atomic:
|
||||
type: string
|
||||
description: License amount component only.
|
||||
total_amount:
|
||||
type: string
|
||||
description: Total payable amount for this checkout quote.
|
||||
total_amount_atomic:
|
||||
type: string
|
||||
decimals:
|
||||
type: integer
|
||||
membership_activation_included:
|
||||
type: boolean
|
||||
line_items:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/QuoteLineItem'
|
||||
policy_hash:
|
||||
type: string
|
||||
expires_at:
|
||||
@ -184,6 +207,23 @@ components:
|
||||
tx:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
QuoteLineItem:
|
||||
type: object
|
||||
required: [kind, amount_atomic, decimals, currency, label]
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
enum: [license, membership, network_estimate]
|
||||
label:
|
||||
type: string
|
||||
amount:
|
||||
type: string
|
||||
amount_atomic:
|
||||
type: string
|
||||
decimals:
|
||||
type: integer
|
||||
currency:
|
||||
type: string
|
||||
CheckoutConfirmRequest:
|
||||
type: object
|
||||
required: [quote_id, wallet, offer_id, tx_hash, chain_id]
|
||||
|
||||
35
docs/catalog-distribution-policy.md
Normal file
35
docs/catalog-distribution-policy.md
Normal file
@ -0,0 +1,35 @@
|
||||
# Catalog Distribution Policy
|
||||
|
||||
This policy keeps public web minimal while allowing full commerce inside the launcher app.
|
||||
|
||||
## Public Web (edut.ai)
|
||||
|
||||
1. Public web remains identity and access surface.
|
||||
2. Public web does not serve production catalog details.
|
||||
3. Public web does not execute production checkout.
|
||||
4. Public web may host internal preview routes that are noindex and disabled by default.
|
||||
|
||||
## Launcher App Surface
|
||||
|
||||
1. Launcher app is the canonical catalog and checkout surface.
|
||||
2. Launcher fetches signed catalog manifests from marketplace APIs.
|
||||
3. Launcher verifies manifest signature and hash before display.
|
||||
4. Launcher checkout requires wallet session, ownership binding, and entitlement gating.
|
||||
|
||||
## Anti-Scraping Posture
|
||||
|
||||
1. No public, anonymous catalog endpoint for production offers.
|
||||
2. Offer manifests require app session and rate limits.
|
||||
3. Manifest payloads are short-TTL and signed.
|
||||
4. Checkout endpoints require nonce-bound quotes and ownership-proof rules.
|
||||
|
||||
## Legal Clarity Rule
|
||||
|
||||
1. If first checkout bundles membership activation, quote must show line-item breakdown.
|
||||
2. Checkout totals must reconcile to line-item amounts deterministically.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
1. Marketing the catalog directly on public website pages.
|
||||
2. Relying on obscurity as sole protection.
|
||||
3. Activating runtime rights from unsigned catalog/quote data.
|
||||
@ -112,6 +112,22 @@ This document defines deterministic pass/fail vectors for membership-gated comme
|
||||
- When checkout quote is requested
|
||||
- Then quote is denied
|
||||
|
||||
## Vector Group G: Bundled First Checkout Transparency
|
||||
|
||||
1. G1 `quote_bundle_membership_when_missing`
|
||||
- Given active ownership wallet without membership and `include_membership_if_missing=true`
|
||||
- When governance checkout quote is requested
|
||||
- Then quote includes membership line item and `membership_activation_included=true`
|
||||
|
||||
2. G2 `quote_excludes_membership_when_active`
|
||||
- Given wallet with active membership
|
||||
- When governance checkout quote is requested
|
||||
- Then quote excludes membership line item and `membership_activation_included=false`
|
||||
|
||||
3. G3 `quote_total_matches_line_items`
|
||||
- Given any quote response with line items
|
||||
- Then `total_amount_atomic` equals sum of line item `amount_atomic` values (excluding network_estimate)
|
||||
|
||||
## Vector Group E: Evidence Integrity
|
||||
|
||||
1. E1 `receipt_fields_complete`
|
||||
|
||||
30
docs/deployment/public-surface-checklist.md
Normal file
30
docs/deployment/public-surface-checklist.md
Normal file
@ -0,0 +1,30 @@
|
||||
# Public Surface Deployment Checklist
|
||||
|
||||
This checklist prevents accidental public exposure of app-only commerce surfaces.
|
||||
|
||||
## Required Production Settings
|
||||
|
||||
1. `edut.ai` serves landing + legal + trust pages only.
|
||||
2. `/store` route disabled or restricted for production public domain.
|
||||
3. `/store/offers.json` not publicly served in production.
|
||||
4. Marketplace APIs enforce app/session authentication.
|
||||
5. Robots headers enforce noindex for any preview-only routes.
|
||||
|
||||
## Header and Caching Controls
|
||||
|
||||
1. Preview routes return `X-Robots-Tag: noindex, nofollow, noarchive, nosnippet`.
|
||||
2. Catalog manifests use short cache TTL.
|
||||
3. Quote responses disable shared cache and include anti-replay headers.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Run anonymous request to `/store` and confirm checkout is disabled.
|
||||
2. Run anonymous request to `/store/offers.json` and confirm blocked/unavailable.
|
||||
3. Confirm launcher-authenticated session can fetch catalog.
|
||||
4. Confirm public web page has no links to active checkout surface.
|
||||
|
||||
## Release Blockers
|
||||
|
||||
1. Any production route exposes active catalog without app/session auth.
|
||||
2. Any production route allows quote creation anonymously.
|
||||
3. Any production route indexes preview checkout content.
|
||||
@ -10,6 +10,8 @@ This checklist maps store behavior to required marketplace backend implementatio
|
||||
4. `POST /marketplace/checkout/confirm`
|
||||
5. `GET /marketplace/entitlements?wallet=...`
|
||||
|
||||
All marketplace endpoints require authenticated app/session context.
|
||||
|
||||
## Required Gate Behavior
|
||||
|
||||
1. Quote endpoint must deny checkout when membership is not active.
|
||||
@ -32,9 +34,12 @@ This checklist maps store behavior to required marketplace backend implementatio
|
||||
4. `offer_id`
|
||||
5. `currency`
|
||||
6. `amount` or `amount_atomic + decimals`
|
||||
7. `policy_hash`
|
||||
8. `expires_at`
|
||||
9. `tx` execution object or equivalent fields
|
||||
7. `total_amount` or `total_amount_atomic + decimals`
|
||||
8. `membership_activation_included`
|
||||
9. `line_items` with transparent breakdown (license + optional membership)
|
||||
10. `policy_hash`
|
||||
11. `expires_at`
|
||||
12. `tx` execution object or equivalent fields
|
||||
|
||||
## Confirm Response Requirements
|
||||
|
||||
@ -60,6 +65,7 @@ This checklist maps store behavior to required marketplace backend implementatio
|
||||
3. Tx chain, amount, and destination validation.
|
||||
4. Idempotent confirm handling for repeated tx hash submissions.
|
||||
5. Ownership wallet proof validation when payer wallet differs.
|
||||
6. Bundled membership component must be explicit in quote line items when included.
|
||||
|
||||
## Done Criteria
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
3. Chain verification and policy hash enforcement.
|
||||
4. Member app channel endpoints for device registration and event polling.
|
||||
5. Governance installer endpoints for signed package authorization and activation confirmation.
|
||||
6. Marketplace catalog/checkout auth gates so production commerce is app-session scoped.
|
||||
|
||||
## Runtime/Kernel Responsibilities
|
||||
|
||||
|
||||
@ -15,3 +15,4 @@ This matrix prevents drift between public surfaces and legal posture.
|
||||
1. Membership language must always distinguish access rights from license rights.
|
||||
2. Any copy introducing financial upside claims is blocked.
|
||||
3. Any change to legal-critical copy requires review against this matrix.
|
||||
4. First checkout totals must disclose line-item composition when membership activation is bundled.
|
||||
|
||||
@ -24,6 +24,8 @@ This gate controls deploy/no-deploy decisions for membership-gated commerce chan
|
||||
10. Governance activation blocks inactive/unknown entitlement states.
|
||||
11. Terms/privacy copy still match utility-access framing.
|
||||
12. Structured logs and metrics are emitted for each state transition.
|
||||
13. Bundled membership line-item disclosure is present on first checkout quotes.
|
||||
14. Public web deployment blocks anonymous production catalog and quote endpoints.
|
||||
|
||||
## No-Deploy Triggers
|
||||
|
||||
@ -34,6 +36,8 @@ This gate controls deploy/no-deploy decisions for membership-gated commerce chan
|
||||
5. Any governance runtime activation without valid signed package verification.
|
||||
6. Any missing audit evidence on successful purchase.
|
||||
7. Any breaking API change without version bump and migration note.
|
||||
8. Any checkout quote total that cannot be reconciled to disclosed line items.
|
||||
9. Any production public route exposing active catalog/checkout without app session auth.
|
||||
|
||||
## Evidence Bundle Required for Release
|
||||
|
||||
|
||||
@ -110,3 +110,10 @@ This roadmap is intentionally step-based and dependency-ordered. No timeline com
|
||||
2. Install requires signed package metadata and hash verification.
|
||||
3. Activation requires active governance entitlement and matching policy hash.
|
||||
4. Runtime blocks execution when membership/entitlement status is suspended, revoked, or unknown.
|
||||
|
||||
## Step 17: Enforce Launcher-Only Commerce Surface
|
||||
|
||||
1. Public website remains identity/legal/trust surface.
|
||||
2. Production catalog and checkout require app-session auth.
|
||||
3. Public preview routes are disabled by default and noindexed.
|
||||
4. Release gate blocks deploys that expose anonymous production catalog access.
|
||||
|
||||
@ -45,6 +45,8 @@ Implemented now:
|
||||
18. Governance install API contract, examples, backend handoff checklist, and conformance vectors.
|
||||
19. Repo boundary blueprint and free launcher specification aligned with first paid governance model.
|
||||
20. Store UI now supports distinct payer wallet overrides with ownership-proof signing before quote requests.
|
||||
21. Public web store preview is noindex and disabled by default unless explicit internal preview mode is enabled.
|
||||
22. Catalog distribution and public-surface deployment guardrails are documented for launcher-only commerce.
|
||||
|
||||
Remaining in this repo:
|
||||
|
||||
|
||||
@ -52,6 +52,7 @@ The flow should feel controlled and ambient, not like a conventional signup form
|
||||
- `edut.dev`: developer-facing domain (same landing for now).
|
||||
- `api.edut.ai`: API endpoints for wallet intent/verify and membership quote/confirm.
|
||||
- `/privacy` and `/terms`: legal pages (English authoritative).
|
||||
- Production catalog and checkout surfaces are launcher-app channels, not public web pages.
|
||||
|
||||
## Messaging Boundaries
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
<title>EDUT Store Preview</title>
|
||||
<meta name="description" content="EDUT membership-gated marketplace preview states.">
|
||||
<meta name="theme-color" content="#f0f4f8">
|
||||
<meta name="robots" content="noindex,nofollow,noarchive,nosnippet">
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500&display=swap');
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
@ -146,7 +147,7 @@
|
||||
<div class="container">
|
||||
<a href="/" class="back">← Back</a>
|
||||
<h1>EDUT Store</h1>
|
||||
<p class="sub">Membership-gated checkout behavior (live-state scaffold)</p>
|
||||
<p class="sub" id="preview-mode-note">Membership-gated checkout behavior (internal preview scaffold)</p>
|
||||
|
||||
<div class="grid">
|
||||
<section class="card">
|
||||
@ -205,6 +206,8 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const internalPreview = new URLSearchParams(window.location.search).get('internal_preview') === '1';
|
||||
|
||||
const state = {
|
||||
wallet: null,
|
||||
ownershipProof: null,
|
||||
@ -233,6 +236,7 @@
|
||||
const offerSummary = document.getElementById('offer-summary');
|
||||
const offerPrice = document.getElementById('offer-price');
|
||||
const offerPolicy = document.getElementById('offer-policy');
|
||||
const previewModeNote = document.getElementById('preview-mode-note');
|
||||
|
||||
function abbreviateWallet(wallet) {
|
||||
if (!wallet || wallet.length < 10) return wallet || 'not connected';
|
||||
@ -274,6 +278,30 @@
|
||||
checkoutLog.textContent = message;
|
||||
}
|
||||
|
||||
function disableInteractiveStore(reason) {
|
||||
connectBtn.disabled = true;
|
||||
refreshBtn.disabled = true;
|
||||
signProofBtn.disabled = true;
|
||||
checkoutBtn.disabled = true;
|
||||
offerSelect.disabled = true;
|
||||
mockSelect.disabled = true;
|
||||
payerWalletInput.disabled = true;
|
||||
offerTitle.textContent = 'Launcher-only catalog';
|
||||
offerSummary.textContent = 'Catalog and checkout are available inside the EDUT launcher app.';
|
||||
offerPrice.textContent = 'Price: hidden on public web';
|
||||
offerPolicy.textContent = 'Policy: launcher access required';
|
||||
setLog(reason);
|
||||
setCheckoutLog('Public web checkout is disabled. Use EDUT launcher.');
|
||||
gatePill.className = 'state warn';
|
||||
gatePill.textContent = 'launcher only';
|
||||
gateLabel.textContent = 'blocked';
|
||||
membershipLabel.textContent = 'hidden';
|
||||
proofLabel.textContent = 'launcher only';
|
||||
if (previewModeNote) {
|
||||
previewModeNote.textContent = 'Public web checkout disabled. Open EDUT launcher for catalog and purchase.';
|
||||
}
|
||||
}
|
||||
|
||||
function getSelectedOffer() {
|
||||
if (!state.selectedOfferId) return null;
|
||||
return state.offers.find((offer) => offer.offer_id === state.selectedOfferId) || null;
|
||||
@ -567,8 +595,23 @@
|
||||
const quotePayload = await response.json();
|
||||
const quoteId = quotePayload.quote_id || 'unknown';
|
||||
const amount = quotePayload.amount || quotePayload.amount_atomic || 'unknown';
|
||||
const total = quotePayload.total_amount || quotePayload.total_amount_atomic || amount;
|
||||
const currency = quotePayload.currency || 'unknown';
|
||||
setCheckoutLog('Quote ready for ' + state.selectedOfferId + ': ' + quoteId + ' (' + amount + ' ' + currency + ').');
|
||||
const lines = Array.isArray(quotePayload.line_items) ? quotePayload.line_items : [];
|
||||
let breakdown = '';
|
||||
if (lines.length > 0) {
|
||||
breakdown = '\\nLine items:\\n' + lines.map(function (item) {
|
||||
const value = item.amount || item.amount_atomic || 'unknown';
|
||||
const unit = item.currency || currency;
|
||||
const label = item.label || item.kind || 'item';
|
||||
return '- ' + label + ': ' + value + ' ' + unit;
|
||||
}).join('\\n');
|
||||
}
|
||||
setCheckoutLog(
|
||||
'Quote ready for ' + state.selectedOfferId + ': ' + quoteId +
|
||||
' (license ' + amount + ' ' + currency + ', total ' + total + ' ' + currency + ').' +
|
||||
breakdown
|
||||
);
|
||||
} catch (err) {
|
||||
setCheckoutLog('Quote request failed: ' + err.message + '. API wiring pending.');
|
||||
}
|
||||
@ -591,6 +634,10 @@
|
||||
});
|
||||
|
||||
applyGateState();
|
||||
if (!internalPreview) {
|
||||
disableInteractiveStore('Preview mode is disabled on public web.');
|
||||
return;
|
||||
}
|
||||
loadOffers();
|
||||
const initialMock = getMockFromQuery();
|
||||
if (initialMock !== 'unknown') {
|
||||
|
||||
@ -169,6 +169,7 @@
|
||||
|
||||
<h2>Payments</h2>
|
||||
<p>Where paid offerings are available, you agree to pay applicable charges at checkout. Accepted payment methods may vary by offering. Unless required by law or stated otherwise in writing, fees are non-refundable.</p>
|
||||
<p>For first-time purchases, EDUT may bundle membership activation with a license purchase in a single checkout total. When bundled, checkout displays the line-item composition (for example, membership activation plus license component) before transaction confirmation.</p>
|
||||
|
||||
<h2>No Investment Expectation</h2>
|
||||
<p>Designations, memberships, and related access credentials are utility access instruments for EDUT services. They are not investment contracts, securities, profit-sharing instruments, or claims on company equity, assets, or revenue. EDUT does not promise appreciation, resale value, financial return, or secondary-market liquidity for any access credential.</p>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user