packages/twenty-apps/internal/twenty-partners/src/skills/twenty-partner-design-doc/design-doc-doctrine.md
Portable doctrine. This file is tool-agnostic. It defines what a Twenty partner design doc is, its structure, the rules, and how to verify it. Two consumers use it: the Claude Code skill twenty-partner-design-doc (see SKILL.md in this folder), and, in a later phase, the content of a Twenty defineSkill driving an in-product agent. Keep it free of any one tool's mechanics (no file paths, no tool names).
Produce a design doc that translates a qualified lead's needs into Twenty terms (data model, permissions, integrations, hosting, plus whatever else the client named) so an implementation partner can scope and quote the work.
Core principle: identify all the work with no blindspots, ground every claim in the source or in live Twenty docs, and present options rather than prescribe. The partner quotes off this doc, so a confident-but-wrong capability claim or a hidden requirement is the worst failure.
Stay on the client's outcome. Every line must change what the partner builds or what the client receives. The doc is a design, not a meeting record: a requirement's backstory (why the vendor or a third party does or doesn't satisfy it) is not a build input. Capture the consequence, cut the backstory.
The doc is partner-facing and may be forwarded verbatim. It is a suggestion to make scoping easier, not a spec that constrains how the partner builds.
Header: a compact table, not a stack of bold lines. Four fields only:
| Field | Value |
|---|---|
| Lead | name (one-line description of who they are) |
| Date | YYYY-MM-DD |
| Author | Twenty (partnerships) |
| Status | one short line (e.g. "Draft for partner review") |
Keep the header to those four fields. The Status line stays short: a load-bearing caveat (e.g. "data model is an inference pending discovery") goes in the "What this is" callout, not in the header cell. Do not list source materials, internal timelines, or who-promised-what: not load-bearing, not customer-safe (the doc is forwarded verbatim).
After the table, one "What this is" callout framing the doc as a partner-scoping suggestion across the full surface, not an MVP.
Flag legend (emoji + short text label, used together so they're scannable and unambiguous):
🔮 inf. modelling inference (yours; the partner should confirm with the client). Used inline, often, unbolded.Use these four pairings only. Don't invent new flags, swap the emoji (🟥 / 🚩 / 🚨 / ✅), or drop the text label and use the emoji alone.
Number sequentially in the final doc, with no gaps:
Object | Std/Custom | Source | Represents | Key fields | Core relations. The Source column is client (object/concept grounded in client speech) or inf. (you coined it). Inline, tag inferred field names and relations 🔮 inf.. Spell out SELECT option sets. Model the relationships, not just the fields: who introduced/sourced a record (e.g. an ambassador to Opportunity sourcedBy link), parent/child, ownership carry as much scoping signal as the attributes. State product constraints only when they change the build or quote, and inline (no formula fields → reporting ratios need a logic-function-maintained stored field; custom objects auto-get attachments/notes/tasks/timeline → relationship tracking is free). Where a customer term collides with a Twenty term (their "partner" = a donor), note the mapping inline as a small "term collisions" bullet list above the table; do not add a glossary section for it.If the client didn't name the topic, omit the section entirely. Do not include a section just to say "X was not named" — that's filler. Unknowns belong in Open questions, not in their own section.
Surface | Shows | Audience, one row per surface (an object or a filtered subset) the workspace should expose day-to-day. Do not add a Type column (table / kanban / page layout): the view type is the partner's call, not a scoping decision. If the client explicitly named pipelines or layouts, capture them in the Shows cell. Keep the table to the surfaces that genuinely matter; supplement with a one-line follow-up bullet only when an open question about a cross-cutting surface needs flagging (e.g. an unscoped admin view).Number whatever you include sequentially. A doc with Context, Data model, Integrations (Gmail named), Permissions, Hosting, Phasing, Open questions, References is §1 through §8. Don't leave gaps.
Scale each section to its content. Coverage of surface area matters more than depth per item.
🔮 inf. tag inline = your modelling guess, every time it appears. The Data-model table carries a Source column (client / inf.). A partner skimming the doc must be able to see at a glance which lines they need to confirm with the client.§N reference must be a markdown anchor link: [§N](#n-section-slug). The slug follows GitHub Flavored Markdown auto-anchoring (lowercase; spaces → hyphens; punctuation dropped; & removed leaving a double hyphen). A bare §N is unreadable to a partner skimming the doc, who just sees numbers with no way to jump.[§N](...)) rather than restate. Open questions and References are deliberate roll-ups: there, give the pointer and the decision the item gates, not a re-explanation of the body. Repetition is the main source of bloat, and two copies of a claim drift out of sync.LOGIC_FUNCTION_TYPE / LAMBDA / region/role/key settings do not belong in a scoping doc. Deep mechanics inflate length and date fast without changing the quote.🔮 inf.; never grow scope. The partner quotes off this, so an invented field or requirement inflates the quote or sets a false expectation. If the concept is from the source but the field name is yours, that is an inference: tag it. Values lifted from the source (the customer's own category list becoming SELECT options) are grounded; only names/fields you coin are inferences.docs.twenty.com URLs only. (Customer quotes that contain "we/our" are fine: they are quotes.)~ for "approximately": GitHub markdown pairs ~...~ into strikethrough. Write "around" / "about".🔮 inf., **❓ open**, **⚠️ heavy**, **🛑 blocker**). Don't swap the emoji or drop the text label.[§N](#n-section-slug)), never bare §N.**❓ open**, never as fact.Any statement of the form "Twenty can / can't / has / lacks X" that changes the partner's quote MUST be verified live before it is stated as fact. Model training is stale on a fast-moving product; the worst failure is a confident, authoritative-sounding claim that is wrong.
Source hierarchy (what to trust, in order):
docs.twenty.com): primary truth. For the highest-stakes claims, read the page's primary text rather than trust a summary.Right doc layer (the trap): capabilities live in two layers, so check the right one.
Always verify (the load-bearing checklist), live, every run:
Fallback chain:
References appendix format: a Claim (§) | Verified against table, then an explicit "Could not be confirmed in public docs (check with Twenty directly):" list. Unverified items stay ❓ open in the body too, never silently promoted to fact. Cite the human-readable (non-.md) URL here: the .md twin is for your fetch, not for the partner (a .md link renders as raw markdown in a browser).
Where to verify (Twenty doc map): docs base = https://docs.twenty.com/. Fetch <path>.md for the clean markdown twin (the form to prefer). Paths:
user-guide/dashboards/overview · user-guide/dashboards/capabilities/widgets · user-guide/permissions-access/how-tos/permissions-faq · user-guide/data-model/overview · user-guide/data-model/capabilities/fields · user-guide/data-migration/how-tos/export-your-datadevelopers/extend/apps/data/objects · developers/extend/apps/data/extending-objects · developers/extend/apps/data/relations · developers/extend/apps/logic/logic-functions · developers/extend/apps/logic/connections · developers/extend/apps/layout/views · developers/extend/apps/layout/page-layouts · developers/extend/apps/config/rolesdevelopers/self-host/self-hosthttps://twenty.com/pricing (marketing page, no .md; fetch as HTML).
If a .md 404s, drop the suffix or re-derive from the docs index: the map can go stale.| Mistake | Reality / fix |
|---|---|
| "Twenty isn't a BI tool" / "can't do row-level" | Stale training. Twenty has Dashboards; row-level is on the Organization plan. Verify live. |
| Checked only the app-SDK doc for a product capability | Row-level lives in the product/pricing layer. Verify the right layer. |
| Added fields not in the source | Scope growth, wrong quote. Ground every field; tag inferences 🔮 inf.. |
| Made a human actor (e.g. ambassador) its own object by default | A human is a Person + role flag first; an object only if it needs its own pipeline/reporting. |
| Flagged an automation as "Workflow" | Prescribes the build, penalizes app-builders. Present both. |
| Flagged a limit with no fix | Dead-end flag. Pair every problem with a path. |
Spelled out runtime/env-var mechanics (LOGIC_FUNCTION_TYPE / LAMBDA, region/role/key, Docker version, OAuth flavour, CI/CD workflow detail) | Wrong altitude. Name the decision + its cost; defer the mechanics to the technical phase. |
| Same point restated across sections | State it once in its home section; cross-reference with a functional [§N](...) link. |
| Padded prose / feature-tour narration | Maximum signal per word. State the content; cut the scene-setting. |
| Added a domain-language map / glossary section | Removed. Note a genuine term collision inline in Data model; no standalone glossary. |
| First-person "not ours" in a partner doc | Say "Twenty." The doc is forwarded verbatim. |
| Local file paths in the output | Mean nothing to a partner. Cite docs.twenty.com only. |
| Data-model section as prose; fields modelled but not relationships | Use a per-object field table; model the links, not just attributes. |
Hard-wrapped mid-sentence / used ~ / used an em dash | Broken lines / accidental strikethrough / banned dash. One line per paragraph; "around" not ~; colon or comma, never an em dash. |
| Used an emoji other than the four canonical (🟥 / 🚩 / 🚨 / ✅), or used the emoji without the text label | Stick to 🔮 inf., **❓ open**, **⚠️ heavy**, **🛑 blocker**. The text label disambiguates. |
| Section is mostly paragraphs | Default to bullets and tables; prose only when a nuance can't fit a list. Exception: Open questions stays a numbered list. |
| Included build / runtime / SDK mechanics that don't move the quote | Wrong altitude. Business decisions, scope consequences, and References only; mechanics belong in the later technical phase. |
| Data-model table missing a Source column | Partner can't see at a glance which rows the client confirmed vs which are your inferences. Add client / inf.. |
Cross-references are bare §N instead of functional links | The partner sees disconnected numbers and can't navigate. Use [§N](#n-section-slug) everywhere. |
| Included a section that just says "X was not named" / "no automations named" / a "left out on purpose" list | Cut the whole section / bullet. Unknowns go in Open questions; the doc speaks only about grounded content. |
| Renumbered with gaps (e.g. cut Reporting but kept §5-§11 numbering as §5, §6, §8) | Renumber sequentially, no gaps. A partner doesn't know which sections were omitted. |
| Views section prescribed the view type (table / kanban / page layout) | The partner picks the view type. List what to surface (objects, subsets, who they're for), not how. |
| Views section rendered as a bullet list | Render as a Surface | Shows | Audience table; supplement with a follow-up bullet only for a cross-cutting open question. |
| Wrote up the vendor's corporate status / legal domicile / ownership / sign-off | Backstory, not a build input. Record only the consequence: requirement → the client's path; gate the open item as ❓ open in Open questions. |
| Filled a thin brief with an elaborate inferred model | Over-reach scares a hesitant buyer with unrequested scope. Model the few certain objects; flag the rest as ❓ open; say it's a minimal sketch. |
| Added an Integrations section with connectors the customer never named | Integrations is conditional, not default. Omit it absent a named system; at most flag one ❓ open. |
| Kept buyer characterisations / competitor asides / internal timelines | Not customer-safe. State needs neutrally; cut the rest. |
| Listed source materials / who-promised-what in the header, or stacked it as bold lines | Header is a four-field table. Not customer-safe content goes nowhere. |