diff --git a/packages/app-scope/__tests__/membership.test.ts b/packages/app-scope/__tests__/membership.test.ts index 1e6f494a..54111980 100644 --- a/packages/app-scope/__tests__/membership.test.ts +++ b/packages/app-scope/__tests__/membership.test.ts @@ -111,7 +111,7 @@ describe('app_scope scope frames (membership climb + platform fall-through)', () database_id, scope, schema_id, private_schema_id, memberships_table_id, membership_defaults_table_id, members_table_id, grants_table_id, sprt_table_id, actor_table_id, limits_table_id, - default_limits_table_id, permissions_table_id, default_permissions_table_id, + default_limits_table_id, capabilities_table_id, default_capabilities_table_id, entity_table_id, entity_table_owner_id ) VALUES ( $1, $2, $3, $3, diff --git a/packages/function-resolution/__tests__/staging-bucket.test.ts b/packages/function-resolution/__tests__/staging-bucket.test.ts new file mode 100644 index 00000000..842f0f31 --- /dev/null +++ b/packages/function-resolution/__tests__/staging-bucket.test.ts @@ -0,0 +1,140 @@ +import { getConnections, PgTestClient } from 'pgsql-test'; + +let pg: PgTestClient; +let teardown: () => Promise; + +// Deterministic fixture ids. +const TENANT_DB = '22222222-2222-2222-2222-222222222222'; +// A tenant whose storage bootstrap never labelled a staging bucket. +const BARE_DB = '33333333-3333-3333-3333-333333333333'; +// A tenant that labelled two, which is a tagging mistake and not a coin flip. +const AMBIGUOUS_DB = '44444444-4444-4444-4444-444444444444'; + +const ids: Record = {}; + +// The database's staging bucket: the answer to "where does an upload land before +// anything has vouched for it". It resolves by the same exactly-one tag rule the +// default bucket uses, so staging is not a second lookup mechanism — and a +// module that has no staging bucket raises rather than staging into a permanent +// one. +describe('staging bucket resolution', () => { + beforeAll(async () => { + ({ pg, teardown } = await getConnections()); + + await pg.query( + `INSERT INTO metaschema_public.database (id, name, platform) + VALUES ($1, 'tenant_db', false), ($2, 'bare_db', false), + ($3, 'ambiguous_db', false)`, + [TENANT_DB, BARE_DB, AMBIGUOUS_DB] + ); + + // The published bucket plane, as the catalog-sync triggers maintain it. + await pg.query(`CREATE SCHEMA catalog_private`); + await pg.query( + `CREATE TABLE catalog_private.buckets ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + owner_scope text NOT NULL, + owner_key uuid, + is_visible boolean NOT NULL DEFAULT false, + database_id uuid NOT NULL, + key text NOT NULL, + type text NOT NULL, + physical_name text, + tags text[] + )` + ); + + const bucket = async ( + database: string, + key: string, + type: string, + tags: string[] + ) => { + const row = await pg.one( + `INSERT INTO catalog_private.buckets + (owner_scope, owner_key, is_visible, database_id, key, type, physical_name, tags) + VALUES ('database', $1, false, $1, $2, $3, $4, $5) RETURNING id`, + [database, key, type, `phys-${key}`, tags] + ); + return row.id; + }; + + ids.default = await bucket(TENANT_DB, 'default', 'private', ['default']); + ids.staging = await bucket(TENANT_DB, 'default-temp', 'temp', [ + 'default-temp', + ]); + + // A permanent bucket carrying the staging tag is not a staging bucket: the + // type filter is part of the rule, not a hint. + ids.mislabelled = await bucket(BARE_DB, 'uploads', 'private', [ + 'default-temp', + ]); + + ids.ambiguousOne = await bucket(AMBIGUOUS_DB, 'staging_a', 'temp', [ + 'default-temp', + ]); + ids.ambiguousTwo = await bucket(AMBIGUOUS_DB, 'staging_b', 'temp', [ + 'default-temp', + ]); + }); + + afterAll(async () => { + await teardown(); + }); + + it('the reserved staging tag is one fact, not a literal per call site', async () => { + const [row] = await pg.any( + `SELECT function_resolution.staging_bucket_tag() AS staging_tag` + ); + expect(row).toEqual({ staging_tag: 'default-temp' }); + }); + + it('resolves the database staging bucket without anyone naming one', async () => { + const [row] = await pg.any( + `SELECT bucket_id, resolved_key, bucket_type, physical_name, owner_database_id + FROM function_resolution.resolve_staging_bucket($1, 'database', $1)`, + [TENANT_DB] + ); + expect(row).toEqual({ + bucket_id: ids.staging, + resolved_key: 'default-temp', + bucket_type: 'temp', + physical_name: 'phys-default-temp', + owner_database_id: TENANT_DB, + }); + }); + + it('a database with no staging bucket raises rather than staging into a permanent one', async () => { + // BARE_DB does carry the tag — on a private bucket, which is exactly the + // case that must not resolve. + await expect( + pg.any( + `SELECT * FROM function_resolution.resolve_staging_bucket($1, 'database', $1)`, + [BARE_DB] + ) + ).rejects.toThrow(/STORAGE_STAGING_BUCKET_NOT_FOUND/); + }); + + it('two staging buckets raise, naming the candidates', async () => { + let failure: (Error & { detail?: string }) | null = null; + try { + await pg.any( + `SELECT * FROM function_resolution.resolve_staging_bucket($1, 'database', $1)`, + [AMBIGUOUS_DB] + ); + } catch (error) { + failure = error as Error & { detail?: string }; + } + + expect(failure).not.toBeNull(); + expect(failure!.message).toMatch(/STORAGE_STAGING_BUCKET_AMBIGUOUS/); + + const detail = JSON.parse(failure!.detail!); + expect(detail.code).toBe('STORAGE_STAGING_BUCKET_AMBIGUOUS'); + expect(detail.context.tag).toBe('default-temp'); + expect(detail.context.candidates).toEqual([ + { bucket_id: ids.ambiguousOne, key: 'staging_a', type: 'temp' }, + { bucket_id: ids.ambiguousTwo, key: 'staging_b', type: 'temp' }, + ]); + }); +}); diff --git a/packages/function-resolution/deploy/schemas/function_resolution/procedures/resolve_api.sql b/packages/function-resolution/deploy/schemas/function_resolution/procedures/resolve_api.sql index 2dc93b7a..d8b6ab95 100644 --- a/packages/function-resolution/deploy/schemas/function_resolution/procedures/resolve_api.sql +++ b/packages/function-resolution/deploy/schemas/function_resolution/procedures/resolve_api.sql @@ -15,9 +15,9 @@ BEGIN; -- module has several, and a scope when a module is registered at several: -- -- notifications_module the module's api surface --- permissions_module.admin this api from this module +-- capabilities_module.admin this api from this module -- limits_module@org the module's org-scope registration --- permissions_module.admin@org both +-- capabilities_module.admin@org both -- admin an api by its owner-local name -- -- Anything ending in _module (before its suffixes) is a module selector; a diff --git a/packages/function-resolution/deploy/schemas/function_resolution/procedures/resolve_staging_bucket.sql b/packages/function-resolution/deploy/schemas/function_resolution/procedures/resolve_staging_bucket.sql new file mode 100644 index 00000000..96d87845 --- /dev/null +++ b/packages/function-resolution/deploy/schemas/function_resolution/procedures/resolve_staging_bucket.sql @@ -0,0 +1,101 @@ +-- Deploy schemas/function_resolution/procedures/resolve_staging_bucket to pg + +-- requires: schemas/function_resolution/schema +-- requires: schemas/function_resolution/procedures/bucket_matches +-- requires: schemas/function_resolution/procedures/staging_bucket_tag + +BEGIN; + +-- resolve_staging_bucket: answer "which bucket does this database stage an +-- upload through", server-side, by the same rule resolve_default_bucket uses +-- for the destination. +-- +-- Staging is not a second mechanism: the reserved staging tag is matched in the +-- caller's own frame chain, filtered to type = 'temp', and exactly one match is +-- the only acceptable answer. Zero raises (a module whose storage bootstrap +-- never labelled a staging bucket cannot silently stage into its permanent +-- bucket), several raise naming the candidates (picking one would stage a +-- tenant's uploads through whichever bucket sorted first). +-- +-- The staging bucket's destination is its own destination_bucket_id, enforced on +-- the module's buckets table, so promotion reads the destination from the row +-- rather than being handed one by a client. +CREATE FUNCTION function_resolution.resolve_staging_bucket( + database_id uuid, + scope text, + entity_id uuid +) RETURNS TABLE ( + bucket_id uuid, + resolved_key text, + bucket_type text, + physical_name text, + owner_database_id uuid, + owner_scope text, + owner_key uuid +) AS $$ +DECLARE + v_tag text; + v_matches jsonb; + v_match jsonb; +BEGIN + v_tag := function_resolution.staging_bucket_tag(); + + SELECT COALESCE(jsonb_agg(to_jsonb(m) ORDER BY m.bucket_id), '[]'::jsonb) + INTO v_matches + FROM function_resolution.bucket_matches( + resolve_staging_bucket.database_id, + resolve_staging_bucket.scope, + resolve_staging_bucket.entity_id, + ARRAY[v_tag], + 'temp' + ) m; + + IF jsonb_array_length(v_matches) = 0 THEN + PERFORM errors.raise_error( + 'STORAGE_STAGING_BUCKET_NOT_FOUND', + jsonb_build_object( + 'database_id', resolve_staging_bucket.database_id, + 'scope', resolve_staging_bucket.scope, + 'entity_id', resolve_staging_bucket.entity_id, + 'tag', v_tag + ), + 'internal' + ); + END IF; + + IF jsonb_array_length(v_matches) > 1 THEN + PERFORM errors.raise_error( + 'STORAGE_STAGING_BUCKET_AMBIGUOUS', + jsonb_build_object( + 'database_id', resolve_staging_bucket.database_id, + 'scope', resolve_staging_bucket.scope, + 'entity_id', resolve_staging_bucket.entity_id, + 'tag', v_tag, + 'candidates', ( + SELECT jsonb_agg(jsonb_build_object( + 'bucket_id', c->>'bucket_id', + 'key', c->>'bucket_key', + 'type', c->>'bucket_type' + ) ORDER BY c->>'bucket_key') + FROM jsonb_array_elements(v_matches) c + ) + ), + 'internal' + ); + END IF; + + v_match := v_matches->0; + + resolve_staging_bucket.bucket_id := (v_match->>'bucket_id')::uuid; + resolve_staging_bucket.resolved_key := v_match->>'bucket_key'; + resolve_staging_bucket.bucket_type := v_match->>'bucket_type'; + resolve_staging_bucket.physical_name := v_match->>'physical_name'; + resolve_staging_bucket.owner_database_id := (v_match->>'owner_database_id')::uuid; + resolve_staging_bucket.owner_scope := v_match->>'owner_scope'; + resolve_staging_bucket.owner_key := (v_match->>'owner_key')::uuid; + + RETURN NEXT; +END; +$$ LANGUAGE plpgsql STABLE SECURITY DEFINER; + +COMMIT; diff --git a/packages/function-resolution/deploy/schemas/function_resolution/procedures/staging_bucket_tag.sql b/packages/function-resolution/deploy/schemas/function_resolution/procedures/staging_bucket_tag.sql new file mode 100644 index 00000000..847ae0ab --- /dev/null +++ b/packages/function-resolution/deploy/schemas/function_resolution/procedures/staging_bucket_tag.sql @@ -0,0 +1,20 @@ +-- Deploy schemas/function_resolution/procedures/staging_bucket_tag to pg + +-- requires: schemas/function_resolution/schema + +BEGIN; + +-- staging_bucket_tag: the reserved tag for "the bucket this module stages +-- uploads through before they are promoted", in one place. +-- +-- Third label in the same reserved vocabulary default_bucket_tag owns +-- ('default', 'default-public'), for the same reason: staging is a bucket a +-- tenant labelled, resolved by the tag rule capabilities already use, not a +-- boolean on the row. A staging bucket is additionally a 'temp' bucket, so +-- resolution filters on type as well and a mislabelled permanent bucket cannot +-- become a staging destination by tag alone. +CREATE FUNCTION function_resolution.staging_bucket_tag() RETURNS text AS $$ + SELECT 'default-temp'; +$$ LANGUAGE sql IMMUTABLE; + +COMMIT; diff --git a/packages/function-resolution/pgpm.plan b/packages/function-resolution/pgpm.plan index 3c9a1435..e6aeb36d 100644 --- a/packages/function-resolution/pgpm.plan +++ b/packages/function-resolution/pgpm.plan @@ -13,6 +13,8 @@ schemas/function_resolution/procedures/bucket_matches [schemas/function_resoluti schemas/function_resolution/procedures/resolve_bucket [schemas/function_resolution/schema schemas/function_resolution/procedures/frame_candidates schemas/function_resolution/procedures/bucket_matches] 2017-08-11T08:11:51Z constructive # bucket selector {tags,type} resolution schemas/function_resolution/procedures/default_bucket_tag [schemas/function_resolution/schema] 2017-08-11T08:11:51Z constructive # reserved default-bucket tag vocabulary schemas/function_resolution/procedures/resolve_default_bucket [schemas/function_resolution/schema schemas/function_resolution/procedures/bucket_matches schemas/function_resolution/procedures/default_bucket_tag] 2017-08-11T08:11:51Z constructive # the database's default bucket, or an explicit key +schemas/function_resolution/procedures/staging_bucket_tag [schemas/function_resolution/schema] 2017-08-11T08:11:51Z constructive # reserved staging-bucket tag vocabulary +schemas/function_resolution/procedures/resolve_staging_bucket [schemas/function_resolution/schema schemas/function_resolution/procedures/bucket_matches schemas/function_resolution/procedures/staging_bucket_tag] 2017-08-11T08:11:51Z constructive # the module's staging bucket for uploads schemas/function_resolution/procedures/bucket_catalog_row [schemas/function_resolution/schema schemas/function_resolution/procedures/frame_candidates] 2017-08-11T08:11:51Z constructive # reachable bucket by id (same-tenant proof) schemas/function_resolution/procedures/api_catalog_row [schemas/function_resolution/schema schemas/function_resolution/procedures/frame_candidates] 2017-08-11T08:11:51Z constructive # reachable api by id schemas/function_resolution/procedures/resolve_api [schemas/function_resolution/schema schemas/function_resolution/procedures/frame_candidates schemas/function_resolution/procedures/api_catalog_row] 2017-08-11T08:11:51Z constructive # api selector module:/name: resolution diff --git a/packages/function-resolution/revert/schemas/function_resolution/procedures/resolve_staging_bucket.sql b/packages/function-resolution/revert/schemas/function_resolution/procedures/resolve_staging_bucket.sql new file mode 100644 index 00000000..173ef4a9 --- /dev/null +++ b/packages/function-resolution/revert/schemas/function_resolution/procedures/resolve_staging_bucket.sql @@ -0,0 +1,7 @@ +-- Revert schemas/function_resolution/procedures/resolve_staging_bucket from pg + +BEGIN; + +DROP FUNCTION function_resolution.resolve_staging_bucket(uuid, text, uuid); + +COMMIT; diff --git a/packages/function-resolution/revert/schemas/function_resolution/procedures/staging_bucket_tag.sql b/packages/function-resolution/revert/schemas/function_resolution/procedures/staging_bucket_tag.sql new file mode 100644 index 00000000..c07246ee --- /dev/null +++ b/packages/function-resolution/revert/schemas/function_resolution/procedures/staging_bucket_tag.sql @@ -0,0 +1,7 @@ +-- Revert schemas/function_resolution/procedures/staging_bucket_tag from pg + +BEGIN; + +DROP FUNCTION function_resolution.staging_bucket_tag(); + +COMMIT; diff --git a/packages/function-resolution/sql/pgpm-function-resolution--0.40.0.bundle.tar.gz b/packages/function-resolution/sql/pgpm-function-resolution--0.40.0.bundle.tar.gz index 2efd16d5..d6b24f7f 100644 Binary files a/packages/function-resolution/sql/pgpm-function-resolution--0.40.0.bundle.tar.gz and b/packages/function-resolution/sql/pgpm-function-resolution--0.40.0.bundle.tar.gz differ diff --git a/packages/function-resolution/sql/pgpm-function-resolution--0.40.0.sql b/packages/function-resolution/sql/pgpm-function-resolution--0.40.0.sql index 8a74261a..d64bf13b 100644 --- a/packages/function-resolution/sql/pgpm-function-resolution--0.40.0.sql +++ b/packages/function-resolution/sql/pgpm-function-resolution--0.40.0.sql @@ -658,6 +658,88 @@ BEGIN END; $EOFCODE$ LANGUAGE plpgsql STABLE SECURITY DEFINER; +CREATE FUNCTION function_resolution.staging_bucket_tag() RETURNS text AS $EOFCODE$ + SELECT 'default-temp'; +$EOFCODE$ LANGUAGE sql IMMUTABLE; + +CREATE FUNCTION function_resolution.resolve_staging_bucket( + database_id uuid, + scope text, + entity_id uuid +) RETURNS TABLE ( + bucket_id uuid, + resolved_key text, + bucket_type text, + physical_name text, + owner_database_id uuid, + owner_scope text, + owner_key uuid +) AS $EOFCODE$ +DECLARE + v_tag text; + v_matches jsonb; + v_match jsonb; +BEGIN + v_tag := function_resolution.staging_bucket_tag(); + + SELECT COALESCE(jsonb_agg(to_jsonb(m) ORDER BY m.bucket_id), '[]'::jsonb) + INTO v_matches + FROM function_resolution.bucket_matches( + resolve_staging_bucket.database_id, + resolve_staging_bucket.scope, + resolve_staging_bucket.entity_id, + ARRAY[v_tag], + 'temp' + ) m; + + IF jsonb_array_length(v_matches) = 0 THEN + PERFORM errors.raise_error( + 'STORAGE_STAGING_BUCKET_NOT_FOUND', + jsonb_build_object( + 'database_id', resolve_staging_bucket.database_id, + 'scope', resolve_staging_bucket.scope, + 'entity_id', resolve_staging_bucket.entity_id, + 'tag', v_tag + ), + 'internal' + ); + END IF; + + IF jsonb_array_length(v_matches) > 1 THEN + PERFORM errors.raise_error( + 'STORAGE_STAGING_BUCKET_AMBIGUOUS', + jsonb_build_object( + 'database_id', resolve_staging_bucket.database_id, + 'scope', resolve_staging_bucket.scope, + 'entity_id', resolve_staging_bucket.entity_id, + 'tag', v_tag, + 'candidates', ( + SELECT jsonb_agg(jsonb_build_object( + 'bucket_id', c->>'bucket_id', + 'key', c->>'bucket_key', + 'type', c->>'bucket_type' + ) ORDER BY c->>'bucket_key') + FROM jsonb_array_elements(v_matches) c + ) + ), + 'internal' + ); + END IF; + + v_match := v_matches->0; + + resolve_staging_bucket.bucket_id := (v_match->>'bucket_id')::uuid; + resolve_staging_bucket.resolved_key := v_match->>'bucket_key'; + resolve_staging_bucket.bucket_type := v_match->>'bucket_type'; + resolve_staging_bucket.physical_name := v_match->>'physical_name'; + resolve_staging_bucket.owner_database_id := (v_match->>'owner_database_id')::uuid; + resolve_staging_bucket.owner_scope := v_match->>'owner_scope'; + resolve_staging_bucket.owner_key := (v_match->>'owner_key')::uuid; + + RETURN NEXT; +END; +$EOFCODE$ LANGUAGE plpgsql STABLE SECURITY DEFINER; + CREATE FUNCTION function_resolution.bucket_catalog_row( database_id uuid, scope text, diff --git a/packages/function-resolution/verify/schemas/function_resolution/procedures/resolve_staging_bucket.sql b/packages/function-resolution/verify/schemas/function_resolution/procedures/resolve_staging_bucket.sql new file mode 100644 index 00000000..5b6408fd --- /dev/null +++ b/packages/function-resolution/verify/schemas/function_resolution/procedures/resolve_staging_bucket.sql @@ -0,0 +1,7 @@ +-- Verify schemas/function_resolution/procedures/resolve_staging_bucket on pg + +BEGIN; + +SELECT assert_function('function_resolution.resolve_staging_bucket(uuid, text, uuid)'::regprocedure); + +ROLLBACK; diff --git a/packages/function-resolution/verify/schemas/function_resolution/procedures/staging_bucket_tag.sql b/packages/function-resolution/verify/schemas/function_resolution/procedures/staging_bucket_tag.sql new file mode 100644 index 00000000..2a20ff49 --- /dev/null +++ b/packages/function-resolution/verify/schemas/function_resolution/procedures/staging_bucket_tag.sql @@ -0,0 +1,7 @@ +-- Verify schemas/function_resolution/procedures/staging_bucket_tag on pg + +BEGIN; + +SELECT assert_function('function_resolution.staging_bucket_tag()'::regprocedure); + +ROLLBACK; diff --git a/packages/metaschema-modules/__tests__/__snapshots__/modules.test.ts.snap b/packages/metaschema-modules/__tests__/__snapshots__/modules.test.ts.snap index aaa56b07..0d7567e1 100644 --- a/packages/metaschema-modules/__tests__/__snapshots__/modules.test.ts.snap +++ b/packages/metaschema-modules/__tests__/__snapshots__/modules.test.ts.snap @@ -8,10 +8,12 @@ exports[`db_meta_modules should have all expected module tables 1`] = ` "app_module", "billing_module", "billing_provider_module", + "capabilities_module", "catalog_module", "compute_log_module", "config_secrets_user_module", "connected_accounts_module", + "content_preset_module", "crypto_addresses_module", "crypto_auth_module", "database_settings_module", @@ -20,6 +22,7 @@ exports[`db_meta_modules should have all expected module tables 1`] = ` "default_ids_module", "devices_module", "domain_module", + "email_sender_module", "emails_module", "events_module", "function_deployment_module", @@ -43,8 +46,8 @@ exports[`db_meta_modules should have all expected module tables 1`] = ` "merkle_store_module", "namespace_module", "notifications_module", + "oauth_requests_module", "pages_module", - "permissions_module", "phone_numbers_module", "plans_module", "principal_auth_module", @@ -77,8 +80,8 @@ exports[`db_meta_modules should have all expected module tables 1`] = ` exports[`db_meta_modules should verify all module tables exist in metaschema_modules_public schema 1`] = ` { - "moduleTablesCount": 68, - "totalTables": 75, + "moduleTablesCount": 71, + "totalTables": 80, } `; @@ -145,13 +148,13 @@ exports[`db_meta_modules should verify emails_module table structure 1`] = ` exports[`db_meta_modules should verify module table structures have database_id foreign keys 1`] = ` { - "constraintCount": 68, + "constraintCount": 71, } `; exports[`db_meta_modules should verify module tables have proper foreign key relationships 1`] = ` { - "constraintCount": 486, + "constraintCount": 505, "foreignTables": [ "catalog_module", "database", diff --git a/packages/metaschema-modules/__tests__/modules.test.ts b/packages/metaschema-modules/__tests__/modules.test.ts index 115cc19a..b1b02098 100644 --- a/packages/metaschema-modules/__tests__/modules.test.ts +++ b/packages/metaschema-modules/__tests__/modules.test.ts @@ -36,7 +36,7 @@ describe('db_meta_modules', () => { 'limits_module', 'membership_types_module', 'memberships_module', - 'permissions_module', + 'capabilities_module', 'phone_numbers_module', 'rls_module', 'user_state_module', diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/agent_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/agent_module/table.sql index 10cd8713..d6c81128 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/agent_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/agent_module/table.sql @@ -76,9 +76,9 @@ CREATE TABLE metaschema_modules_public.agent_module ( -- secure_table_provision applies the custom grants/policies instead. provisions jsonb NULL, - -- Default permissions: permission names auto-granted to new members. + -- Default capabilities: capability names auto-granted to new members. -- NULL uses the module's built-in defaults; explicit array overrides them. - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, -- Constraints CONSTRAINT agent_module_db_fkey FOREIGN KEY (database_id) REFERENCES metaschema_public.database (id) ON DELETE CASCADE, diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/api_surface_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/api_surface_module/table.sql index 021ed405..76ec941e 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/api_surface_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/api_surface_module/table.sql @@ -55,8 +55,8 @@ CREATE TABLE metaschema_modules_public.api_surface_module ( -- Per-table provisions overrides from blueprint config provisions jsonb NULL, - -- Default permissions: permission names auto-granted to new members - default_permissions text[] DEFAULT NULL, + -- Default capabilities: capability names auto-granted to new members + default_capabilities text[] DEFAULT NULL, CONSTRAINT api_module_db_fkey FOREIGN KEY (database_id) diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/app_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/app_module/table.sql index 664f4b0f..3c57a1e0 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/app_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/app_module/table.sql @@ -53,8 +53,8 @@ CREATE TABLE metaschema_modules_public.app_module ( -- Per-table provisions overrides from blueprint config provisions jsonb NULL, - -- Default permissions: permission names auto-granted to new members - default_permissions text[] DEFAULT NULL, + -- Default capabilities: capability names auto-granted to new members + default_capabilities text[] DEFAULT NULL, CONSTRAINT app_module_db_fkey FOREIGN KEY (database_id) diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/billing_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/billing_module/table.sql index fc62424a..a28278e9 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/billing_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/billing_module/table.sql @@ -56,9 +56,9 @@ CREATE TABLE metaschema_modules_public.billing_module ( -- NULL seeds nothing (clean catalog). default_meter_catalog jsonb DEFAULT NULL, - -- Default permissions: permission names auto-granted to new members. + -- Default capabilities: capability names auto-granted to new members. -- NULL uses the module's built-in defaults; explicit array overrides them. - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, -- API routing (configurable per-module) api_name text DEFAULT 'usage', diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/permissions_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/capabilities_module/table.sql similarity index 71% rename from packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/permissions_module/table.sql rename to packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/capabilities_module/table.sql index ab71cf2d..496b3889 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/permissions_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/capabilities_module/table.sql @@ -1,10 +1,10 @@ --- Deploy schemas/metaschema_modules_public/tables/permissions_module/table to pg +-- Deploy schemas/metaschema_modules_public/tables/capabilities_module/table to pg -- requires: schemas/metaschema_modules_public/schema BEGIN; -CREATE TABLE metaschema_modules_public.permissions_module ( +CREATE TABLE metaschema_modules_public.capabilities_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, @@ -28,9 +28,9 @@ CREATE TABLE metaschema_modules_public.permissions_module ( default_table_name text NOT NULL DEFAULT '', -- - -- Default bit-width of the permission mask for this module. + -- Default bit-width of the capability mask for this module. -- - -- Chosen to maximize permission headroom without costing extra storage or + -- Chosen to maximize capability headroom without costing extra storage or -- compute. PostgreSQL lays out heap tuples to MAXALIGN (8 bytes on x86_64), -- so the row-size bucket that holds bit(24) already extends up to bit(64): -- @@ -42,14 +42,14 @@ CREATE TABLE metaschema_modules_public.permissions_module ( -- 65 | 73 | 81 MB | 47 MB <-- next bucket -- -- Bitwise AND/OR on bit(<=64) fits in a single 64-bit machine word, so - -- permission checks at 64 cost the same as at 24. Raising the default from - -- 24 to 64 gives new modules 6.4x more permission slots before anyone has - -- to think about running update_bitlen_permissions, at identical storage + -- capability checks at 64 cost the same as at 24. Raising the default from + -- 24 to 64 gives new modules 6.4x more capability slots before anyone has + -- to think about running update_bitlen_capabilities, at identical storage -- and compute cost. Do not raise past 64 casually -- bit(65+) jumps to the -- next 8-byte tuple bucket (+~10% heap) and pays on every write. -- -- Existing databases are unaffected; this only changes the default for - -- newly inserted permissions_module rows. + -- newly inserted capabilities_module rows. bitlen int NOT NULL DEFAULT 64, -- Scope: determines the security level for this module instance. @@ -86,12 +86,12 @@ CREATE TABLE metaschema_modules_public.permissions_module ( CONSTRAINT actor_table_fkey FOREIGN KEY (actor_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE ); -CREATE INDEX permissions_module_database_id_idx ON metaschema_modules_public.permissions_module ( database_id ); -CREATE INDEX permissions_module_actor_table_id_idx ON metaschema_modules_public.permissions_module ( actor_table_id ); -CREATE INDEX permissions_module_default_table_id_idx ON metaschema_modules_public.permissions_module ( default_table_id ); -CREATE INDEX permissions_module_entity_table_id_idx ON metaschema_modules_public.permissions_module ( entity_table_id ); -CREATE INDEX permissions_module_table_id_idx ON metaschema_modules_public.permissions_module ( table_id ); -CREATE INDEX permissions_module_private_schema_id_idx ON metaschema_modules_public.permissions_module ( private_schema_id ); -CREATE INDEX permissions_module_schema_id_idx ON metaschema_modules_public.permissions_module ( schema_id ); +CREATE INDEX capabilities_module_database_id_idx ON metaschema_modules_public.capabilities_module ( database_id ); +CREATE INDEX capabilities_module_actor_table_id_idx ON metaschema_modules_public.capabilities_module ( actor_table_id ); +CREATE INDEX capabilities_module_default_table_id_idx ON metaschema_modules_public.capabilities_module ( default_table_id ); +CREATE INDEX capabilities_module_entity_table_id_idx ON metaschema_modules_public.capabilities_module ( entity_table_id ); +CREATE INDEX capabilities_module_table_id_idx ON metaschema_modules_public.capabilities_module ( table_id ); +CREATE INDEX capabilities_module_private_schema_id_idx ON metaschema_modules_public.capabilities_module ( private_schema_id ); +CREATE INDEX capabilities_module_schema_id_idx ON metaschema_modules_public.capabilities_module ( schema_id ); COMMIT; diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/catalog_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/catalog_module/table.sql index 6e179049..bd811a26 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/catalog_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/catalog_module/table.sql @@ -72,8 +72,8 @@ CREATE TABLE metaschema_modules_public.catalog_module ( -- Per-table provisions overrides from blueprint config provisions jsonb NULL, - -- Default permissions: permission names auto-granted to new members - default_permissions text[] DEFAULT NULL, + -- Default capabilities: capability names auto-granted to new members + default_capabilities text[] DEFAULT NULL, CONSTRAINT catalog_module_db_fkey FOREIGN KEY (database_id) diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/content_preset_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/content_preset_module/table.sql new file mode 100644 index 00000000..247b8c70 --- /dev/null +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/content_preset_module/table.sql @@ -0,0 +1,44 @@ +-- Deploy schemas/metaschema_modules_public/tables/content_preset_module/table to pg + +-- requires: schemas/metaschema_modules_public/schema +-- requires: schemas/metaschema_modules_public/tables/merkle_store_module/table + +BEGIN; + +CREATE TABLE metaschema_modules_public.content_preset_module ( + id uuid PRIMARY KEY DEFAULT uuidv7(), + database_id uuid NOT NULL, + public_schema_id uuid NOT NULL DEFAULT uuid_nil(), + private_schema_id uuid NOT NULL DEFAULT uuid_nil(), + public_schema_name text, + private_schema_name text, + scope text NOT NULL, + prefix text NOT NULL, + merkle_store_module_id uuid NOT NULL, + content_presets_table_id uuid NOT NULL DEFAULT uuid_nil(), + + -- Store row name inside the Merkle store tables (one shared infra store) + store_name text NOT NULL, + + api_name text, + private_api_name text, + entity_table_id uuid NULL, + policies jsonb NULL, + provisions jsonb NULL, + created_at timestamptz NOT NULL DEFAULT now(), + CONSTRAINT db_fkey FOREIGN KEY (database_id) REFERENCES metaschema_public.database (id) ON DELETE CASCADE, + CONSTRAINT public_schema_fkey FOREIGN KEY (public_schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, + CONSTRAINT private_schema_fkey FOREIGN KEY (private_schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, + CONSTRAINT merkle_store_fkey FOREIGN KEY (merkle_store_module_id) REFERENCES metaschema_modules_public.merkle_store_module (id) ON DELETE CASCADE, + CONSTRAINT content_presets_table_fkey FOREIGN KEY (content_presets_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT content_preset_module_entity_table_fkey FOREIGN KEY (entity_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT content_preset_module_database_merkle_unique UNIQUE (database_id, merkle_store_module_id) +); + +CREATE INDEX content_preset_module_entity_table_id_idx ON metaschema_modules_public.content_preset_module ( entity_table_id ); +CREATE INDEX content_preset_module_content_presets_table_id_idx ON metaschema_modules_public.content_preset_module ( content_presets_table_id ); +CREATE INDEX content_preset_module_private_schema_id_idx ON metaschema_modules_public.content_preset_module ( private_schema_id ); +CREATE INDEX content_preset_module_public_schema_id_idx ON metaschema_modules_public.content_preset_module ( public_schema_id ); +CREATE INDEX content_preset_module_merkle_store_module_id_idx ON metaschema_modules_public.content_preset_module ( merkle_store_module_id ); + +COMMIT; diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/data_capabilities_field/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/data_capabilities_field/table.sql new file mode 100644 index 00000000..fb4f3427 --- /dev/null +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/data_capabilities_field/table.sql @@ -0,0 +1,97 @@ +-- Deploy schemas/metaschema_modules_public/tables/data_capabilities_field/table to pg + +-- requires: schemas/metaschema_modules_public/schema +-- requires: schemas/metaschema_modules_public/tables/capabilities_module/table + +BEGIN; + +-- One row per DataCapabilities declaration — a schema usage, never a record. +-- +-- The SPRT materializes what an actor holds; this registry marks the column +-- that materializes what a row requires, so a read is a flat bitwise subset +-- test (sprt.capabilities & row.required = row.required) with no join onto a +-- grant table. +-- +-- Two set-level questions are answered here, and both are the reason the +-- declaration is recorded rather than inferred: +-- +-- 1. bitlen — widening a scope's capability mask must repad every row mask +-- and every mapping mask that feeds one, or the AND becomes a type error. +-- 2. propagation — a derived mask is stamped from a mapping row, so a mask +-- change on the mapping table has to find the tables that copied it. +CREATE TABLE metaschema_modules_public.data_capabilities_field ( + id uuid PRIMARY KEY DEFAULT uuidv7(), + database_id uuid NOT NULL, + + -- The protected table and its bit(bitlen) required-permissions column. + table_id uuid NOT NULL, + field_id uuid NOT NULL, + + -- The capability vocabulary the mask is written in. Names it explicitly: + -- bitlen and capability names are per (scope, prefix), so a mask is only + -- meaningful against one module. + capabilities_module_id uuid NOT NULL, + + -- 'direct' — the mask is written on the protected row. + -- 'derived' — the mask is stamped from a mapping row (classification etc.). + mode text NOT NULL DEFAULT 'direct', + + -- Derived mode only: the FK on the protected table, the mapping table it + -- points into, the key column it points at, and the mapping table's own + -- mask column that gets copied down. + from_field_id uuid NULL, + mapping_table_id uuid NULL, + mapping_key_field_id uuid NULL, + mapping_field_id uuid NULL, + + -- Direct mode only: writes are guarded so a writer cannot require a bit + -- they do not themselves hold (new_mask & writer_mask = new_mask). + subset_guard boolean NOT NULL DEFAULT true, + + CONSTRAINT db_fkey FOREIGN KEY (database_id) REFERENCES metaschema_public.database (id) ON DELETE CASCADE, + CONSTRAINT table_fkey FOREIGN KEY (table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT field_fkey FOREIGN KEY (field_id) REFERENCES metaschema_public.field (id) ON DELETE CASCADE, + CONSTRAINT capabilities_module_fkey FOREIGN KEY (capabilities_module_id) REFERENCES metaschema_modules_public.capabilities_module (id) ON DELETE CASCADE, + CONSTRAINT from_field_fkey FOREIGN KEY (from_field_id) REFERENCES metaschema_public.field (id) ON DELETE CASCADE, + CONSTRAINT mapping_table_fkey FOREIGN KEY (mapping_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT mapping_key_field_fkey FOREIGN KEY (mapping_key_field_id) REFERENCES metaschema_public.field (id) ON DELETE CASCADE, + CONSTRAINT mapping_field_fkey FOREIGN KEY (mapping_field_id) REFERENCES metaschema_public.field (id) ON DELETE CASCADE, + + CONSTRAINT mode_chk CHECK (mode IN ('direct', 'derived')), + + -- Derived mode is the whole propagation path or none of it: a half-declared + -- mapping stamps nothing and silently leaves rows at their default mask. + CONSTRAINT derived_mode_chk CHECK ( + (mode = 'derived' + AND from_field_id IS NOT NULL + AND mapping_table_id IS NOT NULL + AND mapping_key_field_id IS NOT NULL + AND mapping_field_id IS NOT NULL) + OR + (mode = 'direct' + AND from_field_id IS NULL + AND mapping_table_id IS NULL + AND mapping_key_field_id IS NULL + AND mapping_field_id IS NULL) + ), + + -- One mask column carries one row's requirement, so one row describes it. + UNIQUE (field_id) +); + +CREATE INDEX data_capabilities_field_database_id_idx ON metaschema_modules_public.data_capabilities_field ( database_id ); +CREATE INDEX data_capabilities_field_table_id_idx ON metaschema_modules_public.data_capabilities_field ( table_id ); + +-- The bitlen migration asks "every row mask written in this module's vocabulary". +CREATE INDEX data_capabilities_field_capabilities_module_id_idx ON metaschema_modules_public.data_capabilities_field ( capabilities_module_id ); + +-- Restamping asks "which protected tables copied this mapping table's mask". +CREATE INDEX data_capabilities_field_mapping_table_id_idx ON metaschema_modules_public.data_capabilities_field ( mapping_table_id ); + +-- The derived-mode field references, so dropping a field does not scan this +-- table three times to find its cascades (field_id is covered by its UNIQUE). +CREATE INDEX data_capabilities_field_from_field_id_idx ON metaschema_modules_public.data_capabilities_field ( from_field_id ); +CREATE INDEX data_capabilities_field_mapping_key_field_id_idx ON metaschema_modules_public.data_capabilities_field ( mapping_key_field_id ); +CREATE INDEX data_capabilities_field_mapping_field_id_idx ON metaschema_modules_public.data_capabilities_field ( mapping_field_id ); + +COMMIT; diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/database_settings_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/database_settings_module/table.sql index ef16a177..e22caa51 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/database_settings_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/database_settings_module/table.sql @@ -49,8 +49,8 @@ CREATE TABLE metaschema_modules_public.database_settings_module ( -- Per-table provisions overrides from blueprint config provisions jsonb NULL, - -- Default permissions: permission names auto-granted to new members - default_permissions text[] DEFAULT NULL, + -- Default capabilities: capability names auto-granted to new members + default_capabilities text[] DEFAULT NULL, CONSTRAINT database_settings_module_db_fkey FOREIGN KEY (database_id) diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/db_usage_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/db_usage_module/table.sql index ebb2f173..375e3169 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/db_usage_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/db_usage_module/table.sql @@ -53,9 +53,9 @@ CREATE TABLE metaschema_modules_public.db_usage_module ( -- Table name prefix. Auto-derived from scope by the trigger when empty. prefix text NOT NULL DEFAULT '', - -- Default permissions: permission names auto-granted to new members. + -- Default capabilities: capability names auto-granted to new members. -- NULL uses the module's built-in defaults; explicit array overrides them. - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, -- API routing (configurable per-module) api_name text DEFAULT 'usage', diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/domain_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/domain_module/table.sql index aa394198..1601d6f1 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/domain_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/domain_module/table.sql @@ -62,8 +62,8 @@ CREATE TABLE metaschema_modules_public.domain_module ( -- Per-table provisions overrides from blueprint config provisions jsonb NULL, - -- Default permissions: permission names auto-granted to new members - default_permissions text[] DEFAULT NULL, + -- Default capabilities: capability names auto-granted to new members + default_capabilities text[] DEFAULT NULL, CONSTRAINT domain_module_db_fkey FOREIGN KEY (database_id) diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/email_sender_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/email_sender_module/table.sql new file mode 100644 index 00000000..58057f4a --- /dev/null +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/email_sender_module/table.sql @@ -0,0 +1,86 @@ +-- Deploy schemas/metaschema_modules_public/tables/email_sender_module/table to pg + +-- requires: schemas/metaschema_modules_public/schema +-- requires: schemas/metaschema_modules_public/tables/site_surface_module/table + +BEGIN; + +CREATE TABLE metaschema_modules_public.email_sender_module ( + id uuid PRIMARY KEY DEFAULT uuidv7(), + database_id uuid NOT NULL, + + -- Scope-key column name on the generated tables. The insert trigger records + -- database_id for database scope, entity_id for entity scopes, NULL for + -- global tiers. Consumers read this instead of re-deriving the literal. + entity_field text, + + -- Schema reference (uuid_nil is resolved from schema names/defaults). + schema_id uuid NOT NULL DEFAULT uuid_nil(), + public_schema_name text, + + -- Generated table IDs. The site binding table is only generated when a + -- site_surface_module exists at the same scope, so it stays nullable. + email_provider_accounts_table_id uuid NOT NULL DEFAULT uuid_nil(), + email_identities_table_id uuid NOT NULL DEFAULT uuid_nil(), + email_site_identities_table_id uuid NULL, + + -- Bare table names; the trigger prepends the scope prefix. + email_provider_accounts_table_name text NOT NULL DEFAULT 'email_provider_accounts', + email_identities_table_name text NOT NULL DEFAULT 'email_identities', + email_site_identities_table_name text NOT NULL DEFAULT 'email_site_identities', + + -- Site surface at the exact same scope, when one exists. + site_surface_module_id uuid, + + -- API routing (optional administrative CRUD surface). + api_name text, + private_api_name text, + + scope text NOT NULL, + prefix text NOT NULL DEFAULT '', + entity_table_id uuid NULL, + + policies jsonb NULL, + provisions jsonb NULL, + default_capabilities text[] DEFAULT NULL, + + CONSTRAINT email_sender_module_db_fkey + FOREIGN KEY (database_id) + REFERENCES metaschema_public.database (id) + ON DELETE CASCADE, + CONSTRAINT email_sender_module_schema_fkey + FOREIGN KEY (schema_id) + REFERENCES metaschema_public.schema (id) + ON DELETE CASCADE, + CONSTRAINT email_sender_module_provider_accounts_table_fkey + FOREIGN KEY (email_provider_accounts_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT email_sender_module_identities_table_fkey + FOREIGN KEY (email_identities_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT email_sender_module_site_identities_table_fkey + FOREIGN KEY (email_site_identities_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT email_sender_module_site_surface_module_fkey + FOREIGN KEY (site_surface_module_id) + REFERENCES metaschema_modules_public.site_surface_module (id) + ON DELETE CASCADE, + CONSTRAINT email_sender_module_entity_table_fkey + FOREIGN KEY (entity_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE +); + +CREATE UNIQUE INDEX email_sender_module_unique_scope + ON metaschema_modules_public.email_sender_module (database_id, scope); +CREATE INDEX email_sender_module_schema_id_idx ON metaschema_modules_public.email_sender_module ( schema_id ); +CREATE INDEX email_sender_module_provider_accounts_table_id_idx ON metaschema_modules_public.email_sender_module ( email_provider_accounts_table_id ); +CREATE INDEX email_sender_module_identities_table_id_idx ON metaschema_modules_public.email_sender_module ( email_identities_table_id ); +CREATE INDEX email_sender_module_site_identities_table_id_idx ON metaschema_modules_public.email_sender_module ( email_site_identities_table_id ); +CREATE INDEX email_sender_module_site_surface_module_id_idx ON metaschema_modules_public.email_sender_module ( site_surface_module_id ); +CREATE INDEX email_sender_module_entity_table_id_idx ON metaschema_modules_public.email_sender_module ( entity_table_id ); + +COMMIT; diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/entity_type_provision/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/entity_type_provision/table.sql index e2665f92..d8861d13 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/entity_type_provision/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/entity_type_provision/table.sql @@ -156,7 +156,7 @@ CREATE TABLE metaschema_modules_public.entity_type_provision ( COMMENT ON TABLE metaschema_modules_public.entity_type_provision IS 'Provisions a new membership entity type. Each INSERT creates an entity table, registers a membership type, - and installs the required modules (permissions, memberships, limits) plus optional modules (profiles, levels, invites). + and installs the required modules (capabilities, memberships, limits) plus optional modules (profiles, levels, invites). Uses provision_membership_table() internally. Graceful: duplicate (database_id, prefix) pairs are silently skipped via the unique constraint (use INSERT ... ON CONFLICT DO NOTHING). Policy behavior: by default the five entity-table RLS policies are applied (gated by is_visible). @@ -180,7 +180,7 @@ COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.name IS COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.prefix IS 'SQL prefix used for table and module naming, e.g. ''data_room'', ''team_channel''. Required. - Drives entity table name (prefix || ''s'' by default), module labels (permissions_module:prefix), + Drives entity table name (prefix || ''s'' by default), module labels (capabilities_module:prefix), and membership table names (prefix_memberships, prefix_members, etc.). Must be unique per database — the (database_id, prefix) constraint ensures graceful ON CONFLICT DO NOTHING.'; @@ -231,8 +231,11 @@ COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.has_limits IS COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.has_profiles IS 'Whether to provision profiles_module for this type. Defaults to false. - Profiles provide named permission roles (e.g. ''Editor'', ''Viewer'') with pre-configured permission bitmasks. - When true, creates profile tables and applies profiles security.'; + Profiles provide named capability roles (e.g. ''Editor'', ''Viewer'') with pre-configured capability bitmasks. + When true, creates profile tables and applies profiles security. A membership may hold + any number of profiles: the membership_profiles assignment table holds every profile a + membership holds and the membership mask is granted | bit_or(held profile masks), with + memberships.profile_id kept as a pointer at one held profile.'; COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.has_levels IS 'Whether to provision events_module for this type. Defaults to false. @@ -270,8 +273,8 @@ COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.skip_entity_po Defaults (applied when table_provision IS NULL and skip_entity_policies=false): - SELECT (parent_member): parent entity members can see child entities (only when is_visible=true) - SELECT (self_member): direct members of the entity can see it - - INSERT: create_entity permission on the parent entity - - UPDATE: admin_entity permission on the entity itself + - INSERT: create_entity capability on the parent entity + - UPDATE: admin_entity capability on the entity itself - DELETE: owner of the entity can delete it'; COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.table_provision IS @@ -321,7 +324,7 @@ COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.out_entity_tab 'Output: the name of the created entity table (e.g. ''data_rooms''). Populated by the trigger.'; COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.out_installed_modules IS - 'Output: array of installed module labels (e.g. ARRAY[''permissions_module:data_room'', ''memberships_module:data_room'', ''invites_module:data_room'']). + 'Output: array of installed module labels (e.g. ARRAY[''capabilities_module:data_room'', ''memberships_module:data_room'', ''invites_module:data_room'']). Populated by the trigger. Useful for verifying which modules were provisioned.'; COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.storage IS @@ -339,7 +342,7 @@ COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.storage IS - download_url_expiry_seconds (integer) presigned GET URL expiry override - default_max_file_size (bigint) global max file size in bytes for this module - allowed_origins (text[]) default CORS origins for all buckets in this module - - restrict_reads (boolean) require read_files permission for SELECT on files + - restrict_reads (boolean) require read_files capability for SELECT on files - has_path_shares (boolean) enable virtual filesystem + path share policies - has_versioning (boolean) enable file version chains - has_content_hash (boolean) enable content hash for dedup @@ -378,7 +381,7 @@ COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.namespaces IS - policies (jsonb array) RLS policy overrides. NULL = apply defaults from apply_namespace_security(). Creates {prefix}_namespaces (or {prefix}_{key}_namespaces for non-default keys) with entity-scoped RLS (AuthzEntityMembership) and a rename proxy trigger. - Registers manage_namespaces permission bit on first provision. + Registers manage_namespaces capability bit on first provision. Example: namespaces := ''[{}]''::jsonb'; COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.functions IS @@ -389,7 +392,7 @@ COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.functions IS - policies (jsonb array) RLS policy overrides. NULL = apply defaults from apply_function_security(). Creates {prefix}_function_definitions (or {prefix}_{key}_function_definitions for non-default keys) with entity-scoped RLS and a job trigger dispatching function:provision tasks. - Registers manage_functions + invoke_functions permission bits on first provision. + Registers manage_functions + invoke_functions capability bits on first provision. Example: functions := ''[{}]''::jsonb'; COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.graphs IS @@ -398,9 +401,9 @@ COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.graphs IS Each element recognizes (all optional): - key (text) module discriminator. Defaults to ''default''. - policies (jsonb array) RLS policy overrides. NULL = apply defaults from apply_graph_security(). - Registers manage_graphs + execute_graphs permission bits on first provision. + Registers manage_graphs + execute_graphs capability bits on first provision. Graph module requires a merkle_store_module_id dependency, so entity_type_provision - only registers permissions here. The graph module itself must be provisioned + only registers capabilities here. The graph module itself must be provisioned separately with the merkle store dependency resolved. Example: graphs := ''[{}]''::jsonb'; diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/events_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/events_module/table.sql index dabe75a0..c2707c4a 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/events_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/events_module/table.sql @@ -56,6 +56,10 @@ CREATE TABLE metaschema_modules_public.events_module ( tg_check_achievements text NOT NULL DEFAULT '', grant_achievement text NOT NULL DEFAULT '', tg_achievement_reward text NOT NULL DEFAULT '', + revoke_achievement text NOT NULL DEFAULT '', + recompute_capabilities text NOT NULL DEFAULT '', + tg_level_grant_sync text NOT NULL DEFAULT '', + expire_grants text NOT NULL DEFAULT '', -- Partition lifecycle configuration for events table "interval" text NOT NULL DEFAULT '1 month', @@ -75,9 +79,15 @@ CREATE TABLE metaschema_modules_public.events_module ( actor_table_id uuid NOT NULL DEFAULT uuid_nil(), - -- Default permissions: permission names auto-granted to new members. + -- Default capabilities: capability names auto-granted to new members. -- NULL uses the module's built-in defaults; explicit array overrides them. - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, + + -- Trust ladder seeded at provision, as an array of rungs. NULL seeds nothing; + -- the usual value is a content_presets row named by slug, so which evidence + -- counts is data. App scope only — an entity ladder belongs to an + -- organization that does not exist at provision time. + trust_ladder jsonb DEFAULT NULL, -- API routing (configurable per-module) api_name text DEFAULT 'usage', diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/file_ref_field/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/file_ref_field/table.sql new file mode 100644 index 00000000..61e50ab6 --- /dev/null +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/file_ref_field/table.sql @@ -0,0 +1,83 @@ +-- Deploy schemas/metaschema_modules_public/tables/file_ref_field/table to pg + +-- requires: schemas/metaschema_modules_public/schema +-- requires: schemas/metaschema_modules_public/tables/storage_module/table + +BEGIN; + +-- One row per file-ref field declaration — a schema usage, never a record. +-- +-- An upload/image column is a projection of a files row: the doc carries +-- {id, key, mime, bucket_id, ...} copied from it, so a read needs no join. This +-- table is the registry that makes the projection maintainable, and it answers +-- the three set-level questions the storage lane asks: +-- +-- 1. upload — which bucket does a write to this column land in? +-- 2. sync — when a files row changes, which columns project it? +-- 3. gc — before deleting an object, which columns still refer to it? +-- +-- storage_module_id is the load-bearing column. Module identity is +-- (database_id, scope, prefix), and a logical bucket key is owner-local: an +-- entity-scoped module has one 'avatars' bucket per entity row. So the module is +-- named at declaration time, and the physical bucket is resolved per written row +-- from the module's scope plus the row's own scope key. It also identifies the +-- files table a doc's id points into — there is no single global files table, so +-- a bare uuid in a document is unresolvable without it. +-- +-- Intent is stored, never a bucket_id: at declaration time there is no single +-- bucket to record. The concrete bucket lands on the files row and the document +-- when the upload is written. +CREATE TABLE metaschema_modules_public.file_ref_field ( + id uuid PRIMARY KEY DEFAULT uuidv7(), + database_id uuid NOT NULL, + + -- The referring table and its upload/image document column. + table_id uuid NOT NULL, + field_id uuid NOT NULL, + + -- The buckets/files pair this field's references live in. + storage_module_id uuid NOT NULL, + + -- Bucket intent, resolved at upload time by function_resolution: + -- bucket_key — a logical key, resolved inside the tenant + -- bucket_tags — resolve whichever bucket carries these tags + -- neither — the reserved default tag for is_public + -- Never a physical bucket name and never an id: a blueprint names a role, + -- and buckets stay a tenant-owned concern. + bucket_key text, + bucket_tags citext[], + + -- Publicness intent. Selector when no bucket is named (true resolves the + -- 'default-public' reserved tag, false 'default'); assertion when one is + -- (resolution raises when the named bucket's type disagrees). Serving + -- behaviour always comes from the bucket, never from here. + is_public boolean, + + -- Strict mode: a real FK column beside the document, so Postgres enforces + -- referential integrity instead of the GC scan. The column is recorded here, + -- but the generator does not emit the FK yet and refuses a true value rather + -- than accepting configuration it would silently ignore. + enforce_fk boolean NOT NULL DEFAULT false, + + CONSTRAINT db_fkey FOREIGN KEY (database_id) REFERENCES metaschema_public.database (id) ON DELETE CASCADE, + CONSTRAINT table_fkey FOREIGN KEY (table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT field_fkey FOREIGN KEY (field_id) REFERENCES metaschema_public.field (id) ON DELETE CASCADE, + CONSTRAINT storage_module_fkey FOREIGN KEY (storage_module_id) REFERENCES metaschema_modules_public.storage_module (id) ON DELETE CASCADE, + + -- A key and a tag selector are two answers to one question. + CONSTRAINT bucket_intent_chk CHECK (bucket_key IS NULL OR bucket_tags IS NULL), + + -- A blank key would resolve to nothing; refuse it at write time. + CONSTRAINT bucket_key_not_blank_chk CHECK (bucket_key IS NULL OR btrim(bucket_key) <> ''), + + -- One document column projects one file, so one registry row describes it. + UNIQUE (field_id) +); + +CREATE INDEX file_ref_field_database_id_idx ON metaschema_modules_public.file_ref_field ( database_id ); +CREATE INDEX file_ref_field_table_id_idx ON metaschema_modules_public.file_ref_field ( table_id ); + +-- The sync and GC readers both ask "every file-ref field of this module". +CREATE INDEX file_ref_field_storage_module_id_idx ON metaschema_modules_public.file_ref_field ( storage_module_id ); + +COMMIT; diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/function_deployment_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/function_deployment_module/table.sql index 108507a7..ae4fcddb 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/function_deployment_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/function_deployment_module/table.sql @@ -63,9 +63,9 @@ CREATE TABLE metaschema_modules_public.function_deployment_module ( -- secure_table_provision applies the custom grants/policies instead. provisions jsonb NULL, - -- Default permissions: permission names auto-granted to new members. + -- Default capabilities: capability names auto-granted to new members. -- NULL uses the module's built-in defaults; explicit array overrides them. - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, -- Constraints CONSTRAINT function_deployment_module_db_fkey FOREIGN KEY (database_id) REFERENCES metaschema_public.database (id) ON DELETE CASCADE, diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/function_invocation_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/function_invocation_module/table.sql index f2cb10df..a7f85e23 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/function_invocation_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/function_invocation_module/table.sql @@ -54,8 +54,8 @@ CREATE TABLE metaschema_modules_public.function_invocation_module ( -- Keys are table keys (invocations, execution_logs). provisions jsonb NULL, - -- Default permissions: permission names auto-granted to new members. - default_permissions text[] DEFAULT NULL, + -- Default capabilities: capability names auto-granted to new members. + default_capabilities text[] DEFAULT NULL, -- Constraints CONSTRAINT function_invocation_module_db_fkey FOREIGN KEY (database_id) REFERENCES metaschema_public.database (id) ON DELETE CASCADE, diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/function_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/function_module/table.sql index e68d45ff..3f0c50f6 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/function_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/function_module/table.sql @@ -67,9 +67,9 @@ CREATE TABLE metaschema_modules_public.function_module ( -- secure_table_provision applies the custom grants/policies instead. provisions jsonb NULL, - -- Default permissions: permission names auto-granted to new members. + -- Default capabilities: capability names auto-granted to new members. -- NULL uses the module's built-in defaults; explicit array overrides them. - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, -- Constraints CONSTRAINT function_module_db_fkey FOREIGN KEY (database_id) REFERENCES metaschema_public.database (id) ON DELETE CASCADE, diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/graph_execution_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/graph_execution_module/table.sql index 66677cb9..b6fefd56 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/graph_execution_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/graph_execution_module/table.sql @@ -62,8 +62,8 @@ CREATE TABLE metaschema_modules_public.graph_execution_module ( -- Keys are table keys (executions, outputs, node_states). provisions jsonb NULL, - -- Default permissions: permission names auto-granted to new members. - default_permissions text[] DEFAULT NULL, + -- Default capabilities: capability names auto-granted to new members. + default_capabilities text[] DEFAULT NULL, -- Timestamps created_at timestamptz NOT NULL DEFAULT now(), diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/graph_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/graph_module/table.sql index 85f0feef..38dc006c 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/graph_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/graph_module/table.sql @@ -53,9 +53,9 @@ CREATE TABLE metaschema_modules_public.graph_module ( -- secure_table_provision applies the custom grants/policies instead. provisions jsonb NULL, - -- Default permissions: permission names auto-granted to new members. + -- Default capabilities: capability names auto-granted to new members. -- NULL uses the module's built-in defaults; explicit array overrides them. - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, -- Timestamps created_at timestamptz NOT NULL DEFAULT now(), diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/hierarchy_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/hierarchy_module/table.sql index 0717be66..90c0b6df 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/hierarchy_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/hierarchy_module/table.sql @@ -47,9 +47,9 @@ CREATE TABLE metaschema_modules_public.hierarchy_module ( get_managers_function text NOT NULL DEFAULT '', is_manager_of_function text NOT NULL DEFAULT '', - -- Default permissions: permission names auto-granted to new members. + -- Default capabilities: capability names auto-granted to new members. -- NULL uses the module's built-in defaults; explicit array overrides them. - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, -- Timestamps created_at timestamptz NOT NULL DEFAULT now(), diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/http_route_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/http_route_module/table.sql index d51abb19..7fedb29c 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/http_route_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/http_route_module/table.sql @@ -34,7 +34,7 @@ CREATE TABLE metaschema_modules_public.http_route_module ( policies jsonb NULL, provisions jsonb NULL, - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, CONSTRAINT http_route_module_db_fkey FOREIGN KEY (database_id) diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/limits_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/limits_module/table.sql index c8dd2326..fe605a89 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/limits_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/limits_module/table.sql @@ -82,6 +82,11 @@ CREATE TABLE metaschema_modules_public.limits_module ( -- required tables actor_table_id uuid NOT NULL DEFAULT uuid_nil(), + -- Limit defaults seeded at provision, in seed_limit_defaults' shape: + -- [{"name": "api_requests_per_day", "max": 500}, ...]. NULL seeds nothing, + -- which is every tenant that existed before this column. + limit_defaults jsonb DEFAULT NULL, + -- API routing (configurable per-module) api_name text DEFAULT 'usage', private_api_name text DEFAULT NULL, diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/memberships_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/memberships_module/table.sql index edccf1ba..13b5b76c 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/memberships_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/memberships_module/table.sql @@ -40,8 +40,8 @@ CREATE TABLE metaschema_modules_public.memberships_module ( actor_table_id uuid NOT NULL DEFAULT uuid_nil(), limits_table_id uuid NOT NULL DEFAULT uuid_nil(), default_limits_table_id uuid NOT NULL DEFAULT uuid_nil(), - permissions_table_id uuid NOT NULL DEFAULT uuid_nil(), - default_permissions_table_id uuid NOT NULL DEFAULT uuid_nil(), + capabilities_table_id uuid NOT NULL DEFAULT uuid_nil(), + default_capabilities_table_id uuid NOT NULL DEFAULT uuid_nil(), sprt_table_id uuid NOT NULL DEFAULT uuid_nil(), admin_grants_table_id uuid NOT NULL DEFAULT uuid_nil(), @@ -73,9 +73,9 @@ CREATE TABLE metaschema_modules_public.memberships_module ( member_profiles_table_id uuid NULL, - -- Audit tables for permission defaults (created by memberships_module when has_permissions=true) - permission_default_permissions_table_id uuid NULL, - permission_default_grants_table_id uuid NULL, + -- Audit tables for capability defaults (created by memberships_module when has_capabilities=true) + capability_default_capabilities_table_id uuid NULL, + capability_default_grants_table_id uuid NULL, -- API routing (configurable per-module) api_name text DEFAULT 'admin', @@ -98,15 +98,15 @@ CREATE TABLE metaschema_modules_public.memberships_module ( CONSTRAINT limits_table_fkey FOREIGN KEY (limits_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT default_limits_table_fkey FOREIGN KEY (default_limits_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT permissions_table_fkey FOREIGN KEY (permissions_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT default_permissions_table_fkey FOREIGN KEY (default_permissions_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT capabilities_table_fkey FOREIGN KEY (capabilities_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT default_capabilities_table_fkey FOREIGN KEY (default_capabilities_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT memberships_module_unique UNIQUE (database_id, scope, prefix) ); CREATE INDEX memberships_module_actor_table_id_idx ON metaschema_modules_public.memberships_module ( actor_table_id ); CREATE INDEX memberships_module_default_limits_table_id_idx ON metaschema_modules_public.memberships_module ( default_limits_table_id ); -CREATE INDEX memberships_module_default_permissions_table_id_idx ON metaschema_modules_public.memberships_module ( default_permissions_table_id ); +CREATE INDEX memberships_module_default_capabilities_table_id_idx ON metaschema_modules_public.memberships_module ( default_capabilities_table_id ); CREATE INDEX memberships_module_entity_table_id_idx ON metaschema_modules_public.memberships_module ( entity_table_id ); CREATE INDEX memberships_module_grants_table_id_idx ON metaschema_modules_public.memberships_module ( grants_table_id ); CREATE INDEX memberships_module_limits_table_id_idx ON metaschema_modules_public.memberships_module ( limits_table_id ); @@ -114,7 +114,7 @@ CREATE INDEX memberships_module_members_table_id_idx ON metaschema_modules_publi CREATE INDEX memberships_module_membership_defaults_table_id_idx ON metaschema_modules_public.memberships_module ( membership_defaults_table_id ); CREATE INDEX memberships_module_membership_settings_table_id_idx ON metaschema_modules_public.memberships_module ( membership_settings_table_id ); CREATE INDEX memberships_module_memberships_table_id_idx ON metaschema_modules_public.memberships_module ( memberships_table_id ); -CREATE INDEX memberships_module_permissions_table_id_idx ON metaschema_modules_public.memberships_module ( permissions_table_id ); +CREATE INDEX memberships_module_capabilities_table_id_idx ON metaschema_modules_public.memberships_module ( capabilities_table_id ); CREATE INDEX memberships_module_sprt_table_id_idx ON metaschema_modules_public.memberships_module ( sprt_table_id ); CREATE INDEX memberships_module_private_schema_id_idx ON metaschema_modules_public.memberships_module ( private_schema_id ); CREATE INDEX memberships_module_schema_id_idx ON metaschema_modules_public.memberships_module ( schema_id ); diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/merkle_store_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/merkle_store_module/table.sql index d8b1ba43..8b4a83bb 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/merkle_store_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/merkle_store_module/table.sql @@ -44,10 +44,10 @@ CREATE TABLE metaschema_modules_public.merkle_store_module ( -- any other value is used as-is. Tables always keep their prefix regardless of this setting. function_prefix text DEFAULT NULL, - -- Permission key for SELECT gating: when set, all 4 merkle tables require this - -- permission for SELECT at platform/app scope (e.g., 'manage_graphs'). + -- Capability key for SELECT gating: when set, all 4 merkle tables require this + -- capability for SELECT at platform/app scope (e.g., 'manage_graphs'). -- NULL means the caller intentionally wants open membership SELECT. - permission_key text DEFAULT NULL, + capability_key text DEFAULT NULL, -- Timestamps created_at timestamptz NOT NULL DEFAULT now(), diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/namespace_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/namespace_module/table.sql index 43ca357b..bc696fa0 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/namespace_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/namespace_module/table.sql @@ -56,9 +56,9 @@ CREATE TABLE metaschema_modules_public.namespace_module ( -- secure_table_provision applies the custom grants/policies instead. provisions jsonb NULL, - -- Default permissions: permission names auto-granted to new members. + -- Default capabilities: capability names auto-granted to new members. -- NULL uses the module's built-in defaults; explicit array overrides them. - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, -- Constraints CONSTRAINT namespace_module_db_fkey FOREIGN KEY (database_id) REFERENCES metaschema_public.database (id) ON DELETE CASCADE, diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/notifications_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/notifications_module/table.sql index ed893a39..0ea9c855 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/notifications_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/notifications_module/table.sql @@ -45,9 +45,9 @@ CREATE TABLE metaschema_modules_public.notifications_module ( has_digest_metadata boolean NOT NULL DEFAULT false, has_subscriptions boolean NOT NULL DEFAULT false, - -- Default permissions: permission names auto-granted to new members. + -- Default capabilities: capability names auto-granted to new members. -- NULL uses the module's built-in defaults; explicit array overrides them. - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, -- API routing (configurable per-module) api_name text DEFAULT 'notifications', diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/oauth_requests_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/oauth_requests_module/table.sql new file mode 100644 index 00000000..b6f61d76 --- /dev/null +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/oauth_requests_module/table.sql @@ -0,0 +1,70 @@ +-- Deploy schemas/metaschema_modules_public/tables/oauth_requests_module/table to pg + +-- requires: schemas/metaschema_modules_public/schema + +BEGIN; + +CREATE TABLE metaschema_modules_public.oauth_requests_module ( + id uuid PRIMARY KEY DEFAULT uuidv7(), + database_id uuid NOT NULL, + + -- Scope-key column name on the generated tables. The insert trigger records + -- database_id for database scope, entity_id for entity scopes, NULL for + -- global tiers. Consumers read this instead of re-deriving the literal. + entity_field text, + + -- Both tables live on the private schema and have no public counterpart: + -- nothing outside the generated SECURITY DEFINER procedures may read a + -- code_verifier or a link ticket, so there is no public schema to route. + private_schema_id uuid NOT NULL DEFAULT uuid_nil(), + private_schema_name text, + + oauth_authorization_requests_table_id uuid NOT NULL DEFAULT uuid_nil(), + pending_identity_links_table_id uuid NOT NULL DEFAULT uuid_nil(), + + oauth_authorization_requests_table_name text NOT NULL DEFAULT 'oauth_authorization_requests', + pending_identity_links_table_name text NOT NULL DEFAULT 'pending_identity_links', + + scope text NOT NULL, + prefix text NOT NULL DEFAULT '', + entity_table_id uuid NULL, + + CONSTRAINT oauth_requests_module_db_fkey + FOREIGN KEY (database_id) + REFERENCES metaschema_public.database (id) + ON DELETE CASCADE, + CONSTRAINT oauth_requests_module_private_schema_fkey + FOREIGN KEY (private_schema_id) + REFERENCES metaschema_public.schema (id) + ON DELETE CASCADE, + CONSTRAINT oauth_requests_module_requests_table_fkey + FOREIGN KEY (oauth_authorization_requests_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT oauth_requests_module_links_table_fkey + FOREIGN KEY (pending_identity_links_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT oauth_requests_module_entity_table_fkey + FOREIGN KEY (entity_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE +); + +-- One install per database per scope +CREATE UNIQUE INDEX oauth_requests_module_unique_scope + ON metaschema_modules_public.oauth_requests_module (database_id, scope); +CREATE INDEX oauth_requests_module_private_schema_id_idx ON metaschema_modules_public.oauth_requests_module ( private_schema_id ); +CREATE INDEX oauth_requests_module_requests_table_id_idx ON metaschema_modules_public.oauth_requests_module ( oauth_authorization_requests_table_id ); +CREATE INDEX oauth_requests_module_links_table_id_idx ON metaschema_modules_public.oauth_requests_module ( pending_identity_links_table_id ); +CREATE INDEX oauth_requests_module_entity_table_id_idx ON metaschema_modules_public.oauth_requests_module ( entity_table_id ); + +COMMENT ON TABLE metaschema_modules_public.oauth_requests_module IS + 'Config row for the oauth_requests_module, which provisions the in-flight half of an SSO + sign-in: the OAuth authorization requests table (state + PKCE code_verifier) and the + pending identity links table (a verified identity parked under a single-use ticket), + both private, plus the five SECURITY DEFINER procedures that are their only surface. + Sibling of identity_providers_module (durable provider configuration) rather than part + of it: this is ephemeral, purged flow state with its own retention.'; + +COMMIT; diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/profiles_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/profiles_module/table.sql index 4226861c..217d2926 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/profiles_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/profiles_module/table.sql @@ -24,9 +24,9 @@ CREATE TABLE metaschema_modules_public.profiles_module ( table_id uuid NOT NULL DEFAULT uuid_nil(), table_name text NOT NULL DEFAULT '', - -- Profile permissions join table (for UI) - profile_permissions_table_id uuid NOT NULL DEFAULT uuid_nil(), - profile_permissions_table_name text NOT NULL DEFAULT '', + -- Profile capabilities join table (for UI) + profile_capabilities_table_id uuid NOT NULL DEFAULT uuid_nil(), + profile_capabilities_table_name text NOT NULL DEFAULT '', -- Profile grants audit table profile_grants_table_id uuid NOT NULL DEFAULT uuid_nil(), @@ -36,6 +36,12 @@ CREATE TABLE metaschema_modules_public.profiles_module ( profile_definition_grants_table_id uuid NOT NULL DEFAULT uuid_nil(), profile_definition_grants_table_name text NOT NULL DEFAULT '', + -- Profile assignment set: every profile a membership holds, and the only + -- profile-derived authorization input. memberships.profile_id is a pointer at + -- one of its rows, kept in sync by the generated triggers. + membership_profiles_table_id uuid NOT NULL DEFAULT uuid_nil(), + membership_profiles_table_name text NOT NULL DEFAULT '', + -- Profile templates table (for seeding profiles into new entities) profile_templates_table_id uuid NOT NULL DEFAULT uuid_nil(), profile_templates_table_name text NOT NULL DEFAULT '', @@ -51,7 +57,7 @@ CREATE TABLE metaschema_modules_public.profiles_module ( -- Required tables actor_table_id uuid NOT NULL DEFAULT uuid_nil(), - permissions_table_id uuid NOT NULL DEFAULT uuid_nil(), + capabilities_table_id uuid NOT NULL DEFAULT uuid_nil(), memberships_table_id uuid NOT NULL DEFAULT uuid_nil(), -- API routing (configurable per-module) @@ -63,13 +69,14 @@ CREATE TABLE metaschema_modules_public.profiles_module ( CONSTRAINT schema_fkey FOREIGN KEY (schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, CONSTRAINT private_schema_fkey FOREIGN KEY (private_schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, CONSTRAINT table_fkey FOREIGN KEY (table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT profile_permissions_table_fkey FOREIGN KEY (profile_permissions_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT profile_capabilities_table_fkey FOREIGN KEY (profile_capabilities_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT profile_grants_table_fkey FOREIGN KEY (profile_grants_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT profile_definition_grants_table_fkey FOREIGN KEY (profile_definition_grants_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT membership_profiles_table_fkey FOREIGN KEY (membership_profiles_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT profile_templates_table_fkey FOREIGN KEY (profile_templates_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT entity_table_fkey FOREIGN KEY (entity_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT actor_table_fkey FOREIGN KEY (actor_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT permissions_table_fkey FOREIGN KEY (permissions_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT capabilities_table_fkey FOREIGN KEY (capabilities_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT memberships_table_fkey FOREIGN KEY (memberships_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT profiles_module_unique UNIQUE (database_id, scope, prefix) @@ -78,11 +85,12 @@ CREATE TABLE metaschema_modules_public.profiles_module ( CREATE INDEX profiles_module_actor_table_id_idx ON metaschema_modules_public.profiles_module ( actor_table_id ); CREATE INDEX profiles_module_entity_table_id_idx ON metaschema_modules_public.profiles_module ( entity_table_id ); CREATE INDEX profiles_module_memberships_table_id_idx ON metaschema_modules_public.profiles_module ( memberships_table_id ); -CREATE INDEX profiles_module_permissions_table_id_idx ON metaschema_modules_public.profiles_module ( permissions_table_id ); +CREATE INDEX profiles_module_capabilities_table_id_idx ON metaschema_modules_public.profiles_module ( capabilities_table_id ); CREATE INDEX profiles_module_profile_definition_grants_table_id_idx ON metaschema_modules_public.profiles_module ( profile_definition_grants_table_id ); CREATE INDEX profiles_module_profile_grants_table_id_idx ON metaschema_modules_public.profiles_module ( profile_grants_table_id ); -CREATE INDEX profiles_module_profile_permissions_table_id_idx ON metaschema_modules_public.profiles_module ( profile_permissions_table_id ); +CREATE INDEX profiles_module_profile_capabilities_table_id_idx ON metaschema_modules_public.profiles_module ( profile_capabilities_table_id ); CREATE INDEX profiles_module_profile_templates_table_id_idx ON metaschema_modules_public.profiles_module ( profile_templates_table_id ); +CREATE INDEX profiles_module_membership_profiles_table_id_idx ON metaschema_modules_public.profiles_module ( membership_profiles_table_id ); CREATE INDEX profiles_module_table_id_idx ON metaschema_modules_public.profiles_module ( table_id ); CREATE INDEX profiles_module_private_schema_id_idx ON metaschema_modules_public.profiles_module ( private_schema_id ); CREATE INDEX profiles_module_schema_id_idx ON metaschema_modules_public.profiles_module ( schema_id ); diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/rate_limit_meters_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/rate_limit_meters_module/table.sql index 36fe9fcf..d2dcdda7 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/rate_limit_meters_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/rate_limit_meters_module/table.sql @@ -34,9 +34,9 @@ CREATE TABLE metaschema_modules_public.rate_limit_meters_module ( prefix text NULL, - -- Default permissions: permission names auto-granted to new members. + -- Default capabilities: capability names auto-granted to new members. -- NULL uses the module's built-in defaults; explicit array overrides them. - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, -- API routing (configurable per-module) api_name text DEFAULT 'usage', diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/resource_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/resource_module/table.sql index 888f1666..fef07595 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/resource_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/resource_module/table.sql @@ -85,8 +85,8 @@ CREATE TABLE metaschema_modules_public.resource_module ( -- Keys are table keys (resources, resource_events). provisions jsonb NULL, - -- Default permissions: permission names auto-granted to new members. - default_permissions text[] DEFAULT NULL, + -- Default capabilities: capability names auto-granted to new members. + default_capabilities text[] DEFAULT NULL, -- Constraints CONSTRAINT resource_module_db_fkey FOREIGN KEY (database_id) REFERENCES metaschema_public.database (id) ON DELETE CASCADE, diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/route_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/route_module/table.sql index da0c1ca0..6992b422 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/route_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/route_module/table.sql @@ -71,8 +71,8 @@ CREATE TABLE metaschema_modules_public.route_module ( -- Per-table provisions overrides from blueprint config provisions jsonb NULL, - -- Default permissions: permission names auto-granted to new members - default_permissions text[] DEFAULT NULL, + -- Default capabilities: capability names auto-granted to new members + default_capabilities text[] DEFAULT NULL, CONSTRAINT route_module_db_fkey FOREIGN KEY (database_id) diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/site_surface_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/site_surface_module/table.sql index c602a6b0..109198d5 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/site_surface_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/site_surface_module/table.sql @@ -69,8 +69,8 @@ CREATE TABLE metaschema_modules_public.site_surface_module ( -- Per-table provisions overrides from blueprint config provisions jsonb NULL, - -- Default permissions: permission names auto-granted to new members - default_permissions text[] DEFAULT NULL, + -- Default capabilities: capability names auto-granted to new members + default_capabilities text[] DEFAULT NULL, CONSTRAINT site_module_db_fkey FOREIGN KEY (database_id) diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/storage_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/storage_module/table.sql index e629667d..296aef84 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/storage_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/storage_module/table.sql @@ -67,7 +67,7 @@ CREATE TABLE metaschema_modules_public.storage_module ( -- CORS configuration (NULL = use plugin defaults) allowed_origins text[] NULL, -- Default CORS origins for all buckets in this database (e.g., ARRAY['https://app.example.com']). ['*'] = open/CDN mode. - -- Storage permissions: when true, SELECT on files requires read_files permission + -- Storage capabilities: when true, SELECT on files requires read_files capability -- (opt-in restrictive mode for sensitive entity types like data rooms with confidential docs). -- When false (default), any entity member can read all files (baseline = membership). restrict_reads boolean NOT NULL DEFAULT false, @@ -102,9 +102,9 @@ CREATE TABLE metaschema_modules_public.storage_module ( -- Generated table ID for file_events (populated by the generator when has_audit_log=true) file_events_table_id uuid NULL DEFAULT NULL, - -- Default permissions: permission names auto-granted to new members. + -- Default capabilities: capability names auto-granted to new members. -- NULL uses the module's built-in defaults; explicit array overrides them. - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, -- Constraints -- API routing (configurable per-module) diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/webhook_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/webhook_module/table.sql index f2abd2fd..4cc85a12 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/webhook_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/webhook_module/table.sql @@ -47,7 +47,7 @@ CREATE TABLE metaschema_modules_public.webhook_module ( policies jsonb NULL, provisions jsonb NULL, - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, CONSTRAINT webhook_module_db_fkey FOREIGN KEY (database_id) diff --git a/packages/metaschema-modules/pgpm.plan b/packages/metaschema-modules/pgpm.plan index dd490eba..8c6e5373 100644 --- a/packages/metaschema-modules/pgpm.plan +++ b/packages/metaschema-modules/pgpm.plan @@ -15,7 +15,7 @@ schemas/metaschema_modules_public/tables/events_module/table [schemas/metaschema schemas/metaschema_modules_public/tables/limits_module/table [schemas/metaschema_modules_public/schema] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_modules_public/tables/limits_module/table schemas/metaschema_modules_public/tables/membership_types_module/table [schemas/metaschema_modules_public/schema] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_modules_public/tables/membership_types_module/table schemas/metaschema_modules_public/tables/memberships_module/table [schemas/metaschema_modules_public/schema] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_modules_public/tables/memberships_module/table -schemas/metaschema_modules_public/tables/permissions_module/table [schemas/metaschema_modules_public/schema] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_modules_public/tables/permissions_module/table +schemas/metaschema_modules_public/tables/capabilities_module/table [schemas/metaschema_modules_public/schema] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_modules_public/tables/capabilities_module/table schemas/metaschema_modules_public/tables/phone_numbers_module/table [schemas/metaschema_modules_public/schema] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_modules_public/tables/phone_numbers_module/table schemas/metaschema_modules_public/tables/profiles_module/table [schemas/metaschema_modules_public/schema] 2026-01-01T00:00:00Z devin # add schemas/metaschema_modules_public/tables/profiles_module/table schemas/metaschema_modules_public/tables/rls_module/table [schemas/metaschema_modules_public/schema] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_modules_public/tables/rls_module/table @@ -89,3 +89,8 @@ schemas/metaschema_modules_public/tables/database_settings_module/table [schemas schemas/metaschema_modules_public/tables/pages_module/table [schemas/metaschema_modules_public/schema schemas/metaschema_modules_public/tables/merkle_store_module/table schemas/metaschema_modules_public/tables/site_surface_module/table] 2026-07-31T19:00:00Z devin # add pages_module config table for site-owned merkle-versioned page content schemas/metaschema_modules_public/tables/user_settings_security_module/table [schemas/metaschema_modules_public/schema] 2026-08-05T00:00:00Z devin # add user_settings_security_module for per-user MFA enrollment state (1:1 with users) schemas/metaschema_modules_public/tables/scope_types_module/table [schemas/metaschema_modules_public/schema] 2026-08-06T00:00:00Z devin # add scope type projection module config (which scope encloses each scope) +schemas/metaschema_modules_public/tables/file_ref_field/table [schemas/metaschema_modules_public/schema schemas/metaschema_modules_public/tables/storage_module/table] 2026-08-08T01:00:00Z devin # add file_ref_field registry: one row per upload/image field declaration +schemas/metaschema_modules_public/tables/data_capabilities_field/table [schemas/metaschema_modules_public/schema schemas/metaschema_modules_public/tables/capabilities_module/table] 2026-08-08T02:00:00Z devin # add data_capabilities_field registry: one row per DataCapabilities declaration +schemas/metaschema_modules_public/tables/email_sender_module/table [schemas/metaschema_modules_public/schema schemas/metaschema_modules_public/tables/site_surface_module/table] 2026-08-09T00:00:00Z devin # add scoped email sender module registration +schemas/metaschema_modules_public/tables/oauth_requests_module/table [schemas/metaschema_modules_public/schema] 2026-08-09T06:00:00Z devin # add oauth requests module registration: in-flight OAuth state + pending identity links +schemas/metaschema_modules_public/tables/content_preset_module/table [schemas/metaschema_modules_public/schema schemas/metaschema_modules_public/tables/merkle_store_module/table] 2026-08-09T08:00:00Z devin # add content_preset_module config table for the merkle-versioned seed-content catalog diff --git a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/capabilities_module/table.sql b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/capabilities_module/table.sql new file mode 100644 index 00000000..476eefa5 --- /dev/null +++ b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/capabilities_module/table.sql @@ -0,0 +1,7 @@ +-- Revert schemas/metaschema_modules_public/tables/capabilities_module/table from pg + +BEGIN; + +DROP TABLE metaschema_modules_public.capabilities_module; + +COMMIT; diff --git a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/content_preset_module/table.sql b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/content_preset_module/table.sql new file mode 100644 index 00000000..73db41d8 --- /dev/null +++ b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/content_preset_module/table.sql @@ -0,0 +1,7 @@ +-- Revert schemas/metaschema_modules_public/tables/content_preset_module/table from pg + +BEGIN; + +DROP TABLE IF EXISTS metaschema_modules_public.content_preset_module CASCADE; + +COMMIT; diff --git a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/data_capabilities_field/table.sql b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/data_capabilities_field/table.sql new file mode 100644 index 00000000..af841f1a --- /dev/null +++ b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/data_capabilities_field/table.sql @@ -0,0 +1,7 @@ +-- Revert schemas/metaschema_modules_public/tables/data_capabilities_field/table from pg + +BEGIN; + +DROP TABLE metaschema_modules_public.data_capabilities_field; + +COMMIT; diff --git a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/email_sender_module/table.sql b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/email_sender_module/table.sql new file mode 100644 index 00000000..7d1bc0ba --- /dev/null +++ b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/email_sender_module/table.sql @@ -0,0 +1,7 @@ +-- Revert schemas/metaschema_modules_public/tables/email_sender_module/table from pg + +BEGIN; + +DROP TABLE metaschema_modules_public.email_sender_module; + +COMMIT; diff --git a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/file_ref_field/table.sql b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/file_ref_field/table.sql new file mode 100644 index 00000000..cb291b8b --- /dev/null +++ b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/file_ref_field/table.sql @@ -0,0 +1,7 @@ +-- Revert schemas/metaschema_modules_public/tables/file_ref_field/table from pg + +BEGIN; + +DROP TABLE metaschema_modules_public.file_ref_field; + +COMMIT; diff --git a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/oauth_requests_module/table.sql b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/oauth_requests_module/table.sql new file mode 100644 index 00000000..7b4cfc61 --- /dev/null +++ b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/oauth_requests_module/table.sql @@ -0,0 +1,7 @@ +-- Revert schemas/metaschema_modules_public/tables/oauth_requests_module/table from pg + +BEGIN; + +DROP TABLE metaschema_modules_public.oauth_requests_module; + +COMMIT; diff --git a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/permissions_module/table.sql b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/permissions_module/table.sql deleted file mode 100644 index 42ace72b..00000000 --- a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/permissions_module/table.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert schemas/metaschema_modules_public/tables/permissions_module/table from pg - -BEGIN; - -DROP TABLE metaschema_modules_public.permissions_module; - -COMMIT; diff --git a/packages/metaschema-modules/sql/metaschema-modules--0.40.0.bundle.tar.gz b/packages/metaschema-modules/sql/metaschema-modules--0.40.0.bundle.tar.gz index 6a63fa94..c51d1764 100644 Binary files a/packages/metaschema-modules/sql/metaschema-modules--0.40.0.bundle.tar.gz and b/packages/metaschema-modules/sql/metaschema-modules--0.40.0.bundle.tar.gz differ diff --git a/packages/metaschema-modules/sql/metaschema-modules--0.40.0.sql b/packages/metaschema-modules/sql/metaschema-modules--0.40.0.sql index c5a0a76a..9ddc694e 100644 --- a/packages/metaschema-modules/sql/metaschema-modules--0.40.0.sql +++ b/packages/metaschema-modules/sql/metaschema-modules--0.40.0.sql @@ -393,6 +393,10 @@ CREATE TABLE metaschema_modules_public.events_module ( tg_check_achievements text NOT NULL DEFAULT '', grant_achievement text NOT NULL DEFAULT '', tg_achievement_reward text NOT NULL DEFAULT '', + revoke_achievement text NOT NULL DEFAULT '', + recompute_capabilities text NOT NULL DEFAULT '', + tg_level_grant_sync text NOT NULL DEFAULT '', + expire_grants text NOT NULL DEFAULT '', "interval" text NOT NULL DEFAULT '1 month', retention text DEFAULT '12 months', premake int NOT NULL DEFAULT 2, @@ -400,7 +404,8 @@ CREATE TABLE metaschema_modules_public.events_module ( prefix text NOT NULL DEFAULT '', entity_table_id uuid NULL, actor_table_id uuid NOT NULL DEFAULT uuid_nil(), - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, + trust_ladder jsonb DEFAULT NULL, api_name text DEFAULT 'usage', private_api_name text DEFAULT NULL, CONSTRAINT db_fkey @@ -513,6 +518,7 @@ CREATE TABLE metaschema_modules_public.limits_module ( prefix text NOT NULL DEFAULT '', entity_table_id uuid NULL, actor_table_id uuid NOT NULL DEFAULT uuid_nil(), + limit_defaults jsonb DEFAULT NULL, api_name text DEFAULT 'usage', private_api_name text DEFAULT NULL, CONSTRAINT db_fkey @@ -666,8 +672,8 @@ CREATE TABLE metaschema_modules_public.memberships_module ( actor_table_id uuid NOT NULL DEFAULT uuid_nil(), limits_table_id uuid NOT NULL DEFAULT uuid_nil(), default_limits_table_id uuid NOT NULL DEFAULT uuid_nil(), - permissions_table_id uuid NOT NULL DEFAULT uuid_nil(), - default_permissions_table_id uuid NOT NULL DEFAULT uuid_nil(), + capabilities_table_id uuid NOT NULL DEFAULT uuid_nil(), + default_capabilities_table_id uuid NOT NULL DEFAULT uuid_nil(), sprt_table_id uuid NOT NULL DEFAULT uuid_nil(), admin_grants_table_id uuid NOT NULL DEFAULT uuid_nil(), admin_grants_table_name text NOT NULL DEFAULT '', @@ -684,8 +690,8 @@ CREATE TABLE metaschema_modules_public.memberships_module ( entity_ids_by_perm text NULL, entity_ids_function text NULL, member_profiles_table_id uuid NULL, - permission_default_permissions_table_id uuid NULL, - permission_default_grants_table_id uuid NULL, + capability_default_capabilities_table_id uuid NULL, + capability_default_grants_table_id uuid NULL, api_name text DEFAULT 'admin', private_api_name text DEFAULT NULL, CONSTRAINT db_fkey @@ -744,12 +750,12 @@ CREATE TABLE metaschema_modules_public.memberships_module ( FOREIGN KEY(default_limits_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT permissions_table_fkey - FOREIGN KEY(permissions_table_id) + CONSTRAINT capabilities_table_fkey + FOREIGN KEY(capabilities_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT default_permissions_table_fkey - FOREIGN KEY(default_permissions_table_id) + CONSTRAINT default_capabilities_table_fkey + FOREIGN KEY(default_capabilities_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT memberships_module_unique @@ -760,7 +766,7 @@ CREATE INDEX memberships_module_actor_table_id_idx ON metaschema_modules_public. CREATE INDEX memberships_module_default_limits_table_id_idx ON metaschema_modules_public.memberships_module (default_limits_table_id); -CREATE INDEX memberships_module_default_permissions_table_id_idx ON metaschema_modules_public.memberships_module (default_permissions_table_id); +CREATE INDEX memberships_module_default_capabilities_table_id_idx ON metaschema_modules_public.memberships_module (default_capabilities_table_id); CREATE INDEX memberships_module_entity_table_id_idx ON metaschema_modules_public.memberships_module (entity_table_id); @@ -776,7 +782,7 @@ CREATE INDEX memberships_module_membership_settings_table_id_idx ON metaschema_m CREATE INDEX memberships_module_memberships_table_id_idx ON metaschema_modules_public.memberships_module (memberships_table_id); -CREATE INDEX memberships_module_permissions_table_id_idx ON metaschema_modules_public.memberships_module (permissions_table_id); +CREATE INDEX memberships_module_capabilities_table_id_idx ON metaschema_modules_public.memberships_module (capabilities_table_id); CREATE INDEX memberships_module_sprt_table_id_idx ON metaschema_modules_public.memberships_module (sprt_table_id); @@ -786,7 +792,7 @@ CREATE INDEX memberships_module_schema_id_idx ON metaschema_modules_public.membe CREATE INDEX memberships_module_entity_table_owner_id_idx ON metaschema_modules_public.memberships_module (entity_table_owner_id); -CREATE TABLE metaschema_modules_public.permissions_module ( +CREATE TABLE metaschema_modules_public.capabilities_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, entity_field text, @@ -839,19 +845,19 @@ CREATE TABLE metaschema_modules_public.permissions_module ( ON DELETE CASCADE ); -CREATE INDEX permissions_module_database_id_idx ON metaschema_modules_public.permissions_module (database_id); +CREATE INDEX capabilities_module_database_id_idx ON metaschema_modules_public.capabilities_module (database_id); -CREATE INDEX permissions_module_actor_table_id_idx ON metaschema_modules_public.permissions_module (actor_table_id); +CREATE INDEX capabilities_module_actor_table_id_idx ON metaschema_modules_public.capabilities_module (actor_table_id); -CREATE INDEX permissions_module_default_table_id_idx ON metaschema_modules_public.permissions_module (default_table_id); +CREATE INDEX capabilities_module_default_table_id_idx ON metaschema_modules_public.capabilities_module (default_table_id); -CREATE INDEX permissions_module_entity_table_id_idx ON metaschema_modules_public.permissions_module (entity_table_id); +CREATE INDEX capabilities_module_entity_table_id_idx ON metaschema_modules_public.capabilities_module (entity_table_id); -CREATE INDEX permissions_module_table_id_idx ON metaschema_modules_public.permissions_module (table_id); +CREATE INDEX capabilities_module_table_id_idx ON metaschema_modules_public.capabilities_module (table_id); -CREATE INDEX permissions_module_private_schema_id_idx ON metaschema_modules_public.permissions_module (private_schema_id); +CREATE INDEX capabilities_module_private_schema_id_idx ON metaschema_modules_public.capabilities_module (private_schema_id); -CREATE INDEX permissions_module_schema_id_idx ON metaschema_modules_public.permissions_module (schema_id); +CREATE INDEX capabilities_module_schema_id_idx ON metaschema_modules_public.capabilities_module (schema_id); CREATE TABLE metaschema_modules_public.phone_numbers_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), @@ -905,19 +911,21 @@ CREATE TABLE metaschema_modules_public.profiles_module ( private_schema_name text, table_id uuid NOT NULL DEFAULT uuid_nil(), table_name text NOT NULL DEFAULT '', - profile_permissions_table_id uuid NOT NULL DEFAULT uuid_nil(), - profile_permissions_table_name text NOT NULL DEFAULT '', + profile_capabilities_table_id uuid NOT NULL DEFAULT uuid_nil(), + profile_capabilities_table_name text NOT NULL DEFAULT '', profile_grants_table_id uuid NOT NULL DEFAULT uuid_nil(), profile_grants_table_name text NOT NULL DEFAULT '', profile_definition_grants_table_id uuid NOT NULL DEFAULT uuid_nil(), profile_definition_grants_table_name text NOT NULL DEFAULT '', + membership_profiles_table_id uuid NOT NULL DEFAULT uuid_nil(), + membership_profiles_table_name text NOT NULL DEFAULT '', profile_templates_table_id uuid NOT NULL DEFAULT uuid_nil(), profile_templates_table_name text NOT NULL DEFAULT '', scope text NOT NULL, prefix text NOT NULL DEFAULT '', entity_table_id uuid NULL, actor_table_id uuid NOT NULL DEFAULT uuid_nil(), - permissions_table_id uuid NOT NULL DEFAULT uuid_nil(), + capabilities_table_id uuid NOT NULL DEFAULT uuid_nil(), memberships_table_id uuid NOT NULL DEFAULT uuid_nil(), api_name text DEFAULT 'admin', private_api_name text DEFAULT NULL, @@ -937,8 +945,8 @@ CREATE TABLE metaschema_modules_public.profiles_module ( FOREIGN KEY(table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT profile_permissions_table_fkey - FOREIGN KEY(profile_permissions_table_id) + CONSTRAINT profile_capabilities_table_fkey + FOREIGN KEY(profile_capabilities_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT profile_grants_table_fkey @@ -949,6 +957,10 @@ CREATE TABLE metaschema_modules_public.profiles_module ( FOREIGN KEY(profile_definition_grants_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT membership_profiles_table_fkey + FOREIGN KEY(membership_profiles_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, CONSTRAINT profile_templates_table_fkey FOREIGN KEY(profile_templates_table_id) REFERENCES metaschema_public.table (id) @@ -961,8 +973,8 @@ CREATE TABLE metaschema_modules_public.profiles_module ( FOREIGN KEY(actor_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT permissions_table_fkey - FOREIGN KEY(permissions_table_id) + CONSTRAINT capabilities_table_fkey + FOREIGN KEY(capabilities_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT memberships_table_fkey @@ -979,16 +991,18 @@ CREATE INDEX profiles_module_entity_table_id_idx ON metaschema_modules_public.pr CREATE INDEX profiles_module_memberships_table_id_idx ON metaschema_modules_public.profiles_module (memberships_table_id); -CREATE INDEX profiles_module_permissions_table_id_idx ON metaschema_modules_public.profiles_module (permissions_table_id); +CREATE INDEX profiles_module_capabilities_table_id_idx ON metaschema_modules_public.profiles_module (capabilities_table_id); CREATE INDEX profiles_module_profile_definition_grants_table_id_idx ON metaschema_modules_public.profiles_module (profile_definition_grants_table_id); CREATE INDEX profiles_module_profile_grants_table_id_idx ON metaschema_modules_public.profiles_module (profile_grants_table_id); -CREATE INDEX profiles_module_profile_permissions_table_id_idx ON metaschema_modules_public.profiles_module (profile_permissions_table_id); +CREATE INDEX profiles_module_profile_capabilities_table_id_idx ON metaschema_modules_public.profiles_module (profile_capabilities_table_id); CREATE INDEX profiles_module_profile_templates_table_id_idx ON metaschema_modules_public.profiles_module (profile_templates_table_id); +CREATE INDEX profiles_module_membership_profiles_table_id_idx ON metaschema_modules_public.profiles_module (membership_profiles_table_id); + CREATE INDEX profiles_module_table_id_idx ON metaschema_modules_public.profiles_module (table_id); CREATE INDEX profiles_module_private_schema_id_idx ON metaschema_modules_public.profiles_module (private_schema_id); @@ -1287,7 +1301,7 @@ CREATE TABLE metaschema_modules_public.hierarchy_module ( get_subordinates_function text NOT NULL DEFAULT '', get_managers_function text NOT NULL DEFAULT '', is_manager_of_function text NOT NULL DEFAULT '', - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, created_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT db_fkey FOREIGN KEY(database_id) @@ -1778,7 +1792,7 @@ CREATE TABLE metaschema_modules_public.storage_module ( has_confirm_upload boolean NOT NULL DEFAULT false, confirm_upload_delay interval NOT NULL DEFAULT '30 seconds', file_events_table_id uuid NULL DEFAULT NULL, - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, api_name text DEFAULT NULL, private_api_name text DEFAULT NULL, CONSTRAINT db_fkey @@ -1880,7 +1894,7 @@ CREATE TABLE metaschema_modules_public.entity_type_provision ( ); COMMENT ON TABLE metaschema_modules_public.entity_type_provision IS 'Provisions a new membership entity type. Each INSERT creates an entity table, registers a membership type, - and installs the required modules (permissions, memberships, limits) plus optional modules (profiles, levels, invites). + and installs the required modules (capabilities, memberships, limits) plus optional modules (profiles, levels, invites). Uses provision_membership_table() internally. Graceful: duplicate (database_id, prefix) pairs are silently skipped via the unique constraint (use INSERT ... ON CONFLICT DO NOTHING). Policy behavior: by default the five entity-table RLS policies are applied (gated by is_visible). @@ -1896,7 +1910,7 @@ COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.name IS 'Human Stored in the entity_types registry table.'; COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.prefix IS 'SQL prefix used for table and module naming, e.g. ''data_room'', ''team_channel''. Required. - Drives entity table name (prefix || ''s'' by default), module labels (permissions_module:prefix), + Drives entity table name (prefix || ''s'' by default), module labels (capabilities_module:prefix), and membership table names (prefix_memberships, prefix_members, etc.). Must be unique per database — the (database_id, prefix) constraint ensures graceful ON CONFLICT DO NOTHING.'; @@ -1925,8 +1939,11 @@ COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.has_limits IS Set to true if this entity type needs configurable resource limits per membership.'; COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.has_profiles IS 'Whether to provision profiles_module for this type. Defaults to false. - Profiles provide named permission roles (e.g. ''Editor'', ''Viewer'') with pre-configured permission bitmasks. - When true, creates profile tables and applies profiles security.'; + Profiles provide named capability roles (e.g. ''Editor'', ''Viewer'') with pre-configured capability bitmasks. + When true, creates profile tables and applies profiles security. A membership may hold + any number of profiles: the membership_profiles assignment table holds every profile a + membership holds and the membership mask is granted | bit_or(held profile masks), with + memberships.profile_id kept as a pointer at one held profile.'; COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.has_levels IS 'Whether to provision events_module for this type. Defaults to false. Levels provide gamification/achievement tracking for members. @@ -1956,8 +1973,8 @@ COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.skip_entity_po Defaults (applied when table_provision IS NULL and skip_entity_policies=false): - SELECT (parent_member): parent entity members can see child entities (only when is_visible=true) - SELECT (self_member): direct members of the entity can see it - - INSERT: create_entity permission on the parent entity - - UPDATE: admin_entity permission on the entity itself + - INSERT: create_entity capability on the parent entity + - UPDATE: admin_entity capability on the entity itself - DELETE: owner of the entity can delete it'; COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.table_provision IS 'Single jsonb object describing the full security setup to apply to the entity table. @@ -1998,7 +2015,7 @@ COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.out_entity_tab COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.out_entity_table_name IS 'Output: the name of the created entity table (e.g. ''data_rooms''). Populated by the trigger.'; -COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.out_installed_modules IS 'Output: array of installed module labels (e.g. ARRAY[''permissions_module:data_room'', ''memberships_module:data_room'', ''invites_module:data_room'']). +COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.out_installed_modules IS 'Output: array of installed module labels (e.g. ARRAY[''capabilities_module:data_room'', ''memberships_module:data_room'', ''invites_module:data_room'']). Populated by the trigger. Useful for verifying which modules were provisioned.'; COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.storage IS 'Optional JSON array of storage module definitions. Presence triggers provisioning @@ -2015,7 +2032,7 @@ COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.storage IS 'Op - download_url_expiry_seconds (integer) presigned GET URL expiry override - default_max_file_size (bigint) global max file size in bytes for this module - allowed_origins (text[]) default CORS origins for all buckets in this module - - restrict_reads (boolean) require read_files permission for SELECT on files + - restrict_reads (boolean) require read_files capability for SELECT on files - has_path_shares (boolean) enable virtual filesystem + path share policies - has_versioning (boolean) enable file version chains - has_content_hash (boolean) enable content hash for dedup @@ -2049,7 +2066,7 @@ COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.namespaces IS - policies (jsonb array) RLS policy overrides. NULL = apply defaults from apply_namespace_security(). Creates {prefix}_namespaces (or {prefix}_{key}_namespaces for non-default keys) with entity-scoped RLS (AuthzEntityMembership) and a rename proxy trigger. - Registers manage_namespaces permission bit on first provision. + Registers manage_namespaces capability bit on first provision. Example: namespaces := ''[{}]''::jsonb'; COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.functions IS 'Optional JSON array of function module definitions. Presence triggers provisioning. @@ -2059,7 +2076,7 @@ COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.functions IS ' - policies (jsonb array) RLS policy overrides. NULL = apply defaults from apply_function_security(). Creates {prefix}_function_definitions (or {prefix}_{key}_function_definitions for non-default keys) with entity-scoped RLS and a job trigger dispatching function:provision tasks. - Registers manage_functions + invoke_functions permission bits on first provision. + Registers manage_functions + invoke_functions capability bits on first provision. Example: functions := ''[{}]''::jsonb'; COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.graphs IS 'Optional JSON array of graph module definitions. Presence triggers provisioning. @@ -2067,9 +2084,9 @@ COMMENT ON COLUMN metaschema_modules_public.entity_type_provision.graphs IS 'Opt Each element recognizes (all optional): - key (text) module discriminator. Defaults to ''default''. - policies (jsonb array) RLS policy overrides. NULL = apply defaults from apply_graph_security(). - Registers manage_graphs + execute_graphs permission bits on first provision. + Registers manage_graphs + execute_graphs capability bits on first provision. Graph module requires a merkle_store_module_id dependency, so entity_type_provision - only registers permissions here. The graph module itself must be provisioned + only registers capabilities here. The graph module itself must be provisioned separately with the merkle store dependency resolved. Example: graphs := ''[{}]''::jsonb'; @@ -2399,7 +2416,7 @@ CREATE TABLE metaschema_modules_public.notifications_module ( has_settings_extension boolean NOT NULL DEFAULT false, has_digest_metadata boolean NOT NULL DEFAULT false, has_subscriptions boolean NOT NULL DEFAULT false, - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, api_name text DEFAULT 'notifications', private_api_name text DEFAULT NULL, CONSTRAINT db_fkey @@ -2611,7 +2628,7 @@ CREATE TABLE metaschema_modules_public.billing_module ( rollup_usage_summary_function text NOT NULL DEFAULT '', prefix text NULL, default_meter_catalog jsonb DEFAULT NULL, - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, api_name text DEFAULT 'usage', private_api_name text DEFAULT NULL, CONSTRAINT db_fkey @@ -2861,7 +2878,7 @@ CREATE TABLE metaschema_modules_public.rate_limit_meters_module ( rate_window_limits_table_name text NOT NULL DEFAULT '', check_rate_limit_function text NOT NULL DEFAULT '', prefix text NULL, - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, api_name text DEFAULT 'usage', private_api_name text DEFAULT NULL, CONSTRAINT db_fkey @@ -3145,7 +3162,7 @@ CREATE TABLE metaschema_modules_public.db_usage_module ( premake int NOT NULL DEFAULT 2, scope text NOT NULL, prefix text NOT NULL DEFAULT '', - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, api_name text DEFAULT 'usage', private_api_name text DEFAULT NULL, CONSTRAINT db_fkey @@ -3228,7 +3245,7 @@ CREATE TABLE metaschema_modules_public.agent_module ( policies jsonb NULL, resources jsonb NULL, provisions jsonb NULL, - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, CONSTRAINT agent_module_db_fkey FOREIGN KEY(database_id) REFERENCES metaschema_public.database (id) @@ -3320,7 +3337,7 @@ CREATE TABLE metaschema_modules_public.merkle_store_module ( private_api_name text, scope text NOT NULL, function_prefix text DEFAULT NULL, - permission_key text DEFAULT NULL, + capability_key text DEFAULT NULL, created_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT db_fkey FOREIGN KEY(database_id) @@ -3383,7 +3400,7 @@ CREATE TABLE metaschema_modules_public.graph_module ( entity_table_id uuid NULL, policies jsonb NULL, provisions jsonb NULL, - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, created_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT db_fkey FOREIGN KEY(database_id) @@ -3445,7 +3462,7 @@ CREATE TABLE metaschema_modules_public.graph_execution_module ( entity_table_id uuid NULL, policies jsonb NULL, provisions jsonb NULL, - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, created_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT graph_execution_module_db_fkey FOREIGN KEY(database_id) @@ -3572,7 +3589,7 @@ CREATE TABLE metaschema_modules_public.namespace_module ( entity_table_id uuid NULL, policies jsonb NULL, provisions jsonb NULL, - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, CONSTRAINT namespace_module_db_fkey FOREIGN KEY(database_id) REFERENCES metaschema_public.database (id) @@ -3633,7 +3650,7 @@ CREATE TABLE metaschema_modules_public.function_module ( entity_table_id uuid NULL, policies jsonb NULL, provisions jsonb NULL, - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, CONSTRAINT function_module_db_fkey FOREIGN KEY(database_id) REFERENCES metaschema_public.database (id) @@ -3705,7 +3722,7 @@ CREATE TABLE metaschema_modules_public.function_invocation_module ( entity_table_id uuid NULL, policies jsonb NULL, provisions jsonb NULL, - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, CONSTRAINT function_invocation_module_db_fkey FOREIGN KEY(database_id) REFERENCES metaschema_public.database (id) @@ -4024,7 +4041,7 @@ CREATE TABLE metaschema_modules_public.function_deployment_module ( namespace_module_id uuid NULL, policies jsonb NULL, provisions jsonb NULL, - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, CONSTRAINT function_deployment_module_db_fkey FOREIGN KEY(database_id) REFERENCES metaschema_public.database (id) @@ -4186,7 +4203,7 @@ CREATE TABLE metaschema_modules_public.resource_module ( installation_store_name text NOT NULL DEFAULT 'infra', policies jsonb NULL, provisions jsonb NULL, - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, CONSTRAINT resource_module_db_fkey FOREIGN KEY(database_id) REFERENCES metaschema_public.database (id) @@ -4357,7 +4374,7 @@ CREATE TABLE metaschema_modules_public.webhook_module ( entity_table_id uuid NULL, policies jsonb NULL, provisions jsonb NULL, - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, CONSTRAINT webhook_module_db_fkey FOREIGN KEY(database_id) REFERENCES metaschema_public.database (id) @@ -4441,7 +4458,7 @@ CREATE TABLE metaschema_modules_public.http_route_module ( entity_table_id uuid NULL, policies jsonb NULL, provisions jsonb NULL, - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, CONSTRAINT http_route_module_db_fkey FOREIGN KEY(database_id) REFERENCES metaschema_public.database (id) @@ -4533,7 +4550,7 @@ CREATE TABLE metaschema_modules_public.catalog_module ( entity_table_id uuid NULL, policies jsonb NULL, provisions jsonb NULL, - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, CONSTRAINT catalog_module_db_fkey FOREIGN KEY(database_id) REFERENCES metaschema_public.database (id) @@ -4668,7 +4685,7 @@ CREATE TABLE metaschema_modules_public.domain_module ( entity_table_id uuid NULL, policies jsonb NULL, provisions jsonb NULL, - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, CONSTRAINT domain_module_db_fkey FOREIGN KEY(database_id) REFERENCES metaschema_public.database (id) @@ -4747,7 +4764,7 @@ CREATE TABLE metaschema_modules_public.api_surface_module ( entity_table_id uuid NULL, policies jsonb NULL, provisions jsonb NULL, - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, CONSTRAINT api_module_db_fkey FOREIGN KEY(database_id) REFERENCES metaschema_public.database (id) @@ -4830,7 +4847,7 @@ CREATE TABLE metaschema_modules_public.site_surface_module ( entity_table_id uuid NULL, policies jsonb NULL, provisions jsonb NULL, - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, CONSTRAINT site_module_db_fkey FOREIGN KEY(database_id) REFERENCES metaschema_public.database (id) @@ -4935,7 +4952,7 @@ CREATE TABLE metaschema_modules_public.route_module ( entity_table_id uuid NULL, policies jsonb NULL, provisions jsonb NULL, - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, CONSTRAINT route_module_db_fkey FOREIGN KEY(database_id) REFERENCES metaschema_public.database (id) @@ -5012,7 +5029,7 @@ CREATE TABLE metaschema_modules_public.app_module ( entity_table_id uuid NULL, policies jsonb NULL, provisions jsonb NULL, - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, CONSTRAINT app_module_db_fkey FOREIGN KEY(database_id) REFERENCES metaschema_public.database (id) @@ -5078,7 +5095,7 @@ CREATE TABLE metaschema_modules_public.database_settings_module ( entity_table_id uuid NULL, policies jsonb NULL, provisions jsonb NULL, - default_permissions text[] DEFAULT NULL, + default_capabilities text[] DEFAULT NULL, CONSTRAINT database_settings_module_db_fkey FOREIGN KEY(database_id) REFERENCES metaschema_public.database (id) @@ -5253,4 +5270,298 @@ CREATE INDEX scope_types_module_database_id_idx ON metaschema_modules_public.sco CREATE INDEX scope_types_module_schema_id_idx ON metaschema_modules_public.scope_types_module (schema_id); -CREATE INDEX scope_types_module_scope_types_table_id_idx ON metaschema_modules_public.scope_types_module (scope_types_table_id); \ No newline at end of file +CREATE INDEX scope_types_module_scope_types_table_id_idx ON metaschema_modules_public.scope_types_module (scope_types_table_id); + +CREATE TABLE metaschema_modules_public.file_ref_field ( + id uuid PRIMARY KEY DEFAULT uuidv7(), + database_id uuid NOT NULL, + table_id uuid NOT NULL, + field_id uuid NOT NULL, + storage_module_id uuid NOT NULL, + bucket_key text, + bucket_tags citext[], + is_public boolean, + enforce_fk boolean NOT NULL DEFAULT false, + CONSTRAINT db_fkey + FOREIGN KEY(database_id) + REFERENCES metaschema_public.database (id) + ON DELETE CASCADE, + CONSTRAINT table_fkey + FOREIGN KEY(table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT field_fkey + FOREIGN KEY(field_id) + REFERENCES metaschema_public.field (id) + ON DELETE CASCADE, + CONSTRAINT storage_module_fkey + FOREIGN KEY(storage_module_id) + REFERENCES metaschema_modules_public.storage_module (id) + ON DELETE CASCADE, + CONSTRAINT bucket_intent_chk + CHECK ( + bucket_key IS NULL + OR bucket_tags IS NULL + ), + CONSTRAINT bucket_key_not_blank_chk + CHECK ( + bucket_key IS NULL + OR btrim(bucket_key) <> '' + ), + UNIQUE (field_id) +); + +CREATE INDEX file_ref_field_database_id_idx ON metaschema_modules_public.file_ref_field (database_id); + +CREATE INDEX file_ref_field_table_id_idx ON metaschema_modules_public.file_ref_field (table_id); + +CREATE INDEX file_ref_field_storage_module_id_idx ON metaschema_modules_public.file_ref_field (storage_module_id); + +CREATE TABLE metaschema_modules_public.data_capabilities_field ( + id uuid PRIMARY KEY DEFAULT uuidv7(), + database_id uuid NOT NULL, + table_id uuid NOT NULL, + field_id uuid NOT NULL, + capabilities_module_id uuid NOT NULL, + mode text NOT NULL DEFAULT 'direct', + from_field_id uuid NULL, + mapping_table_id uuid NULL, + mapping_key_field_id uuid NULL, + mapping_field_id uuid NULL, + subset_guard boolean NOT NULL DEFAULT true, + CONSTRAINT db_fkey + FOREIGN KEY(database_id) + REFERENCES metaschema_public.database (id) + ON DELETE CASCADE, + CONSTRAINT table_fkey + FOREIGN KEY(table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT field_fkey + FOREIGN KEY(field_id) + REFERENCES metaschema_public.field (id) + ON DELETE CASCADE, + CONSTRAINT capabilities_module_fkey + FOREIGN KEY(capabilities_module_id) + REFERENCES metaschema_modules_public.capabilities_module (id) + ON DELETE CASCADE, + CONSTRAINT from_field_fkey + FOREIGN KEY(from_field_id) + REFERENCES metaschema_public.field (id) + ON DELETE CASCADE, + CONSTRAINT mapping_table_fkey + FOREIGN KEY(mapping_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT mapping_key_field_fkey + FOREIGN KEY(mapping_key_field_id) + REFERENCES metaschema_public.field (id) + ON DELETE CASCADE, + CONSTRAINT mapping_field_fkey + FOREIGN KEY(mapping_field_id) + REFERENCES metaschema_public.field (id) + ON DELETE CASCADE, + CONSTRAINT mode_chk + CHECK (mode IN ('direct', 'derived')), + CONSTRAINT derived_mode_chk + CHECK ( + (mode = 'derived' + AND from_field_id IS NOT NULL + AND mapping_table_id IS NOT NULL + AND mapping_key_field_id IS NOT NULL + AND mapping_field_id IS NOT NULL) + OR (mode = 'direct' + AND from_field_id IS NULL + AND mapping_table_id IS NULL + AND mapping_key_field_id IS NULL + AND mapping_field_id IS NULL) + ), + UNIQUE (field_id) +); + +CREATE INDEX data_capabilities_field_database_id_idx ON metaschema_modules_public.data_capabilities_field (database_id); + +CREATE INDEX data_capabilities_field_table_id_idx ON metaschema_modules_public.data_capabilities_field (table_id); + +CREATE INDEX data_capabilities_field_capabilities_module_id_idx ON metaschema_modules_public.data_capabilities_field (capabilities_module_id); + +CREATE INDEX data_capabilities_field_mapping_table_id_idx ON metaschema_modules_public.data_capabilities_field (mapping_table_id); + +CREATE INDEX data_capabilities_field_from_field_id_idx ON metaschema_modules_public.data_capabilities_field (from_field_id); + +CREATE INDEX data_capabilities_field_mapping_key_field_id_idx ON metaschema_modules_public.data_capabilities_field (mapping_key_field_id); + +CREATE INDEX data_capabilities_field_mapping_field_id_idx ON metaschema_modules_public.data_capabilities_field (mapping_field_id); + +CREATE TABLE metaschema_modules_public.email_sender_module ( + id uuid PRIMARY KEY DEFAULT uuidv7(), + database_id uuid NOT NULL, + entity_field text, + schema_id uuid NOT NULL DEFAULT uuid_nil(), + public_schema_name text, + email_provider_accounts_table_id uuid NOT NULL DEFAULT uuid_nil(), + email_identities_table_id uuid NOT NULL DEFAULT uuid_nil(), + email_site_identities_table_id uuid NULL, + email_provider_accounts_table_name text NOT NULL DEFAULT 'email_provider_accounts', + email_identities_table_name text NOT NULL DEFAULT 'email_identities', + email_site_identities_table_name text NOT NULL DEFAULT 'email_site_identities', + site_surface_module_id uuid, + api_name text, + private_api_name text, + scope text NOT NULL, + prefix text NOT NULL DEFAULT '', + entity_table_id uuid NULL, + policies jsonb NULL, + provisions jsonb NULL, + default_capabilities text[] DEFAULT NULL, + CONSTRAINT email_sender_module_db_fkey + FOREIGN KEY(database_id) + REFERENCES metaschema_public.database (id) + ON DELETE CASCADE, + CONSTRAINT email_sender_module_schema_fkey + FOREIGN KEY(schema_id) + REFERENCES metaschema_public.schema (id) + ON DELETE CASCADE, + CONSTRAINT email_sender_module_provider_accounts_table_fkey + FOREIGN KEY(email_provider_accounts_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT email_sender_module_identities_table_fkey + FOREIGN KEY(email_identities_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT email_sender_module_site_identities_table_fkey + FOREIGN KEY(email_site_identities_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT email_sender_module_site_surface_module_fkey + FOREIGN KEY(site_surface_module_id) + REFERENCES metaschema_modules_public.site_surface_module (id) + ON DELETE CASCADE, + CONSTRAINT email_sender_module_entity_table_fkey + FOREIGN KEY(entity_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE +); + +CREATE UNIQUE INDEX email_sender_module_unique_scope ON metaschema_modules_public.email_sender_module (database_id, scope); + +CREATE INDEX email_sender_module_schema_id_idx ON metaschema_modules_public.email_sender_module (schema_id); + +CREATE INDEX email_sender_module_provider_accounts_table_id_idx ON metaschema_modules_public.email_sender_module (email_provider_accounts_table_id); + +CREATE INDEX email_sender_module_identities_table_id_idx ON metaschema_modules_public.email_sender_module (email_identities_table_id); + +CREATE INDEX email_sender_module_site_identities_table_id_idx ON metaschema_modules_public.email_sender_module (email_site_identities_table_id); + +CREATE INDEX email_sender_module_site_surface_module_id_idx ON metaschema_modules_public.email_sender_module (site_surface_module_id); + +CREATE INDEX email_sender_module_entity_table_id_idx ON metaschema_modules_public.email_sender_module (entity_table_id); + +CREATE TABLE metaschema_modules_public.oauth_requests_module ( + id uuid PRIMARY KEY DEFAULT uuidv7(), + database_id uuid NOT NULL, + entity_field text, + private_schema_id uuid NOT NULL DEFAULT uuid_nil(), + private_schema_name text, + oauth_authorization_requests_table_id uuid NOT NULL DEFAULT uuid_nil(), + pending_identity_links_table_id uuid NOT NULL DEFAULT uuid_nil(), + oauth_authorization_requests_table_name text NOT NULL DEFAULT 'oauth_authorization_requests', + pending_identity_links_table_name text NOT NULL DEFAULT 'pending_identity_links', + scope text NOT NULL, + prefix text NOT NULL DEFAULT '', + entity_table_id uuid NULL, + CONSTRAINT oauth_requests_module_db_fkey + FOREIGN KEY(database_id) + REFERENCES metaschema_public.database (id) + ON DELETE CASCADE, + CONSTRAINT oauth_requests_module_private_schema_fkey + FOREIGN KEY(private_schema_id) + REFERENCES metaschema_public.schema (id) + ON DELETE CASCADE, + CONSTRAINT oauth_requests_module_requests_table_fkey + FOREIGN KEY(oauth_authorization_requests_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT oauth_requests_module_links_table_fkey + FOREIGN KEY(pending_identity_links_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT oauth_requests_module_entity_table_fkey + FOREIGN KEY(entity_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE +); + +CREATE UNIQUE INDEX oauth_requests_module_unique_scope ON metaschema_modules_public.oauth_requests_module (database_id, scope); + +CREATE INDEX oauth_requests_module_private_schema_id_idx ON metaschema_modules_public.oauth_requests_module (private_schema_id); + +CREATE INDEX oauth_requests_module_requests_table_id_idx ON metaschema_modules_public.oauth_requests_module (oauth_authorization_requests_table_id); + +CREATE INDEX oauth_requests_module_links_table_id_idx ON metaschema_modules_public.oauth_requests_module (pending_identity_links_table_id); + +CREATE INDEX oauth_requests_module_entity_table_id_idx ON metaschema_modules_public.oauth_requests_module (entity_table_id); + +COMMENT ON TABLE metaschema_modules_public.oauth_requests_module IS 'Config row for the oauth_requests_module, which provisions the in-flight half of an SSO + sign-in: the OAuth authorization requests table (state + PKCE code_verifier) and the + pending identity links table (a verified identity parked under a single-use ticket), + both private, plus the five SECURITY DEFINER procedures that are their only surface. + Sibling of identity_providers_module (durable provider configuration) rather than part + of it: this is ephemeral, purged flow state with its own retention.'; + +CREATE TABLE metaschema_modules_public.content_preset_module ( + id uuid PRIMARY KEY DEFAULT uuidv7(), + database_id uuid NOT NULL, + public_schema_id uuid NOT NULL DEFAULT uuid_nil(), + private_schema_id uuid NOT NULL DEFAULT uuid_nil(), + public_schema_name text, + private_schema_name text, + scope text NOT NULL, + prefix text NOT NULL, + merkle_store_module_id uuid NOT NULL, + content_presets_table_id uuid NOT NULL DEFAULT uuid_nil(), + store_name text NOT NULL, + api_name text, + private_api_name text, + entity_table_id uuid NULL, + policies jsonb NULL, + provisions jsonb NULL, + created_at timestamptz NOT NULL DEFAULT now(), + CONSTRAINT db_fkey + FOREIGN KEY(database_id) + REFERENCES metaschema_public.database (id) + ON DELETE CASCADE, + CONSTRAINT public_schema_fkey + FOREIGN KEY(public_schema_id) + REFERENCES metaschema_public.schema (id) + ON DELETE CASCADE, + CONSTRAINT private_schema_fkey + FOREIGN KEY(private_schema_id) + REFERENCES metaschema_public.schema (id) + ON DELETE CASCADE, + CONSTRAINT merkle_store_fkey + FOREIGN KEY(merkle_store_module_id) + REFERENCES metaschema_modules_public.merkle_store_module (id) + ON DELETE CASCADE, + CONSTRAINT content_presets_table_fkey + FOREIGN KEY(content_presets_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT content_preset_module_entity_table_fkey + FOREIGN KEY(entity_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT content_preset_module_database_merkle_unique + UNIQUE (database_id, merkle_store_module_id) +); + +CREATE INDEX content_preset_module_entity_table_id_idx ON metaschema_modules_public.content_preset_module (entity_table_id); + +CREATE INDEX content_preset_module_content_presets_table_id_idx ON metaschema_modules_public.content_preset_module (content_presets_table_id); + +CREATE INDEX content_preset_module_private_schema_id_idx ON metaschema_modules_public.content_preset_module (private_schema_id); + +CREATE INDEX content_preset_module_public_schema_id_idx ON metaschema_modules_public.content_preset_module (public_schema_id); + +CREATE INDEX content_preset_module_merkle_store_module_id_idx ON metaschema_modules_public.content_preset_module (merkle_store_module_id); \ No newline at end of file diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/capabilities_module/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/capabilities_module/table.sql new file mode 100644 index 00000000..12742162 --- /dev/null +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/capabilities_module/table.sql @@ -0,0 +1,7 @@ +-- Verify schemas/metaschema_modules_public/tables/capabilities_module/table on pg + +BEGIN; + +SELECT assert_table('metaschema_modules_public.capabilities_module'::regclass); + +ROLLBACK; diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/content_preset_module/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/content_preset_module/table.sql new file mode 100644 index 00000000..117bcaf0 --- /dev/null +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/content_preset_module/table.sql @@ -0,0 +1,9 @@ +-- Verify schemas/metaschema_modules_public/tables/content_preset_module/table on pg + +BEGIN; + +SELECT id, database_id, public_schema_id, private_schema_id, merkle_store_module_id, store_name, scope, prefix + FROM metaschema_modules_public.content_preset_module + WHERE FALSE; + +ROLLBACK; diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/data_capabilities_field/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/data_capabilities_field/table.sql new file mode 100644 index 00000000..b4a35910 --- /dev/null +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/data_capabilities_field/table.sql @@ -0,0 +1,7 @@ +-- Verify schemas/metaschema_modules_public/tables/data_capabilities_field/table on pg + +BEGIN; + +SELECT assert_table('metaschema_modules_public.data_capabilities_field'::regclass); + +ROLLBACK; diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/email_sender_module/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/email_sender_module/table.sql new file mode 100644 index 00000000..f5d709f4 --- /dev/null +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/email_sender_module/table.sql @@ -0,0 +1,9 @@ +-- Verify schemas/metaschema_modules_public/tables/email_sender_module/table on pg + +BEGIN; + +SELECT id, database_id, scope +FROM metaschema_modules_public.email_sender_module +WHERE false; + +ROLLBACK; diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/file_ref_field/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/file_ref_field/table.sql new file mode 100644 index 00000000..488ea01b --- /dev/null +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/file_ref_field/table.sql @@ -0,0 +1,7 @@ +-- Verify schemas/metaschema_modules_public/tables/file_ref_field/table on pg + +BEGIN; + +SELECT assert_table('metaschema_modules_public.file_ref_field'::regclass); + +ROLLBACK; diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/function_invocation_module/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/function_invocation_module/table.sql index a4fa95db..2117b00d 100644 --- a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/function_invocation_module/table.sql +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/function_invocation_module/table.sql @@ -8,7 +8,7 @@ SELECT id, database_id, schema_id, private_schema_id, invocations_table_name, execution_logs_table_name, api_name, private_api_name, scope, prefix, entity_table_id, - policies, provisions, default_permissions + policies, provisions, default_capabilities FROM metaschema_modules_public.function_invocation_module WHERE false; diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/oauth_requests_module/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/oauth_requests_module/table.sql new file mode 100644 index 00000000..4839f24e --- /dev/null +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/oauth_requests_module/table.sql @@ -0,0 +1,9 @@ +-- Verify schemas/metaschema_modules_public/tables/oauth_requests_module/table on pg + +BEGIN; + +SELECT id, database_id, scope +FROM metaschema_modules_public.oauth_requests_module +WHERE false; + +ROLLBACK; diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/permissions_module/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/permissions_module/table.sql deleted file mode 100644 index 16d0f5f3..00000000 --- a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/permissions_module/table.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify schemas/metaschema_modules_public/tables/permissions_module/table on pg - -BEGIN; - -SELECT assert_table('metaschema_modules_public.permissions_module'::regclass); - -ROLLBACK; diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/profiles_module/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/profiles_module/table.sql index 995dd6f6..00dd6583 100644 --- a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/profiles_module/table.sql +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/profiles_module/table.sql @@ -3,11 +3,12 @@ BEGIN; SELECT id, database_id, schema_id, private_schema_id, table_id, table_name, - profile_permissions_table_id, profile_permissions_table_name, + profile_capabilities_table_id, profile_capabilities_table_name, profile_grants_table_id, profile_grants_table_name, profile_definition_grants_table_id, profile_definition_grants_table_name, entity_table_id, actor_table_id, - permissions_table_id, memberships_table_id, prefix + capabilities_table_id, memberships_table_id, prefix, + membership_profiles_table_id, membership_profiles_table_name FROM metaschema_modules_public.profiles_module WHERE FALSE; diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/resource_module/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/resource_module/table.sql index 19ab8e40..b4eac27a 100644 --- a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/resource_module/table.sql +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/resource_module/table.sql @@ -7,7 +7,7 @@ SELECT id, database_id, schema_id, private_schema_id, resources_table_name, resource_events_table_name, resolved_requirements_view_name, requirements_state_view_name, scope, prefix, entity_table_id, namespace_module_id, - policies, provisions, default_permissions + policies, provisions, default_capabilities FROM metaschema_modules_public.resource_module WHERE FALSE; diff --git a/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/schema/triggers/enforce_api_exposure_ratchet.sql b/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/schema/triggers/enforce_api_exposure_ratchet.sql index 526ef9ba..ba015f41 100644 --- a/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/schema/triggers/enforce_api_exposure_ratchet.sql +++ b/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/schema/triggers/enforce_api_exposure_ratchet.sql @@ -5,7 +5,7 @@ BEGIN; -- never_expose is a one-way ratchet: once set, it cannot be changed via the app. --- Loosening internal_only → exposable is governed by introspection-layer permissions. +-- Loosening internal_only → exposable is governed by introspection-layer capabilities. CREATE FUNCTION metaschema_public.tg_enforce_api_exposure_ratchet() RETURNS TRIGGER AS $$ BEGIN diff --git a/packages/metaschema-schema/deploy/schemas/metaschema_public/types/object_category.sql b/packages/metaschema-schema/deploy/schemas/metaschema_public/types/object_category.sql index 41399e2e..9f4f994d 100644 --- a/packages/metaschema-schema/deploy/schemas/metaschema_public/types/object_category.sql +++ b/packages/metaschema-schema/deploy/schemas/metaschema_public/types/object_category.sql @@ -6,11 +6,11 @@ BEGIN; -- Unified category type for all metaschema objects (tables, fields, procedures, triggers, indexes, policies, constraints, etc.) -- 'core' - system-level objects (id fields, entity_id, actor_id, etc.) --- 'module' - objects created by modules (users, permissions, memberships, etc.) --- 'permissions' - permission-framework objects (SPRTs, grants, permission defaults) — excluded from exports via excludeCategories +-- 'module' - objects created by modules (users, capabilities, memberships, etc.) +-- 'capabilities' - capability-framework objects (SPRTs, grants, capability defaults) — excluded from exports via excludeCategories -- 'auth' - authentication/session objects (sessions, rate limits, identity providers) — excluded from exports via excludeCategories -- 'memberships' - membership-structure objects (memberships, members, profiles, settings) — excluded from exports via excludeCategories -- 'app' - user-defined application objects -CREATE TYPE metaschema_public.object_category AS ENUM ('core', 'module', 'permissions', 'auth', 'memberships', 'app'); +CREATE TYPE metaschema_public.object_category AS ENUM ('core', 'module', 'capabilities', 'auth', 'memberships', 'app'); COMMIT; diff --git a/packages/metaschema-schema/sql/metaschema-schema--0.40.0.bundle.tar.gz b/packages/metaschema-schema/sql/metaschema-schema--0.40.0.bundle.tar.gz index 1ebc4059..dfdaa4ce 100644 Binary files a/packages/metaschema-schema/sql/metaschema-schema--0.40.0.bundle.tar.gz and b/packages/metaschema-schema/sql/metaschema-schema--0.40.0.bundle.tar.gz differ diff --git a/packages/metaschema-schema/sql/metaschema-schema--0.40.0.sql b/packages/metaschema-schema/sql/metaschema-schema--0.40.0.sql index b087a14a..e4b1314f 100644 --- a/packages/metaschema-schema/sql/metaschema-schema--0.40.0.sql +++ b/packages/metaschema-schema/sql/metaschema-schema--0.40.0.sql @@ -277,7 +277,7 @@ BEGIN END; $EOFCODE$ LANGUAGE plpgsql IMMUTABLE; -CREATE TYPE metaschema_public.object_category AS ENUM ('core', 'module', 'permissions', 'auth', 'memberships', 'app'); +CREATE TYPE metaschema_public.object_category AS ENUM ('core', 'module', 'capabilities', 'auth', 'memberships', 'app'); CREATE TYPE metaschema_public.api_exposure_level AS ENUM ('exposable', 'internal_only', 'never_expose'); diff --git a/packages/object-store/deploy/schemas/object_store_public/procedures/insert_nodes_at_paths.sql b/packages/object-store/deploy/schemas/object_store_public/procedures/insert_nodes_at_paths.sql index 6df26acc..c912bf6c 100644 --- a/packages/object-store/deploy/schemas/object_store_public/procedures/insert_nodes_at_paths.sql +++ b/packages/object-store/deploy/schemas/object_store_public/procedures/insert_nodes_at_paths.sql @@ -467,6 +467,34 @@ BEGIN END; $$ LANGUAGE plpgsql -VOLATILE; +VOLATILE +-- Every query here is driven by unnested arrays, whose row counts the planner +-- cannot know: it costed one level of a 20k-path batch at 825,000 rows when the +-- level produced 79, and a cost that high turns JIT on. So each level paid +-- ~460ms compiling ~90 functions (Optimization 249ms, Emission 179ms) to +-- execute in single-digit milliseconds: 2.67s of JIT to do 0.32s of work, and +-- it recurs on every level of every call. The estimates are the defect and they +-- are not fixable from here, so decline the compiler rather than the plan. +SET jit = off +-- The same missing estimates also decide the hash sizes, so every level's +-- jsonb_object_agg and its joins batch against whatever work_mem happens to be. +-- At the 4MB default one level spills ~5MB: provisioning constructive wrote +-- 6,942,736 temp blocks (~53GB) across 10,366 level queries and the phase took +-- 1118s. Pinning work_mem here took it to 222s with the spill gone -- 5x, from +-- one setting, on the same box and commit. 64MB is where the spill reaches zero +-- for that batch shape; larger values measured no faster. +SET work_mem = '64MB' +-- And the estimates are only right while the plan is a custom one. unnest's +-- support function reads the array's real length from the Param, so the first +-- executions of a level plan it correctly -- but a plpgsql statement switches to +-- a generic plan on its third execution, and a generic plan has no Param to +-- read: the level comes out estimated at 1 row, which turns dir_kids into the +-- inner side of a nested loop and re-aggregates every directory of the level +-- below once per row of this one. Measured on a 20k-path batch, repeated in one +-- session: 5.7s, 6.2s, then 73s, 76s, 76s, ... -- and it only appears once the +-- table has statistics, so whether a run hits it depends on autovacuum, which +-- is where the ~55s-vs-437s bimodality in CI came from. Replanning each level +-- costs ~30% on the batches that were already fast and removes the 12x cliff. +SET plan_cache_mode = 'force_custom_plan'; COMMIT; diff --git a/packages/object-store/sql/object-store--0.40.0.bundle.tar.gz b/packages/object-store/sql/object-store--0.40.0.bundle.tar.gz index 72de6a57..ae8e5ef4 100644 Binary files a/packages/object-store/sql/object-store--0.40.0.bundle.tar.gz and b/packages/object-store/sql/object-store--0.40.0.bundle.tar.gz differ diff --git a/packages/object-store/sql/object-store--0.40.0.sql b/packages/object-store/sql/object-store--0.40.0.sql index 7dae5626..550eeb0c 100644 --- a/packages/object-store/sql/object-store--0.40.0.sql +++ b/packages/object-store/sql/object-store--0.40.0.sql @@ -991,4 +991,4 @@ BEGIN root_id := pd_id[1]; RETURN root_id; END; -$EOFCODE$ LANGUAGE plpgsql VOLATILE; \ No newline at end of file +$EOFCODE$ LANGUAGE plpgsql VOLATILE SET jit TO off SET work_mem TO '64MB' SET plan_cache_mode TO force_custom_plan; \ No newline at end of file diff --git a/packages/object-tree/__tests__/objects/set-many-and-commit.test.ts b/packages/object-tree/__tests__/objects/set-many-and-commit.test.ts index 9d175298..cf2a7d0d 100644 --- a/packages/object-tree/__tests__/objects/set-many-and-commit.test.ts +++ b/packages/object-tree/__tests__/objects/set-many-and-commit.test.ts @@ -79,6 +79,45 @@ const setManyAndCommit = async (scope_id: string, store_id: string, entries: Ent return row.tree_id as string; }; +const setPropsAndCommit = async ( + scope_id: string, + store_id: string, + path: string[], + data: unknown +) => { + const [row] = await pg.any( + `SELECT (object_tree_public.set_props_and_commit( + s_id := $1::uuid, + store_id := $2::uuid, + refname := 'main', + path := $3::text[], + data := $4::jsonb + )).tree_id AS tree_id`, + [scope_id, store_id, path, JSON.stringify(data)] + ); + return row.tree_id as string; +}; + +const storeHash = async (scope_id: string, store_id: string) => { + const [row] = await pg.any( + `SELECT s.hash FROM object_tree_public.store s + WHERE s.scope_id = $1::uuid AND s.id = $2::uuid`, + [scope_id, store_id] + ); + return row.hash as string; +}; + +const treeId = async (scope_id: string, store_id: string) => { + const [row] = await pg.any( + `SELECT c.tree_id + FROM object_tree_public.ref r + JOIN object_tree_public.commit c ON c.id = r.commit_id AND c.scope_id = r.scope_id + WHERE r.scope_id = $1::uuid AND r.store_id = $2::uuid AND r.name = 'main'`, + [scope_id, store_id] + ); + return row.tree_id as string; +}; + const countCommits = async (scope_id: string, store_id: string) => { const [row] = await pg.any( `SELECT count(*)::int AS n FROM object_tree_public.commit @@ -143,6 +182,28 @@ describe('set_many_and_commit', () => { expect(await countCommits(batched_scope, store_id)).toEqual(3); }); + it('the store carries the head tree from initialization onward', async () => { + const store_id = '55555555-5555-4555-8555-555555555555'; + // The other cases never need the store row itself: init_empty_repo writes the + // ref and the commit, and the store is the caller's to create. + await pg.any( + `INSERT INTO object_tree_public.store (id, scope_id, name) VALUES ($1::uuid, $2::uuid, 'head')`, + [store_id, batched_scope] + ); + await initRepo(batched_scope, store_id); + expect(await storeHash(batched_scope, store_id)).toEqual( + await treeId(batched_scope, store_id) + ); + + const tree_id = await setManyAndCommit(batched_scope, store_id, entries); + expect(await storeHash(batched_scope, store_id)).toEqual(tree_id); + + const after_props = await setPropsAndCommit(batched_scope, store_id, ['package.json'], { + name: 'renamed' + }); + expect(await storeHash(batched_scope, store_id)).toEqual(after_props); + }); + it('nothing to write leaves the ref alone', async () => { const store_id = '44444444-4444-4444-8444-444444444444'; await initRepo(batched_scope, store_id); diff --git a/packages/object-tree/deploy/schemas/object_tree_public/procedures/init_empty_repo.sql b/packages/object-tree/deploy/schemas/object_tree_public/procedures/init_empty_repo.sql index 2cb029a2..3696d160 100644 --- a/packages/object-tree/deploy/schemas/object_tree_public/procedures/init_empty_repo.sql +++ b/packages/object-tree/deploy/schemas/object_tree_public/procedures/init_empty_repo.sql @@ -3,6 +3,7 @@ -- requires: schemas/object_tree_public/schema -- requires: schemas/object_tree_public/tables/commit/table -- requires: schemas/object_tree_public/tables/ref/table +-- requires: schemas/object_tree_public/tables/store/table BEGIN; @@ -36,6 +37,11 @@ BEGIN UPDATE object_tree_public.ref SET commit_id = vcommit_id WHERE id = vref_id; + -- the store's head tree, kept current from here on by the commit functions + UPDATE object_tree_public.store s SET hash = vtree_id + WHERE s.id = init_empty_repo.store_id + AND s.scope_id = s_id; + END; $$ LANGUAGE 'plpgsql' VOLATILE; diff --git a/packages/object-tree/deploy/schemas/object_tree_public/procedures/set_and_commit.sql b/packages/object-tree/deploy/schemas/object_tree_public/procedures/set_and_commit.sql index 9107b73d..ae39169d 100644 --- a/packages/object-tree/deploy/schemas/object_tree_public/procedures/set_and_commit.sql +++ b/packages/object-tree/deploy/schemas/object_tree_public/procedures/set_and_commit.sql @@ -3,6 +3,7 @@ -- requires: schemas/object_tree_public/schema -- requires: schemas/object_tree_public/tables/commit/table -- requires: schemas/object_tree_public/tables/ref/table +-- requires: schemas/object_tree_public/tables/store/table BEGIN; @@ -49,6 +50,9 @@ LANGUAGE 'plpgsql' VOLATILE; -- commits. Not expressible through set_many_and_commit: the batched primitive -- takes a node's children as given (an absent kids/ktree means "no children"), -- whereas this reads the existing node to carry them over. +-- +-- Takes the ref FOR UPDATE for the same reason set_many_and_commit does: read +-- ref, compute, repoint is a lost update between concurrent writers to one ref. CREATE FUNCTION object_tree_public.set_props_and_commit( s_id uuid, store_id uuid, @@ -58,19 +62,19 @@ CREATE FUNCTION object_tree_public.set_props_and_commit( message text DEFAULT NULL ) returns object_tree_public.commit as $$ DECLARE - hash uuid; + tree uuid; ref object_tree_public.ref; com object_tree_public.commit; BEGIN -SELECT * FROM +SELECT * INTO ref FROM object_tree_public.ref r WHERE r.scope_id = s_id AND r.store_id = set_props_and_commit.store_id AND r.name = refname -INTO ref; +FOR UPDATE; IF (NOT FOUND) THEN RAISE EXCEPTION 'REF_NOT_FOUND'; @@ -90,7 +94,7 @@ END IF; SELECT * FROM object_store_public.set_data_at_path (s_id, com.tree_id, path, data) -INTO hash; +INTO tree; INSERT INTO object_tree_public.commit ( scope_id, @@ -98,13 +102,18 @@ INSERT INTO object_tree_public.commit ( message, parent_ids, tree_id -) VALUES (s_id, set_props_and_commit.store_id, set_props_and_commit.message, ARRAY[com.id]::uuid[], hash) +) VALUES (s_id, set_props_and_commit.store_id, set_props_and_commit.message, ARRAY[com.id]::uuid[], tree) RETURNING * INTO com; UPDATE object_tree_public.ref r SET commit_id = com.id WHERE r.id = ref.id; +UPDATE object_tree_public.store s + SET hash = tree +WHERE s.id = set_props_and_commit.store_id + AND s.scope_id = s_id; + RETURN com; END; $$ diff --git a/packages/object-tree/deploy/schemas/object_tree_public/procedures/set_many_and_commit.sql b/packages/object-tree/deploy/schemas/object_tree_public/procedures/set_many_and_commit.sql index 8301dc49..c918a3dc 100644 --- a/packages/object-tree/deploy/schemas/object_tree_public/procedures/set_many_and_commit.sql +++ b/packages/object-tree/deploy/schemas/object_tree_public/procedures/set_many_and_commit.sql @@ -3,6 +3,7 @@ -- requires: schemas/object_tree_public/schema -- requires: schemas/object_tree_public/tables/commit/table -- requires: schemas/object_tree_public/tables/ref/table +-- requires: schemas/object_tree_public/tables/store/table BEGIN; @@ -18,11 +19,19 @@ BEGIN; -- the tree reads .tree_id, and a caller stamping provenance (every generated -- merkle writer does) gets .id without re-reading the ref. This is the same -- shape the generated stores' {prefix}set_many_and_commit returns. +-- +-- The ref row is taken FOR UPDATE, so concurrent writers to one ref queue here +-- rather than each reading the same parent commit and racing to repoint the ref, +-- which loses whichever write landed first. Two writers of identical content +-- collide on object_store_public.object's primary key instead, since node ids +-- are content hashes; different content is exactly the case that does not +-- collide, so nothing but this lock serializes it. The lock is per ref, so +-- unrelated stores and unrelated branches never wait on each other. CREATE FUNCTION object_tree_public.set_many_and_commit (s_id uuid, store_id uuid, refname text, entries jsonb, message text DEFAULT NULL) RETURNS object_tree_public.commit AS $$ DECLARE - hash uuid; + tree uuid; paths jsonb; datas jsonb[]; kids_list jsonb; @@ -31,13 +40,14 @@ DECLARE com object_tree_public.commit; BEGIN SELECT - * + * INTO ref FROM object_tree_public.ref AS r WHERE r.scope_id = s_id AND r.store_id = set_many_and_commit.store_id - AND r.name = refname INTO ref; + AND r.name = refname + FOR UPDATE; IF (NOT FOUND) THEN RAISE EXCEPTION 'REF_NOT_FOUND'; END IF; @@ -67,9 +77,9 @@ BEGIN SELECT * FROM - object_store_public.insert_nodes_at_paths (s_id := s_id, root := com.tree_id, paths := paths, datas := datas, kids_list := kids_list, ktree_list := ktree_list) INTO hash; + object_store_public.insert_nodes_at_paths (s_id := s_id, root := com.tree_id, paths := paths, datas := datas, kids_list := kids_list, ktree_list := ktree_list) INTO tree; INSERT INTO object_tree_public.commit (scope_id, store_id, message, parent_ids, tree_id) - VALUES (s_id, set_many_and_commit.store_id, set_many_and_commit.message, ARRAY[com.id]::uuid[], hash) + VALUES (s_id, set_many_and_commit.store_id, set_many_and_commit.message, ARRAY[com.id]::uuid[], tree) RETURNING * INTO com; UPDATE @@ -78,6 +88,15 @@ BEGIN commit_id = com.id WHERE r.id = ref.id; + -- store.hash is documented as the store's current head tree, and the + -- generated merkle stores keep theirs current; keep this one current too. + UPDATE + object_tree_public.store AS s + SET + hash = tree + WHERE + s.id = set_many_and_commit.store_id + AND s.scope_id = s_id; RETURN com; END; $$ diff --git a/packages/object-tree/sql/object-tree--0.40.0.bundle.tar.gz b/packages/object-tree/sql/object-tree--0.40.0.bundle.tar.gz index d02f2ef4..583461ac 100644 Binary files a/packages/object-tree/sql/object-tree--0.40.0.bundle.tar.gz and b/packages/object-tree/sql/object-tree--0.40.0.bundle.tar.gz differ diff --git a/packages/object-tree/sql/object-tree--0.40.0.sql b/packages/object-tree/sql/object-tree--0.40.0.sql index 1cf896f3..e0993b5f 100644 --- a/packages/object-tree/sql/object-tree--0.40.0.sql +++ b/packages/object-tree/sql/object-tree--0.40.0.sql @@ -153,6 +153,11 @@ BEGIN UPDATE object_tree_public.ref SET commit_id = vcommit_id WHERE id = vref_id; + -- the store's head tree, kept current from here on by the commit functions + UPDATE object_tree_public.store s SET hash = vtree_id + WHERE s.id = init_empty_repo.store_id + AND s.scope_id = s_id; + END; $EOFCODE$ LANGUAGE plpgsql VOLATILE; @@ -200,19 +205,19 @@ CREATE FUNCTION object_tree_public.set_props_and_commit( message text DEFAULT NULL ) RETURNS object_tree_public.commit AS $EOFCODE$ DECLARE - hash uuid; + tree uuid; ref object_tree_public.ref; com object_tree_public.commit; BEGIN -SELECT * FROM +SELECT * INTO ref FROM object_tree_public.ref r WHERE r.scope_id = s_id AND r.store_id = set_props_and_commit.store_id AND r.name = refname -INTO ref; +FOR UPDATE; IF (NOT FOUND) THEN RAISE EXCEPTION 'REF_NOT_FOUND'; @@ -232,7 +237,7 @@ END IF; SELECT * FROM object_store_public.set_data_at_path (s_id, com.tree_id, path, data) -INTO hash; +INTO tree; INSERT INTO object_tree_public.commit ( scope_id, @@ -240,13 +245,18 @@ INSERT INTO object_tree_public.commit ( message, parent_ids, tree_id -) VALUES (s_id, set_props_and_commit.store_id, set_props_and_commit.message, ARRAY[com.id]::uuid[], hash) +) VALUES (s_id, set_props_and_commit.store_id, set_props_and_commit.message, ARRAY[com.id]::uuid[], tree) RETURNING * INTO com; UPDATE object_tree_public.ref r SET commit_id = com.id WHERE r.id = ref.id; +UPDATE object_tree_public.store s + SET hash = tree +WHERE s.id = set_props_and_commit.store_id + AND s.scope_id = s_id; + RETURN com; END; $EOFCODE$ LANGUAGE plpgsql VOLATILE; @@ -282,7 +292,7 @@ CREATE FUNCTION object_tree_public.set_many_and_commit( message text DEFAULT NULL ) RETURNS object_tree_public.commit AS $EOFCODE$ DECLARE - hash uuid; + tree uuid; paths jsonb; datas jsonb[]; kids_list jsonb; @@ -291,13 +301,14 @@ DECLARE com object_tree_public.commit; BEGIN SELECT - * + * INTO ref FROM object_tree_public.ref AS r WHERE r.scope_id = s_id AND r.store_id = set_many_and_commit.store_id - AND r.name = refname INTO ref; + AND r.name = refname + FOR UPDATE; IF (NOT FOUND) THEN RAISE EXCEPTION 'REF_NOT_FOUND'; END IF; @@ -327,9 +338,9 @@ BEGIN SELECT * FROM - object_store_public.insert_nodes_at_paths (s_id := s_id, root := com.tree_id, paths := paths, datas := datas, kids_list := kids_list, ktree_list := ktree_list) INTO hash; + object_store_public.insert_nodes_at_paths (s_id := s_id, root := com.tree_id, paths := paths, datas := datas, kids_list := kids_list, ktree_list := ktree_list) INTO tree; INSERT INTO object_tree_public.commit (scope_id, store_id, message, parent_ids, tree_id) - VALUES (s_id, set_many_and_commit.store_id, set_many_and_commit.message, ARRAY[com.id]::uuid[], hash) + VALUES (s_id, set_many_and_commit.store_id, set_many_and_commit.message, ARRAY[com.id]::uuid[], tree) RETURNING * INTO com; UPDATE @@ -338,6 +349,15 @@ BEGIN commit_id = com.id WHERE r.id = ref.id; + -- store.hash is documented as the store's current head tree, and the + -- generated merkle stores keep theirs current; keep this one current too. + UPDATE + object_tree_public.store AS s + SET + hash = tree + WHERE + s.id = set_many_and_commit.store_id + AND s.scope_id = s_id; RETURN com; END; $EOFCODE$ LANGUAGE plpgsql VOLATILE; \ No newline at end of file