The commercial-facts contract widens to carry what legal pages need: jurisdiction, privacy contact, refund terms and one block per app
rank: decision outranks: []
Section titled “rank: decision outranks: []”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
Context
Section titled “Context”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 onchronicle.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.
Decision
Section titled “Decision”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.
Alternatives considered
Section titled “Alternatives considered”- 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; anapps: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.
Consequences
Section titled “Consequences”For code:
contracts/commercial-facts.schema.jsonandcli/src/commercial-facts.tschange in the same PR as this doc;cli/tests/commercial-facts.test.tsand the fixtures undercontracts/fixtures/{valid,invalid}/commercial-facts*.yamlcover every new key.- Page generators read
apps.<id>for product-specific copy and filterprocessorsbyapplies_tofor 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.
What this decision does NOT do
Section titled “What this decision does NOT do”- 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.
References
Section titled “References”docs/decisions/2026-10-24-toll-is-the-estate-payment-rail.md(makesestate/commercial-facts.yamlthe single home; F8)contracts/commercial-facts.schema.json(the contract this widens).petrova/gates.yamlG-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)
Sign-off
Section titled “Sign-off”- 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_toscope rule — flight@arno.host, 2026-10-30 (jzero.run T1)