Skip to content

The commercial-facts contract widens to carry what legal pages need: jurisdiction, privacy contact, refund terms and one block per app


The commercial-facts contract widens to carry what legal pages need: jurisdiction, privacy contact, refund terms and one block per app

Section titled “The commercial-facts contract widens to carry what legal pages need: jurisdiction, privacy contact, refund terms and one block per app”

Date: 2026-10-29 Status: open Supersedes: none Superseded-by: none — current MR-compliance: MR-4, MR-7, MR-9

docs/decisions/2026-10-24-toll-is-the-estate-payment-rail.md makes estate/commercial-facts.yaml the single home for the estate’s legal and commercial facts, and says apps point at it instead of carrying their own legal copy. The contract that file must satisfy, contracts/commercial-facts.schema.json, holds eight facts: legal name, registered address, company number, VAT status, merchant of record, processors, a one-line refund policy and a support email.

The next consumers are generated terms, privacy, refund and subprocessors pages for every app, traceo and chronicle first. Those pages cannot be written from the eight facts. Read on 2026-10-29, they also need:

  • Terms: the governing law and the courts with jurisdiction.
  • Privacy: a privacy contact, the data controller (the legal entity unless stated otherwise), an optional DPO and an optional representative, and, per app, what data is collected, why, on what lawful basis, how long deleted content and accounts are kept, and the minimum age.
  • Refunds: the rule behind the sentence — window in days, pro-rata or not, and what happens to annual plans (traceo sells one, decision 0117 in traceo-cat/traceo-mcp-server).
  • Subprocessors: where each processor is, and which apps it serves. traceo sends its own mail through Resend and offers GitHub and Google sign-in; chronicle signs in through the estate’s Airlock. One estate-wide list without a scope would tell chronicle’s users about traceo’s providers.
  • Per app: the product name and primary domain (traceo now on traceo.cloud, chronicle on chronicle.cat), whether there is a contact page and where, and an optional support-email override.

No estate/commercial-facts.yaml exists yet (gate G-1-2 is still open), so no real document is invalidated by widening the contract now.

contracts/commercial-facts.schema.json gains five required keys — governing_law, courts, privacy (with a required contact_email), refund_terms (window_days, pro_rata, annual_plans) and apps (at least one block, keyed by a lowercase app id) — and three optional ones: data_controller (renderers fall back to legal_name), trading_names, and privacy.dpo / privacy.representative. Each processor may carry an optional location and an optional applies_to list of app ids; a processor with no applies_to applies to every app. Each app block requires product_name, domain (a bare hostname), contact_page (and contact_url when it is true), data_categories (each with category, purpose and an optional basis from contract / legitimate-interests / consent / legal-obligation), retention (deleted_content_days, deleted_account_days) and minimum_age (13–21); support_email is an optional override. validateCommercialFacts additionally refuses an applies_to id that names no declared app. The existing eight keys are unchanged.

  • Keep the eight facts and let each app’s legal page add its own — that is the per-app legal copy the 2026-10-24 decision (finding F8) set out to end.
  • Make the new keys optional — G-1-2 asserts the facts are complete; a file that validates but cannot render a privacy page would discharge the gate without meeting it.
  • A second file per app (estate/apps/<id>.yaml) — splits one operator ruling across several files and several validators for no gain while the estate has two apps; an apps: map keeps one source and one check.
  • A closed enum of data categories — every new app would need a contract change; free-text category + purpose, with only the lawful basis enumerated, keeps the contract stable.

For code:

  • contracts/commercial-facts.schema.json and cli/src/commercial-facts.ts change in the same PR as this doc; cli/tests/commercial-facts.test.ts and the fixtures under contracts/fixtures/{valid,invalid}/commercial-facts*.yaml cover every new key.
  • Page generators read apps.<id> for product-specific copy and filter processors by applies_to for each app’s subprocessors page.

For docs:

  • None beyond this record.

For in-flight phases:

  • RS-DL-004 gate G-1-2 (.petrova/gates.yaml) is still discharged by the operator’s facts finding; that finding now has to carry the wider fact set.

For invariants:

  • None. No MR is added, changed or repealed.
  • It does not fill any fact, choose a governing law or name a processor; the operator does that through the facts wizard, and it lands as its own PR.
  • It does not write or generate any legal page.
  • It does not decide whether a GDPR representative or DPO is needed.
  • docs/decisions/2026-10-24-toll-is-the-estate-payment-rail.md (makes estate/commercial-facts.yaml the single home; F8)
  • contracts/commercial-facts.schema.json (the contract this widens)
  • .petrova/gates.yaml G-1-2 (the gate the operator’s facts discharge)
  • traceo-cat/traceo-mcp-server:web/apps/client/src/lib/email/send.ts (traceo mail goes through Resend)
  • traceo-cat/traceo-mcp-server:web/apps/client/src/lib/auth.ts:136 (traceo offers GitHub and Google sign-in)
  • chronicle-cat/app:src/lib/airlockConfig.ts (chronicle signs in through Airlock)
  • Subagent: Claude Opus 5.5, 2026-10-29 (drafted with the schema and validator change in the same PR; read at petrova-codes b5ff6863)
  • Human countersign — the five new required keys, the per-app block shape and the applies_to scope rule — flight@arno.host, 2026-10-30 (jzero.run T1)