The Substrate Line
The Doctrine commits Helm to per-shop reprogrammability. Without a clear line between what reprograms and what doesn't, "reprogrammable" becomes "anything goes" and the platform dissolves into a thousand forks.
This page draws the line.
What Is Canonical
The substrate. Identical at every shop. Never drifts. Only Kvick modifies it.
- Data model. Table structure, column types, foreign keys, soft-delete semantics, cents-as-integers. The shape of stored truth.
- Audit chain. The eight-table tamper-evident hash chain. Append-only. No exceptions.
- Regulatory layer. Tax codes, PST/GST handling, jurisdictional rules, anything tied to law. Changes are pushed by Kvick when the law changes.
- API surface. The contract between the front-end and the data. New endpoints can be added; existing endpoints can be extended but not broken.
- Security boundary. Authentication, authorization, role gates, the audit-write rules. Single source.
- Identity model. Who counts as staff, customer, vendor; the rules for merging or deleting them.
If a per-shop change would alter any of the above, it does not happen at the shop. It happens at Kvick, in the substrate, and propagates.
What Is Open
The surface. Free to drift per shop.
- Workflows. Ticket lifecycles, approval steps, who-does-what-when.
- UI. Layouts, colors, screen organization, surface affordance positions if a shop insists.
- Modules. Optional features turned on or off; entirely new modules built for one shop.
- Reports. Anything aggregating the canonical data into shop-specific views.
- Integrations. Per-shop connectors to tools the substrate doesn't know about.
- Language. Field labels, button text, what the shop calls things.
Drift here is desired. Drift here is the product.
The Test
When a request arrives — from a walkthrough, a feature ask, a Claude Code session — it sits on one side of the line or the other. The test:
Would a change here, made at one shop, become unsafe or incorrect if not made at every shop?
If yes — substrate. Kvick decides. Propagate.
If no — surface. The shop decides. Ship.
Examples:
| Request | Side | Why |
|---|---|---|
| BC PST rate changes | Substrate | Every shop must apply it or the books are wrong |
| New work-order column for Swicked | Surface | Swicked's column doesn't have to exist anywhere else |
New field on the customers table | Substrate | Changes the data model — propagates by definition |
| New sort order on the customer list | Surface | Pure UI decision |
| Per-shop role permissions config | Surface | Reads the canonical role gates without changing them |
| A new role gate itself | Substrate | Changes the security model |
Why This Line Matters
Without it, two failure modes:
Substrate drift. One shop's "small change" alters the data model. Three years later, that shop's database cannot accept a regulatory update without a custom migration. Multiply by a hundred shops. Kvick becomes a maintenance company servicing a hundred broken forks. Service multiples, not platform multiples.
Surface paralysis. Without explicit permission for drift, every change gets escalated as a substrate decision. The shop waits. The Doctrine is broken. PSaaS becomes regular SaaS with extra steps.
The line protects both directions. It is the load-bearing wall of the platform.
The bike vertical sorted: what's portable, what's tissue
The Substrate Line above is abstract. Applied to a concrete L1 (the bike vertical's code), the test sorts capabilities into three buckets — and the third is the interesting one.
| L1 capability | Classification | Why |
|---|---|---|
| Identity, PINs, role gates | Portable core | Every retail business has staff and access control |
| Audit chain | Portable core | Tamper-evidence is industry-independent |
| Customers (people + orgs) | Portable core | Every business has customers |
| Money / transactions / refunds / tax engine | Portable core | Commercial arithmetic is universal; BC tax is regulatory substrate |
| Inventory / SKU / variant | Portable core (mostly) | Any goods business stocks things; bike attributes are tissue |
| Purchase orders / receiving / vendors | Portable core | Any business that buys stock |
| Bikes-on-record | Vertical tissue | A serial-numbered customer asset is specific to bikes |
| Trade-in flow | Vertical tissue | Used-bike-as-partial-payment is a bike-shop workflow |
| Service kanban (Dropped Off → … → Picked Up) | Vertical tissue, pattern portable | The columns are bike-specific; "intake → work → ready → collected" generalises |
| Rental fleet / bookings | Vertical tissue | Specific to shops that rent |
The third column — patterns that generalise
The interesting cases are the ones where the implementation is tissue but the pattern is portable. The service kanban is the canonical example: the four bike-specific column names mean nothing to a plumber, but "intake → work → ready → collected" is the universal shape of any trade where a customer drops something off, you work on it, you notify them it's ready, and they pick it up.
When you harvest L1 for a new vertical (Scenario 2), the rule is:
- Portable core → harvest verbatim with
concept_keys intact - Vertical tissue (pattern portable) → repurpose the shape under a new name appropriate to the new vertical
- Vertical tissue (pattern doesn't generalise) → retire — rentals for a trade that doesn't rent, trade-in for a trade with no used market
This sort is the spec for what a fork of the bike vertical keeps, renames, and drops. The concept_key of every harvested L1 row is what lets the new vertical's L2 canon line up against it later.
Where The Line Lives
Encoded in this bible. Every substrate table, audit rule, regulatory module, and API contract is documented in its own chapter. Anything not documented as substrate is, by default, surface.
When in doubt, the substrate page wins. When the substrate page is silent, the change is surface.
See also
- The Bespoke Web System — the umbrella anatomy the line lives inside
- Three Scenarios — how the line is applied when building for a new customer vs a new vertical
- Process Library — the three-layer model that makes the line addressable in SQL