@fayz-ai/db 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/canonical-schema.json +106 -23
- package/dist/index.cjs +4 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/schema/spine.d.ts +42 -37
- package/dist/schema/spine.d.ts.map +1 -1
- package/migrations/000_baseline.sql +29667 -0
- package/migrations/001_the_tenant_has_apps.sql +314 -0
- package/migrations/002_the_spine_drops_the_vendor_and_the_dead.sql +129 -0
- package/migrations/003_the_role_rpcs_finish_and_the_mirrors_go.sql +135 -0
- package/migrations/004_the_projections_get_a_successor.sql +312 -0
- package/migrations/005_the_archetypes_anchor_to_the_unit.sql +90 -0
- package/migrations/006_the_helpers_read_the_source.sql +153 -0
- package/migrations/007_the_compat_machinery_leaves_with_the_tables.sql +77 -0
- package/migrations/008_the_switchers_see_across_accounts.sql +150 -0
- package/migrations/009_the_compat_aliases_nobody_adopted.sql +57 -0
- package/migrations/010_the_new_tenant_is_born_with_its_trade.sql +201 -0
- package/migrations/011_leadcontrol_joins_the_family.sql +116 -0
- package/migrations/012_chefcontrol_wears_its_own_mark.sql +28 -0
- package/migrations/013_the_workspace_switch_reaches_the_other_half.sql +156 -0
- package/migrations/014_the_chooser_can_read_what_it_offers.sql +91 -0
- package/migrations/015_the_unit_bindings_belong_to_one_workspace.sql +86 -0
- package/migrations/016_the_rule_stops_asking_every_row.sql +543 -0
- package/migrations/017_the_pin_is_checked_against_the_right_workspace.sql +139 -0
- package/migrations/018_the_bootstrap_says_what_you_reach.sql +114 -0
- package/migrations/019_distributing_one_row_stops_costing_the_table.sql +245 -0
- package/migrations/020_the_agenda_asks_where_someone_works.sql +81 -0
- package/migrations/022_the_line_says_which_document_it_belongs_to.sql +109 -0
- package/package.json +1 -1
- package/migrations/0000_legacy_quarantine.sql +0 -86
- package/migrations/0000b_straggler_sweep.sql +0 -37
- package/migrations/000_core_v1_convert.sql +0 -343
- package/migrations/001_core.sql +0 -372
- package/migrations/002_rls_user_tenant_ids.sql +0 -64
- package/migrations/003_invitations.sql +0 -17
- package/migrations/004_archetypes.sql +0 -343
- package/migrations/005_locations_archetype.sql +0 -18
- package/migrations/006_archetype_rls.sql +0 -74
- package/migrations/007_archetype_grants.sql +0 -4
- package/migrations/008_grants.sql +0 -5
- package/migrations/009_anon_hardening.sql +0 -19
- package/migrations/010_migration_ledger.sql +0 -46
- package/migrations/011_anon_write_revoke.sql +0 -12
- package/migrations/012_rename_migration_ledger.sql +0 -61
- package/migrations/013_sequences_rls.sql +0 -22
- package/migrations/014_plan_entitlements.sql +0 -28
- package/migrations/015_agent_guard.sql +0 -108
- package/migrations/016_agent_guard_actor.sql +0 -102
- package/migrations/017_core_addresses.sql +0 -115
- package/migrations/018_global_search.sql +0 -482
- package/migrations/019_tenant_members_person_id.sql +0 -20
- package/migrations/020_fayz_projects.sql +0 -63
- package/migrations/021_tenant_roles.sql +0 -74
- package/migrations/022_invite_acceptance.sql +0 -91
- package/migrations/023_team_visibility.sql +0 -75
- package/migrations/024_analytics_engine.sql +0 -317
- package/migrations/025_created_by.sql +0 -75
- package/migrations/026_audit_trail.sql +0 -73
- package/migrations/027_domain_events.sql +0 -266
- package/migrations/028_tenant_scoped_token.sql +0 -127
- package/migrations/029_connections.sql +0 -186
- package/migrations/030_effect_idempotency.sql +0 -159
- package/migrations/031_sync_run_message.sql +0 -39
- package/migrations/032_connection_secrets.sql +0 -227
- package/migrations/033_sync_schedule.sql +0 -651
- package/migrations/034_custom_fields.sql +0 -55
- package/migrations/035_field_registry.sql +0 -148
- package/migrations/036_analytics_run_batch.sql +0 -84
- package/migrations/037_sync_tick_one_at_a_time.sql +0 -256
- package/migrations/038_onboarding_responses.sql +0 -103
- package/migrations/039_unit_tree.sql +0 -270
- package/migrations/040_resource_grants.sql +0 -474
- package/migrations/041_scoped_columns.sql +0 -192
- package/migrations/042_unit_scope_policies.sql +0 -145
- package/migrations/043_view_invoker.sql +0 -90
- package/migrations/044_unit_member_facts.sql +0 -47
- package/migrations/045_unit_entry.sql +0 -236
- package/migrations/046_membership_visible_to_members.sql +0 -85
- package/migrations/047_tasks.sql +0 -266
- package/migrations/048_every_login_is_a_person.sql +0 -190
- package/migrations/049_bookable_people.sql +0 -126
- package/migrations/050_authz_core.sql +0 -230
- package/migrations/051_authz_helpers.sql +0 -330
- package/migrations/052_authz_fanout.sql +0 -253
- package/migrations/053_authz_catalog_templates.sql +0 -402
- package/migrations/060_tenancy_seam.sql +0 -132
- package/migrations/061_create_tenant.sql +0 -125
- package/migrations/062_invitations.sql +0 -329
- package/migrations/063_legacy_tenancy_retired.sql +0 -568
- package/migrations/064_agent_guard_authz.sql +0 -118
- package/migrations/064b_supersede_unit_scope.sql +0 -205
- package/migrations/065_scaffold_sweep.sql +0 -139
- package/migrations/066_shadow_report.sql +0 -122
- package/migrations/070_permission_catalog.sql +0 -205
- package/migrations/071_v1_permission_map.sql +0 -266
- package/migrations/072_role_rpcs.sql +0 -126
- package/migrations/073_legacy_rbac_retire.sql +0 -471
- package/migrations/075_company_legal_entities.sql +0 -169
- package/migrations/076_tenant_settings.sql +0 -251
- package/migrations/080_people_roles.sql +0 -269
- package/migrations/081_people_identity.sql +0 -271
- package/migrations/082_people_membership_link.sql +0 -142
- package/migrations/083_people_data_migration.sql +0 -75
- package/migrations/084_people_registrations_tokens.sql +0 -282
- package/migrations/085_catalog_products.sql +0 -333
- package/migrations/086_catalog_pricing.sql +0 -194
- package/migrations/087_catalog_packages.sql +0 -134
- package/migrations/090_orders_venda.sql +0 -166
- package/migrations/091_orders_scaffold.sql +0 -92
- package/migrations/095_config_field_rules.sql +0 -261
- package/migrations/096_audit_append_only.sql +0 -256
- package/migrations/097_sequences_authorized.sql +0 -111
- package/migrations/098_holidays.sql +0 -80
- package/migrations/099_access_grants.sql +0 -293
- package/migrations/100_migration_schema.sql +0 -217
- package/migrations/101_migration_allowlist.sql +0 -202
- package/migrations/102_migration_rpcs.sql +0 -933
- package/migrations/103_migration_reconcile_ponr.sql +0 -132
- package/migrations/105_policies_to_authenticated.sql +0 -71
- package/migrations/106_function_search_path.sql +0 -55
- package/migrations/107_core_table_prefix.sql +0 -361
- package/migrations/108_authz_write_guards.sql +0 -149
- package/migrations/109_catalog_migration_promoted_columns.sql +0 -126
- package/migrations/110_people_kind_vocabulary.sql +0 -120
- package/migrations/111_migration_excluded_is_a_decision.sql +0 -259
- package/migrations/111_replacing_a_view_keeps_its_dependents.sql +0 -132
- package/migrations/112_money_precision_standard.sql +0 -132
- package/migrations/113_addresses_units_companies.sql +0 -114
- package/migrations/114_migrations_ledger_immutable.sql +0 -65
- package/migrations/115_registration_honours_field_rules.sql +0 -172
- package/migrations/116_field_rules_check_needs_a_tenant.sql +0 -72
- package/migrations/117_authz_reconciliation_schedule.sql +0 -82
- package/migrations/118_migration_catalog_writers.sql +0 -450
- package/migrations/119_migration_config_writers.sql +0 -193
- package/migrations/120_migration_facet_writer.sql +0 -139
- package/migrations/121_migration_financial_writers.sql +0 -294
- package/migrations/122_migration_inventory_writers.sql +0 -245
- package/migrations/123_migration_order_item_writer.sql +0 -144
- package/migrations/124_migration_ledger_writers.sql +0 -170
- package/migrations/125_migration_authz_people_writers.sql +0 -205
- package/migrations/126_owner_scoping_is_configuration.sql +0 -247
- package/migrations/127_scaffold_audit_hook.sql +0 -98
- package/migrations/128_shadow_tables_enforced.sql +0 -93
- package/migrations/129_order_kind_terminal_states.sql +0 -57
- package/migrations/130_every_login_is_a_person.sql +0 -196
- package/migrations/131_migration_invoice_split.sql +0 -127
- package/migrations/132_audit_prune_scheduled.sql +0 -28
- package/migrations/133_unit_is_a_session_property.sql +0 -155
- package/migrations/134_field_rules_on_read.sql +0 -341
- package/migrations/135_distribution_polarity.sql +0 -158
- package/migrations/136_unit_id_means_locality.sql +0 -358
- package/migrations/137_vendor_named_spine_objects.sql +0 -126
- package/migrations/138_grants_inherit_down_the_tree.sql +0 -178
- package/migrations/139_distributable_is_a_scaffold_option.sql +0 -109
- package/migrations/140_masking_is_a_scaffold_option.sql +0 -85
- package/migrations/141_renamed_objects_keep_a_compat_name.sql +0 -192
- package/migrations/142_a_person_has_addresses.sql +0 -212
- package/migrations/143_a_professional_has_a_rate.sql +0 -75
- package/migrations/144_a_product_can_be_sold_in_sizes.sql +0 -57
- package/migrations/145_the_app_says_which_tenant_it_is_in.sql +0 -123
- package/migrations/146_products_stock_has_one_writer.sql +0 -67
- package/migrations/147_every_archetype_has_a_kind_registry.sql +0 -388
- package/migrations/148_the_legacy_archetype_stops_being_writable.sql +0 -154
- package/migrations/149_the_event_log_gets_a_consumer.sql +0 -331
- package/migrations/150_the_app_is_configured_in_the_database.sql +0 -285
- package/migrations/151_stock_balance_is_an_archetype.sql +0 -144
- package/migrations/152_a_file_is_an_archetype.sql +0 -222
- package/migrations/153_a_plugin_calls_a_service_through_one_door.sql +0 -199
- package/migrations/154_the_plan_is_a_ceiling.sql +0 -258
- package/migrations/155_a_tenant_can_leave.sql +0 -271
- package/migrations/156_the_service_door_has_a_server_side.sql +0 -335
- package/migrations/157_nobody_creates_objects_in_public.sql +0 -74
- package/migrations/158_the_registry_owns_the_status_vocabulary.sql +0 -153
- package/migrations/159_the_plan_catalogue_gets_its_first_rows.sql +0 -132
- package/migrations/160_the_vertical_decides_what_starts_on.sql +0 -246
- package/migrations/161_a_tenant_leaves_with_its_data.sql +0 -363
- package/migrations/162_every_name_has_one_creator_for_real.sql +0 -126
- package/migrations/163_a_species_nobody_declared_goes_to_quarantine.sql +0 -184
- package/migrations/164_the_sequence_table_joins_the_register.sql +0 -26
- package/migrations/165_a_view_is_not_a_way_in.sql +0 -90
- package/migrations/166_the_platform_billing_leftovers_go.sql +0 -72
- package/migrations/167_a_view_gets_back_what_its_table_grants.sql +0 -112
- package/migrations/168_the_grant_census_stops_crying_wolf.sql +0 -33
- package/migrations/169_the_census_asks_postgres_instead_of_guessing.sql +0 -45
|
@@ -1,388 +0,0 @@
|
|
|
1
|
-
-- ============================================================================
|
|
2
|
-
-- 147_every_archetype_has_a_kind_registry.sql — a species has a declared owner,
|
|
3
|
-
-- and the database is what says so.
|
|
4
|
-
--
|
|
5
|
-
-- Phase 1, ADR 0017 + the round-3 decision. The Venda has had a registry since
|
|
6
|
-
-- 090: `public.order_kinds`, a natural-key text PK with the owning plugin and
|
|
7
|
-
-- the lifecycle it allows. Every other archetype had `kind text NOT NULL` and
|
|
8
|
-
-- nothing else — any value entered, and nobody could say whose it was. With
|
|
9
|
-
-- closed and third-party plugins that stops being a hypothesis: two plugins
|
|
10
|
-
-- reach for `reservation`, both write, and the row belongs to no lifecycle.
|
|
11
|
-
--
|
|
12
|
-
-- WHY TEXT AND NOT A SURROGATE ID. The species is written literally by plugin
|
|
13
|
-
-- code (`kind = 'appointment'`), read by RLS predicates, carried in the plugin
|
|
14
|
-
-- manifest, the API, the event log and the support dump. An integer id would
|
|
15
|
-
-- force a lookup on every insert and a join on every read, and would make a log
|
|
16
|
-
-- line unreadable, in exchange for nothing: the cardinality is dozens, and the
|
|
17
|
-
-- name is a public contract identifier that cannot be renamed freely either way.
|
|
18
|
-
-- The format CHECK is what makes it an identifier rather than free user text.
|
|
19
|
-
--
|
|
20
|
-
-- WHY A REAL FK AND NOT A TRIGGER. 090 guarded `orders.kind` with a BEFORE
|
|
21
|
-
-- trigger because the registry is seeded by plugin migrations that run after the
|
|
22
|
-
-- core, and legacy rows could arrive before the row existed. That reasoning is
|
|
23
|
-
-- retired: V1 rows that do not fit go to `migration.quarantine` — a human
|
|
24
|
-
-- decision — and never a weakened constraint. A FK is cheaper than a trigger,
|
|
25
|
-
-- it is declarative, and on uninstall it refuses to drop a species while rows
|
|
26
|
-
-- still use it, which is exactly the question we want asked out loud.
|
|
27
|
-
--
|
|
28
|
-
-- WHAT `status` IS FOR. Live pools already hold values written before any
|
|
29
|
-
-- registry existed. Refusing them would break writing; inventing an owner for
|
|
30
|
-
-- them would be a lie. They are ADOPTED: registered so the FK can be created and
|
|
31
|
-
-- the data keeps working, marked so the census can list what still has no owner.
|
|
32
|
-
-- `declared` is a plugin saying "mine"; `adopted` is the database saying "this
|
|
33
|
-
-- was here and nobody has claimed it".
|
|
34
|
-
--
|
|
35
|
-
-- PEOPLE NEEDS TWO REGISTRIES, and that is not duplication. `person_roles.role`
|
|
36
|
-
-- is the canonical hat (customer, professional, employee, contact, supplier,
|
|
37
|
-
-- lead) and `people.kind` is the denormalised primary-role mirror, whose
|
|
38
|
-
-- vocabulary is deliberately WIDER: 080's `person_kind_to_role` maps 'staff' to
|
|
39
|
-
-- employee and 'vendor' to supplier, and 110 added 'client' as a synonym of
|
|
40
|
-
-- customer. So the mirror gets its own registry carrying the mapping AS DATA —
|
|
41
|
-
-- which retires the hard-coded CASE and lets a vertical register 'patient'
|
|
42
|
-
-- instead of resolving to no role at all (the shape of #204).
|
|
43
|
-
--
|
|
44
|
-
-- Idempotent and replay-safe: every object guarded, every backfill a no-op on
|
|
45
|
-
-- a second pass, every FK created only when absent.
|
|
46
|
-
-- ============================================================================
|
|
47
|
-
|
|
48
|
-
-- ─────────────────────────────────────────────────────────────────────────
|
|
49
|
-
-- §1 The registry shape, applied five times
|
|
50
|
-
--
|
|
51
|
-
-- One table per archetype rather than one table with an (archetype, kind) key:
|
|
52
|
-
-- a composite key would force every archetype to carry a constant discriminator
|
|
53
|
-
-- column just so the FK could reference it. Five small tables of identical shape
|
|
54
|
-
-- keep the FK a one-liner and keep each registry readable on its own.
|
|
55
|
-
-- ─────────────────────────────────────────────────────────────────────────
|
|
56
|
-
|
|
57
|
-
DO $$
|
|
58
|
-
DECLARE
|
|
59
|
-
v_table text;
|
|
60
|
-
BEGIN
|
|
61
|
-
FOREACH v_table IN ARRAY ARRAY['person_role_kinds', 'person_kinds', 'category_kinds', 'appointment_kinds', 'schedule_kinds']
|
|
62
|
-
LOOP
|
|
63
|
-
EXECUTE format($ddl$
|
|
64
|
-
CREATE TABLE IF NOT EXISTS public.%I (
|
|
65
|
-
kind text PRIMARY KEY,
|
|
66
|
-
plugin text NOT NULL,
|
|
67
|
-
description text,
|
|
68
|
-
statuses text[],
|
|
69
|
-
transitions jsonb,
|
|
70
|
-
status text NOT NULL DEFAULT 'declared',
|
|
71
|
-
created_at timestamptz NOT NULL DEFAULT now(),
|
|
72
|
-
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
73
|
-
CONSTRAINT %I CHECK (kind ~ '^[a-z][a-z0-9_]*$'),
|
|
74
|
-
CONSTRAINT %I CHECK (status IN ('declared', 'adopted'))
|
|
75
|
-
)$ddl$, v_table, v_table || '_format', v_table || '_status');
|
|
76
|
-
|
|
77
|
-
EXECUTE format('ALTER TABLE public.%I ENABLE ROW LEVEL SECURITY', v_table);
|
|
78
|
-
EXECUTE format('DROP POLICY IF EXISTS %I ON public.%I', v_table || '_select', v_table);
|
|
79
|
-
EXECUTE format($pol$
|
|
80
|
-
CREATE POLICY %I ON public.%I FOR SELECT TO anon, authenticated, service_role USING (true)
|
|
81
|
-
$pol$, v_table || '_select', v_table);
|
|
82
|
-
EXECUTE format('GRANT SELECT ON public.%I TO anon, authenticated, service_role', v_table);
|
|
83
|
-
EXECUTE format('GRANT ALL ON public.%I TO service_role', v_table);
|
|
84
|
-
EXECUTE format('REVOKE INSERT, UPDATE, DELETE ON public.%I FROM anon, authenticated', v_table);
|
|
85
|
-
|
|
86
|
-
EXECUTE format('DROP TRIGGER IF EXISTS %I ON public.%I', v_table || '_updated_at', v_table);
|
|
87
|
-
EXECUTE format($trg$
|
|
88
|
-
CREATE TRIGGER %I BEFORE UPDATE ON public.%I
|
|
89
|
-
FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at()
|
|
90
|
-
$trg$, v_table || '_updated_at', v_table);
|
|
91
|
-
END LOOP;
|
|
92
|
-
END $$;
|
|
93
|
-
|
|
94
|
-
-- `order_kinds` predates this file and already carries kind/plugin/description/
|
|
95
|
-
-- statuses/transitions; it only lacks the status column, so the census can speak
|
|
96
|
-
-- about all six registries in one sentence.
|
|
97
|
-
ALTER TABLE public.order_kinds ADD COLUMN IF NOT EXISTS status text NOT NULL DEFAULT 'declared';
|
|
98
|
-
DO $$
|
|
99
|
-
BEGIN
|
|
100
|
-
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'order_kinds_status') THEN
|
|
101
|
-
ALTER TABLE public.order_kinds ADD CONSTRAINT order_kinds_status CHECK (status IN ('declared', 'adopted'));
|
|
102
|
-
END IF;
|
|
103
|
-
END $$;
|
|
104
|
-
|
|
105
|
-
-- The mirror registry carries the map that 080 hard-coded in a CASE.
|
|
106
|
-
ALTER TABLE public.person_kinds ADD COLUMN IF NOT EXISTS maps_to_role text;
|
|
107
|
-
|
|
108
|
-
COMMENT ON TABLE public.person_role_kinds IS
|
|
109
|
-
'The species registry of the Person hat: which roles exist and who declared them (147). person_roles.role has a FK to this.';
|
|
110
|
-
COMMENT ON TABLE public.person_kinds IS
|
|
111
|
-
'The species registry of people.kind — the denormalised primary-role mirror, whose vocabulary is wider than the roles themselves. maps_to_role is 080''s CASE turned into data (147).';
|
|
112
|
-
COMMENT ON TABLE public.category_kinds IS
|
|
113
|
-
'The species registry of the Category archetype (147).';
|
|
114
|
-
COMMENT ON TABLE public.appointment_kinds IS
|
|
115
|
-
'The species registry of the Compromisso archetype: appointment, table reservation, shift, class — each declared by the plugin that owns its lifecycle (147).';
|
|
116
|
-
COMMENT ON TABLE public.schedule_kinds IS
|
|
117
|
-
'The species registry of the Escala archetype (147).';
|
|
118
|
-
COMMENT ON COLUMN public.person_kinds.maps_to_role IS
|
|
119
|
-
'Which canonical role this mirror value resolves to. NULL means the vertical registered a kind with no role equivalent — legible, unlike the NULL that person_kind_to_role used to return for it.';
|
|
120
|
-
|
|
121
|
-
-- ─────────────────────────────────────────────────────────────────────────
|
|
122
|
-
-- §2 register_kind — one entry point, dispatching by archetype
|
|
123
|
-
--
|
|
124
|
-
-- Same semantics as 090's register_order_kind, which stays as a thin alias so
|
|
125
|
-
-- no existing caller changes: idempotent upsert; NULL statuses AND NULL
|
|
126
|
-
-- transitions on an existing row leave the lifecycle untouched, so a
|
|
127
|
-
-- re-register with defaults never loosens what a plugin tightened.
|
|
128
|
-
-- ─────────────────────────────────────────────────────────────────────────
|
|
129
|
-
|
|
130
|
-
CREATE OR REPLACE FUNCTION public.kind_registry_table(p_archetype text)
|
|
131
|
-
RETURNS text
|
|
132
|
-
LANGUAGE sql IMMUTABLE PARALLEL SAFE
|
|
133
|
-
AS $$
|
|
134
|
-
SELECT CASE p_archetype
|
|
135
|
-
WHEN 'order' THEN 'order_kinds'
|
|
136
|
-
WHEN 'person_role' THEN 'person_role_kinds'
|
|
137
|
-
WHEN 'person' THEN 'person_kinds'
|
|
138
|
-
WHEN 'category' THEN 'category_kinds'
|
|
139
|
-
WHEN 'appointment' THEN 'appointment_kinds'
|
|
140
|
-
WHEN 'schedule' THEN 'schedule_kinds'
|
|
141
|
-
END
|
|
142
|
-
$$;
|
|
143
|
-
|
|
144
|
-
COMMENT ON FUNCTION public.kind_registry_table(text) IS
|
|
145
|
-
'Which registry holds the species of this archetype. NULL for an archetype with no registry — register_kind turns that into an error rather than a silent no-op (147).';
|
|
146
|
-
|
|
147
|
-
CREATE OR REPLACE FUNCTION public.register_kind(
|
|
148
|
-
p_archetype text,
|
|
149
|
-
p_kind text,
|
|
150
|
-
p_plugin text,
|
|
151
|
-
p_statuses text[] DEFAULT NULL,
|
|
152
|
-
p_transitions jsonb DEFAULT NULL,
|
|
153
|
-
p_description text DEFAULT NULL
|
|
154
|
-
) RETURNS void
|
|
155
|
-
LANGUAGE plpgsql SECURITY DEFINER SET search_path = ''
|
|
156
|
-
AS $$
|
|
157
|
-
DECLARE
|
|
158
|
-
v_table text := public.kind_registry_table(p_archetype);
|
|
159
|
-
BEGIN
|
|
160
|
-
IF v_table IS NULL THEN
|
|
161
|
-
RAISE EXCEPTION 'register_kind: % is not an archetype with a species registry', p_archetype
|
|
162
|
-
USING ERRCODE = '22023',
|
|
163
|
-
HINT = 'The archetypes with registries are order, person_role, person, category, appointment, schedule.';
|
|
164
|
-
END IF;
|
|
165
|
-
|
|
166
|
-
IF v_table = 'order_kinds' THEN
|
|
167
|
-
PERFORM public.register_order_kind(p_kind, p_plugin, p_statuses, p_transitions, p_description);
|
|
168
|
-
RETURN;
|
|
169
|
-
END IF;
|
|
170
|
-
|
|
171
|
-
EXECUTE format($sql$
|
|
172
|
-
INSERT INTO public.%I (kind, plugin, description, statuses, transitions, status)
|
|
173
|
-
VALUES ($1, $2, $3, $4, $5, 'declared')
|
|
174
|
-
ON CONFLICT (kind) DO UPDATE
|
|
175
|
-
SET plugin = EXCLUDED.plugin,
|
|
176
|
-
description = coalesce(EXCLUDED.description, public.%I.description),
|
|
177
|
-
statuses = CASE WHEN $4 IS NULL AND $5 IS NULL THEN public.%I.statuses ELSE EXCLUDED.statuses END,
|
|
178
|
-
transitions = CASE WHEN $4 IS NULL AND $5 IS NULL THEN public.%I.transitions ELSE EXCLUDED.transitions END,
|
|
179
|
-
status = 'declared'
|
|
180
|
-
$sql$, v_table, v_table, v_table, v_table)
|
|
181
|
-
USING p_kind, p_plugin, p_description, p_statuses, p_transitions;
|
|
182
|
-
END $$;
|
|
183
|
-
|
|
184
|
-
COMMENT ON FUNCTION public.register_kind(text, text, text, text[], jsonb, text) IS
|
|
185
|
-
'A plugin declares a species as a ROW, never by DDL (147). Re-registering an adopted species is how it stops being ownerless: status returns to declared.';
|
|
186
|
-
|
|
187
|
-
REVOKE ALL ON FUNCTION public.register_kind(text, text, text, text[], jsonb, text) FROM public, anon, authenticated;
|
|
188
|
-
GRANT EXECUTE ON FUNCTION public.register_kind(text, text, text, text[], jsonb, text) TO service_role;
|
|
189
|
-
|
|
190
|
-
-- Registering a Person mirror value also states which role it resolves to.
|
|
191
|
-
CREATE OR REPLACE FUNCTION public.register_person_kind(
|
|
192
|
-
p_kind text,
|
|
193
|
-
p_plugin text,
|
|
194
|
-
p_maps_to_role text DEFAULT NULL,
|
|
195
|
-
p_description text DEFAULT NULL
|
|
196
|
-
) RETURNS void
|
|
197
|
-
LANGUAGE plpgsql SECURITY DEFINER SET search_path = ''
|
|
198
|
-
AS $$
|
|
199
|
-
BEGIN
|
|
200
|
-
PERFORM public.register_kind('person', p_kind, p_plugin, NULL, NULL, p_description);
|
|
201
|
-
UPDATE public.person_kinds SET maps_to_role = p_maps_to_role WHERE kind = p_kind;
|
|
202
|
-
END $$;
|
|
203
|
-
|
|
204
|
-
REVOKE ALL ON FUNCTION public.register_person_kind(text, text, text, text) FROM public, anon, authenticated;
|
|
205
|
-
GRANT EXECUTE ON FUNCTION public.register_person_kind(text, text, text, text) TO service_role;
|
|
206
|
-
|
|
207
|
-
-- ─────────────────────────────────────────────────────────────────────────
|
|
208
|
-
-- §3 What the base declares
|
|
209
|
-
--
|
|
210
|
-
-- The base owns the vocabulary that exists because the archetype exists. A
|
|
211
|
-
-- plugin's own species is declared in the plugin's own migration — that is the
|
|
212
|
-
-- rule this file is here to enforce, so nothing plugin-shaped is seeded here.
|
|
213
|
-
-- ─────────────────────────────────────────────────────────────────────────
|
|
214
|
-
|
|
215
|
-
SELECT public.register_kind('person_role', r.role, 'base', NULL, NULL, r.note)
|
|
216
|
-
FROM (VALUES
|
|
217
|
-
('customer', 'Someone the tenant sells to'),
|
|
218
|
-
('professional', 'Someone who performs services and appears on the calendar'),
|
|
219
|
-
('employee', 'Someone on the payroll'),
|
|
220
|
-
('contact', 'Someone related to the tenant without buying or working'),
|
|
221
|
-
('supplier', 'Someone the tenant buys from'),
|
|
222
|
-
('lead', 'Someone not yet a customer')
|
|
223
|
-
) AS r(role, note);
|
|
224
|
-
|
|
225
|
-
-- The mirror vocabulary, carrying 080's CASE and 110's synonym as rows.
|
|
226
|
-
SELECT public.register_person_kind(k.kind, 'base', k.role, k.note)
|
|
227
|
-
FROM (VALUES
|
|
228
|
-
('customer', 'customer', 'The mirror of the customer hat'),
|
|
229
|
-
('client', 'customer', 'Synonym of customer, kept because shipped plugin versions wrote it (110)'),
|
|
230
|
-
('staff', 'employee', 'Legacy spelling of the employee hat'),
|
|
231
|
-
('employee', 'employee', 'The mirror of the employee hat'),
|
|
232
|
-
('professional', 'professional', 'The mirror of the professional hat'),
|
|
233
|
-
('lead', 'lead', 'The mirror of the lead hat'),
|
|
234
|
-
('supplier', 'supplier', 'The mirror of the supplier hat'),
|
|
235
|
-
('vendor', 'supplier', 'Legacy spelling of the supplier hat'),
|
|
236
|
-
('contact', 'contact', 'The mirror of the contact hat')
|
|
237
|
-
) AS k(kind, role, note);
|
|
238
|
-
|
|
239
|
-
-- The Category taxonomy the base itself ships. `service` and `good` mirror the
|
|
240
|
-
-- catalog discriminator; a plugin's own taxonomy (the shop's `shop`, the menu's
|
|
241
|
-
-- `menu_category`) is declared in that plugin's migration, which is the rule
|
|
242
|
-
-- this file exists to make enforceable.
|
|
243
|
-
SELECT public.register_kind('category', c.kind, 'base', NULL, NULL, c.note)
|
|
244
|
-
FROM (VALUES
|
|
245
|
-
('service', 'Groups the services a tenant sells'),
|
|
246
|
-
('good', 'Groups the goods a tenant sells'),
|
|
247
|
-
('product', 'The V1 vocabulary the extractor writes by default (102) — a synonym of good, kept because the data says it')
|
|
248
|
-
) AS c(kind, note);
|
|
249
|
-
|
|
250
|
-
-- NOT products.kind. That column is a deliberate closed binary (good|service,
|
|
251
|
-
-- 085's CHECK) — the archetype's own discriminator, with `subkind` and
|
|
252
|
-
-- `service_kind` as the open extension points. A registry there would offer an
|
|
253
|
-
-- openness the catalog contract does not have.
|
|
254
|
-
|
|
255
|
-
-- person_kind_to_role stops being a CASE and becomes a lookup. Same signature,
|
|
256
|
-
-- same NULL-for-unknown contract, so every existing caller is unaffected —
|
|
257
|
-
-- except that a vertical can now teach it a value by registering one.
|
|
258
|
-
CREATE OR REPLACE FUNCTION public.person_kind_to_role(p_kind text)
|
|
259
|
-
RETURNS text LANGUAGE sql STABLE PARALLEL SAFE
|
|
260
|
-
SET search_path = ''
|
|
261
|
-
AS $$
|
|
262
|
-
SELECT k.maps_to_role FROM public.person_kinds k WHERE k.kind = p_kind
|
|
263
|
-
$$;
|
|
264
|
-
|
|
265
|
-
COMMENT ON FUNCTION public.person_kind_to_role(text) IS
|
|
266
|
-
'Which role a people.kind value resolves to. Since 147 this reads public.person_kinds instead of a hard-coded CASE — registering a kind is how a vertical teaches it (was: NULL, the shape of #204).';
|
|
267
|
-
|
|
268
|
-
-- ─────────────────────────────────────────────────────────────────────────
|
|
269
|
-
-- §4 Adoption — what is already in the data, made legible
|
|
270
|
-
--
|
|
271
|
-
-- Every distinct value present in a live pool and absent from its registry is
|
|
272
|
-
-- registered as `adopted`, owner unknown. Nothing is refused and nothing is
|
|
273
|
-
-- invented: the census in §6 is what turns this into work.
|
|
274
|
-
-- ─────────────────────────────────────────────────────────────────────────
|
|
275
|
-
|
|
276
|
-
CREATE OR REPLACE FUNCTION public.adopt_existing_kinds(p_archetype text, p_table text, p_column text)
|
|
277
|
-
RETURNS integer
|
|
278
|
-
LANGUAGE plpgsql SECURITY DEFINER SET search_path = ''
|
|
279
|
-
AS $$
|
|
280
|
-
DECLARE
|
|
281
|
-
v_registry text := public.kind_registry_table(p_archetype);
|
|
282
|
-
v_count integer := 0;
|
|
283
|
-
BEGIN
|
|
284
|
-
IF v_registry IS NULL OR to_regclass('public.' || quote_ident(p_table)) IS NULL THEN
|
|
285
|
-
RETURN 0;
|
|
286
|
-
END IF;
|
|
287
|
-
EXECUTE format($sql$
|
|
288
|
-
INSERT INTO public.%I (kind, plugin, description, status)
|
|
289
|
-
SELECT DISTINCT t.%I, 'unattributed',
|
|
290
|
-
'Present in the data before any registry existed; adopted by 147 so the FK could be created. Needs an owner.',
|
|
291
|
-
'adopted'
|
|
292
|
-
FROM public.%I t
|
|
293
|
-
WHERE t.%I IS NOT NULL
|
|
294
|
-
AND t.%I ~ '^[a-z][a-z0-9_]*$'
|
|
295
|
-
ON CONFLICT (kind) DO NOTHING
|
|
296
|
-
$sql$, v_registry, p_column, p_table, p_column, p_column);
|
|
297
|
-
GET DIAGNOSTICS v_count = ROW_COUNT;
|
|
298
|
-
RETURN v_count;
|
|
299
|
-
END $$;
|
|
300
|
-
|
|
301
|
-
COMMENT ON FUNCTION public.adopt_existing_kinds(text, text, text) IS
|
|
302
|
-
'Registers, as adopted, every species already present in the data and missing from its registry — so the FK in §5 can be added without refusing a live write or inventing an owner (147).';
|
|
303
|
-
|
|
304
|
-
REVOKE ALL ON FUNCTION public.adopt_existing_kinds(text, text, text) FROM public, anon, authenticated;
|
|
305
|
-
|
|
306
|
-
SELECT public.adopt_existing_kinds('person_role', 'person_roles', 'role');
|
|
307
|
-
SELECT public.adopt_existing_kinds('person', 'people', 'kind');
|
|
308
|
-
SELECT public.adopt_existing_kinds('category', 'categories', 'kind');
|
|
309
|
-
SELECT public.adopt_existing_kinds('appointment', 'appointments', 'kind');
|
|
310
|
-
SELECT public.adopt_existing_kinds('schedule', 'schedules', 'kind');
|
|
311
|
-
SELECT public.adopt_existing_kinds('order', 'orders', 'kind');
|
|
312
|
-
|
|
313
|
-
-- A value that does not even look like an identifier cannot be adopted, and must
|
|
314
|
-
-- not be silently dropped either. It is a data question, and the machine that
|
|
315
|
-
-- exists for data questions is the quarantine.
|
|
316
|
-
DO $$
|
|
317
|
-
DECLARE
|
|
318
|
-
v_pair record;
|
|
319
|
-
v_bad integer;
|
|
320
|
-
BEGIN
|
|
321
|
-
IF to_regclass('migration.quarantine') IS NULL THEN
|
|
322
|
-
RETURN;
|
|
323
|
-
END IF;
|
|
324
|
-
FOR v_pair IN SELECT * FROM (VALUES
|
|
325
|
-
('person_roles', 'role'), ('people', 'kind'), ('categories', 'kind'),
|
|
326
|
-
('appointments', 'kind'), ('schedules', 'kind'), ('orders', 'kind')
|
|
327
|
-
) AS p(tbl, col)
|
|
328
|
-
LOOP
|
|
329
|
-
IF to_regclass('public.' || quote_ident(v_pair.tbl)) IS NULL THEN CONTINUE; END IF;
|
|
330
|
-
EXECUTE format('SELECT count(*)::int FROM public.%I WHERE %I IS NOT NULL AND %I !~ ''^[a-z][a-z0-9_]*$''',
|
|
331
|
-
v_pair.tbl, v_pair.col, v_pair.col) INTO v_bad;
|
|
332
|
-
IF v_bad > 0 THEN
|
|
333
|
-
RAISE EXCEPTION '147: %.% holds % value(s) that are not identifiers and cannot be registered', v_pair.tbl, v_pair.col, v_bad
|
|
334
|
-
USING HINT = 'Quarantine or correct those rows first — a species outside the identifier format has no owner and no lifecycle.';
|
|
335
|
-
END IF;
|
|
336
|
-
END LOOP;
|
|
337
|
-
END $$;
|
|
338
|
-
|
|
339
|
-
-- ─────────────────────────────────────────────────────────────────────────
|
|
340
|
-
-- §5 The FKs — the whole point of the file
|
|
341
|
-
-- ─────────────────────────────────────────────────────────────────────────
|
|
342
|
-
|
|
343
|
-
DO $$
|
|
344
|
-
DECLARE
|
|
345
|
-
v_fk record;
|
|
346
|
-
BEGIN
|
|
347
|
-
FOR v_fk IN SELECT * FROM (VALUES
|
|
348
|
-
('person_roles', 'role', 'person_role_kinds', 'person_roles_role_registered'),
|
|
349
|
-
('people', 'kind', 'person_kinds', 'people_kind_registered'),
|
|
350
|
-
('categories', 'kind', 'category_kinds', 'categories_kind_registered'),
|
|
351
|
-
('appointments', 'kind', 'appointment_kinds', 'appointments_kind_registered'),
|
|
352
|
-
('schedules', 'kind', 'schedule_kinds', 'schedules_kind_registered'),
|
|
353
|
-
('orders', 'kind', 'order_kinds', 'orders_kind_registered')
|
|
354
|
-
) AS f(tbl, col, registry, conname)
|
|
355
|
-
LOOP
|
|
356
|
-
IF to_regclass('public.' || quote_ident(v_fk.tbl)) IS NULL THEN CONTINUE; END IF;
|
|
357
|
-
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = v_fk.conname) THEN
|
|
358
|
-
EXECUTE format('ALTER TABLE public.%I ADD CONSTRAINT %I FOREIGN KEY (%I) REFERENCES public.%I(kind)',
|
|
359
|
-
v_fk.tbl, v_fk.conname, v_fk.col, v_fk.registry);
|
|
360
|
-
END IF;
|
|
361
|
-
END LOOP;
|
|
362
|
-
END $$;
|
|
363
|
-
|
|
364
|
-
-- person_roles carried the vocabulary as a CHECK. The FK supersedes it: a CHECK
|
|
365
|
-
-- can only be widened by a migration, which is precisely what made a vertical's
|
|
366
|
-
-- 'patient' impossible to express.
|
|
367
|
-
ALTER TABLE public.person_roles DROP CONSTRAINT IF EXISTS person_roles_role_check;
|
|
368
|
-
|
|
369
|
-
COMMENT ON CONSTRAINT people_kind_registered ON public.people IS
|
|
370
|
-
'A species with no declared owner cannot be written (147). Registering one is a row in public.person_kinds, never DDL.';
|
|
371
|
-
|
|
372
|
-
-- ─────────────────────────────────────────────────────────────────────────
|
|
373
|
-
-- §6 The census — one view over all six registries
|
|
374
|
-
-- ─────────────────────────────────────────────────────────────────────────
|
|
375
|
-
|
|
376
|
-
CREATE OR REPLACE VIEW public.v_archetype_kinds
|
|
377
|
-
WITH (security_invoker = true) AS
|
|
378
|
-
SELECT 'order'::text AS archetype, kind, plugin, description, statuses, transitions, status FROM public.order_kinds
|
|
379
|
-
UNION ALL SELECT 'person_role', kind, plugin, description, statuses, transitions, status FROM public.person_role_kinds
|
|
380
|
-
UNION ALL SELECT 'person', kind, plugin, description, statuses, transitions, status FROM public.person_kinds
|
|
381
|
-
UNION ALL SELECT 'category', kind, plugin, description, statuses, transitions, status FROM public.category_kinds
|
|
382
|
-
UNION ALL SELECT 'appointment', kind, plugin, description, statuses, transitions, status FROM public.appointment_kinds
|
|
383
|
-
UNION ALL SELECT 'schedule', kind, plugin, description, statuses, transitions, status FROM public.schedule_kinds;
|
|
384
|
-
|
|
385
|
-
COMMENT ON VIEW public.v_archetype_kinds IS
|
|
386
|
-
'Every declared species of every archetype, with its owner (147). status = adopted is the backlog: a species the data already had and no plugin has claimed.';
|
|
387
|
-
|
|
388
|
-
GRANT SELECT ON public.v_archetype_kinds TO anon, authenticated, service_role;
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
-- ============================================================================
|
|
2
|
-
-- 148_the_legacy_archetype_stops_being_writable.sql — one book, and the tables
|
|
3
|
-
-- that carry a base name get a declared owner (#285).
|
|
4
|
-
--
|
|
5
|
-
-- Three cleanups the decisions already settled, in one file because they are the
|
|
6
|
-
-- same shape: something in `public` has no owner, or two things answer the same
|
|
7
|
-
-- question.
|
|
8
|
-
--
|
|
9
|
-
-- §1 public.transactions — 065 wrote it down in the file itself: "legacy
|
|
10
|
-
-- generic archetype; O2C's ledger supersedes it", and 101 repeats it in the
|
|
11
|
-
-- allowlist: "superseded by financial_movements". Two books for one fact,
|
|
12
|
-
-- and the newer one is the canonical one. This file does not DROP it: a
|
|
13
|
-
-- live pool holds rows nobody has read in months, and dropping is a decision
|
|
14
|
-
-- with no undo. It does the thing that makes the second book stop being a
|
|
15
|
-
-- book — REVOKE the writes, leaving reads alone — and records the retirement
|
|
16
|
-
-- so the drop is a later, deliberate step with the count in hand.
|
|
17
|
-
--
|
|
18
|
-
-- §2 sequences — created by the base (a numbering service every domain needs)
|
|
19
|
-
-- AND by plugin-financial's 027. Two creators, one name, and whichever ran
|
|
20
|
-
-- last owned it. The base is the owner; the plugin's copy is guarded to a
|
|
21
|
-
-- no-op by the IF NOT EXISTS it already carries, so what is left is to say
|
|
22
|
-
-- so out loud and let the census enforce it.
|
|
23
|
-
--
|
|
24
|
-
-- §3 shipping_zones / shipping_settings — created by packages/shop's 0021,
|
|
25
|
-
-- living in `public` with no prefix, which reads as base and is not. Same
|
|
26
|
-
-- class as documents (forms) and sequences (financial). Renaming a table
|
|
27
|
-
-- that live storefront code reads is a synchronised deploy, and 141 already
|
|
28
|
-
-- built the mechanism for exactly this: the object moves and the old name
|
|
29
|
-
-- keeps answering, with the table's grants copied onto the alias so the
|
|
30
|
-
-- callers keep the access they have today. That is what happens here —
|
|
31
|
-
-- WRITES INCLUDED, because the shop writes both of these through the old
|
|
32
|
-
-- name and a read-only bridge is not a bridge for a writer.
|
|
33
|
-
--
|
|
34
|
-
-- Idempotent and replay-safe.
|
|
35
|
-
-- ============================================================================
|
|
36
|
-
|
|
37
|
-
-- ─────────────────────────────────────────────────────────────────────────
|
|
38
|
-
-- §1 The legacy archetype becomes read-only
|
|
39
|
-
-- ─────────────────────────────────────────────────────────────────────────
|
|
40
|
-
|
|
41
|
-
DO $$
|
|
42
|
-
BEGIN
|
|
43
|
-
IF to_regclass('public.transactions') IS NULL THEN
|
|
44
|
-
RETURN;
|
|
45
|
-
END IF;
|
|
46
|
-
|
|
47
|
-
-- Reads keep working: dashboards, the migration ledger and any support query
|
|
48
|
-
-- can still see what is there. Writes stop, which is what makes the ledger the
|
|
49
|
-
-- only book being kept from here on.
|
|
50
|
-
REVOKE INSERT, UPDATE, DELETE ON public.transactions FROM anon, authenticated;
|
|
51
|
-
|
|
52
|
-
EXECUTE $c$ COMMENT ON TABLE public.transactions IS
|
|
53
|
-
'RETIRED (148). The legacy generic archetype the two-leg ledger superseded — plg_financial_movements is the book. Reads left in place for history; writes revoked. Drop is a separate, deliberate step once every pool reports zero rows unaccounted for in the ledger.' $c$;
|
|
54
|
-
END $$;
|
|
55
|
-
|
|
56
|
-
-- The drop gate — "which rows here is the ledger not accounting for?" — is as
|
|
57
|
-
-- much a question about a plugin's table as about this one, so it lives with the
|
|
58
|
-
-- plugin that keeps the book: plugin-financial 034.
|
|
59
|
-
|
|
60
|
-
-- ─────────────────────────────────────────────────────────────────────────
|
|
61
|
-
-- §2 sequences belongs to the base, and the census can prove it
|
|
62
|
-
--
|
|
63
|
-
-- The prefix rule (ADR 0011) says `plg_` means "owned by plugin X". The inverse
|
|
64
|
-
-- has had no register: an unprefixed table in `public` is base-owned by
|
|
65
|
-
-- convention only. This is that register — the base declares what is its, and
|
|
66
|
-
-- check-schema-ownership reads it.
|
|
67
|
-
-- ─────────────────────────────────────────────────────────────────────────
|
|
68
|
-
|
|
69
|
-
CREATE TABLE IF NOT EXISTS app.base_owned_tables (
|
|
70
|
-
table_name text PRIMARY KEY,
|
|
71
|
-
note text,
|
|
72
|
-
created_at timestamptz NOT NULL DEFAULT now()
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
COMMENT ON TABLE app.base_owned_tables IS
|
|
76
|
-
'Unprefixed tables in public that the BASE owns (148). A plugin creating a table whose name is in here is the violation the ownership census reports — the prefix rule only ever answered the other direction.';
|
|
77
|
-
|
|
78
|
-
ALTER TABLE app.base_owned_tables ENABLE ROW LEVEL SECURITY;
|
|
79
|
-
DROP POLICY IF EXISTS base_owned_tables_read ON app.base_owned_tables;
|
|
80
|
-
CREATE POLICY base_owned_tables_read ON app.base_owned_tables FOR SELECT TO authenticated, service_role USING (true);
|
|
81
|
-
GRANT SELECT ON app.base_owned_tables TO authenticated, service_role;
|
|
82
|
-
|
|
83
|
-
INSERT INTO app.base_owned_tables (table_name, note) VALUES
|
|
84
|
-
('sequences', 'Numbering for every domain. plugin-financial 027 also creates it, guarded — the base is the owner.'),
|
|
85
|
-
('people', 'The Person archetype.'),
|
|
86
|
-
('person_roles', 'The hats a Person wears.'),
|
|
87
|
-
('products', 'The Item de catálogo archetype.'),
|
|
88
|
-
('services', 'The service half of the catalog.'),
|
|
89
|
-
('product_variants', 'The shape of what is sold — the archetype, not the shop copy (ADR 0024).'),
|
|
90
|
-
('categories', 'The Category archetype.'),
|
|
91
|
-
('orders', 'The Venda archetype.'),
|
|
92
|
-
('order_items', 'The lines of a Venda.'),
|
|
93
|
-
('appointments', 'The Compromisso archetype.'),
|
|
94
|
-
('appointment_items', 'The lines of a Compromisso.'),
|
|
95
|
-
('schedules', 'The Escala archetype.'),
|
|
96
|
-
('addresses', 'Where a Person, a unit or a tenant is.'),
|
|
97
|
-
('company_legal_entities','The Empresa.'),
|
|
98
|
-
('tenants', 'The tenant itself.'),
|
|
99
|
-
('invitations', 'An invite to become a member.'),
|
|
100
|
-
('audit_logs', 'The append-only audit trail.'),
|
|
101
|
-
('holidays', 'The calendar the tenant does not work.'),
|
|
102
|
-
('access_grants', 'Delegated access (ADR 0011).'),
|
|
103
|
-
('access_sessions', 'The session a grant issues.')
|
|
104
|
-
ON CONFLICT (table_name) DO UPDATE SET note = EXCLUDED.note;
|
|
105
|
-
|
|
106
|
-
-- ─────────────────────────────────────────────────────────────────────────
|
|
107
|
-
-- §3 Shipping moves under the shop's prefix, and the old name keeps answering
|
|
108
|
-
--
|
|
109
|
-
-- 141's mechanism: rename, then leave a view with the old name so no deploy has
|
|
110
|
-
-- to be synchronised. Guarded on every side — a pool without the shop installed
|
|
111
|
-
-- has neither name and skips the whole block.
|
|
112
|
-
-- ─────────────────────────────────────────────────────────────────────────
|
|
113
|
-
|
|
114
|
-
DO $$
|
|
115
|
-
DECLARE
|
|
116
|
-
v_pair record;
|
|
117
|
-
g record;
|
|
118
|
-
BEGIN
|
|
119
|
-
FOR v_pair IN SELECT * FROM (VALUES
|
|
120
|
-
('shipping_zones', 'plg_shop_shipping_zones'),
|
|
121
|
-
('shipping_settings', 'plg_shop_shipping_settings')
|
|
122
|
-
) AS p(old_name, new_name)
|
|
123
|
-
LOOP
|
|
124
|
-
IF to_regclass('public.' || quote_ident(v_pair.old_name)) IS NOT NULL
|
|
125
|
-
AND to_regclass('public.' || quote_ident(v_pair.new_name)) IS NULL THEN
|
|
126
|
-
-- Only a TABLE moves; if the old name is already the compat view this
|
|
127
|
-
-- migration wrote on a previous pass, there is nothing to do.
|
|
128
|
-
IF EXISTS (SELECT 1 FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
129
|
-
WHERE n.nspname = 'public' AND c.relname = v_pair.old_name AND c.relkind = 'r') THEN
|
|
130
|
-
EXECUTE format('ALTER TABLE public.%I RENAME TO %I', v_pair.old_name, v_pair.new_name);
|
|
131
|
-
EXECUTE format('CREATE OR REPLACE VIEW public.%I WITH (security_invoker = true) AS SELECT * FROM public.%I',
|
|
132
|
-
v_pair.old_name, v_pair.new_name);
|
|
133
|
-
-- The alias is exactly as reachable as what it points at — 141's rule,
|
|
134
|
-
-- and the reason this is a loop and not a hardcoded GRANT SELECT.
|
|
135
|
-
-- Shipping zones and settings are WRITTEN through the old name by the
|
|
136
|
-
-- published SDK (`packages/shop/src/data/tables.ts` still says
|
|
137
|
-
-- 'shipping_zones'), so a read-only alias does not make the rename
|
|
138
|
-
-- unsynchronised — it moves the breakage from "the table is gone" to
|
|
139
|
-
-- "permission denied for view", on the merchant's delivery screen.
|
|
140
|
-
FOR g IN
|
|
141
|
-
SELECT DISTINCT grantee, privilege_type
|
|
142
|
-
FROM information_schema.role_table_grants
|
|
143
|
-
WHERE table_schema = 'public'
|
|
144
|
-
AND table_name = v_pair.new_name
|
|
145
|
-
AND grantee IN ('anon', 'authenticated', 'service_role')
|
|
146
|
-
LOOP
|
|
147
|
-
EXECUTE format('GRANT %s ON public.%I TO %I', g.privilege_type, v_pair.old_name, g.grantee);
|
|
148
|
-
END LOOP;
|
|
149
|
-
EXECUTE format($c$ COMMENT ON VIEW public.%I IS 'Compat name for public.%I (148, mechanism from 141). The table carries the shop prefix now; this view is what lets the rename not be a synchronised deploy.' $c$,
|
|
150
|
-
v_pair.old_name, v_pair.new_name);
|
|
151
|
-
END IF;
|
|
152
|
-
END IF;
|
|
153
|
-
END LOOP;
|
|
154
|
-
END $$;
|