Commit 05a6a63e authored by Mahmoud Aglan's avatar Mahmoud Aglan

docs: the test is all-clients or one-client, not structure or records

Corrects the rule I wrote two commits ago, which was wrong in the other
direction. I had said rows never belong in a migration. They do, whenever every
client needs them — a lookup table, reference data, a permission the code checks,
a default setting. Seeding those from a migration is the correct pattern, not a
workaround, and add_branches_view_all_permission is the example.

The repository is common ownership: it defines what every client gets. So the
only question worth asking is whether a change is for all clients or for one
specific client. All clients means the repo, and anything touching the database
goes in a migration whether it is schema or data. One client means SSH to that
instance and it never enters the repo — because a migration applies to every
tenant at once and cannot be scoped to one.
Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
parent d20598a2
...@@ -23,31 +23,27 @@ Therefore, non-negotiable: ...@@ -23,31 +23,27 @@ Therefore, non-negotiable:
- Seeders must be **idempotent** and must never contain client-specific content. - Seeders must be **idempotent** and must never contain client-specific content.
- Any migration must be safe against every existing populated client DB. - Any migration must be safe against every existing populated client DB.
### Structure goes in the repo. Records go in the client's database. ### The repository is what every client has
One product, installed once per client, each running the same software over their own Treat this repo as **common ownership**: it defines what *every* client gets. One product,
records. That split decides the mechanism, and the deciding question is **structure or installed once per client. Before touching anything, ask one question — **is this for all
records** — never "does this need to reach everyone". clients, or for one specific client?**
| What | Where it goes | | Scope | Where it goes |
|---|---| |---|---|
| Structure of the software — any code change | Commit, push to `main`. | | **All clients** — code, schema, **and records every client needs**: lookup tables, reference data, permissions, default settings | The repo. Code is committed and pushed; anything touching the database, structural *or* data, is a migration. It runs on every instance on the next deploy. |
| Structure of the database — tables, columns, constraints, indexes, types | A migration, committed with the code. It runs everywhere on the next deploy. | | **One specific client** — a fix, an edit, or data requested for that instance | SSH to the server and apply it to **that client's database only**. It never enters the repo. |
| Records — actual rows, in one client's data | SSH to the server and write them against **that client's** database. Never a migration. |
The tempting mistake is reaching for a migration because a data change "should apply to **Data is not the deciding factor.** Records every client needs belong in a migration
every client". Schema belongs in a migration because it is *structure*; rows belong in a exactly as schema does — seeding a permission or a lookup row from a migration is the
client's database because they are *that client's data* — even when several clients happen correct pattern, not a workaround (see
to need the same correction. A migration that edits rows edits them on every tenant at `2026_09_01_000001_add_branches_view_all_permission.php`). The only thing that must never
once, with no review and no way to do it for one client only. enter the repo is something specific to one client, because a migration applies to every
tenant at once and there is no way to scope it to one.
**We do not deploy.** Code is committed and pushed; the platform builds and ships it. **We do not deploy.** Code is committed and pushed; the platform builds and ships it.
Do not trigger a CapRover build or force a service update. Do not trigger a CapRover build or force a service update.
Grey area, worth asking rather than assuming: rows the code cannot run without — a
permission the new code checks, a default setting it reads. Records by shape, structure by
dependency. Ask first.
## Hard invariants (violating these breaks production) ## Hard invariants (violating these breaks production)
**Money** — all amounts are `bigInteger` **piasters**, never decimal/float. **Money** — all amounts are `bigInteger` **piasters**, never decimal/float.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment