Skip to content

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.

  1. 🧮 architecture/user-lifecycle.md measured that conversations.consumer_user_id is NOT NULL, quoted that fact, and then in the same document recommended changing it to ON DELETE SET NULL. A NOT NULL column 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.
  2. ⚠ 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.

#StepWhat it must contain
1existing_architectureWhat exists today, read from migrations and source, never from a portal page
2data_model_and_relationshipsActual tables, columns, nullability, constraints, FKs and their ON DELETE behaviour, triggers, policies
3current_implementationEvery consumer: SQL functions, Edge Functions, packages/data, both apps, tests, gates
4impact_analysisWhat changes meaning, not only what stops compiling
5gapsWhat genuinely does not exist — with the search space stated
6alternativesAt least one rejected option and why the measured evidence rejects it
7proposed_changeThe change itself, expand-contract wherever a contraction is involved
8customer_type_validationAll four, by name: consumer · solo_smb · dealership · enterprise
9production_riskLevel, migration strategy, and rollback or forward-fix
10final_recommendationimplement · 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 formMeans
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.

RuleCatches
P1a missing protocol step, or no front matter at all
P2a 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
P4a customer type left unassessed
P5high/severe risk with no migration strategy, or no rollback and no forward-fix
P6⚠ the teeth — any insufficient_evidence without do_not_implement
P7a 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 seeIt cannot see
a step with no verdictwhether the verdict is true
a citation that does not resolvewhether the cited line says what the claim says
a safe verdict with no measured citationwhether the enumeration of dependents is complete
an undeclared core entitywhether the proposal found the right alternatives
an open question paired with a green recommendationwhether 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 ​

  1. Copy architecture/proposals/_template.md to architecture/proposals/<slug>.md.
  2. Gather evidence first, write the recommendation last. The order is the control: a recommendation written first will find evidence for itself.
  3. Fill every step. Where you did not inspect something, say insufficient_evidence — that passes.
  4. npm run check:arch-proposal.
  5. Register the page in .vitepress/config.mjs (check:portal-nav enforces it).
  6. 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:

RuleGovernsFailure it prevents
Third rule — automate the checkreadiness claims"no blockers" with no enumeration behind it
Fourth rule — no silent design omissionsdesign coveragevalidating the default state and reporting the screen
This protocolarchitecture recommendationsrecommending a change to a schema nobody inspected

All three are the same defect in different clothing: a conclusion asserted where an enumeration was owed.