Skip to content

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) and architecture/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).


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.

{
"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.


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=).

GET /v1/catalog?genre=sci-fi&rating=T&cursor=&limit=20
200 → {
"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_gutter
200 → {
"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/001
200 → {
"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 required
200 → { "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 required
200 → {
"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).

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;
};
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 licensee context (e.g. VARIANT itself, or a sublicensee partner).
{
"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).

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

  1. Mints entitlement ent_gutter_foil (Contract 3), source = order_9931.
  2. Writes royalty_events.rev_2026Q2_0001 (source_ref order_9931, work work_gutter, net 18.00).
  3. The ledger derives accruals from royalty_terms whose applies_to covers this work:
    • rt_variant_to_found (payer lic_variant → payee FOUNDATION, rate 0.15) → accrual $2.70 (passive royalty, §512(b)(2) UBIT-excluded — CANON §2).
    • rt_creator_okafor (payee cr_0xA17, rate 0.50 of net licensing attributable) → accrual $9.00 (CANON §5 author-favorable default).
  4. 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.


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.

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
};
  • If is_minor (from Contract 3): no account_id, no geo_coarse, no cross-session linking, no retargeting; only aggregate counts may be derived.
  • anon_id and session_id rotate; raw events expire on a short retention window; long-term storage is aggregated only.
  • download events 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.


Change process (semver per contract).

  1. Propose — RFC PR against this file describing the change, motivation, and blast radius (which consumers break). One contract per RFC.
  2. 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.
  3. Version — bump the contract’s semver: patch (clarification), minor (additive, backward-compatible), major (breaking, requires migration plan + deprecation window).
  4. Land — merge with a changelog entry; breaking changes ship behind a version path (/v2, cpf_version major) 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 it

Spine: 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.