Appearance
Architecture change protocol
Mandatory. 📘 Owner instruction, 2026-08-24: "Going forward, whenever we propose an architectural change, data-model change, relationship change, or lifecycle change, the recommendation must first be backed by an actual assessment of the available architecture and implementation… A recommendation should not be made simply because it is a common SaaS pattern or theoretically sounds correct."
⚠ This exists because of two specific defects, and naming them is the calibration
On 2026-08-23/24 four architectural recommendations touching core entities were put to the owner. Some were measured against the live migrations. Some were not, and were written in the same confident register as the ones that were — which is the actual harm, because a reader cannot tell them apart.
- 🧮
architecture/user-lifecycle.mdmeasured thatconversations.consumer_user_idisNOT NULL, quoted that fact, and then in the same document recommended changing it toON DELETE SET NULL. ANOT NULLcolumn cannot be set null. The recommendation was impossible to execute and the evidence contradicting it was on the same page. Rule L9 generalised the same instruction across 11 FKs without checking the nullability of any of them. - ⚠ A touchpoint QR mechanism — a re-pointable 302 redirect falling back to the workspace card — was described as a refinement of existing behaviour. No redirect layer was ever inspected. It was a plausible SaaS pattern presented as an architectural finding.
🔎 The generalisable defect: a recommendation was asserted where an assessment was owed. That is CLAUDE.md's third rule (QRS-626) applied to architecture rather than to readiness — "a conclusion was asserted where an enumeration was owed."
1 · The ten steps
Every proposal answers all ten, in order, and each one carries a verdict and at least one citation.
| # | Step | What it must contain |
|---|---|---|
| 1 | existing_architecture | What exists today, read from migrations and source, never from a portal page |
| 2 | data_model_and_relationships | Actual tables, columns, nullability, constraints, FKs and their ON DELETE behaviour, triggers, policies |
| 3 | current_implementation | Every consumer: SQL functions, Edge Functions, packages/data, both apps, tests, gates |
| 4 | impact_analysis | What changes meaning, not only what stops compiling |
| 5 | gaps | What genuinely does not exist — with the search space stated |
| 6 | alternatives | At least one rejected option and why the measured evidence rejects it |
| 7 | proposed_change | The change itself, expand-contract wherever a contraction is involved |
| 8 | customer_type_validation | All four, by name: consumer · solo_smb · dealership · enterprise |
| 9 | production_risk | Level, migration strategy, and rollback or forward-fix |
| 10 | final_recommendation | implement · implement_with_changes · do_not_implement |
2 · The verdict that must always be available
"Architecture evidence is insufficient to make this change recommendation yet."
📘 The owner's own words, and the protocol makes them the default rather than the fallback. A step whose evidence was not gathered is insufficient_evidence, and it names what must be inspected first.
⚠ And it BINDS the recommendation. One insufficient_evidence anywhere — in a step or in a customer type — forces final_recommendation.action: do_not_implement. 🔎 You cannot carry an open question and a green recommendation in the same document, which is precisely what happened with the NOT NULL contradiction.
3 · Evidence classes, and why the labels matter
| Citation form | Means |
|---|---|
supabase/migrations/x.sql:412 | 🧮 measured — a specific line. The path and the line must exist |
packages/data/src/foo/service.ts | 🧮 measured — a file that must exist |
$ grep -c "..." supabase/migrations/*.sql | 🧮 measured — a command that was run |
inferred: <reasoning> | 🔎 NOT measured. Legitimate, but it must say so |
⚠ A safe verdict may not rest on inferred: alone. That combination is the defect this protocol exists to stop, so the gate refuses it. Refusing a change on reasoning alone is always allowed — you never need a measurement to decline.
4 · The gate
npm run check:arch-proposal (tools/check-architecture-proposal.js), pre-commit and CI. Mutation-tested in both directions, 22 cases (tools/check-architecture-proposal.test.mjs), per QRS-013.
| Rule | Catches |
|---|---|
| P1 | a missing protocol step, or no front matter at all |
| P2 | a step with no verdict — "not checked" and "fine" must never look the same |
| P3 | ⚠ a citation that does not resolve — a path that does not exist, or a file:line whose line is past the end of the file |
| P4 | a customer type left unassessed |
| P5 | high/severe risk with no migration strategy, or no rollback and no forward-fix |
| P6 | ⚠ the teeth — any insufficient_evidence without do_not_implement |
| P7 | a core entity discussed in the body but undeclared in affects_core_entities |
| P8 | ⚠ a safe verdict whose evidence is inferred: only |
4.1 · ⚠ What the gate CANNOT do, stated so a green run is never over-read
No script can read a migration and judge an architectural argument. Claiming otherwise would repeat QRS-246 exactly — a standard documented for months and implemented by nothing.
| It can see | It cannot see |
|---|---|
| a step with no verdict | whether the verdict is true |
| a citation that does not resolve | whether the cited line says what the claim says |
a safe verdict with no measured citation | whether the enumeration of dependents is complete |
| an undeclared core entity | whether the proposal found the right alternatives |
| an open question paired with a green recommendation | whether the reasoning is sound |
🔎 The enumeration remains human work. The gate's contribution is that an unbacked recommendation becomes loud instead of indistinguishable from a backed one — which is the whole of the requirement.
5 · Core entities
A change to any of these can reach a customer that already exists, so it may never be assessed as one vertical's concern:
users · workspaces · workspace_members · organizations · setu_cards · feature_grants · orders · conversations · messages · audit_log
⚠ The list is incident-driven, like check:naming's root list: each name is here because a proposal touching it was put forward without cross-tenant validation. Add to it when that happens again.
6 · How to raise a proposal
- Copy
architecture/proposals/_template.mdtoarchitecture/proposals/<slug>.md. - Gather evidence first, write the recommendation last. The order is the control: a recommendation written first will find evidence for itself.
- Fill every step. Where you did not inspect something, say
insufficient_evidence— that passes. npm run check:arch-proposal.- Register the page in
.vitepress/config.mjs(check:portal-navenforces it). - Only then present it as an implementation decision.
🔎 Why a document rather than a conversation
A recommendation made in conversation has no artifact to gate, and this repo's measured completion rate for things that live only in a conversation is poor (QRS-180). A file can be checked, diffed, and re-read six months later by someone deciding whether the reasoning still holds.
7 · Where this sits among the existing rules
It is the architecture-side sibling of CLAUDE.md's third and fourth rules, and the distinction is worth keeping straight:
| Rule | Governs | Failure it prevents |
|---|---|---|
| Third rule — automate the check | readiness claims | "no blockers" with no enumeration behind it |
| Fourth rule — no silent design omissions | design coverage | validating the default state and reporting the screen |
| This protocol | architecture recommendations | recommending a change to a schema nobody inspected |
All three are the same defect in different clothing: a conclusion asserted where an enumeration was owed.