Harden bundled checkout disclosure and launcher-only commerce policy

This commit is contained in:
Joshua 2026-02-17 14:02:03 -08:00
parent a215a7d0f0
commit 2b313db814
15 changed files with 229 additions and 9 deletions

View File

@ -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

View File

@ -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

View File

@ -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]

View 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.

View File

@ -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`

View 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.

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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:

View File

@ -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

View File

@ -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') {

View File

@ -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>