@@ -72,12 +72,13 @@ Three scopes cover the use cases (`public` for the pre-login shell):
7272 joined to ` app_config_allow_list ` only for each fragment's ` rank ` — an
7373 indexed ` (config_name, scope_type) ` join. No permission or policy is
7474 evaluated at read time.
75- - ** Allow-list = the write gate for every fragment .** ` app_config_allow_list `
75+ - ** Allow-list = the write gate and the merge order .** ` app_config_allow_list `
7676 holds ** one record per ` (config_name, scope_type) ` ** ; a fragment at
77- that scope may be created/updated/purged ** only if** the record exists
78- — through the admin mutations and the regular ones alike. What sets
79- admins apart is that they alone manage the allow-list (and the
80- ` app_config_definitions ` ) itself. It governs ** writes only** — never reads.
77+ that scope may be created ** only if** the record exists — through the
78+ admin mutations and the regular ones alike. Fragments reference their
79+ entry by FK (` ON DELETE CASCADE ` ), so removing an entry removes its
80+ fragments. What sets admins apart is that they alone manage the
81+ allow-list (and the ` app_config_definitions ` ) itself.
8182- ** ` rank ` lives on the allow-list entry.** Merge priority is an
8283 admin-owned policy: it sits on the (admin-managed) allow-list entry,
8384 not on the fragment — a fragment owner editing their own fragment can
@@ -110,12 +111,13 @@ Keyed by the natural composite `(scope_type, scope_id, config_name)`
110111- ` scope_type ` — ` public | domain | user ` .
111112- ` scope_id ` — the scope's identifier (see convention below).
112113- ` config_name ` — FK → ` app_config_definitions.config_name ` .
114+ - ` (config_name, scope_type) ` — composite FK →
115+ ` app_config_allow_list ` with ` ON DELETE CASCADE ` : a fragment exists
116+ only while its allow-list entry does, and carries no rank of its own —
117+ its merge priority is the entry's ` rank ` (see §2).
113118- ` config ` — schema-less JSON payload.
114119- ` created_at ` / ` updated_at ` .
115120
116- The fragment carries no rank of its own — its merge priority is its
117- allow-list entry's ` rank ` (see §2).
118-
119121### ` app_config_allow_list ` — the per-` (config_name, scope_type) ` write gate
120122
121123One row per ` (config_name, scope_type) ` (unique) — a normalized,
@@ -135,7 +137,8 @@ advance.
135137
136138A row's ** presence** is the write grant, and its ` rank ` is the merge
137139order of its fragments. It gates ** both** write paths; admins, unlike
138- users, may also create/update/purge the allow-list rows themselves.
140+ users, may also create/purge the allow-list rows themselves — and
141+ purging one cascades to its fragments.
139142
140143### Scope-ID convention
141144
@@ -147,20 +150,22 @@ users, may also create/update/purge the allow-list rows themselves.
147150
148151### Integrity
149152
150- - ** Every** fragment write (admin or regular) requires (a) a registered
153+ - ** Every** fragment create (admin or regular) requires (a) a registered
151154 ` config_name ` (FK to ` app_config_definitions ` ) and (b) an
152155 ` app_config_allow_list ` row for the write's `(config_name,
153- scope_type)`. The service layer rejects per-row when either is missing.
156+ scope_type)` — enforced both by the write gate (domain error) and by
157+ the composite FK. Updates and purges of an existing fragment need no
158+ gate: the FK guarantees the entry exists while the fragment does.
154159- A regular (non-admin) mutation is further restricted to the caller's
155160 own ` user ` row; admin mutations may target any scope (still gated by
156161 the allow-list) and are the only writes that may touch the allow-list
157162 and ` app_config_definitions ` .
158163- ` app_config_definitions ` purge is rejected while any fragment or allow-list
159164 entry still references the ` config_name ` (` ON DELETE NO ACTION ` ).
160- - ` app_config_allow_list ` purge ** revokes future writes ** at that
161- ` (config_name, scope_type) ` . Because reads never consult the
162- allow-list, ** existing fragments are untouched and keep merging ** — to
163- actually drop a value, an admin purges the fragments themselves .
165+ - ` app_config_allow_list ` purge ** revokes the grant and drops its data ** :
166+ the fragments at that ` (config_name, scope_type) ` are removed by the
167+ ` ON DELETE CASCADE ` FK, so a revoked value disappears from the merge
168+ in the same statement — no orphaned fragments to clean up separately .
164169
165170<a id =" write-model " ></a >
166171### Write model
@@ -196,9 +201,8 @@ boundary.
196201
197202To promote a fixed value to user-customizable, the admin adds a single
198203` (config_name, user) ` grant — no data migration. To lock it back down,
199- the admin removes the grant ** and** purges any existing ` user ` fragments
200- (removing the grant alone only blocks new writes; reads still merge what
201- is already stored).
204+ the admin removes the grant: the cascade drops the existing ` user `
205+ fragments with it, so the admin value applies again immediately.
202206
203207---
204208
@@ -300,12 +304,11 @@ likewise `null` — clients fall back to their built-in defaults.
300304 user)` grant; users then create/update/purge their own copy. No data
301305 migration.
302306- ** Admin locks a value back down** — remove the ` (config_name, user) `
303- grant ** and** purge existing ` user ` fragments (the grant gates writes,
304- not reads).
307+ grant; the cascade drops the existing ` user ` fragments with it.
305308- ** Admin reorders contributions** — set the allow-list entries'
306309 ` rank ` s (per ` (config_name, scope_type) ` , not per fragment).
307- - ** Admin retires a config name** — purge the fragments, then the
308- allow-list entries , then the ` app_config_definitions ` row (purge is rejected
310+ - ** Admin retires a config name** — purge the allow-list entries (their
311+ fragments cascade) , then the ` app_config_definitions ` row (purge is rejected
309312 while references remain).
310313- ** Admin audit** — cross-scope fragment search and cross-user merged
311314 search for support.
0 commit comments