OPEN PANEL — Foundational Contracts
OPEN PANEL — Foundational Contracts
Section titled “OPEN PANEL — Foundational Contracts”Detailed specs for the durable interfaces every workstream depends on (CANON §6). These change rarely and break loudly. Status: DRAFT v0.1 — provisional, not legal/financial advice. Companion to
CANON.md(single source of truth) andarchitecture/OPERATING_CONTRACTS.md.
These seven contracts are the load-bearing schemas every other document and codebase
must conform to. IDs are self-consistent across contracts: the same work_id,
comic_id, series_id, creator_id, account_id, and entitlement_id thread through
every example below. The running example is the comic cpf:openpanel/the-gutter/001
(“THE GUTTER” #1) by creator cr_0xA17 (Mara Okafor).
Contract 1 — Comic Package Format (CPF)
Section titled “Contract 1 — Comic Package Format (CPF)”Purpose. The single open manifest the shared reader engine (@openpanel/reader)
consumes on web, mobile, and desktop. One artifact describes a comic completely:
pages, panels, guided-view path, metadata, ratings, locale, accessibility alt-text,
credits, and a rights pointer back to the LEDGER. CPF is open — anyone may produce
or read a valid package; it is the durable boundary between content production and the
reader.
Owner. WS1 Reader Core (schema) + WS5 Backend/content pipeline (production).
Consumers. Reader engine (all 3 clients), CONTENT API (serves manifests), media
pipeline (emits them), accessibility tooling, the LEDGER (via rights pointer).
Format. A package.cpf.json manifest + an asset directory (tiled/whole images on
R2/CDN). Image refs are CDN-relative paths resolved against asset_base.
Full example (rich, realistic)
Section titled “Full example (rich, realistic)”{ "cpf_version": "1.0.0", "comic_id": "cpf:openpanel/the-gutter/001", "work_id": "work_gutter", // FK → LEDGER works.work_id (Contract 4) "series": { "series_id": "series_the_gutter", "title": "THE GUTTER", "publisher": "OPEN PANEL FOUNDATION", "imprint": "standard-issue" // "standard-issue" (free) | "variant-edition" }, "issue": { "number": "1", "title": "Negative Space", "published_at": "2026-06-01", "page_count": 24, "edition": "standard" // mirrors entitlement tier (Contract 3) }, "credits": [ { "creator_id": "cr_0xA17", "name": "Mara Okafor", "role": "writer" }, { "creator_id": "cr_0xA17", "name": "Mara Okafor", "role": "artist" }, { "creator_id": "cr_0xB42", "name": "Devon Liu", "role": "letterer" }, { "creator_id": "cr_0xC09", "name": "Inez Park", "role": "colorist" } ], "asset_base": "https://cdn.openpanel.org/cpf/the-gutter/001/", "pages": [ { "page_id": "p01", "index": 0, "image": { "src": "pages/p01.avif", "width": 1988, "height": 3056, "dpi": 300 }, "tiles": { "format": "dzi", "src": "tiles/p01.dzi" }, // for guided-view zoom "thumb": "thumbs/p01.webp", "panels": [ { "panel_id": "p01-pn1", "reading_order": 1, "bbox": { "x": 0.04, "y": 0.03, "w": 0.92, "h": 0.30 }, // normalized 0..1 "alt_text": "A rain-slicked city street at night; neon signs blur in puddles." }, { "panel_id": "p01-pn2", "reading_order": 2, "bbox": { "x": 0.04, "y": 0.35, "w": 0.45, "h": 0.28 }, "alt_text": "Close on Ada's eyes, reflecting a flickering panel of static." }, { "panel_id": "p01-pn3", "reading_order": 3, "bbox": { "x": 0.51, "y": 0.35, "w": 0.45, "h": 0.28 }, "alt_text": "Ada's hand hovers over a doorbell labeled 'GUTTER MEDIA'." } ] } // ... pages p02..p24 elided for brevity ], "guided_view": { "default": true, "path": [ { "page_id": "p01", "panel_id": "p01-pn1", "transition": "cut", "hold_ms": 0 }, { "page_id": "p01", "panel_id": "p01-pn2", "transition": "pan", "hold_ms": 400 }, { "page_id": "p01", "panel_id": "p01-pn3", "transition": "zoom", "hold_ms": 400 } // path is an ordered traversal of (page, panel) stops; engine pans/zooms between ] }, "metadata": { "language_primary": "en", "genres": ["sci-fi", "noir"], "summary": "A signal hunter discovers the silence between panels is listening back.", "keywords": ["cyberpunk", "media", "mystery"] }, "content_rating": { "system": "openpanel-cr-1", "rating": "T", // E | T | T+ | M (Contract: editorial policy) "descriptors": ["mild-language", "stylized-violence"] }, "locale": { "available": ["en", "fr", "es"], "default": "en", "text_layers": { // localized lettering overlays, optional "fr": "locales/fr/text.json", "es": "locales/es/text.json" } }, "accessibility": { "alt_text_complete": true, // every panel has alt_text "dyslexia_mode_supported": true, "reading_level": "grade-6", "audio_description": null }, "rights": { "rights_uri": "ledger://rights/right_gutter_dist", // FK → LEDGER rights (Contract 4) "license_label": "Free distribution — OPEN PANEL FOUNDATION", "license_url": "https://openpanel.org/rights/the-gutter-001" }, "checksum": { "algo": "sha256", "manifest": "f3a9...", "assets_manifest": "assets.sha256" }}Versioning & compatibility. cpf_version is semver. Readers MUST accept any
manifest whose major matches and minor ≤ their supported minor (forward-compatible
within a major; unknown fields ignored). Minor/patch add optional fields only.
Breaking = removing/renaming a required field (comic_id, pages[].image,
panels[].bbox, panels[].reading_order, rights.rights_uri), changing bbox
normalization (0..1), or altering guided_view.path semantics — any of which can make an
existing reader mis-render or refuse a previously valid package across all three clients.
Contract 2 — Content API contract
Section titled “Contract 2 — Content API contract”Purpose. The HTTP boundary clients use to browse the catalog, fetch CPF manifests, sync reading progress, and resolve entitlements. Read-heavy, CDN-cacheable for public catalog; authenticated for progress/entitlements.
Owner. WS5 Backend. Consumers. Web/mobile/desktop clients, reader engine.
Conventions. REST/JSON, base https://api.openpanel.org/v1. Auth via bearer JWT
(Contract 3). Public catalog endpoints are anonymous-cacheable; progress and
entitlements require auth. Cursor pagination (?cursor=&limit=).
Endpoints
Section titled “Endpoints”GET /v1/catalog?genre=sci-fi&rating=T&cursor=&limit=20200 → { "items": [ { "series_id": "series_the_gutter", "title": "THE GUTTER", "cover": "https://cdn.openpanel.org/cpf/the-gutter/cover.webp", "issue_count": 6, "rating": "T", "imprint": "standard-issue" } ], "next_cursor": "eyJvIjoyMH0"}
GET /v1/series/series_the_gutter200 → { "series_id": "series_the_gutter", "title": "THE GUTTER", "publisher": "OPEN PANEL FOUNDATION", "summary": "...", "creators": ["cr_0xA17", "cr_0xB42", "cr_0xC09"], "issues": [ { "comic_id": "cpf:openpanel/the-gutter/001", "number": "1", "title": "Negative Space", "edition": "standard", "published_at": "2026-06-01" } ]}
GET /v1/issues/cpf:openpanel/the-gutter/001200 → { "comic_id": "cpf:openpanel/the-gutter/001", "manifest_url": "https://cdn.openpanel.org/cpf/the-gutter/001/package.cpf.json", "entitlement_required": "free", // "free" | "variant" (Contract 3) "rating": "T", "page_count": 24}
GET /v1/progress/cpf:openpanel/the-gutter/001 # auth required200 → { "account_id": "acct_7f3", "comic_id": "cpf:openpanel/the-gutter/001", "page_index": 7, "panel_id": "p08-pn2", "percent": 0.30, "updated_at": "2026-06-12T22:14:00Z" }
PUT /v1/progress/cpf:openpanel/the-gutter/001 # auth required body → { "page_index": 9, "panel_id": "p10-pn1", "percent": 0.41 }200 → { "ok": true, "updated_at": "2026-06-13T09:02:00Z" }
GET /v1/entitlements # auth required200 → { "account_id": "acct_7f3", "entitlements": [ { "entitlement_id": "ent_free_global", "type": "free", "scope": "*" }, { "entitlement_id": "ent_gutter_foil", "type": "variant", "scope": "cpf:openpanel/the-gutter/001#foil", "source": "order_9931" } ]}Errors. RFC-7807 application/problem+json: 401 (no/invalid token), 402
(entitlement required — premium VARIANT edition), 403 (rating gate / COPPA), 404,
429. A 402 carries a purchase_url into the VARIANT store.
Versioning & compatibility. Path-versioned (/v1). Additive changes (new fields,
new endpoints, new optional query params) stay in /v1. Breaking = removing an
endpoint/field, changing a response shape or status semantics, or making a previously
optional param required — requires /v2 with a documented deprecation window.
Contract 3 — Identity & Entitlements contract
Section titled “Contract 3 — Identity & Entitlements contract”Purpose. One identity across reader / buyer / licensee contexts (CANON §4). Defines the account model, entitlement types, and the JWT claims that gate content. Free access is always granted; premium entitlements unlock specific VARIANT editions.
Owner. WS5 Backend (Supabase Auth + RLS). Consumers. All clients, CONTENT API, VARIANT commerce (issues entitlements on purchase), LEDGER (a sale → entitlement + royalty event).
Account model
Section titled “Account model”type Account = { account_id: string; // "acct_7f3" — stable across all surfaces email: string; display_name: string; contexts: ("reader" | "buyer" | "licensee" | "creator")[]; creator_id?: string; // "cr_0xA17" when contexts includes "creator" licensee_id?: string; // "lic_variant" when contexts includes "licensee" is_minor: boolean; // drives COPPA-safe analytics (Contract 7) + rating gates birthyear_band?: "u13" | "13-17" | "adult"; // coarse, never exact DOB created_at: string;};Entitlement types
Section titled “Entitlement types”type Entitlement = | { entitlement_id: string; type: "free"; scope: "*"; } // always present | { entitlement_id: string; type: "variant"; scope: string; // CPF id + "#variant-key" source: string; // order id / grant id granted_at: string; expires_at?: string; } | { entitlement_id: string; type: "license"; scope: string; // licensee rights, e.g. region licensee_id: string; } // ties to LEDGER grants (Contract 4)- free — granted to every account for every
standard-issue. Never revoked. - variant — premium collectible / licensed edition; minted by a VARIANT store purchase or a comp grant. Entitlement check, not hard DRM (CANON §4 DRM stance).
- license — for
licenseecontext (e.g. VARIANT itself, or a sublicensee partner).
Token claims (JWT)
Section titled “Token claims (JWT)”{ "sub": "acct_7f3", "ctx": ["reader", "buyer"], "minor": false, "ent": ["free:*", "variant:cpf:openpanel/the-gutter/001#foil"], // compact entitlement set "iss": "https://auth.openpanel.org", "aud": "openpanel-content-api", "exp": 1781000000, "iat": 1780996400}The ent claim is a denormalized fast-path; the CONTENT API treats /v1/entitlements
as source of truth and re-checks variant/license scopes server-side.
Versioning & compatibility. Claim set is additive; consumers ignore unknown claims.
Breaking = removing/renaming sub/ent/minor, changing scope-string grammar
(type:cpf-id#key), or altering what free covers — would silently mis-gate content or
break COPPA handling everywhere.
Contract 4 — Rights & Royalty Ledger schema
Section titled “Contract 4 — Rights & Royalty Ledger schema”Purpose. The canonical model of works → rights → grants → licensees → royalty terms → royalty events → payouts. It is the bridge between product and business (CANON §2): a sale or license event becomes a royalty owed to the creator and the FOUNDATION, fully auditable for UBIT monitoring.
Owner. WS6 Rights & Royalty platform (Rust/axum service over Postgres).
Consumers. VARIANT commerce (writes royalty events), Creator dashboards (WS8),
FOUNDATION finance (UBIT/audit), CPF rights pointer (Contract 1).
SQL-ish schema
Section titled “SQL-ish schema”-- A creative property the FOUNDATION owns/controls.CREATE TABLE works ( work_id text PRIMARY KEY, -- 'work_gutter' series_id text NOT NULL, -- 'series_the_gutter' title text NOT NULL, -- 'THE GUTTER' owner_entity text NOT NULL, -- 'OPEN PANEL FOUNDATION' created_at timestamptz NOT NULL);
-- A specific right in a work (distribution, merch, adaptation, ...).CREATE TABLE rights ( right_id text PRIMARY KEY, -- 'right_gutter_dist', 'right_gutter_merch' work_id text NOT NULL REFERENCES works(work_id), right_type text NOT NULL, -- 'free_distribution'|'merchandise'|'adaptation'|'collectible' description text);
-- Parties who can hold grants.CREATE TABLE licensees ( licensee_id text PRIMARY KEY, -- 'lic_variant', 'lic_acme_tees' legal_name text NOT NULL, -- 'Variant Licensing Co.' relationship text NOT NULL -- 'inter_entity'|'sublicensee'|'partner');
-- A grant of a right to a licensee (the License Agreement as data — Contract 6).CREATE TABLE grants ( grant_id text PRIMARY KEY, -- 'grant_found_variant_master' right_id text NOT NULL REFERENCES rights(right_id), licensee_id text NOT NULL REFERENCES licensees(licensee_id), grantor_entity text NOT NULL, -- 'OPEN PANEL FOUNDATION' scope jsonb NOT NULL, -- {territory, exclusivity, fields_of_use} term_start date NOT NULL, term_end date, -- null = perpetual agreement_uri text -- pointer to legal doc + Contract 6 record);
-- Royalty terms attached to a grant or a creator agreement (Contract 5).CREATE TABLE royalty_terms ( terms_id text PRIMARY KEY, -- 'rt_variant_to_found', 'rt_creator_okafor' applies_to text NOT NULL, -- grant_id OR creator_agreement_id payer text NOT NULL, -- 'lic_variant' | 'lic_acme_tees' payee text NOT NULL, -- 'OPEN PANEL FOUNDATION' | 'cr_0xA17' basis text NOT NULL, -- 'net_licensing_revenue'|'gross_sales' rate numeric(6,4) NOT NULL, -- 0.1500 = 15% min_guarantee numeric(12,2), priority int NOT NULL DEFAULT 100 -- creator paid before/with foundation per policy);
-- An immutable event: a sale/license that accrues royalties.CREATE TABLE royalty_events ( event_id text PRIMARY KEY, -- 'rev_2026Q2_0001' occurred_at timestamptz NOT NULL, source_type text NOT NULL, -- 'variant_sale'|'merch_license'|'adaptation' source_ref text NOT NULL, -- 'order_9931' (ties to entitlement source, Contract 3) work_id text NOT NULL REFERENCES works(work_id), gross_amount numeric(12,2) NOT NULL, net_amount numeric(12,2) NOT NULL, currency char(3) NOT NULL DEFAULT 'USD');
-- Accrual rows: one royalty owed per (event, terms). Derived, auditable.CREATE TABLE royalty_accruals ( accrual_id text PRIMARY KEY, event_id text NOT NULL REFERENCES royalty_events(event_id), terms_id text NOT NULL REFERENCES royalty_terms(terms_id), payee text NOT NULL, amount numeric(12,2) NOT NULL, -- net_amount * rate (per basis) status text NOT NULL DEFAULT 'accrued' -- 'accrued'|'payable'|'paid');
-- A disbursement batch settling accruals.CREATE TABLE payouts ( payout_id text PRIMARY KEY, -- 'po_2026Q2_found', 'po_2026Q2_okafor' payee text NOT NULL, period text NOT NULL, -- '2026-Q2' amount numeric(12,2) NOT NULL, method text NOT NULL, -- 'ach'|'stripe'|'internal_transfer' paid_at timestamptz, status text NOT NULL DEFAULT 'pending');How a sale becomes royalties (worked example)
Section titled “How a sale becomes royalties (worked example)”A buyer purchases the foil VARIANT edition of THE GUTTER #1 for $20.00 (net $18.00 after fees). VARIANT commerce:
- Mints entitlement
ent_gutter_foil(Contract 3),source = order_9931. - Writes
royalty_events.rev_2026Q2_0001(source_reforder_9931, workwork_gutter, net18.00). - The ledger derives accruals from
royalty_termswhoseapplies_tocovers this work:rt_variant_to_found(payerlic_variant→ payee FOUNDATION, rate 0.15) → accrual $2.70 (passive royalty, §512(b)(2) UBIT-excluded — CANON §2).rt_creator_okafor(payeecr_0xA17, rate 0.50 of net licensing attributable) → accrual $9.00 (CANON §5 author-favorable default).
- Quarterly, accruals roll into
payouts(po_2026Q2_found,po_2026Q2_okafor). The FOUNDATION’s residuals are reinvested per the Reinvestment Policy; never distributed to insiders.
Versioning & compatibility. Schema migrations are forward-only; royalty_events
and royalty_accruals are append-only/immutable (corrections via reversing events,
never edits) for audit integrity. Breaking = changing rate/amount semantics,
mutating historical events, dropping the event→accrual→payout lineage, or breaking the
work_id/source_ref joins — destroys auditability and UBIT defensibility.
Contract 5 — Creator Agreement data model
Section titled “Contract 5 — Creator Agreement data model”Purpose. Machine-readable creator terms that drive the LEDGER (CANON §5). Each
agreement instantiates royalty_terms rows and declares how a creator’s work flows into
the free program and downstream commercial exploitation.
Owner. WS8 Creator platform. Consumers. LEDGER (Contract 4), creator dashboards, FOUNDATION legal/finance, contract signing flow.
type CreatorAgreement = { agreement_id: string; // 'ca_okafor_gutter' creator_id: string; // 'cr_0xA17' work_id: string; // 'work_gutter' (FK → LEDGER works) model: "work_for_hire" | "co_ownership" | "hybrid"; // CANON §5; default 'hybrid' ip_ownership: { foundation_owns: boolean; // true under work_for_hire creator_co_owns: boolean; // true under hybrid/co_ownership foundation_distribution_license: "perpetual_free"; // always, the charitable program foundation_first_commercial_option: boolean; // true under hybrid }; compensation: { upfront_amount: number; // fair page/flat rate, USD royalty_terms_id: string; // 'rt_creator_okafor' → LEDGER royalty_terms royalty_basis: "net_licensing_revenue"; royalty_rate: number; // 0.50 (author-favorable default, operator-set) min_guarantee?: number; }; attribution: { credited_roles: ("writer"|"artist"|"letterer"|"colorist")[] }; term: { start: string; end?: string }; // end null = perpetual signed: { creator_signed_at?: string; foundation_signed_at?: string; envelope_id?: string }; status: "draft" | "active" | "terminated";};Versioning & compatibility. model and compensation are the load-bearing fields;
each agreement is immutable once active (amendments create a new versioned
agreement that supersedes). Breaking = changing how royalty_rate/royalty_basis
map to royalty_terms, or altering ip_ownership semantics — would silently restate
what creators are owed and what the FOUNDATION owns.
Contract 6 — Inter-entity License Agreement (FOUNDATION → VARIANT)
Section titled “Contract 6 — Inter-entity License Agreement (FOUNDATION → VARIANT)”Purpose. The master license from the FOUNDATION to VARIANT, expressed as data that mirrors the legal document (CANON §2 bridge). Encodes the arm’s-length royalty flow that keeps royalties UBIT-excluded. Exactly one canonical master grant; merch/ adaptation sublicenses hang off it.
Owner. WS6 + FOUNDATION legal. Consumers. LEDGER (grants + royalty_terms),
UBIT monitoring, VARIANT commerce (operates under it), governance/audit.
{ "agreement_id": "ila_found_variant_master", "grant_id": "grant_found_variant_master", // → LEDGER grants (Contract 4) "grantor": "OPEN PANEL FOUNDATION", "grantee_licensee_id": "lic_variant", // → LEDGER licensees "grant_scope": { "fields_of_use": ["merchandise", "adaptation", "collectible", "partnership"], "excludes": ["free_distribution"], // free dist stays with the FOUNDATION "works": "all_current_and_future" // or explicit work_id[] }, "territory": "worldwide", "exclusivity": "exclusive", // 'exclusive'|'non_exclusive'|'sole' "royalty": { "royalty_terms_id": "rt_variant_to_found", // → LEDGER royalty_terms "payer": "lic_variant", "payee": "OPEN PANEL FOUNDATION", "basis": "net_licensing_revenue", "rate": 0.15, // arm's-length, independently valued "min_guarantee": 50000.00, "payment_cadence": "quarterly" }, "term": { "start": "2026-07-01", "end": null, "renewal": "auto_annual" }, "arms_length_controls": { "independent_valuation": true, "conflict_recusal_documented": true, "foundation_renders_no_substantial_services": true // protects §512(b)(2) exclusion }, "legal_doc_uri": "docs://legal/inter-entity-license-v1.pdf", // the signed legal text "mirrors_legal_doc": true}Note. This record is a faithful data mirror of the executed legal agreement at
legal_doc_uri; on any conflict, the signed legal document controls. The data form exists so the LEDGER and UBIT monitoring can reason about scope, royalty, and term.
Versioning & compatibility. Amendments are new versioned records superseding the
prior (never in-place edits) so historical royalty accruals remain explainable.
Breaking = changing rate/royalty_terms_id, exclusivity, grant_scope, or the
arms_length_controls flags — any of which has tax (UBIT) and governance consequences
under CANON §2 and must route through the conflict-of-interest process.
Contract 7 — Event / Analytics contract
Section titled “Contract 7 — Event / Analytics contract”Purpose. Privacy-respecting reading & engagement telemetry (CANON §4). No third-party ad trackers; COPPA-safe because comics skew young. Powers product analytics, creator dashboards (aggregate), and never identifies minors.
Owner. WS5 Backend (event pipeline) + WS10 Trust/Safety (privacy review). Consumers. Analytics warehouse, creator dashboards (aggregated only), product.
Event schema
Section titled “Event schema”type AnalyticsEvent = { event: "read_start" | "page_view" | "panel_view" | "complete" | "download"; ts: string; // ISO-8601, server-stamped comic_id: string; // 'cpf:openpanel/the-gutter/001' // Identity is COPPA-gated: anon_id: string; // rotating, per-device pseudonym (NOT account_id for minors) account_id?: string; // present ONLY when account.is_minor === false + consent is_minor: boolean; // when true → NO account_id, NO precise geo, NO retargeting session_id: string; // ephemeral, rotates per session // Event payloads: page_index?: number; // page_view panel_id?: string; // panel_view ('p08-pn2') percent?: number; // complete / progress surface: "web" | "mobile" | "desktop"; locale?: string; // 'en' // Privacy envelope: geo_coarse?: string; // country only ('US'); never for minors, never precise consent: { analytics: boolean; functional: true }; // functional always; analytics opt-in};COPPA-safe rules (binding)
Section titled “COPPA-safe rules (binding)”- If
is_minor(from Contract 3): noaccount_id, nogeo_coarse, no cross-session linking, no retargeting; only aggregate counts may be derived. anon_idandsession_idrotate; raw events expire on a short retention window; long-term storage is aggregated only.downloadevents record that an offline copy was taken — never its contents.
Versioning & compatibility. Event names are a closed enum; adding an event or an
optional field is non-breaking. Breaking = renaming/removing an event or a payload
field, weakening a COPPA rule (e.g. attaching account_id to minor events), or
broadening identity collection — has legal (COPPA) consequences and must pass WS10
privacy review.
Contract governance
Section titled “Contract governance”Change process (semver per contract).
- Propose — RFC PR against this file describing the change, motivation, and blast radius (which consumers break). One contract per RFC.
- Review — owning workstream + every listed consumer workstream sign off; Contracts 4/5/6 additionally require FOUNDATION legal/finance (UBIT exposure); Contract 7 requires WS10 privacy review.
- Version — bump the contract’s semver: patch (clarification), minor (additive, backward-compatible), major (breaking, requires migration plan + deprecation window).
- Land — merge with a changelog entry; breaking changes ship behind a version path
(
/v2,cpf_versionmajor) with a dual-support window, never a silent flip.
Each contract carries its own version line, independent of CANON.md.
Dependency graph (which contract blocks which workstream).
Contract 1 CPF ........... blocks WS1 Reader Core → WS2/WS3/WS4 (all clients)Contract 2 Content API ... blocks WS5 Backend, all clients (depends on 1 + 3)Contract 3 Identity/Ent .. blocks WS5, WS7 Variant commerce (gates 2; feeds 4 via sales)Contract 4 Ledger ........ blocks WS6 Rights/Royalty → WS7 + WS8 (the business backbone)Contract 5 Creator Agmt .. blocks WS8 Creator platform (writes into 4)Contract 6 Inter-entity .. blocks WS7 Variant monetization (instantiates 4; legal-gated)Contract 7 Events ........ blocks analytics across WS2/WS3/WS4/WS5; WS10 privacy-gates itSpine: 1 → 2 → 3 unlock the reading product; 3 → 4 ← 5, 6 unlock monetization; 7 rides alongside all client work. CANON §8 delivery order (WS0→WS1→{WS2,WS3,WS4}; WS5→WS6→WS7/WS8) follows directly from this graph.