Skip to content

Core platform capability vs industry-specific capability ​

📘 Owner principle, 2026-08-24: "Google Business Management and similar capabilities should be evaluated as core platform capabilities where appropriate, with the dealership implementation becoming one of their use cases — not something permanently locked to the dealership product." With the instruction to push back hard if the categorisation is wrong.

✅ The verdict: correct, and it should go FURTHER than stated

The principle is right, and the architecture already agrees with it — ADR-0009's "a new vertical is config, not code" and ADR-0020's closed primitive set are the same idea expressed as schema.

🔎 But the two-bucket framing is too coarse, and one of the buckets is a hazard. There are three tiers, not two, and "industry-specific capability" should be a bucket we expect to find almost empty — because a capability labelled dealership-specific gets implemented as if (industry === 'car_sales'), which is precisely the conditional sprawl the whole model exists to prevent (ADR-0021 D4).

⚠⚠ And the rule that was supposed to prevent that is NOT ENFORCED. 🧮 Measured 2026-08-24: tooling/eslint-config/guardrails.js carries ten restricted-import rules — colours, PressableScale, tier boundaries, package purity, the card-manifest seam — and not one of them mentions industry, archetype or plan. CLAUDE.md states D4 is "lint-gated". It is not. So the owner's instinct to worry about fragmentation is correct for a measured reason, not a speculative one (QRS-879).

1 · Three tiers, and only one of them carries cost ​

TierWhat it isCost of adding one
1 · PrimitiveThe closed set of eleven. A new one requires a written justification that it serves ≥3 industries — the governing gate of the whole architectureHigh, and permanent
2 · CapabilityA workflow composed from primitives. Google Business, reviews, messaging, campaigns, leads, bookingsModerate, and paid once for every vertical
3 · Industry configurationWhich primitives are on, what things are called, what the defaults and journey are, what the compliance profile forbidsZero code. A row and some i18n

🔎 The sharpened rule: the "industry-specific capability" bucket earns the SAME gate as a new primitive, inverted. A new primitive must prove ≥3 industries want it. A capability claimed as industry-specific must prove no other industry wants it. If that cannot be shown, it is a core capability with vertical configuration, and calling it otherwise is how fragmentation starts.

2 · The dealership list, classified ​

🧮 Tested item by item against the primitive set rather than by intuition.

CandidateVerdictReasoning
Google Business Management🟢 CORELocation + an external integration. ⚠ A Ganapati stall or a boutique wants this MORE than a dealership does, because it may be their only web presence at all — a dealership already has OEM microsites. Locking it to the dealership product would withhold it from the segment with the highest need
Reputation / reviews / replies🟢 CORESame substrate. Every local business is reviewed
Messaging — native chat + WhatsApp🟢 CORE🧮 The chat schema already shipped (2026-08-11) and contains nothing dealership-shaped
Campaigns / marketing🟢 CORE, with an org-scoped extensionADR-0025 already models central-publish + subtree-target. The fan-out is an enterprise concern, not a vertical one
Visitor register🟢 CORE (Party + a visit event)Every walk-in business: clinic, salon, showroom, boutique
Leads + follow-up SLA🟢 CORE (Party + Schedule)Every enquiry-driven business. The SLA thresholds are industry config
Bookings / "test drives"🟢 CORE (Schedule + Resource)⚠ "Test drive" is VOCABULARY, not a feature. A salon books a stylist, a gym books a court, a dealership books a vehicle. One primitive pair, three words
Discount-approval governance🟢 CORE (Ledger + an approval queue)Generic governance. Any business with negotiable price
Targets and performance🟢 CORE (subtree-scoped)Any business with staff and a number
Service history / odometer reminders🟡 CORE, but a PRIMITIVE EXTENSION🔎 Recurrence today is date-based only. Odometer is a usage-based trigger — and usage-based recurrence serves vehicles (km), machinery (hours), printers (pages), gym equipment (cycles). ≥3 industries, so it earns its place as an extension of Recurrence rather than as a dealership feature
F&I attach workflow🟡 CORE workflow, vertical COMPLIANCE"Attach a third-party product to a sale" is generic (extended warranty on a phone). The licensing — MISP appointment, RBI Digital Lending Directions — is a compliance_profile, not a feature
Dealer-group hierarchy🟢 COREThe workspace tree already exists (ADR-0022/0023)
⚠ OEM-facing view restriction🔴 GENUINELY INDUSTRY-SPECIFICThe one item that survives the test, and it is not a feature — it is a legal constraint. The CCI order restricts what an OEM may see of a dealer's data. That is car-industry law, and it belongs in compliance_profile on the industry row

🔎 So exactly one item is genuinely industry-specific, and it is a compliance rule rather than a capability. That is the strongest available form of the owner's principle: the dealership is not asking for dealership features. It is asking for core capabilities it happens to need first.

3 · Three ways this classification goes wrong if applied naively ​

3.1 · ⚠ Scope is not industry, and conflating them hardcodes enterprise to a vertical ​

Google Business for one outlet and Google Business across eight outlets with a group view are the same capability at two scopes. feature_grants already has eight of them (platform · archetype · industry · plan · workspace_group · workspace · workspace_member · user).

Calling the multi-outlet version "the dealership one" would bind an enterprise capability to a single vertical — and the next multi-location customer (a salon chain, a clinic group) would need it rebuilt. Ask "at what scope?" before asking "for which industry?"

3.2 · ⚠ Vocabulary is not a feature ​

Test drive · repair order · trade-in · showroom are words. They belong in @qrsetu/i18n and industry configuration. 🔎 If a word ever drives a code branch, the model has already failed — that is the moment to go looking for the missing primitive instead.

3.3 · ⚠⚠ And the enforcement does not exist, which is why this page cannot be the whole answer ​

🧮 Measured: no guardrail mentions industry, archetype or plan. CLAUDE.md's claim that ADR-0021 D4 is lint-gated is false. A principle with no gate decays — this repo's most-repeated lesson, with QRS-013 (a green no-op lint), QRS-246 (Sonar documented for months, implemented by nothing) and QRS-327 (deno lint never wired) as its three precedents. Recommended: land the rule (QRS-879).

4 · ⚠ But the rule as DOCUMENTED is too broad, and the code proves it ​

CLAUDE.md says: "Never branch on archetype, industry or plan in app code." 🧮 Measured, there is exactly one archetype branch in the whole tree, and it is correct:

ts
// packages/domain/src/consumer/cta.ts
if (vendor.archetype === 'goods') return 'order';
if (vendor.archetype === 'time') return 'book';
// Expertise has nothing to buy at a price, so the conversation IS the transaction.
return null;

🔎 Archetype → action verb IS the definition of archetype (Goods = a stocked item → order; Time = a slot → book; Expertise = an enquiry). Expressing that as a feature grant would model one fact twice, which is the QRS-249 duplicate-source-of-truth class. It is a pure exhaustive mapping in packages/domain, not a conditional in a screen.

So the enforceable rule is narrower than the documented one:

  • INDUSTRY and PLAN — never branch on them, anywhere. Ask for a feature.
  • ARCHETYPE — only inside packages/domain, only as an exhaustive mapping, never in a screen or a service.

⚠ And that file has a latent defect worth fixing while we are here: the mapping is NOT exhaustive. The trailing return null absorbs any archetype the union gains later, so a fourth archetype would silently get no call-to-action rather than failing to compile. A switch with an exhaustiveness assertion turns that into a build error (QRS-880).

5 · Substrate reality check, so "core" is not read as "close" ​

🧮 Measured across all 69 live migrations — none of the tables Google Business Management needs exists:

TableState
external_accounts🔴 missing
reviews · review_replies🔴 missing
integrations · oauth_tokens🔴 missing

🔎 Classifying a capability as core says where it BELONGS, not that it is near. Google Business is core and unbuilt, and the honest sequencing is that it is downstream of the Location primitive being finished and of a token-storage decision nobody has taken.

6 · How to use this page ​

  1. Before designing any capability, place it in a tier. If it lands in tier 3, it is config and needs no code.
  2. If it looks industry-specific, run the inverted gate: name three industries that would not want it. Usually you cannot, and it is core.
  3. Ask "at what scope?" before "for which industry?" (§3.1).
  4. Never let vocabulary reach a branch (§3.2).
  5. A core-entity or relationship change goes through the architecture change protocol first.

Not verified ​

❓ The primitive-composition claims for each row in §2 are reasoned from the ADR-0020 primitive set, not measured against tables — five of the seven primitives this vertical declares have no tables at all, so most of that column is design intent. ❓ The CCI position in §2 is from secondary reading and is not legal advice.