design: TermsGate coverage parity for cl8y.com and bridge.cl8y.com #34
Labels
No labels
agent:implement
agent:ready
api
bot
bug
ci
enhancement
ready
security
terra-classic
testing
ux
web
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-ecosystem-legal#34
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Record TermsGate coverage parity for the two first-party hosts that are not the DEX, then implement the missing Legal-side work.
Decisions (do not re-litigate in a follow-up unless product changes them):
https://dex.cl8y.comConnectedTermsGate, propertydex.cl8y.com).https://bridge.cl8y.comBridgeTermsGate.https://cl8y.com@plasticdigits/cl8y-clickwrapTermsGaterequires anetwork+account; gating this host like DEX would force a wallet-sign flow on an informational site (or a fake account). Document the exception; optional footer link to the hosted T&Cs reader, not a signature.Bundle (do not split):
bridge.cl8y.com(same playbook as #8 / #12).code/CL8Y-web(invariants + optional Terms href). Separate worktree; do not patch CL8Y-web from a Legal checkout.Not a duplicate of #134 (frontend wiring is done there; that issue stays open until production verify). Not a duplicate of DEX #517 (closed). Not a duplicate of CL8Y-web#12 (reserved
/privacy//cookies//opt-outpaths — T&Cs are not a Privacy Notice).Current codebase
Legal platform (
code/cl8y-ecosystem-legal) — this worktreeAcceptance is per property (hostname). One signature row per
(property, terms version, network, account). Signing oncl8y.comdoes not satisfybridge.cl8y.comordex.cl8y.com.packages/cl8y-clickwrap—createClient,TermsGate,useSignatureStatus,buildSignUrl.TermsGatewith emptyaccountrendersfallback, not children. Networks:EVM/TerraClassic/Solana/Telegramonly. There is no anonymous/web-session network.web/athttps://terms.cl8y.com. Redirects only to origins onVITE_REDIRECT_URI_ALLOWLIST.api/athttps://api.terms.cl8y.com. Browser status calls need the dapp origin onCORS_ORIGINS.scripts/register-property.sh→POST /admin/properties. PublicGET /api/v1/terms/latest?property=can upsert a website row; still register display names explicitly for production.cl8y.comas the canonical website property (GET …?property=cl8y.com, portal allowlist examplehttps://cl8y.com,https://www.cl8y.com). That reads as if the marketing site were a gated integrator. Live gated dapps aredex.cl8y.com(and, once ops land,bridge.cl8y.com). Other already-registered properties (ust1cmm.com,vote.cl8y.com) are out of scope except as playbook references.DEX (
code/cl8y-dex-terraclassic) — reference onlyfrontend-dapp/src/components/legal/ConnectedTermsGate.tsxwraps connected Terra ClassicaddresswithTermsGate(propertydefaultdex.cl8y.com,appName="CL8Y DEX").frontend-dapp/src/utils/legalClickwrap.ts— client singleton, production HTTPS bases,sanitizeRedirectUri, Playwright skip only viaVITE_PLAYWRIGHT_E2E.@plasticdigits/cl8y-clickwrap@^0.1.1.RiskAcknowledgementModalis not clickwrap.Bridge (
code/cl8y-bridge-monorepo) — frontend done, Legal ops not@plasticdigits/cl8y-clickwrap@0.1.1inpackages/frontend/package.json.bridge.cl8y.cominpackages/frontend/src/utils/clickwrap.ts(BRIDGE_CLICKWRAP_PROPERTY). Must not be derived fromwindow.location.hostname.BridgeTermsGatewraps mutative transfer/status controls; header Connect stays outside the gate (packages/frontend/src/components/transfer/BridgeTermsGate.tsx).useBridgeClickwrapGate—allowsMutativeonly after successfulsigned_latest(fail closed while loading / on error).packages/frontend/e2e/clickwrap.spec.ts, Vitest aroundclickwrap.ts/BridgeTermsGate.docs/FRONTEND_BRIDGE_INVARIANTS.md+skills/agent-frontend-clickwrap.md.Marketing (
code/CL8Y-web) — no TermsGatesrc/app/index.tsx:/, blog, legacy section redirects, whitepaper PDF redirect. No/sign, no clickwrap, no Connect Wallet chrome.src/content/invariants.tsCANONICAL_PRODUCT_URLS= Bridge + DEX only. Footer (src/components/chrome/SiteFooter.tsx) has disclaimer + law-enforcement copy; no link tohttps://terms.cl8y.com.AppProvidersstill mountsWagmiProvider(reconnectOnMount) with optional WalletConnect whenVITE_WALLETCONNECT_PROJECT_IDis set (src/providers/AppProviders.tsx). That is leftover read-path infra, not a user-facing wallet gate. Do not reuse it to fake a TermsGate account.@plasticdigits/cl8y-clickwrapdependency.Why this is needed
signed_latestfordex.cl8y.com. Bridge code will fail-closed in production until Legal allows the origin. Marketing visitors can read product CTAs with no wallet-bound T&Cs — that is acceptable only if it is an explicit, tested exception, not an accident.web/.env.examplestill hold upcl8y.comas the default website property. Operators and agents will register or CORS-allow the apex “to match the docs” and then wonder why the marketing SPA never calls/signatures/status.TermsGateis account-scoped. Mounting it oncl8y.comwithout a connected address shows the checking fallback forever (or blocks the whole page). Inventing a synthetic account, a localStorage “I agree”, or a querysigned=1is not Legal acceptance.This is coverage policy + Legal env/allowlists + marketing documentation (and an optional Terms link). No new signing crypto. No bridge/DEX clickwrap rewrite.
Constraints and guardrails
/privacy//cookies//opt-out. A footer “Terms” href must go tohttps://terms.cl8y.com(or/api/v1/terms/latest/content?property=…only as a raw text link). Never label it Privacy / cookies / opt-out.bridge.cl8y.comexactly. Do not reusecl8y.com,dex.cl8y.com,www.bridge.cl8y.com(unless that host is actually served — it is not the SPA origin).signed_latestfor the marketing host. Do not writelocalStorage/ cookies / query flags that later dapps could trust.useAccountintoTermsGate, do not add the clickwrap package to CL8Y-web.https://bridge.cl8y.com(no trailing slash, no*). #7 wildcards do not replace this. Do not addhttps://cl8y.comto production CORS because of this issue (a simple<a href>to the portal needs no CORS). Ifcl8y.comis already on an allowlist from older examples, do not treat that as “marketing is gated.”window.location.hrefonly. Marketing Terms link is a compile-time HTTPS constant (URL/ existingExternalLink), never from query / hash /Referer.ADMIN_TOKENin any frontend.register-property.shprompts hidden; do not paste tokens into the issue, comments, or env examples beyond the existing placeholder pattern./sign/solanacannot produce a verifiable signature. Do not skip Solana legal while gating EVM/Terra.Relevant files
This repo (
code/cl8y-ecosystem-legal)README.mdcl8y.comas the default gated-website example withdex.cl8y.com; add the coverage tableskills/README.mdskills/integrator-coverage/SKILL.mdscripts/register-property.shbridge.cl8y.com.env.exampleCORS_ORIGINS(examplehttps://dex.cl8y.com,https://bridge.cl8y.com) — not the marketing apex as a required integratorweb/.env.examplepackages/cl8y-clickwrap/README.mdskills/security-ops/SKILL.mdSibling
code/CL8Y-web(separate PR)src/content/invariants.tsCANONICAL_PRODUCT_URLSas Bridge/DEXsrc/content/invariants.test.ts@/ redirectors; assert clickwrap package is not a dependencysrc/data/products.tsorsrc/lib/siteUrls.tshttps://terms.cl8y.comnext to other first-party URLssrc/components/chrome/SiteFooter.tsx+src/data/copy.tsskills/cl8y-site-positioning/invariants.mdpackage.json@plasticdigits/cl8y-clickwrapSibling
code/cl8y-bridge-monorepo(read / verify only)packages/frontend/src/utils/clickwrap.tsbridge.cl8y.compackages/frontend/src/components/transfer/BridgeTermsGate.tsxdocs/FRONTEND_BRIDGE_INVARIANTS.mdReference
code/cl8y-dex-terraclassicfrontend-dapp/src/components/legal/ConnectedTermsGate.tsxRecommended direction
1. Coverage matrix (this repo)
Add
skills/integrator-coverage/SKILL.mdwith a table agents must copy:dex.cl8y.comcode/cl8y-dex-terraclassicTerraClassicbridge.cl8y.comcode/cl8y-bridge-monorepocl8y.comcode/CL8Y-webust1cmm.com/vote.cl8y.comRules in the skill:
Update
README.mdpublic API examples toproperty=dex.cl8y.com. Keep a short note thatcl8y.comis the marketing origin and is not a required clickwrap property.2. Legal ops for the bridge (this repo / API+portal env)
Same as #8 / #12:
Append exact origin (keep existing entries):
CORS_ORIGINS=…,https://bridge.cl8y.comVITE_REDIRECT_URI_ALLOWLIST=…,https://bridge.cl8y.comand rebuild the static portal (Vite inlines the allowlist).Do not enable
VITE_ALLOW_LOCALHOST_REDIRECTon the production portal.Verify (no secrets in output):
Negative check:
Origin: https://cl8y.commust not be required for this ticket. Optional: confirm a marketing origin preflight is irrelevant because CL8Y-web will not call the status API.3. Marketing exception (CL8Y-web sibling)
LEGAL_TERMS_URL = "https://terms.cl8y.com"(exact, no path required; portal home shows T&Cs).ExternalLink,relalready used by the footer helper.package.json/ lockfile has nocl8y-clickwrap; noTermsGateimport undersrc/.If product later reverses the exception, that is a new issue (would require a wallet UX on the marketing site or an SDK change for anonymous acceptance — both out of scope here).
Acceptance criteria
cl8y.comexplicit exception) in a skill + README. Examples no longer imply the marketing SPA is the default clickwrap integrator.bridge.cl8y.comexists on the Legal API (GET /api/v1/terms/latest?property=bridge.cl8y.comreturns that property + sign URLs).Origin: https://bridge.cl8y.comto/api/v1/signatures/statusreturnsAccess-Control-Allow-Origin: https://bridge.cl8y.com.https://bridge.cl8y.comso post-sign navigation can return to the bridge (rebuild done). Production portal still rejects unlistedredirect_uri.TermsGate/ no clickwrap dependency; exception documented in invariants/skill; optional Terms footer link is the constanthttps://terms.cl8y.comand is not named Privacy./api/v1/signatures/status(grep / e2e: no Legal API from the marketing origin).bridge.cl8y.com(no change unless a bug). #134 stays the frontend tracker.ADMIN_TOKENadded to any SPA. NodangerouslySetInnerHTMLfor terms HTML. No open redirect from the marketing Terms href.Test plan (functional paths)
GET /api/v1/terms/latest?property=bridge.cl8y.compropertyisbridge.cl8y.com; evm/terra/solanasign_urlspresentGET /api/v1/terms/latest?property=dex.cl8y.comOrigin: https://bridge.cl8y.comOrigin: https://evil.exampleredirect_uri=https://bridge.cl8y.com/…redirect_uri=https://phish.example/web/e2e/redirect.spec.tsstay green)/https://terms.cl8y.comin a new tab viaExternalLink--workers=5)clickwrap.spec.tsGET https://cl8y.com/(or preview)/sign/evm; no TermsGate overlayGET …/terms/latest?property=cl8y.comLegal portal Playwright: existing
web/e2esuite, 5 workers as already configured. No new wallet-sign e2e required in CL8Y-web.Test plan (attack, hack, and abuse)
cl8y.comsignature as evidence on the bridgebridge.cl8y.comonly (clickwrap.tsunit tests already pin this)window.location.hostname/ Host header spoofBRIDGE_CLICKWRAP_PROPERTYTermsGateon CL8Y-web with a hardcoded dummy accountsigned_latest=trueon cl8y.com?signed=1/?property=dex.cl8y.comon marketinghreffrom?next=/javascript:///evilhttps://terms.cl8y.comredirect_uricrafted by marketingbuildSignUrl*to “make bridge work”https://cl8y.comto CORS so a future silent status poll can runterms.cl8y.com;ExternalLink/ top navigation. Marketing already intendsframe-ancestors 'none'(CL8Y-web host-headers skill)www.cl8y.comand claim parityreconnectOnMountsupplies an0xand someone wires TermsGate “because the provider exists”Verification criteria
skills/integrator-coverage/SKILL.mdexists;skills/README.mdlinks it; README API examples use a gated dapp property (dex.cl8y.com), not “marketing is gated.”redirect_urisuccess path or by reading the built allowlist constant in the portal bundle (stringhttps://bridge.cl8y.compresent; attacker origin absent).yarn test,yarn typecheck,yarn test:e2e --workers=5green; no clickwrap inpackage.json; footer Terms (if present) is the constant.TermsGate/cl8y-clickwrapabsent undercode/CL8Y-web/src.Out of scope
BridgeTermsGate/ DEXConnectedTermsGate.@plasticdigits/cl8y-clickwrapto npmjs (#14) — integrators already consume0.1.1.References
TermsGate:packages/cl8y-clickwrap/src/react/TermsGate.tsxhttps://terms.cl8y.com·https://api.terms.cl8y.comcl8y-agent-control: queued
implementjobc8fa4ba5-33e2-4cb6-b010-4a977fa6fb1c(not executed; no Hetzner VM).cl8y-agent-control: needs_human inbox card POST failed. Job stays parked.