From ef586197418fe01c949bc182052c8e0448b5124b Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Mon, 31 Aug 2026 08:11:24 +0000 Subject: [PATCH] Sync SQL sources from constructive-db Port the drifted deploy/revert/verify trees and plan entries for app-scope, database-jobs, function-resolution, jwt-claims, metaschema-modules and metaschema-schema, repackage the sql/ artifacts, and update the tests those sources need. --- .../__tests__/actor-entity-type.test.ts | 264 +++++++++ .../app_scope/procedures/actor_entity.sql | 139 +++++ .../app_scope/procedures/frame_owns.sql | 91 ++++ packages/app-scope/pgpm.plan | 2 + .../app_scope/procedures/actor_entity.sql | 7 + .../app_scope/procedures/frame_owns.sql | 7 + .../sql/pgpm-app-scope--0.43.2.bundle.tar.gz | Bin 12114 -> 15743 bytes .../app-scope/sql/pgpm-app-scope--0.43.2.sql | 160 +++++- .../app_scope/procedures/actor_entity.sql | 7 + .../app_scope/procedures/frame_owns.sql | 7 + packages/database-jobs/__tests__/jobs.test.ts | 331 +++++++++++- .../__tests__/queue-locks.test.ts | 134 +++++ .../schemas/app_jobs/procedures/add_job.sql | 38 +- .../app_jobs/procedures/add_scheduled_job.sql | 45 +- ...grant_execute_add_job_to_authenticated.sql | 2 +- ...ute_add_scheduled_job_to_authenticated.sql | 2 +- .../app_jobs/procedures/run_scheduled_job.sql | 21 +- .../schemas/app_jobs/tables/jobs/table.sql | 5 +- .../app_jobs/tables/scheduled_jobs/table.sql | 9 +- packages/database-jobs/pgpm.plan | 6 +- .../schemas/app_jobs/procedures/add_job.sql | 2 +- .../app_jobs/procedures/add_scheduled_job.sql | 2 +- ...grant_execute_add_job_to_authenticated.sql | 2 +- ...ute_add_scheduled_job_to_authenticated.sql | 2 +- .../pgpm-database-jobs--0.43.2.bundle.tar.gz | Bin 22186 -> 23198 bytes .../sql/pgpm-database-jobs--0.43.2.sql | 104 +++- .../schemas/app_jobs/procedures/add_job.sql | 2 +- .../app_jobs/procedures/add_scheduled_job.sql | 2 +- ...grant_execute_add_job_to_authenticated.sql | 2 +- ...ute_add_scheduled_job_to_authenticated.sql | 2 +- .../__tests__/capabilities.test.ts | 8 +- .../__tests__/catalog-fastpath.test.ts | 8 +- .../published-capability-catalog.test.ts | 9 +- .../__tests__/published-catalog.test.ts | 8 +- .../__tests__/resolution.test.ts | 27 +- .../procedures/enqueue.sql | 14 +- .../procedures/install_mantra.sql | 107 ++++ .../procedures/install_route_bindings.sql | 506 ++++++++++++++++++ packages/function-resolution/pgpm.plan | 4 +- .../procedures/enqueue.sql | 2 +- .../procedures/install_mantra.sql | 7 + .../procedures/install_route_bindings.sql | 7 + ...-function-resolution--0.43.2.bundle.tar.gz | Bin 31241 -> 40462 bytes .../sql/pgpm-function-resolution--0.43.2.sql | 498 ++++++++++++++++- .../procedures/enqueue.sql | 2 +- .../procedures/install_mantra.sql | 7 + .../procedures/install_route_bindings.sql | 7 + packages/jwt-claims/__tests__/jwt.test.ts | 264 ++++++++- .../schemas/ctx/procedures/ip_address.sql | 3 +- .../deploy/schemas/ctx/procedures/origin.sql | 3 +- .../deploy/schemas/ctx/procedures/uagent.sql | 3 +- .../deploy/schemas/ctx/procedures/uid.sql | 3 +- .../procedures/assert_attribution.sql | 77 +++ .../jwt_private/procedures/current_api_id.sql | 2 +- .../procedures/current_database_id.sql | 37 +- .../procedures/current_entity_id.sql | 19 + .../procedures/current_entity_type.sql | 15 + .../procedures/current_graph_execution_id.sql | 2 +- .../procedures/current_session_id.sql | 2 +- .../procedures/current_token_id.sql | 3 +- .../procedures/require_database_id.sql | 31 ++ .../procedures/require_entity_id.sql | 31 ++ .../procedures/require_entity_type.sql | 29 + .../procedures/current_ip_address.sql | 2 +- .../jwt_public/procedures/current_origin.sql | 3 +- .../procedures/current_principal_id.sql | 2 +- .../procedures/current_role_type.sql | 2 +- .../procedures/current_user_agent.sql | 2 +- .../jwt_public/procedures/current_user_id.sql | 2 +- .../jwt_public/procedures/require_user_id.sql | 31 ++ packages/jwt-claims/pgpm.plan | 7 + .../procedures/assert_attribution.sql | 7 + .../procedures/current_entity_id.sql | 7 + .../procedures/current_entity_type.sql | 7 + .../procedures/require_database_id.sql | 7 + .../procedures/require_entity_id.sql | 7 + .../procedures/require_entity_type.sql | 7 + .../jwt_public/procedures/require_user_id.sql | 7 + .../sql/pgpm-jwt-claims--0.43.1.bundle.tar.gz | Bin 8296 -> 10917 bytes .../sql/pgpm-jwt-claims--0.43.1.sql | 164 +++++- .../procedures/assert_attribution.sql | 7 + .../procedures/current_entity_id.sql | 14 + .../procedures/current_entity_type.sql | 14 + .../procedures/require_database_id.sql | 7 + .../procedures/require_entity_id.sql | 7 + .../procedures/require_entity_type.sql | 7 + .../jwt_public/procedures/require_user_id.sql | 7 + .../__snapshots__/modules.test.ts.snap | 2 +- .../tables/agent_module/table.sql | 11 + .../tables/app_module/table.sql | 4 + .../tables/billing_provider_module/table.sql | 27 + .../tables/catalog_module/table.sql | 18 +- .../tables/function_module/table.sql | 5 + .../tables/pages_module/table.sql | 19 + .../tables/repository_module/table.sql | 4 + .../tables/resource_module/table.sql | 8 + .../tables/route_module/table.sql | 15 + .../tables/secure_table_provision/table.sql | 12 +- .../tables/site_surface_module/table.sql | 14 + .../tables/storage_module/table.sql | 20 +- .../metaschema-modules--0.43.2.bundle.tar.gz | Bin 98940 -> 102298 bytes .../sql/metaschema-modules--0.43.2.sql | 101 +++- .../tables/pages_module/table.sql | 4 +- .../tables/resource_module/table.sql | 1 + .../tables/secure_table_provision/table.sql | 3 + .../metaschema-schema/__tests__/meta.test.ts | 190 +++++++ .../tables/function/table.sql | 117 ++++ .../identity_provider_registry/table.sql | 32 ++ .../tables/trigger/table.sql | 81 +++ packages/metaschema-schema/pgpm.plan | 3 +- .../identity_provider_registry/table.sql | 7 + .../metaschema-schema--0.43.2.bundle.tar.gz | Bin 29952 -> 34104 bytes .../sql/metaschema-schema--0.43.2.sql | 170 +++++- .../tables/function/table.sql | 6 +- .../identity_provider_registry/table.sql | 9 + 115 files changed, 4163 insertions(+), 189 deletions(-) create mode 100644 packages/app-scope/__tests__/actor-entity-type.test.ts create mode 100644 packages/app-scope/deploy/schemas/app_scope/procedures/actor_entity.sql create mode 100644 packages/app-scope/deploy/schemas/app_scope/procedures/frame_owns.sql create mode 100644 packages/app-scope/revert/schemas/app_scope/procedures/actor_entity.sql create mode 100644 packages/app-scope/revert/schemas/app_scope/procedures/frame_owns.sql create mode 100644 packages/app-scope/verify/schemas/app_scope/procedures/actor_entity.sql create mode 100644 packages/app-scope/verify/schemas/app_scope/procedures/frame_owns.sql create mode 100644 packages/database-jobs/__tests__/queue-locks.test.ts create mode 100644 packages/function-resolution/deploy/schemas/function_resolution/procedures/install_mantra.sql create mode 100644 packages/function-resolution/deploy/schemas/function_resolution/procedures/install_route_bindings.sql create mode 100644 packages/function-resolution/revert/schemas/function_resolution/procedures/install_mantra.sql create mode 100644 packages/function-resolution/revert/schemas/function_resolution/procedures/install_route_bindings.sql create mode 100644 packages/function-resolution/verify/schemas/function_resolution/procedures/install_mantra.sql create mode 100644 packages/function-resolution/verify/schemas/function_resolution/procedures/install_route_bindings.sql create mode 100644 packages/jwt-claims/deploy/schemas/jwt_private/procedures/assert_attribution.sql create mode 100644 packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_entity_id.sql create mode 100644 packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_entity_type.sql create mode 100644 packages/jwt-claims/deploy/schemas/jwt_private/procedures/require_database_id.sql create mode 100644 packages/jwt-claims/deploy/schemas/jwt_private/procedures/require_entity_id.sql create mode 100644 packages/jwt-claims/deploy/schemas/jwt_private/procedures/require_entity_type.sql create mode 100644 packages/jwt-claims/deploy/schemas/jwt_public/procedures/require_user_id.sql create mode 100644 packages/jwt-claims/revert/schemas/jwt_private/procedures/assert_attribution.sql create mode 100644 packages/jwt-claims/revert/schemas/jwt_private/procedures/current_entity_id.sql create mode 100644 packages/jwt-claims/revert/schemas/jwt_private/procedures/current_entity_type.sql create mode 100644 packages/jwt-claims/revert/schemas/jwt_private/procedures/require_database_id.sql create mode 100644 packages/jwt-claims/revert/schemas/jwt_private/procedures/require_entity_id.sql create mode 100644 packages/jwt-claims/revert/schemas/jwt_private/procedures/require_entity_type.sql create mode 100644 packages/jwt-claims/revert/schemas/jwt_public/procedures/require_user_id.sql create mode 100644 packages/jwt-claims/verify/schemas/jwt_private/procedures/assert_attribution.sql create mode 100644 packages/jwt-claims/verify/schemas/jwt_private/procedures/current_entity_id.sql create mode 100644 packages/jwt-claims/verify/schemas/jwt_private/procedures/current_entity_type.sql create mode 100644 packages/jwt-claims/verify/schemas/jwt_private/procedures/require_database_id.sql create mode 100644 packages/jwt-claims/verify/schemas/jwt_private/procedures/require_entity_id.sql create mode 100644 packages/jwt-claims/verify/schemas/jwt_private/procedures/require_entity_type.sql create mode 100644 packages/jwt-claims/verify/schemas/jwt_public/procedures/require_user_id.sql create mode 100644 packages/metaschema-schema/deploy/schemas/metaschema_public/tables/identity_provider_registry/table.sql create mode 100644 packages/metaschema-schema/revert/schemas/metaschema_public/tables/identity_provider_registry/table.sql create mode 100644 packages/metaschema-schema/verify/schemas/metaschema_public/tables/identity_provider_registry/table.sql diff --git a/packages/app-scope/__tests__/actor-entity-type.test.ts b/packages/app-scope/__tests__/actor-entity-type.test.ts new file mode 100644 index 000000000..e35556495 --- /dev/null +++ b/packages/app-scope/__tests__/actor-entity-type.test.ts @@ -0,0 +1,264 @@ +import { getConnections, PgTestClient } from 'pgsql-test'; + +let pg: PgTestClient; +let teardown: () => Promise; + +const DATABASE_ID = '22222222-2222-2222-2222-222222222222'; +const EMPTY_DATABASE_ID = '33333333-3333-3333-3333-333333333333'; +const PRINCIPAL_DATABASE_ID = '77777777-7777-7777-7777-777777777777'; +const APP_USER_ID = '44444444-4444-4444-4444-444444444444'; +const ORG_USER_ID = '55555555-5555-5555-5555-555555555555'; +const UNKNOWN_USER_ID = '66666666-6666-6666-6666-666666666666'; +const PRINCIPAL_OWNER_ID = '88888888-8888-8888-8888-888888888888'; +const PRINCIPAL_USER_ID = '99999999-9999-9999-9999-999999999999'; +const ORPHAN_PRINCIPAL_USER_ID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'; +const NESTED_OWNER_ID = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'; +const NESTED_PRINCIPAL_USER_ID = 'cccccccc-cccc-cccc-cccc-cccccccccccc'; + +describe('app_scope.actor_entity', () => { + beforeAll(async () => { + ({ pg, teardown } = await getConnections()); + + await pg.query( + `INSERT INTO metaschema_public.database (id, name) + VALUES + ($1, 'actor_entity_type_db'), + ($2, 'actor_entity_type_empty_db'), + ($3, 'actor_entity_type_principal_db')`, + [DATABASE_ID, EMPTY_DATABASE_ID, PRINCIPAL_DATABASE_ID] + ); + await pg.query(`CREATE SCHEMA actor_entity_type_test`); + await pg.query(`CREATE TABLE actor_entity_type_test.role_types ( + id integer PRIMARY KEY, + name text NOT NULL + )`); + await pg.query(`CREATE TABLE actor_entity_type_test.users ( + id uuid PRIMARY KEY, + type integer NOT NULL + )`); + await pg.query( + `INSERT INTO actor_entity_type_test.role_types (id, name) + VALUES (1, 'User'), (2, 'Organization'), (3, 'Principal')` + ); + await pg.query( + `INSERT INTO actor_entity_type_test.users (id, type) + VALUES ($1, 1), ($2, 2)`, + [APP_USER_ID, ORG_USER_ID] + ); + + const schema = await pg.one<{ id: string }>( + `INSERT INTO metaschema_public.schema (database_id, name, schema_name) + VALUES ($1, 'actor_entity_type_test', 'actor_entity_type_test') + RETURNING id`, + [DATABASE_ID] + ); + const users = await pg.one<{ id: string }>( + `INSERT INTO metaschema_public."table" (database_id, schema_id, name) + VALUES ($1, $2, 'users') + RETURNING id`, + [DATABASE_ID, schema.id] + ); + const roleTypes = await pg.one<{ id: string }>( + `INSERT INTO metaschema_public."table" (database_id, schema_id, name) + VALUES ($1, $2, 'role_types') + RETURNING id`, + [DATABASE_ID, schema.id] + ); + await pg.query( + `INSERT INTO metaschema_modules_public.users_module + (database_id, schema_id, table_id, type_table_id) + VALUES ($1, $2, $3, $4)`, + [DATABASE_ID, schema.id, users.id, roleTypes.id] + ); + + await pg.query(`CREATE SCHEMA actor_entity_principal_test`); + await pg.query(`CREATE TABLE actor_entity_principal_test.role_types ( + id integer PRIMARY KEY, + name text NOT NULL + )`); + await pg.query(`CREATE TABLE actor_entity_principal_test.users ( + id uuid PRIMARY KEY, + type integer NOT NULL + )`); + await pg.query(`CREATE TABLE actor_entity_principal_test.principals ( + user_id uuid PRIMARY KEY, + owner_id uuid NOT NULL + )`); + await pg.query(`CREATE TABLE actor_entity_principal_test.principal_entities ( + user_id uuid NOT NULL, + entity_id uuid NOT NULL + )`); + await pg.query(`CREATE TABLE actor_entity_principal_test.sessions ( + id uuid PRIMARY KEY + )`); + await pg.query(`CREATE TABLE actor_entity_principal_test.session_credentials ( + id uuid PRIMARY KEY + )`); + await pg.query( + `INSERT INTO actor_entity_principal_test.role_types (id, name) + VALUES (1, 'User'), (2, 'Organization'), (3, 'Principal')` + ); + await pg.query( + `INSERT INTO actor_entity_principal_test.users (id, type) + VALUES + ($1, 1), + ($2, 3), + ($3, 3), + ($4, 3), + ($5, 3)`, + [ + PRINCIPAL_OWNER_ID, + PRINCIPAL_USER_ID, + ORPHAN_PRINCIPAL_USER_ID, + NESTED_OWNER_ID, + NESTED_PRINCIPAL_USER_ID, + ] + ); + await pg.query( + `INSERT INTO actor_entity_principal_test.principals (user_id, owner_id) + VALUES ($1, $2), ($3, $4)`, + [PRINCIPAL_USER_ID, PRINCIPAL_OWNER_ID, NESTED_PRINCIPAL_USER_ID, NESTED_OWNER_ID] + ); + + const principalSchema = await pg.one<{ id: string }>( + `INSERT INTO metaschema_public.schema (database_id, name, schema_name) + VALUES ($1, 'actor_entity_principal_test', 'actor_entity_principal_test') + RETURNING id`, + [PRINCIPAL_DATABASE_ID] + ); + const principalUsers = await pg.one<{ id: string }>( + `INSERT INTO metaschema_public."table" (database_id, schema_id, name) + VALUES ($1, $2, 'users') + RETURNING id`, + [PRINCIPAL_DATABASE_ID, principalSchema.id] + ); + const principalRoleTypes = await pg.one<{ id: string }>( + `INSERT INTO metaschema_public."table" (database_id, schema_id, name) + VALUES ($1, $2, 'role_types') + RETURNING id`, + [PRINCIPAL_DATABASE_ID, principalSchema.id] + ); + const principalPrincipals = await pg.one<{ id: string }>( + `INSERT INTO metaschema_public."table" (database_id, schema_id, name) + VALUES ($1, $2, 'principals') + RETURNING id`, + [PRINCIPAL_DATABASE_ID, principalSchema.id] + ); + const principalEntities = await pg.one<{ id: string }>( + `INSERT INTO metaschema_public."table" (database_id, schema_id, name) + VALUES ($1, $2, 'principal_entities') + RETURNING id`, + [PRINCIPAL_DATABASE_ID, principalSchema.id] + ); + const principalSessions = await pg.one<{ id: string }>( + `INSERT INTO metaschema_public."table" (database_id, schema_id, name) + VALUES ($1, $2, 'sessions') + RETURNING id`, + [PRINCIPAL_DATABASE_ID, principalSchema.id] + ); + const principalSessionCredentials = await pg.one<{ id: string }>( + `INSERT INTO metaschema_public."table" (database_id, schema_id, name) + VALUES ($1, $2, 'session_credentials') + RETURNING id`, + [PRINCIPAL_DATABASE_ID, principalSchema.id] + ); + await pg.query( + `INSERT INTO metaschema_modules_public.users_module + (database_id, schema_id, table_id, type_table_id) + VALUES ($1, $2, $3, $4)`, + [ + PRINCIPAL_DATABASE_ID, + principalSchema.id, + principalUsers.id, + principalRoleTypes.id, + ] + ); + await pg.query( + `INSERT INTO metaschema_modules_public.principal_auth_module + ( + database_id, + schema_id, + principals_table_id, + principal_entities_table_id, + users_table_id, + sessions_table_id, + session_credentials_table_id + ) + VALUES ($1, $2, $3, $4, $5, $6, $7)`, + [ + PRINCIPAL_DATABASE_ID, + principalSchema.id, + principalPrincipals.id, + principalEntities.id, + principalUsers.id, + principalSessions.id, + principalSessionCredentials.id, + ] + ); + }); + + afterAll(() => teardown()); + + it('resolves an ordinary user from the users role-types registry', async () => { + const actual = await pg.one<{ entity_id: string; entity_type: string }>( + `SELECT * FROM app_scope.actor_entity($1, $2)`, + [DATABASE_ID, APP_USER_ID] + ); + expect(actual).toEqual({ entity_id: APP_USER_ID, entity_type: 'app' }); + }); + + it('resolves an organization-typed users row to the org scope', async () => { + const actual = await pg.one<{ entity_id: string; entity_type: string }>( + `SELECT * FROM app_scope.actor_entity($1, $2)`, + [DATABASE_ID, ORG_USER_ID] + ); + expect(actual).toEqual({ entity_id: ORG_USER_ID, entity_type: 'org' }); + }); + + it('raises for an unknown actor', async () => { + await expect( + pg.one(`SELECT * FROM app_scope.actor_entity($1, $2)`, [DATABASE_ID, UNKNOWN_USER_ID]) + ).rejects.toMatchObject({ code: '42704' }); + }); + + it('resolves in a database that installs users without principal_auth (minimal preset)', async () => { + // Minimal is the preset the sync gateway serves; the previous implementation 500'd on it. + const actual = await pg.one<{ entity_id: string; entity_type: string }>( + `SELECT * FROM app_scope.actor_entity($1, $2)`, + [DATABASE_ID, APP_USER_ID] + ); + expect(actual).toEqual({ entity_id: APP_USER_ID, entity_type: 'app' }); + }); + + it('resolves a principal to its type-1 owner in a database with principal_auth', async () => { + const actual = await pg.one<{ entity_id: string; entity_type: string }>( + `SELECT * FROM app_scope.actor_entity($1, $2)`, + [PRINCIPAL_DATABASE_ID, PRINCIPAL_USER_ID] + ); + expect(actual).toEqual({ entity_id: PRINCIPAL_OWNER_ID, entity_type: 'app' }); + }); + + it('raises when a principal has no principals row', async () => { + await expect( + pg.one(`SELECT * FROM app_scope.actor_entity($1, $2)`, [ + PRINCIPAL_DATABASE_ID, + ORPHAN_PRINCIPAL_USER_ID, + ]) + ).rejects.toMatchObject({ code: '42704' }); + }); + + it('raises when a principal owner is itself a principal', async () => { + await expect( + pg.one(`SELECT * FROM app_scope.actor_entity($1, $2)`, [ + PRINCIPAL_DATABASE_ID, + NESTED_PRINCIPAL_USER_ID, + ]) + ).rejects.toMatchObject({ code: '42704' }); + }); + + it('raises when the database has no users module', async () => { + await expect( + pg.one(`SELECT * FROM app_scope.actor_entity($1, $2)`, [EMPTY_DATABASE_ID, UNKNOWN_USER_ID]) + ).rejects.toMatchObject({ code: '42704' }); + }); +}); diff --git a/packages/app-scope/deploy/schemas/app_scope/procedures/actor_entity.sql b/packages/app-scope/deploy/schemas/app_scope/procedures/actor_entity.sql new file mode 100644 index 000000000..40ed9d7e8 --- /dev/null +++ b/packages/app-scope/deploy/schemas/app_scope/procedures/actor_entity.sql @@ -0,0 +1,139 @@ +-- Deploy schemas/app_scope/procedures/actor_entity to pg +-- requires: schemas/app_scope/schema +-- requires: metaschema-schema:schemas/metaschema_public/tables/table/table +-- requires: metaschema-modules:schemas/metaschema_modules_public/tables/users_module/table +-- requires: metaschema-modules:schemas/metaschema_modules_public/tables/principal_auth_module/table + +BEGIN; + +-- actor_entity: the entity pair an actor carries when the actor IS the entity. +-- +-- A request that names no entity anywhere (no route, parameter or header +-- entity) and is not actorless is done on behalf of the actor itself. A +-- principal is not an entity of its own: work authenticated by a credential is +-- done on behalf of the human who owns it, so a principal actor resolves to its +-- owner's pair. +-- +-- users.type is an FK into the users module's own role-types table, which +-- metaschema_generators.users_module seeds with the same three rows in every +-- database (1 User, 2 Organization, 3 Principal) and exposes no grant, policy +-- or insert action for — so the type is a closed set and the scope it maps to +-- is our constant, not tenant data. Reading it back out of a per-database table +-- would only be indirection. The seed is pinned by a test that fails if it ever +-- stops matching this mapping. +-- +-- Raises rather than returning NULL: the caller is stamping the claims a +-- transaction will be attributed by, and half a pair is what strict attribution +-- exists to refuse. A NULL here would surface much later as an unattributable +-- job, with no trace of which request minted it. +-- +-- Dynamic SELECT against dynamically-named tables (identifiers are data, values +-- are bound parameters) because metaschema-generated tables live under each +-- database's schema hash — the same portable idiom as app_scope.dyn_lookup_uuid +-- and app_scope.membership_parent's probe, with no AST/deparser dependency. +CREATE FUNCTION app_scope.actor_entity( + database_id uuid, + actor_id uuid +) RETURNS TABLE ( + entity_id uuid, + entity_type text +) AS $$ +DECLARE + users_schema text; + users_table text; + principals_schema text; + principals_table text; + + -- the actor's own row, and the owner row a principal actor resolves to + actor_type integer; + owner_id uuid; + owner_type integer; + + resolved_id uuid; + resolved_type integer; + resolved_scope text; +BEGIN + IF actor_entity.actor_id IS NULL THEN + RAISE EXCEPTION 'ACTOR_REQUIRED: actor_entity needs an actor to type' + USING ERRCODE = '22004'; + END IF; + + -- The principals table is LEFT JOINed: a database may install users without + -- principal_auth (the minimal preset does), and that only matters if the + -- actor turns out to be a principal. + SELECT users_schema_row.schema_name, users_table_row.name, + principals_schema_row.schema_name, principals_table_row.name + INTO users_schema, users_table, principals_schema, principals_table + FROM metaschema_modules_public.users_module um + JOIN metaschema_public."table" users_table_row + ON (users_table_row.id = um.table_id) + JOIN metaschema_public.schema users_schema_row + ON (users_schema_row.id = users_table_row.schema_id + AND users_schema_row.database_id = users_table_row.database_id) + LEFT JOIN metaschema_modules_public.principal_auth_module pam + ON (pam.database_id = um.database_id) + LEFT JOIN metaschema_public."table" principals_table_row + ON (principals_table_row.id = pam.principals_table_id) + LEFT JOIN metaschema_public.schema principals_schema_row + ON (principals_schema_row.id = principals_table_row.schema_id + AND principals_schema_row.database_id = principals_table_row.database_id) + WHERE um.database_id = actor_entity.database_id; + + IF users_schema IS NULL THEN + RAISE EXCEPTION 'ACTOR_ENTITY_UNRESOLVED: database % installs no users module, so an actor has no entity to carry', actor_entity.database_id + USING ERRCODE = '42704'; + END IF; + + IF principals_schema IS NULL THEN + -- pgsql-lint-disable-next-line no-dynamic-sql -- lookup-only: the generated users table is dynamically named, while actor_id stays a bound parameter + EXECUTE format( + 'SELECT u.type FROM %I.%I u WHERE u.id = $1', + users_schema, users_table + ) INTO actor_type USING actor_entity.actor_id; + ELSE + -- pgsql-lint-disable-next-line no-dynamic-sql -- lookup-only: the generated users and principals tables are dynamically named, while actor_id stays a bound parameter + EXECUTE format( + 'SELECT u.type, owner.id, owner.type' + ' FROM %I.%I u' + ' LEFT JOIN %I.%I p ON p.user_id = u.id' + ' LEFT JOIN %I.%I owner ON owner.id = p.owner_id' + ' WHERE u.id = $1', + users_schema, users_table, + principals_schema, principals_table, + users_schema, users_table + ) INTO actor_type, owner_id, owner_type USING actor_entity.actor_id; + END IF; + + IF actor_type IS NULL THEN + RAISE EXCEPTION 'ACTOR_ENTITY_UNRESOLVED: actor % has no users row in database %', actor_entity.actor_id, actor_entity.database_id + USING ERRCODE = '42704'; + END IF; + + IF actor_type = 3 THEN + resolved_id := owner_id; + resolved_type := owner_type; + ELSE + resolved_id := actor_entity.actor_id; + resolved_type := actor_type; + END IF; + + resolved_scope := CASE resolved_type + WHEN 1 THEN 'app' + WHEN 2 THEN 'org' + END; + + -- Unresolvable: an unknown users.type, a principal with no owner row, or a + -- principal owned by another principal. + IF resolved_id IS NULL OR resolved_scope IS NULL THEN + RAISE EXCEPTION 'ACTOR_ENTITY_UNRESOLVED: actor % of users.type % resolves to no entity in database %', actor_entity.actor_id, actor_type, actor_entity.database_id + USING ERRCODE = '42704'; + END IF; + + RETURN QUERY SELECT resolved_id, resolved_scope; +END; +$$ LANGUAGE plpgsql STABLE SECURITY DEFINER; + +COMMENT ON FUNCTION app_scope.actor_entity(uuid, uuid) IS +'Resolves an actor to a complete entity pair from its users.type, or to the owner pair for a principal. Raises ACTOR_ENTITY_UNRESOLVED rather than returning a partial pair.'; + +COMMIT; diff --git a/packages/app-scope/deploy/schemas/app_scope/procedures/frame_owns.sql b/packages/app-scope/deploy/schemas/app_scope/procedures/frame_owns.sql new file mode 100644 index 000000000..fbb0ceb5b --- /dev/null +++ b/packages/app-scope/deploy/schemas/app_scope/procedures/frame_owns.sql @@ -0,0 +1,91 @@ +-- Deploy schemas/app_scope/procedures/frame_owns to pg +-- requires: schemas/app_scope/schema +-- requires: schemas/app_scope/procedures/frames + +BEGIN; + +-- frame_owns: does an execution's frame chain contain the frame that owns a +-- given catalog row? One boolean over app_scope.frames, so a WRITE-path +-- ownership check can admit exactly what the READ path would have resolved. +-- +-- A typed catalog row carries its owning frame as a triple: +-- (owner_scope, owner_key, owner_database_id) +-- written by catalog sync — a database-scope row is ('database', ), +-- a global-tier row is ('app' | 'platform', NULL) — and the row's home database +-- disambiguates the one physical relation that holds every database's rows. +-- This function answers whether that triple names one of the frames +-- app_scope.frames returns for the execution, using the SAME identity test +-- function_resolution.resolve probes the catalog with: +-- +-- keyed frame: owner_scope = f.scope AND owner_key = f.key_value +-- AND owner_database_id = (owner_key at `database` scope, +-- f.lookup_database_id otherwise) +-- global frame: owner_scope = f.scope AND owner_key IS NULL +-- AND owner_database_id = f.lookup_database_id +-- +-- The chain is an ANCESTRY relation, never a visibility one: it runs from the +-- execution's own scope up through its database, then the platform database's +-- own chain to the global `platform` terminal. A peer — another tenant, another +-- keyed owner — is not on it and can never be admitted, which is why the +-- ownership question is expressed as "is this on my chain", not "may I see +-- this". Publication and visibility flags are not inputs here, exactly as they +-- are not inputs to the same-owner rule this arm sits beside. +-- +-- No scope name is hardcoded, so a hierarchy that grows new levels needs no +-- change here: whatever app_scope.frames walks is what this admits. +CREATE FUNCTION app_scope.frame_owns( + database_id uuid, + scope text, + entity_id uuid, + owner_scope text, + owner_key uuid, + owner_database_id uuid +) RETURNS boolean AS $$ +DECLARE + v_owns boolean; +BEGIN + -- A row whose owning frame is not fully identified cannot be proved to be + -- on the chain, and an ownership check reports "not owned" rather than + -- guessing. The execution's own database is equally required: the frame walk + -- has no starting point without it. + IF frame_owns.database_id IS NULL + OR frame_owns.scope IS NULL + OR frame_owns.owner_scope IS NULL + OR frame_owns.owner_database_id IS NULL THEN + RETURN false; + END IF; + + SELECT EXISTS ( + SELECT 1 + FROM app_scope.frames( + frame_owns.database_id, + frame_owns.scope, + frame_owns.entity_id + ) AS f(scope, lookup_database_id, key_value) + WHERE f.scope = frame_owns.owner_scope + AND ( + ( + frame_owns.owner_key IS NOT NULL + AND f.key_value = frame_owns.owner_key + AND frame_owns.owner_database_id = CASE + WHEN frame_owns.owner_scope = 'database' + THEN frame_owns.owner_key + ELSE f.lookup_database_id + END + ) + OR ( + frame_owns.owner_key IS NULL + AND frame_owns.owner_database_id = f.lookup_database_id + ) + ) + ) + INTO v_owns; + + RETURN v_owns; +END; +$$ LANGUAGE plpgsql STABLE SECURITY DEFINER; + +COMMENT ON FUNCTION app_scope.frame_owns(uuid, text, uuid, text, uuid, uuid) IS +'True when the frame that owns a catalog row — its (owner_scope, owner_key, owner_database_id) triple — is one of the frames app_scope.frames returns for the given execution. The identity test is the one function_resolution.resolve probes the catalog with, so a write-path ownership guard admits exactly what the read path would resolve. The chain is ancestry, not visibility: the execution''s own scopes, its database, then the platform database''s chain — never a peer or another keyed owner. Returns false rather than raising when the row''s owning frame is not fully identified.'; + +COMMIT; diff --git a/packages/app-scope/pgpm.plan b/packages/app-scope/pgpm.plan index 580ac0498..ac11665a9 100644 --- a/packages/app-scope/pgpm.plan +++ b/packages/app-scope/pgpm.plan @@ -7,6 +7,8 @@ schemas/app_scope/procedures/platform_database_id [schemas/app_scope/schema] 201 schemas/app_scope/procedures/dyn_lookup_uuid [schemas/app_scope/schema] 2017-08-11T08:11:51Z constructive # dyn lookup uuid schemas/app_scope/procedures/projected_parent [schemas/app_scope/schema] 2026-08-05T09:00:00Z devin # scope type projection parent lookup schemas/app_scope/procedures/membership_parent [schemas/app_scope/schema schemas/app_scope/procedures/projected_parent] 2017-08-11T08:11:51Z constructive # membership parent +schemas/app_scope/procedures/actor_entity [schemas/app_scope/schema metaschema-modules:schemas/metaschema_modules_public/tables/principal_auth_module/table metaschema-modules:schemas/metaschema_modules_public/tables/users_module/table] 2026-08-27T09:00:00Z devin # actor entity pair lookup schemas/app_scope/procedures/local_frames [schemas/app_scope/schema schemas/app_scope/procedures/dyn_lookup_uuid schemas/app_scope/procedures/membership_parent] 2017-08-11T08:11:51Z constructive # per-database local frames schemas/app_scope/procedures/frames [schemas/app_scope/schema schemas/app_scope/procedures/platform_database_id schemas/app_scope/procedures/local_frames] 2017-08-11T08:11:51Z constructive # ordered scope frames schemas/app_scope/procedures/routing_tables [schemas/app_scope/schema schemas/app_scope/procedures/frames] 2026-07-26T09:00:00Z devin # scoped routing/site/domain/route/app table name resolution +schemas/app_scope/procedures/frame_owns [schemas/app_scope/schema schemas/app_scope/procedures/frames] 2026-08-26T09:00:00Z devin # frame-chain ownership test for write-path guards diff --git a/packages/app-scope/revert/schemas/app_scope/procedures/actor_entity.sql b/packages/app-scope/revert/schemas/app_scope/procedures/actor_entity.sql new file mode 100644 index 000000000..3e9802849 --- /dev/null +++ b/packages/app-scope/revert/schemas/app_scope/procedures/actor_entity.sql @@ -0,0 +1,7 @@ +-- Revert schemas/app_scope/procedures/actor_entity from pg + +BEGIN; + +DROP FUNCTION app_scope.actor_entity(uuid, uuid); + +COMMIT; diff --git a/packages/app-scope/revert/schemas/app_scope/procedures/frame_owns.sql b/packages/app-scope/revert/schemas/app_scope/procedures/frame_owns.sql new file mode 100644 index 000000000..c3aa4ea76 --- /dev/null +++ b/packages/app-scope/revert/schemas/app_scope/procedures/frame_owns.sql @@ -0,0 +1,7 @@ +-- Revert schemas/app_scope/procedures/frame_owns from pg + +BEGIN; + +DROP FUNCTION app_scope.frame_owns(uuid, text, uuid, text, uuid, uuid); + +COMMIT; diff --git a/packages/app-scope/sql/pgpm-app-scope--0.43.2.bundle.tar.gz b/packages/app-scope/sql/pgpm-app-scope--0.43.2.bundle.tar.gz index 4247c0243dad7b815668d69d814b5cf7c142df50..5caa24d69bacf08e4564a0d7bc261a6abdc2091a 100644 GIT binary patch literal 15743 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 literal 12114 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 B0Gj{+ diff --git a/packages/app-scope/sql/pgpm-app-scope--0.43.2.sql b/packages/app-scope/sql/pgpm-app-scope--0.43.2.sql index d7ccc7d18..80caa713f 100644 --- a/packages/app-scope/sql/pgpm-app-scope--0.43.2.sql +++ b/packages/app-scope/sql/pgpm-app-scope--0.43.2.sql @@ -175,6 +175,110 @@ BEGIN END; $EOFCODE$ LANGUAGE plpgsql STABLE SECURITY DEFINER; +CREATE FUNCTION app_scope.actor_entity( + database_id uuid, + actor_id uuid +) RETURNS TABLE ( + entity_id uuid, + entity_type text +) AS $EOFCODE$ +DECLARE + users_schema text; + users_table text; + principals_schema text; + principals_table text; + + -- the actor's own row, and the owner row a principal actor resolves to + actor_type integer; + owner_id uuid; + owner_type integer; + + resolved_id uuid; + resolved_type integer; + resolved_scope text; +BEGIN + IF actor_entity.actor_id IS NULL THEN + RAISE EXCEPTION 'ACTOR_REQUIRED: actor_entity needs an actor to type' + USING ERRCODE = '22004'; + END IF; + + -- The principals table is LEFT JOINed: a database may install users without + -- principal_auth (the minimal preset does), and that only matters if the + -- actor turns out to be a principal. + SELECT users_schema_row.schema_name, users_table_row.name, + principals_schema_row.schema_name, principals_table_row.name + INTO users_schema, users_table, principals_schema, principals_table + FROM metaschema_modules_public.users_module um + JOIN metaschema_public."table" users_table_row + ON (users_table_row.id = um.table_id) + JOIN metaschema_public.schema users_schema_row + ON (users_schema_row.id = users_table_row.schema_id + AND users_schema_row.database_id = users_table_row.database_id) + LEFT JOIN metaschema_modules_public.principal_auth_module pam + ON (pam.database_id = um.database_id) + LEFT JOIN metaschema_public."table" principals_table_row + ON (principals_table_row.id = pam.principals_table_id) + LEFT JOIN metaschema_public.schema principals_schema_row + ON (principals_schema_row.id = principals_table_row.schema_id + AND principals_schema_row.database_id = principals_table_row.database_id) + WHERE um.database_id = actor_entity.database_id; + + IF users_schema IS NULL THEN + RAISE EXCEPTION 'ACTOR_ENTITY_UNRESOLVED: database % installs no users module, so an actor has no entity to carry', actor_entity.database_id + USING ERRCODE = '42704'; + END IF; + + IF principals_schema IS NULL THEN + -- pgsql-lint-disable-next-line no-dynamic-sql -- lookup-only: the generated users table is dynamically named, while actor_id stays a bound parameter + EXECUTE format( + 'SELECT u.type FROM %I.%I u WHERE u.id = $1', + users_schema, users_table + ) INTO actor_type USING actor_entity.actor_id; + ELSE + -- pgsql-lint-disable-next-line no-dynamic-sql -- lookup-only: the generated users and principals tables are dynamically named, while actor_id stays a bound parameter + EXECUTE format( + 'SELECT u.type, owner.id, owner.type' + ' FROM %I.%I u' + ' LEFT JOIN %I.%I p ON p.user_id = u.id' + ' LEFT JOIN %I.%I owner ON owner.id = p.owner_id' + ' WHERE u.id = $1', + users_schema, users_table, + principals_schema, principals_table, + users_schema, users_table + ) INTO actor_type, owner_id, owner_type USING actor_entity.actor_id; + END IF; + + IF actor_type IS NULL THEN + RAISE EXCEPTION 'ACTOR_ENTITY_UNRESOLVED: actor % has no users row in database %', actor_entity.actor_id, actor_entity.database_id + USING ERRCODE = '42704'; + END IF; + + IF actor_type = 3 THEN + resolved_id := owner_id; + resolved_type := owner_type; + ELSE + resolved_id := actor_entity.actor_id; + resolved_type := actor_type; + END IF; + + resolved_scope := CASE resolved_type + WHEN 1 THEN 'app' + WHEN 2 THEN 'org' + END; + + -- Unresolvable: an unknown users.type, a principal with no owner row, or a + -- principal owned by another principal. + IF resolved_id IS NULL OR resolved_scope IS NULL THEN + RAISE EXCEPTION 'ACTOR_ENTITY_UNRESOLVED: actor % of users.type % resolves to no entity in database %', actor_entity.actor_id, actor_type, actor_entity.database_id + USING ERRCODE = '42704'; + END IF; + + RETURN QUERY SELECT resolved_id, resolved_scope; +END; +$EOFCODE$ LANGUAGE plpgsql STABLE SECURITY DEFINER; + +COMMENT ON FUNCTION app_scope.actor_entity(uuid, uuid) IS 'Resolves an actor to a complete entity pair from its users.type, or to the owner pair for a principal. Raises ACTOR_ENTITY_UNRESOLVED rather than returning a partial pair.'; + CREATE FUNCTION app_scope.local_frames( database_id uuid, execution_scope text, @@ -601,4 +705,58 @@ BEGIN END; $EOFCODE$ LANGUAGE plpgsql STABLE; -COMMENT ON FUNCTION app_scope.routing_tables(uuid, text) IS 'Physical (schema, table) names of the scoped routing source/settings tables serving an execution at the given scope, resolved from the api_surface_module, site_surface_module, domain_module, route_module and app_module registrations. Scope resolution one layer above app_scope.frames: each surface is located independently by walking frames (exact-scope frames first, then most-specific first), so an inner frame''s apis surface never hijacks the domain/site/route planes served by an outer frame. No platform special-casing. Surfaces not provisioned on any frame come back NULL; a registration pointing at a missing table raises ROUTING_TABLES_NOT_FOUND.'; \ No newline at end of file +COMMENT ON FUNCTION app_scope.routing_tables(uuid, text) IS 'Physical (schema, table) names of the scoped routing source/settings tables serving an execution at the given scope, resolved from the api_surface_module, site_surface_module, domain_module, route_module and app_module registrations. Scope resolution one layer above app_scope.frames: each surface is located independently by walking frames (exact-scope frames first, then most-specific first), so an inner frame''s apis surface never hijacks the domain/site/route planes served by an outer frame. No platform special-casing. Surfaces not provisioned on any frame come back NULL; a registration pointing at a missing table raises ROUTING_TABLES_NOT_FOUND.'; + +CREATE FUNCTION app_scope.frame_owns( + database_id uuid, + scope text, + entity_id uuid, + owner_scope text, + owner_key uuid, + owner_database_id uuid +) RETURNS boolean AS $EOFCODE$ +DECLARE + v_owns boolean; +BEGIN + -- A row whose owning frame is not fully identified cannot be proved to be + -- on the chain, and an ownership check reports "not owned" rather than + -- guessing. The execution's own database is equally required: the frame walk + -- has no starting point without it. + IF frame_owns.database_id IS NULL + OR frame_owns.scope IS NULL + OR frame_owns.owner_scope IS NULL + OR frame_owns.owner_database_id IS NULL THEN + RETURN false; + END IF; + + SELECT EXISTS ( + SELECT 1 + FROM app_scope.frames( + frame_owns.database_id, + frame_owns.scope, + frame_owns.entity_id + ) AS f(scope, lookup_database_id, key_value) + WHERE f.scope = frame_owns.owner_scope + AND ( + ( + frame_owns.owner_key IS NOT NULL + AND f.key_value = frame_owns.owner_key + AND frame_owns.owner_database_id = CASE + WHEN frame_owns.owner_scope = 'database' + THEN frame_owns.owner_key + ELSE f.lookup_database_id + END + ) + OR ( + frame_owns.owner_key IS NULL + AND frame_owns.owner_database_id = f.lookup_database_id + ) + ) + ) + INTO v_owns; + + RETURN v_owns; +END; +$EOFCODE$ LANGUAGE plpgsql STABLE SECURITY DEFINER; + +COMMENT ON FUNCTION app_scope.frame_owns(uuid, text, uuid, text, uuid, uuid) IS 'True when the frame that owns a catalog row — its (owner_scope, owner_key, owner_database_id) triple — is one of the frames app_scope.frames returns for the given execution. The identity test is the one function_resolution.resolve probes the catalog with, so a write-path ownership guard admits exactly what the read path would resolve. The chain is ancestry, not visibility: the execution''s own scopes, its database, then the platform database''s chain — never a peer or another keyed owner. Returns false rather than raising when the row''s owning frame is not fully identified.'; \ No newline at end of file diff --git a/packages/app-scope/verify/schemas/app_scope/procedures/actor_entity.sql b/packages/app-scope/verify/schemas/app_scope/procedures/actor_entity.sql new file mode 100644 index 000000000..f35b5410d --- /dev/null +++ b/packages/app-scope/verify/schemas/app_scope/procedures/actor_entity.sql @@ -0,0 +1,7 @@ +-- Verify schemas/app_scope/procedures/actor_entity on pg + +BEGIN; + +SELECT assert_function('app_scope.actor_entity(uuid, uuid)'::regprocedure); + +ROLLBACK; diff --git a/packages/app-scope/verify/schemas/app_scope/procedures/frame_owns.sql b/packages/app-scope/verify/schemas/app_scope/procedures/frame_owns.sql new file mode 100644 index 000000000..40538046b --- /dev/null +++ b/packages/app-scope/verify/schemas/app_scope/procedures/frame_owns.sql @@ -0,0 +1,7 @@ +-- Verify schemas/app_scope/procedures/frame_owns on pg + +BEGIN; + +SELECT assert_function('app_scope.frame_owns(uuid, text, uuid, text, uuid, uuid)'::regprocedure); + +ROLLBACK; diff --git a/packages/database-jobs/__tests__/jobs.test.ts b/packages/database-jobs/__tests__/jobs.test.ts index 941459dac..799c49dad 100644 --- a/packages/database-jobs/__tests__/jobs.test.ts +++ b/packages/database-jobs/__tests__/jobs.test.ts @@ -4,11 +4,15 @@ let pg: PgTestClient; let teardown: () => Promise; const database_id = '5b720132-17d5-424d-9bcb-ee7b17c13d43'; +const actor_id = 'b9d22af1-62c7-43a5-b8c4-50630bbd4962'; +const principal_id = 'd9a8e3c1-5d5c-450d-8a95-95e4f7d9d98c'; +const entity_id = 'f12f1f0d-6f62-4f4b-93f2-72ee5d2a5b8e'; const objs: Record = {}; describe('scheduled jobs', () => { beforeAll(async () => { ({ pg, teardown } = await getConnections()); + await pg.any(`SELECT set_config('jwt.strict_attribution', 'false', false)`); }); afterAll(async () => { @@ -138,23 +142,344 @@ describe('scheduled jobs', () => { it('add_job stamps explicit db_id', async () => { const [job] = await pg.any( - `SELECT * FROM app_jobs.add_job(identifier := 'my_job', db_id := $1::uuid)`, - [database_id] + `SELECT * FROM app_jobs.add_job( + identifier := 'my_job', + db_id := $1::uuid, + actor_id := $2::uuid + )`, + [database_id, 'b9d22af1-62c7-43a5-b8c4-50630bbd4962'] ); expect(job.database_id).toBe(database_id); }); + it('add_job stamps entity attribution from transaction-local claims, but never the organization', async () => { + const user_id = 'b9d22af1-62c7-43a5-b8c4-50630bbd4962'; + const principal_id = 'd9a8e3c1-5d5c-450d-8a95-95e4f7d9d98c'; + const entity_id = 'f12f1f0d-6f62-4f4b-93f2-72ee5d2a5b8e'; + const organization_id = 'c89b6b6b-ea18-4d98-bd5a-3f3c0c34f872'; + + await pg.any(`BEGIN`); + try { + await pg.any( + `SELECT + set_config('jwt.claims.database_id', $1, true), + set_config('jwt.claims.user_id', $2, true), + set_config('jwt.claims.principal_id', $3, true), + set_config('jwt.claims.entity_id', $4, true), + set_config('jwt.claims.organization_id', $5, true), + set_config('jwt.claims.entity_type', $6, true)`, + [database_id, user_id, principal_id, entity_id, organization_id, 'org'] + ); + + const [job] = await pg.any( + `SELECT * FROM app_jobs.add_job( + identifier := 'claimed_job', + job_key := 'claimed_job' + )` + ); + + expect({ + database_id: job.database_id, + actor_id: job.actor_id, + principal_id: job.principal_id, + entity_id: job.entity_id, + organization_id: job.organization_id, + entity_type: job.entity_type + }).toEqual({ + database_id, + actor_id: user_id, + principal_id, + entity_id, + // Set as a claim above and deliberately not inherited: the organization + // is derived from the entity pair where usage is recorded, so a claim + // asserting one is not an authority add_job trusts. + organization_id: null, + entity_type: 'org' + }); + } finally { + await pg.any(`ROLLBACK`); + } + }); + it('add_job fails the NOT NULL constraint without database attribution', async () => { await expect( pg.any(`SELECT * FROM app_jobs.add_job(identifier := 'my_job')`) ).rejects.toThrow(/database_id/); }); + it('add_job rejects claimless attribution at the work boundary', async () => { + await pg.any(`BEGIN`); + await pg.any(`SELECT set_config('jwt.strict_attribution', 'true', true)`); + await expect( + pg.any( + `SELECT * FROM app_jobs.add_job( + identifier := 'claimless_job', + db_id := $1::uuid + )`, + [database_id] + ) + ).rejects.toThrow('ATTRIBUTION_REQUIRED'); + await pg.any(`ROLLBACK`); + }); + + it('add_job rejects an entity without an entity type', async () => { + await pg.any(`BEGIN`); + await pg.any(`SELECT set_config('jwt.strict_attribution', 'true', true)`); + await expect( + pg.any( + `SELECT * FROM app_jobs.add_job( + identifier := 'missing_entity_type_job', + db_id := $1::uuid, + entity_id := $2::uuid + )`, + [database_id, 'f12f1f0d-6f62-4f4b-93f2-72ee5d2a5b8e'] + ) + ).rejects.toThrow('ENTITY_TYPE_REQUIRED'); + await pg.any(`ROLLBACK`); + }); + + it('add_job allows actor-only attribution', async () => { + const actor_id = 'b9d22af1-62c7-43a5-b8c4-50630bbd4962'; + const [job] = await pg.any( + `SELECT * FROM app_jobs.add_job( + identifier := 'actor_only_job', + db_id := $1::uuid, + actor_id := $2::uuid + )`, + [database_id, actor_id] + ); + expect(job.actor_id).toBe(actor_id); + expect(job.entity_id).toBeNull(); + expect(job.entity_type).toBeNull(); + }); + + it('add_job allows row-derived entity attribution with its type', async () => { + const entity_id = 'f12f1f0d-6f62-4f4b-93f2-72ee5d2a5b8e'; + const [job] = await pg.any( + `SELECT * FROM app_jobs.add_job( + identifier := 'entity_attributed_job', + db_id := $1::uuid, + entity_id := $2::uuid, + entity_type := $3::text + )`, + [database_id, entity_id, 'org'] + ); + expect(job.entity_id).toBe(entity_id); + expect(job.entity_type).toBe('org'); + }); + it('add_scheduled_job fails the NOT NULL constraint without database attribution', async () => { await expect( pg.any( `SELECT * FROM app_jobs.add_scheduled_job(identifier := 'my_job')` ) - ).rejects.toThrow(/database_id/); + ).rejects.toThrow('DATABASE_CLAIM_REQUIRED'); + }); + + it('add_scheduled_job defaults complete attribution from transaction-local claims', async () => { + await pg.any(`BEGIN`); + try { + await pg.any( + `SELECT + set_config('jwt.claims.database_id', $1, true), + set_config('jwt.claims.user_id', $2, true), + set_config('jwt.claims.principal_id', $3, true), + set_config('jwt.claims.entity_id', $4, true), + set_config('jwt.claims.entity_type', $5, true)`, + [database_id, actor_id, principal_id, entity_id, 'org'] + ); + + const [scheduled] = await pg.any( + `SELECT * FROM app_jobs.add_scheduled_job( + identifier := 'claimed_scheduled_job', + job_key := 'claimed_scheduled_job' + )` + ); + + expect({ + database_id: scheduled.database_id, + actor_id: scheduled.actor_id, + principal_id: scheduled.principal_id, + entity_id: scheduled.entity_id, + organization_id: scheduled.organization_id, + entity_type: scheduled.entity_type + }).toEqual({ + database_id, + actor_id, + principal_id, + entity_id, + organization_id: null, + entity_type: 'org' + }); + } finally { + await pg.any(`ROLLBACK`); + } + }); + + it.each([ + ['entity id without entity type', 'entity_id := $2::uuid, entity_type := NULL', [database_id, entity_id, actor_id]], + ['entity type without entity id', 'entity_id := NULL, entity_type := $2::text', [database_id, 'org', actor_id]] + ])('add_scheduled_job warns by default for %s', async (_description, attribution, params) => { + // The session default is explicitly non-strict in beforeAll. + await pg.any(`BEGIN`); + try { + const [scheduled] = await pg.any( + `SELECT * FROM app_jobs.add_scheduled_job( + identifier := 'partial_scheduled_job', + db_id := $1::uuid, + actor_id := $3::uuid, + ${attribution} + )`, + params + ); + expect(scheduled).toBeDefined(); + } finally { + await pg.any(`ROLLBACK`); + } + }); + + it.each([ + ['entity id without entity type', 'entity_id := $2::uuid, entity_type := NULL', [database_id, entity_id, actor_id], 'ENTITY_TYPE_REQUIRED'], + ['entity type without entity id', 'entity_id := NULL, entity_type := $2::text', [database_id, 'org', actor_id], 'ENTITY_ID_REQUIRED'] + ])('add_scheduled_job rejects %s under strict attribution', async (_description, attribution, params, error) => { + await pg.any(`BEGIN`); + try { + await pg.any(`SELECT set_config('jwt.strict_attribution', 'true', true)`); + await expect( + pg.any( + `SELECT * FROM app_jobs.add_scheduled_job( + identifier := 'strict_partial_scheduled_job', + db_id := $1::uuid, + actor_id := $3::uuid, + ${attribution} + )`, + params + ) + ).rejects.toThrow(error); + } finally { + await pg.any(`ROLLBACK`); + } + }); + + it('add_scheduled_job restamps keyed rows with the caller identity', async () => { + await pg.any(`BEGIN`); + try { + await pg.any( + `SELECT + set_config('jwt.claims.database_id', $1, true), + set_config('jwt.claims.user_id', $2, true), + set_config('jwt.claims.principal_id', $3, true), + set_config('jwt.claims.entity_id', $4, true), + set_config('jwt.claims.entity_type', $5, true)`, + [database_id, actor_id, principal_id, entity_id, 'org'] + ); + await pg.any( + `SELECT * FROM app_jobs.add_scheduled_job( + identifier := 'restamp_scheduled_job', + job_key := 'restamp_scheduled_job' + )` + ); + + const next_actor_id = 'f89a41e7-7e41-4c63-b6c7-e99d7a122f70'; + const next_principal_id = '4ae9a4f9-f785-4d4e-8e4b-2aa367ec4d7a'; + const next_entity_id = '8f09e1b0-eae4-4e76-918a-1e916f6f7c4c'; + await pg.any( + `SELECT + set_config('jwt.claims.user_id', $1, true), + set_config('jwt.claims.principal_id', $2, true), + set_config('jwt.claims.entity_id', $3, true), + set_config('jwt.claims.entity_type', $4, true)`, + [next_actor_id, next_principal_id, next_entity_id, 'team'] + ); + const [scheduled] = await pg.any( + `SELECT * FROM app_jobs.add_scheduled_job( + identifier := 'restamped_scheduled_job', + job_key := 'restamp_scheduled_job' + )` + ); + + expect({ + database_id: scheduled.database_id, + actor_id: scheduled.actor_id, + principal_id: scheduled.principal_id, + entity_id: scheduled.entity_id, + organization_id: scheduled.organization_id, + entity_type: scheduled.entity_type + }).toEqual({ + database_id, + actor_id: next_actor_id, + principal_id: next_principal_id, + entity_id: next_entity_id, + organization_id: null, + entity_type: 'team' + }); + } finally { + await pg.any(`ROLLBACK`); + } + }); + + it('run_scheduled_job copies complete attribution to the spawned job', async () => { + const scheduled = await pg.one( + `INSERT INTO app_jobs.scheduled_jobs ( + database_id, actor_id, principal_id, entity_id, organization_id, + entity_type, task_identifier, schedule_info + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + RETURNING *`, + [ + database_id, + actor_id, + principal_id, + entity_id, + 'c89b6b6b-ea18-4d98-bd5a-3f3c0c34f872', + 'org', + 'attributed_scheduled_job', + { start: new Date(Date.now() + 10000), end: new Date(Date.now() + 180000) } + ] + ); + const [job] = await pg.any( + `SELECT * FROM app_jobs.run_scheduled_job($1)`, + [scheduled.id] + ); + expect({ + database_id: job.database_id, + actor_id: job.actor_id, + principal_id: job.principal_id, + entity_id: job.entity_id, + organization_id: job.organization_id, + entity_type: job.entity_type + }).toEqual({ + database_id, + actor_id, + principal_id, + entity_id, + organization_id: 'c89b6b6b-ea18-4d98-bd5a-3f3c0c34f872', + entity_type: 'org' + }); + }); + + it('run_scheduled_job rejects a malformed entity pair under strict attribution', async () => { + const task_identifier = 'malformed_scheduled_job'; + await pg.any(`BEGIN`); + try { + await pg.any(`SELECT set_config('jwt.strict_attribution', 'true', true)`); + const scheduled = await pg.one( + `INSERT INTO app_jobs.scheduled_jobs ( + database_id, actor_id, entity_id, task_identifier, schedule_info + ) VALUES ($1, $2, $3, $4, $5) + RETURNING id`, + [database_id, actor_id, entity_id, task_identifier, {}] + ); + await expect( + pg.any(`SELECT * FROM app_jobs.run_scheduled_job($1)`, [scheduled.id]) + ).rejects.toThrow('ENTITY_TYPE_REQUIRED'); + } finally { + await pg.any(`ROLLBACK`); + } + const [{ count }] = await pg.any( + `SELECT count(*)::int AS count FROM app_jobs.jobs + WHERE task_identifier = $1`, + [task_identifier] + ); + expect(count).toBe(0); }); }); \ No newline at end of file diff --git a/packages/database-jobs/__tests__/queue-locks.test.ts b/packages/database-jobs/__tests__/queue-locks.test.ts new file mode 100644 index 000000000..140137a4a --- /dev/null +++ b/packages/database-jobs/__tests__/queue-locks.test.ts @@ -0,0 +1,134 @@ +import { getConnections, PgTestClient } from 'pgsql-test'; + +/** + * queue_name is a mutual-exclusion lock: get_job holds the queue row for the + * whole job and claims nothing else on it. The shared literal 'default' is a + * routing label an author never chose, so it must not become a lock domain that + * serializes every job in the database. + */ + +let pg: PgTestClient; +let teardown: () => Promise; + +const database_id = '5b720132-17d5-424d-9bcb-ee7b17c13d43'; + +const addJob = async (identifier: string, queue_name: string | null) => { + const [job] = await pg.any<{ id: string; queue_name: string | null }>( + `SELECT * FROM app_jobs.add_job( + identifier := $1::text, + queue_name := $2::text, + db_id := $3::uuid + )`, + [identifier, queue_name, database_id] + ); + return job; +}; + +describe('queue names as locks', () => { + beforeAll(async () => { + ({ pg, teardown } = await getConnections()); + await pg.any(`SELECT set_config('jwt.strict_attribution', 'false', false)`); + }); + + afterAll(async () => { + await teardown(); + }); + + beforeEach(async () => { + await pg.any(`DELETE FROM app_jobs.jobs`); + }); + + it('records the shared literal, and a blank name, as no lock at all', async () => { + const shared = await addJob('shared_literal', 'default'); + const blank = await addJob('blank_name', ''); + + expect(shared.queue_name).toBeNull(); + expect(blank.queue_name).toBeNull(); + + const queues = await pg.any( + `SELECT queue_name FROM app_jobs.job_queues WHERE queue_name IN ('default', '')` + ); + expect(queues).toEqual([]); + }); + + it('keeps a name its author chose', async () => { + const job = await addJob('chosen_name', 'email'); + expect(job.queue_name).toBe('email'); + + const [queue] = await pg.any<{ job_count: number }>( + `SELECT job_count FROM app_jobs.job_queues WHERE queue_name = 'email'` + ); + expect(queue.job_count).toBe(1); + }); + + it('lets two workers claim jobs that carried the shared literal', async () => { + await addJob('shared_a', 'default'); + await addJob('shared_b', 'default'); + + const first = await pg.one<{ id: string | null }>( + `SELECT id FROM app_jobs.get_job('worker_one')` + ); + const second = await pg.one<{ id: string | null }>( + `SELECT id FROM app_jobs.get_job('worker_two')` + ); + + expect(first.id).not.toBeNull(); + expect(second.id).not.toBeNull(); + expect(second.id).not.toBe(first.id); + }); + + it('still serializes a name its author chose', async () => { + await addJob('email_a', 'email'); + await addJob('email_b', 'email'); + + const first = await pg.one<{ id: string | null }>( + `SELECT id FROM app_jobs.get_job('worker_one')` + ); + const second = await pg.one<{ id: string | null }>( + `SELECT id FROM app_jobs.get_job('worker_two')` + ); + + expect(first.id).not.toBeNull(); + expect(second.id).toBeNull(); + }); + + const addSchedule = async (task_identifier: string) => + pg.one<{ id: string }>( + `INSERT INTO app_jobs.scheduled_jobs (database_id, task_identifier, queue_name, schedule_info) + VALUES ($1, $2, 'default', $3) + RETURNING id`, + [database_id, task_identifier, { start: new Date(), rule: '*/1 * * * *' }] + ); + + it('spawns a scheduled job carrying the shared literal without a lock', async () => { + const schedule = await addSchedule('scheduled_shared'); + + const [job] = await pg.any<{ queue_name: string | null }>( + `SELECT queue_name FROM app_jobs.run_scheduled_job($1)`, + [schedule.id] + ); + + expect(job.queue_name).toBeNull(); + }); + + // A schedule's protection against overlapping itself is its own, and does not + // come from the queue: run_scheduled_job refuses a tick while the previous + // one is still running. Dropping the shared queue therefore lets *different* + // schedules overlap, never a schedule with itself. + it('refuses a second tick while the first is still running', async () => { + const schedule = await addSchedule('scheduled_reentrant'); + + const [job] = await pg.any<{ id: string }>( + `SELECT id FROM app_jobs.run_scheduled_job($1)`, + [schedule.id] + ); + await pg.any( + `UPDATE app_jobs.jobs SET locked_at = now(), locked_by = 'worker_one' WHERE id = $1`, + [job.id] + ); + + await expect( + pg.any(`SELECT id FROM app_jobs.run_scheduled_job($1)`, [schedule.id]) + ).rejects.toThrow('ALREADY_SCHEDULED'); + }); +}); diff --git a/packages/database-jobs/deploy/schemas/app_jobs/procedures/add_job.sql b/packages/database-jobs/deploy/schemas/app_jobs/procedures/add_job.sql index dbcc6a875..2f3aab04b 100644 --- a/packages/database-jobs/deploy/schemas/app_jobs/procedures/add_job.sql +++ b/packages/database-jobs/deploy/schemas/app_jobs/procedures/add_job.sql @@ -2,7 +2,10 @@ -- requires: schemas/app_jobs/schema -- requires: schemas/app_jobs/tables/jobs/table -- requires: schemas/app_jobs/tables/job_queues/table --- requires: pgpm-jwt-claims:schemas/jwt_private/procedures/current_database_id +-- requires: pgpm-jwt-claims:schemas/jwt_private/procedures/current_entity_id +-- requires: pgpm-jwt-claims:schemas/jwt_private/procedures/current_entity_type +-- requires: pgpm-jwt-claims:schemas/jwt_private/procedures/require_database_id +-- requires: pgpm-jwt-claims:schemas/jwt_private/procedures/assert_attribution -- requires: pgpm-jwt-claims:schemas/jwt_public/procedures/current_user_id -- requires: pgpm-jwt-claims:schemas/jwt_public/procedures/current_principal_id @@ -15,12 +18,17 @@ CREATE FUNCTION app_jobs.add_job ( run_at timestamptz DEFAULT now(), max_attempts integer DEFAULT 25, priority integer DEFAULT 0, - entity_id uuid DEFAULT NULL, + entity_id uuid DEFAULT jwt_private.current_entity_id(), + -- The organization is never a claim: it is derived from the entity pair by + -- get_organization_id at the point of recording, so only a caller that + -- already resolved it (a data-job trigger with an entity field) passes one. organization_id uuid DEFAULT NULL, - entity_type text DEFAULT NULL, + entity_type text DEFAULT jwt_private.current_entity_type(), function_definition_id uuid DEFAULT NULL, definition_scope text DEFAULT NULL, - db_id uuid DEFAULT jwt_private.current_database_id() + db_id uuid DEFAULT jwt_private.require_database_id(), + actor_id uuid DEFAULT jwt_public.current_user_id(), + principal_id uuid DEFAULT jwt_public.current_principal_id() ) RETURNS app_jobs.jobs AS $$ @@ -29,6 +37,7 @@ DECLARE v_database_id uuid; v_actor_id uuid; v_principal_id uuid; + v_queue_name text; BEGIN -- db_id defaults to the session's database claim; only callers that act on -- behalf of a different database (e.g. platform-owned births) pass it @@ -36,9 +45,20 @@ BEGIN -- session with no explicit db_id is rejected by the default expression rather -- than producing an unattributable job. v_database_id := db_id; - v_actor_id := jwt_public.current_user_id(); - - v_principal_id := jwt_public.current_principal_id(); + v_actor_id := add_job.actor_id; + v_principal_id := add_job.principal_id; + -- queue_name is a mutual-exclusion lock, never a routing label: get_job holds + -- the queue row for the whole job and claims nothing else on it, and no worker + -- selects work by queue. A name shared by unrelated jobs therefore serializes + -- all of them, and 'default' is precisely that name -- what a function + -- definition carries when its author asked for no serialization at all. Read + -- it, and the empty string, as no lock. + v_queue_name := nullif(nullif(add_job.queue_name, ''), 'default'); + PERFORM jwt_private.assert_attribution( + v_actor_id, + add_job.entity_id, + add_job.entity_type + ); IF job_key IS NOT NULL THEN -- Upsert job @@ -69,7 +89,7 @@ BEGIN add_job.definition_scope, identifier, coalesce(payload, '{}'::json), - queue_name, + v_queue_name, coalesce(run_at, now()), coalesce(max_attempts, 25), job_key, @@ -135,7 +155,7 @@ BEGIN add_job.definition_scope, identifier, payload, - queue_name, + v_queue_name, run_at, max_attempts, priority diff --git a/packages/database-jobs/deploy/schemas/app_jobs/procedures/add_scheduled_job.sql b/packages/database-jobs/deploy/schemas/app_jobs/procedures/add_scheduled_job.sql index b00609fa1..28df66233 100644 --- a/packages/database-jobs/deploy/schemas/app_jobs/procedures/add_scheduled_job.sql +++ b/packages/database-jobs/deploy/schemas/app_jobs/procedures/add_scheduled_job.sql @@ -2,8 +2,12 @@ -- requires: schemas/app_jobs/schema -- requires: schemas/app_jobs/tables/scheduled_jobs/table --- requires: pgpm-jwt-claims:schemas/jwt_private/procedures/current_database_id +-- requires: pgpm-jwt-claims:schemas/jwt_private/procedures/current_entity_id +-- requires: pgpm-jwt-claims:schemas/jwt_private/procedures/current_entity_type +-- requires: pgpm-jwt-claims:schemas/jwt_private/procedures/require_database_id +-- requires: pgpm-jwt-claims:schemas/jwt_private/procedures/assert_attribution -- requires: pgpm-jwt-claims:schemas/jwt_public/procedures/current_user_id +-- requires: pgpm-jwt-claims:schemas/jwt_public/procedures/current_principal_id BEGIN; @@ -15,8 +19,15 @@ CREATE FUNCTION app_jobs.add_scheduled_job( queue_name text DEFAULT NULL, max_attempts integer DEFAULT 25, priority integer DEFAULT 0, - entity_id uuid DEFAULT NULL, - db_id uuid DEFAULT jwt_private.current_database_id() + entity_id uuid DEFAULT jwt_private.current_entity_id(), + db_id uuid DEFAULT jwt_private.require_database_id(), + entity_type text DEFAULT jwt_private.current_entity_type(), + -- The organization is never a claim: it is derived from the entity pair by + -- get_organization_id at the point of recording, so only a caller that + -- already resolved it (a data-job trigger with an entity field) passes one. + organization_id uuid DEFAULT NULL, + actor_id uuid DEFAULT jwt_public.current_user_id(), + principal_id uuid DEFAULT jwt_public.current_principal_id() ) RETURNS app_jobs.scheduled_jobs AS $$ @@ -24,6 +35,7 @@ DECLARE v_job app_jobs.scheduled_jobs; v_database_id uuid; v_actor_id uuid; + v_principal_id uuid; BEGIN -- db_id defaults to the session's database claim; only callers that act on -- behalf of a different database (e.g. provisioning triggers, platform-owned @@ -32,7 +44,13 @@ BEGIN -- scheduled_jobs.database_id NOT NULL constraint rather than producing an -- unattributable job. v_database_id := db_id; - v_actor_id := jwt_public.current_user_id(); + v_actor_id := add_scheduled_job.actor_id; + v_principal_id := add_scheduled_job.principal_id; + PERFORM jwt_private.assert_attribution( + v_actor_id, + add_scheduled_job.entity_id, + add_scheduled_job.entity_type + ); IF job_key IS NOT NULL THEN @@ -40,7 +58,10 @@ BEGIN INSERT INTO app_jobs.scheduled_jobs ( database_id, actor_id, + principal_id, entity_id, + organization_id, + entity_type, task_identifier, payload, queue_name, @@ -51,7 +72,10 @@ BEGIN ) VALUES ( v_database_id, v_actor_id, + v_principal_id, add_scheduled_job.entity_id, + add_scheduled_job.organization_id, + add_scheduled_job.entity_type, identifier, coalesce(payload, '{}'::json), queue_name, @@ -62,6 +86,12 @@ BEGIN ) ON CONFLICT (key) DO UPDATE SET + database_id = EXCLUDED.database_id, + actor_id = EXCLUDED.actor_id, + principal_id = EXCLUDED.principal_id, + entity_id = EXCLUDED.entity_id, + organization_id = EXCLUDED.organization_id, + entity_type = EXCLUDED.entity_type, task_identifier = EXCLUDED.task_identifier, payload = EXCLUDED.payload, queue_name = EXCLUDED.queue_name, @@ -91,7 +121,10 @@ BEGIN INSERT INTO app_jobs.scheduled_jobs ( database_id, actor_id, + principal_id, entity_id, + organization_id, + entity_type, task_identifier, payload, queue_name, @@ -101,7 +134,10 @@ BEGIN ) VALUES ( v_database_id, v_actor_id, + v_principal_id, add_scheduled_job.entity_id, + add_scheduled_job.organization_id, + add_scheduled_job.entity_type, identifier, payload, queue_name, @@ -116,4 +152,3 @@ LANGUAGE 'plpgsql' VOLATILE SECURITY DEFINER; COMMIT; - diff --git a/packages/database-jobs/deploy/schemas/app_jobs/procedures/grants/grant_execute_add_job_to_authenticated.sql b/packages/database-jobs/deploy/schemas/app_jobs/procedures/grants/grant_execute_add_job_to_authenticated.sql index 66a0daec5..1e09cd8f4 100644 --- a/packages/database-jobs/deploy/schemas/app_jobs/procedures/grants/grant_execute_add_job_to_authenticated.sql +++ b/packages/database-jobs/deploy/schemas/app_jobs/procedures/grants/grant_execute_add_job_to_authenticated.sql @@ -5,6 +5,6 @@ BEGIN; -GRANT EXECUTE ON FUNCTION app_jobs.add_job(text, json, text, text, timestamptz, integer, integer, uuid, uuid, text, uuid, text, uuid) TO authenticated; +GRANT EXECUTE ON FUNCTION app_jobs.add_job(text, json, text, text, timestamptz, integer, integer, uuid, uuid, text, uuid, text, uuid, uuid, uuid) TO authenticated; COMMIT; diff --git a/packages/database-jobs/deploy/schemas/app_jobs/procedures/grants/grant_execute_add_scheduled_job_to_authenticated.sql b/packages/database-jobs/deploy/schemas/app_jobs/procedures/grants/grant_execute_add_scheduled_job_to_authenticated.sql index b0daa37ca..8c03f3972 100644 --- a/packages/database-jobs/deploy/schemas/app_jobs/procedures/grants/grant_execute_add_scheduled_job_to_authenticated.sql +++ b/packages/database-jobs/deploy/schemas/app_jobs/procedures/grants/grant_execute_add_scheduled_job_to_authenticated.sql @@ -5,6 +5,6 @@ BEGIN; -GRANT EXECUTE ON FUNCTION app_jobs.add_scheduled_job(text, json, json, text, text, integer, integer, uuid, uuid) TO authenticated; +GRANT EXECUTE ON FUNCTION app_jobs.add_scheduled_job(text, json, json, text, text, integer, integer, uuid, uuid, text, uuid, uuid, uuid) TO authenticated; COMMIT; diff --git a/packages/database-jobs/deploy/schemas/app_jobs/procedures/run_scheduled_job.sql b/packages/database-jobs/deploy/schemas/app_jobs/procedures/run_scheduled_job.sql index 935bcea77..92a3244a6 100644 --- a/packages/database-jobs/deploy/schemas/app_jobs/procedures/run_scheduled_job.sql +++ b/packages/database-jobs/deploy/schemas/app_jobs/procedures/run_scheduled_job.sql @@ -2,6 +2,7 @@ -- requires: schemas/app_jobs/schema -- requires: schemas/app_jobs/tables/jobs/table -- requires: schemas/app_jobs/tables/scheduled_jobs/table +-- requires: pgpm-jwt-claims:schemas/jwt_private/procedures/assert_attribution BEGIN; CREATE FUNCTION app_jobs.run_scheduled_job (id bigint, job_expiry interval DEFAULT '1 hours') @@ -43,6 +44,12 @@ BEGIN END IF; END IF; + PERFORM jwt_private.assert_attribution( + sched.actor_id, + sched.entity_id, + sched.entity_type + ); + -- a job carrying this key that is already in flight covers this tick, and the -- keyed upsert below cannot refresh a locked row IF (sched.key IS NOT NULL) THEN @@ -64,7 +71,10 @@ BEGIN INSERT INTO app_jobs.jobs ( database_id, actor_id, + principal_id, entity_id, + organization_id, + entity_type, queue_name, task_identifier, payload, @@ -74,8 +84,14 @@ BEGIN ) VALUES ( sched.database_id, sched.actor_id, + sched.principal_id, sched.entity_id, - sched.queue_name, + sched.organization_id, + sched.entity_type, + -- same reading as app_jobs.add_job: the shared literal 'default' (copied + -- onto a schedule from its function definition) is not a lock domain, so a + -- tick of one schedule must not block every other job in the database. + nullif(nullif(sched.queue_name, ''), 'default'), sched.task_identifier, sched.payload, sched.priority, @@ -84,7 +100,8 @@ BEGIN ) ON CONFLICT (KEY) DO UPDATE SET - database_id = excluded.database_id, actor_id = excluded.actor_id, entity_id = excluded.entity_id, + database_id = excluded.database_id, actor_id = excluded.actor_id, principal_id = excluded.principal_id, + entity_id = excluded.entity_id, organization_id = excluded.organization_id, entity_type = excluded.entity_type, task_identifier = excluded.task_identifier, payload = excluded.payload, queue_name = excluded.queue_name, max_attempts = excluded.max_attempts, priority = excluded.priority, run_at = excluded.run_at, -- always reset error/retry state attempts = 0, last_error = NULL diff --git a/packages/database-jobs/deploy/schemas/app_jobs/tables/jobs/table.sql b/packages/database-jobs/deploy/schemas/app_jobs/tables/jobs/table.sql index 729ce48fd..5240d8722 100644 --- a/packages/database-jobs/deploy/schemas/app_jobs/tables/jobs/table.sql +++ b/packages/database-jobs/deploy/schemas/app_jobs/tables/jobs/table.sql @@ -42,8 +42,8 @@ COMMENT ON COLUMN app_jobs.jobs.id IS 'Auto-incrementing job identifier'; COMMENT ON COLUMN app_jobs.jobs.database_id IS 'Database this job belongs to; every job is owned by exactly one database'; COMMENT ON COLUMN app_jobs.jobs.actor_id IS 'User who triggered this job, read from JWT claims at enqueue time'; COMMENT ON COLUMN app_jobs.jobs.principal_id IS 'Principal that triggered this job; equals actor_id for human-triggered jobs, differs when an agent/API-key acts on behalf of a user'; -COMMENT ON COLUMN app_jobs.jobs.entity_id IS 'Entity (org/team) this job is scoped to for billing; NULL means platform-level (resolved via database_id → owner_id)'; -COMMENT ON COLUMN app_jobs.jobs.organization_id IS 'Top-level organization for this entity; resolved at enqueue time via get_organization_id(entity_type, entity_id)'; +COMMENT ON COLUMN app_jobs.jobs.entity_id IS 'Entity this job is attributed to for billing; read from the transaction entity claim at enqueue time; NULL means the claim was absent, not platform-level'; +COMMENT ON COLUMN app_jobs.jobs.organization_id IS 'Organization this job is attributed to; resolved from the entity pair via get_organization_id at enqueue time by callers that know it (e.g. data-job triggers) — never read from a claim'; COMMENT ON COLUMN app_jobs.jobs.entity_type IS 'Entity type prefix (org, team, app, etc.) for interpreting entity_id'; COMMENT ON COLUMN app_jobs.jobs.function_definition_id IS 'For function jobs: the exact function definition resolved at enqueue time (scope-chain winner). NULL for handler/system tasks that have no function definition. Not an FK — definitions live in per-scope tables across databases; integrity is enforced by the resolver at enqueue.'; COMMENT ON COLUMN app_jobs.jobs.definition_scope IS 'For function jobs: the scope (database/org/app/platform) the winning definition was resolved at. Together with function_definition_id and database_id it identifies the exact physical definition to execute. NULL when function_definition_id is NULL.'; @@ -61,4 +61,3 @@ COMMENT ON COLUMN app_jobs.jobs.locked_by IS 'Identifier of the worker that curr COMMENT ON COLUMN app_jobs.jobs.is_available IS 'Generated column: true when job is unlocked and has remaining attempts'; COMMIT; - diff --git a/packages/database-jobs/deploy/schemas/app_jobs/tables/scheduled_jobs/table.sql b/packages/database-jobs/deploy/schemas/app_jobs/tables/scheduled_jobs/table.sql index cbf89eba3..493b422a0 100644 --- a/packages/database-jobs/deploy/schemas/app_jobs/tables/scheduled_jobs/table.sql +++ b/packages/database-jobs/deploy/schemas/app_jobs/tables/scheduled_jobs/table.sql @@ -6,7 +6,10 @@ CREATE TABLE app_jobs.scheduled_jobs ( id bigserial PRIMARY KEY, database_id uuid NOT NULL, actor_id uuid, + principal_id uuid, entity_id uuid, + organization_id uuid, + entity_type text, queue_name text DEFAULT NULL, task_identifier text NOT NULL, payload json DEFAULT '{}' ::json NOT NULL, @@ -30,7 +33,10 @@ COMMENT ON TABLE app_jobs.scheduled_jobs IS 'Recurring/cron-style job definition COMMENT ON COLUMN app_jobs.scheduled_jobs.id IS 'Auto-incrementing scheduled job identifier'; COMMENT ON COLUMN app_jobs.scheduled_jobs.database_id IS 'Database this scheduled job belongs to; every scheduled job is owned by exactly one database'; COMMENT ON COLUMN app_jobs.scheduled_jobs.actor_id IS 'User who created this scheduled job, read from JWT claims at creation time'; -COMMENT ON COLUMN app_jobs.scheduled_jobs.entity_id IS 'Entity (org/team) this scheduled job is scoped to for billing; NULL means platform-level (resolved via database_id → owner_id)'; +COMMENT ON COLUMN app_jobs.scheduled_jobs.principal_id IS 'Principal that triggered this scheduled job; equals actor_id for human-triggered jobs, differs when an agent/API-key acts on behalf of a user'; +COMMENT ON COLUMN app_jobs.scheduled_jobs.entity_id IS 'Entity this scheduled job is attributed to for billing; read from the transaction entity claim at registration time; NULL means the claim was absent, not platform-level'; +COMMENT ON COLUMN app_jobs.scheduled_jobs.organization_id IS 'Organization this scheduled job is attributed to; resolved from the entity pair via get_organization_id at registration time by callers that know it (e.g. data-job triggers) — never read from a claim'; +COMMENT ON COLUMN app_jobs.scheduled_jobs.entity_type IS 'Entity type prefix (org, team, app, etc.) for interpreting entity_id'; COMMENT ON COLUMN app_jobs.scheduled_jobs.queue_name IS 'Name of the queue spawned jobs are placed into'; COMMENT ON COLUMN app_jobs.scheduled_jobs.task_identifier IS 'Task type identifier for spawned jobs'; COMMENT ON COLUMN app_jobs.scheduled_jobs.payload IS 'JSON payload passed to each spawned job'; @@ -44,4 +50,3 @@ COMMENT ON COLUMN app_jobs.scheduled_jobs.last_scheduled IS 'Timestamp when a jo COMMENT ON COLUMN app_jobs.scheduled_jobs.last_scheduled_id IS 'ID of the last job spawned from this schedule'; COMMIT; - diff --git a/packages/database-jobs/pgpm.plan b/packages/database-jobs/pgpm.plan index 7e4b6e919..dcd550f0f 100644 --- a/packages/database-jobs/pgpm.plan +++ b/packages/database-jobs/pgpm.plan @@ -24,7 +24,7 @@ schemas/app_jobs/tables/jobs/grants/grant_select_insert_update_delete_to_adminis schemas/app_jobs/tables/job_queues/table [schemas/app_jobs/schema] 2025-08-26T23:57:41Z pgpm # add schemas/app_jobs/tables/job_queues/table schemas/app_jobs/tables/job_queues/indexes/job_queues_locked_by_idx [schemas/app_jobs/schema schemas/app_jobs/tables/job_queues/table] 2025-08-26T23:57:41Z pgpm # add schemas/app_jobs/tables/job_queues/indexes/job_queues_locked_by_idx schemas/app_jobs/tables/job_queues/grants/grant_select_insert_update_delete_to_administrator [schemas/app_jobs/schema schemas/app_jobs/tables/job_queues/table] 2025-08-26T23:57:41Z pgpm # add schemas/app_jobs/tables/job_queues/grants/grant_select_insert_update_delete_to_administrator -schemas/app_jobs/procedures/run_scheduled_job [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table schemas/app_jobs/tables/scheduled_jobs/table] 2025-08-26T23:57:41Z pgpm # add schemas/app_jobs/procedures/run_scheduled_job +schemas/app_jobs/procedures/run_scheduled_job [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table schemas/app_jobs/tables/scheduled_jobs/table pgpm-jwt-claims:schemas/jwt_private/procedures/assert_attribution] 2025-08-26T23:57:41Z pgpm # add schemas/app_jobs/procedures/run_scheduled_job schemas/app_jobs/procedures/reschedule_jobs [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table] 2025-08-26T23:57:41Z pgpm # add schemas/app_jobs/procedures/reschedule_jobs schemas/app_jobs/procedures/release_scheduled_jobs [schemas/app_jobs/schema schemas/app_jobs/tables/scheduled_jobs/table] 2025-08-26T23:57:41Z pgpm # add schemas/app_jobs/procedures/release_scheduled_jobs schemas/app_jobs/procedures/release_jobs [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table schemas/app_jobs/tables/job_queues/table] 2025-08-26T23:57:41Z pgpm # add schemas/app_jobs/procedures/release_jobs @@ -34,10 +34,10 @@ schemas/app_jobs/procedures/get_job [schemas/app_jobs/schema schemas/app_jobs/ta schemas/app_jobs/procedures/fail_job [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table schemas/app_jobs/tables/job_queues/table] 2025-08-26T23:57:41Z pgpm # add schemas/app_jobs/procedures/fail_job schemas/app_jobs/procedures/complete_jobs [schemas/app_jobs/schema schemas/app_jobs/tables/job_queues/table schemas/app_jobs/tables/jobs/table] 2025-08-26T23:57:41Z pgpm # add schemas/app_jobs/procedures/complete_jobs schemas/app_jobs/procedures/complete_job [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table schemas/app_jobs/tables/job_queues/table] 2025-08-26T23:57:41Z pgpm # add schemas/app_jobs/procedures/complete_job -schemas/app_jobs/procedures/add_scheduled_job [schemas/app_jobs/schema schemas/app_jobs/tables/scheduled_jobs/table pgpm-jwt-claims:schemas/jwt_private/procedures/current_database_id] 2025-08-26T23:57:41Z pgpm # add schemas/app_jobs/procedures/add_scheduled_job +schemas/app_jobs/procedures/add_scheduled_job [schemas/app_jobs/schema schemas/app_jobs/tables/scheduled_jobs/table pgpm-jwt-claims:schemas/jwt_private/procedures/current_entity_id pgpm-jwt-claims:schemas/jwt_private/procedures/current_entity_type pgpm-jwt-claims:schemas/jwt_private/procedures/require_database_id pgpm-jwt-claims:schemas/jwt_private/procedures/assert_attribution pgpm-jwt-claims:schemas/jwt_public/procedures/current_user_id pgpm-jwt-claims:schemas/jwt_public/procedures/current_principal_id] 2025-08-26T23:57:41Z pgpm # add schemas/app_jobs/procedures/add_scheduled_job schemas/app_jobs/procedures/grants/grant_execute_add_scheduled_job_to_authenticated [schemas/app_jobs/schema schemas/app_jobs/procedures/add_scheduled_job] 2026-07-15T06:07:00Z pgpm # grant authenticated EXECUTE on add_scheduled_job for INVOKER trigger support schemas/app_jobs/procedures/schedule_min_interval_seconds [schemas/app_jobs/schema] 2026-07-12T13:30:00Z pgpm # add schedule_min_interval_seconds estimator for schedule interval caps -schemas/app_jobs/procedures/add_job [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table schemas/app_jobs/tables/job_queues/table pgpm-jwt-claims:schemas/jwt_private/procedures/current_database_id pgpm-jwt-claims:schemas/jwt_public/procedures/current_user_id] 2025-08-26T23:57:41Z pgpm # add schemas/app_jobs/procedures/add_job +schemas/app_jobs/procedures/add_job [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table schemas/app_jobs/tables/job_queues/table pgpm-jwt-claims:schemas/jwt_private/procedures/require_database_id pgpm-jwt-claims:schemas/jwt_private/procedures/assert_attribution pgpm-jwt-claims:schemas/jwt_public/procedures/current_user_id] 2025-08-26T23:57:41Z pgpm # add schemas/app_jobs/procedures/add_job schemas/app_jobs/procedures/grants/grant_execute_add_job_to_authenticated [schemas/app_jobs/schema schemas/app_jobs/procedures/add_job] 2026-06-03T01:15:00Z pgpm # grant authenticated EXECUTE on add_job for INVOKER trigger support schemas/app_jobs/procedures/remove_job [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table] 2025-08-26T23:57:41Z pgpm # add schemas/app_jobs/procedures/remove_job schemas/app_jobs/procedures/force_unlock_workers [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table schemas/app_jobs/tables/job_queues/table] 2025-08-26T23:57:41Z pgpm # add schemas/app_jobs/procedures/force_unlock_workers diff --git a/packages/database-jobs/revert/schemas/app_jobs/procedures/add_job.sql b/packages/database-jobs/revert/schemas/app_jobs/procedures/add_job.sql index 10b801c1b..100e461f4 100644 --- a/packages/database-jobs/revert/schemas/app_jobs/procedures/add_job.sql +++ b/packages/database-jobs/revert/schemas/app_jobs/procedures/add_job.sql @@ -2,6 +2,6 @@ BEGIN; -DROP FUNCTION app_jobs.add_job(text, json, text, text, timestamptz, int4, int4, uuid, uuid, text, uuid, text, uuid); +DROP FUNCTION app_jobs.add_job(text, json, text, text, timestamptz, int4, int4, uuid, uuid, text, uuid, text, uuid, uuid, uuid); COMMIT; diff --git a/packages/database-jobs/revert/schemas/app_jobs/procedures/add_scheduled_job.sql b/packages/database-jobs/revert/schemas/app_jobs/procedures/add_scheduled_job.sql index d6f6cb544..7fe573b73 100644 --- a/packages/database-jobs/revert/schemas/app_jobs/procedures/add_scheduled_job.sql +++ b/packages/database-jobs/revert/schemas/app_jobs/procedures/add_scheduled_job.sql @@ -2,6 +2,6 @@ BEGIN; -DROP FUNCTION app_jobs.add_scheduled_job(text, json, json, text, text, int4, int4, uuid, uuid); +DROP FUNCTION app_jobs.add_scheduled_job(text, json, json, text, text, int4, int4, uuid, uuid, text, uuid, uuid, uuid); COMMIT; diff --git a/packages/database-jobs/revert/schemas/app_jobs/procedures/grants/grant_execute_add_job_to_authenticated.sql b/packages/database-jobs/revert/schemas/app_jobs/procedures/grants/grant_execute_add_job_to_authenticated.sql index b5c218f1c..22a615dfe 100644 --- a/packages/database-jobs/revert/schemas/app_jobs/procedures/grants/grant_execute_add_job_to_authenticated.sql +++ b/packages/database-jobs/revert/schemas/app_jobs/procedures/grants/grant_execute_add_job_to_authenticated.sql @@ -2,6 +2,6 @@ BEGIN; -REVOKE EXECUTE ON FUNCTION app_jobs.add_job(text, json, text, text, timestamptz, integer, integer, uuid, uuid, text, uuid, text, uuid) FROM authenticated; +REVOKE EXECUTE ON FUNCTION app_jobs.add_job(text, json, text, text, timestamptz, integer, integer, uuid, uuid, text, uuid, text, uuid, uuid, uuid) FROM authenticated; COMMIT; diff --git a/packages/database-jobs/revert/schemas/app_jobs/procedures/grants/grant_execute_add_scheduled_job_to_authenticated.sql b/packages/database-jobs/revert/schemas/app_jobs/procedures/grants/grant_execute_add_scheduled_job_to_authenticated.sql index d0fb9a0d1..3c0aed752 100644 --- a/packages/database-jobs/revert/schemas/app_jobs/procedures/grants/grant_execute_add_scheduled_job_to_authenticated.sql +++ b/packages/database-jobs/revert/schemas/app_jobs/procedures/grants/grant_execute_add_scheduled_job_to_authenticated.sql @@ -2,6 +2,6 @@ BEGIN; -REVOKE EXECUTE ON FUNCTION app_jobs.add_scheduled_job(text, json, json, text, text, integer, integer, uuid, uuid) FROM authenticated; +REVOKE EXECUTE ON FUNCTION app_jobs.add_scheduled_job(text, json, json, text, text, integer, integer, uuid, uuid, text, uuid, uuid, uuid) FROM authenticated; COMMIT; diff --git a/packages/database-jobs/sql/pgpm-database-jobs--0.43.2.bundle.tar.gz b/packages/database-jobs/sql/pgpm-database-jobs--0.43.2.bundle.tar.gz index 3fe8b481401eb564f64853bbd0786f5c50640aca..802eed7680ea89aa90608cd4c0a9606e2480e007 100644 GIT binary patch delta 23169 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 delta 22110 zcmV)ZK&!u=wE?QF0e>Hh2mk;80006L?7i7?8%dTR+RyV9F*#jEvYS-;zDT9AQAx}! zPN~QoimI%-r83%aKgUUAf&dc$rDQK=^K?Jp+I(Su$u$oI_5ct7H>q2Ckc5cv?YR5d zm+On`tAlXU!8Senso(AV!vp+bj4`8B`7dK^_FpB0;$rlydVkJ2)Ak?OKRm*poBjZL z|G*w)tk3Yzzy7zP_~r^atwjC7H_wawyC}X%-QE=p{!G1otK0eJdGQVZW`Fpm16PzE zT<3#>Fn|DkI{2v@`lBymk6=LfV{34kKm4Eh%b)t+#%_evz z?e6VK52^Rf^MB%R<-?-*rXMfq3i{u|_4Rok=-c6gs4YHaQ-n7E=qW9&NF_QD+aa z$SH)BH?AovnQlg-M(0!XYi*-;dFE#C_hxOIx45R>6?CXGXy2YEXtg(JdO?G2fRdXv z{&+N{W?#qd)pfbc%@;AwJ97 zu3DY*R%bxHkI+8vQ|xxI-+WnjU(EJP=+Ra8;|AN1y1hu}H=T?#D)ip}rfm545wq92 z7_yw-Xd`3b7PU8mbW*^Xm4>2~CP=3&)7Aq9FTska1y4F4HvyDPmXjB`-+#}&eskT1 zl7CzMP5-ttfL{+j4jI^&{26=3K6HL_-RoxN>g6Ky`l0iin_jE-#S-?j_*?n$N9wiG z?epoa>X(Qu-E`uh)$RPf5KQO;<_?586Y{yX&lUfFirl#PZT|az>cArRhA0^EyW)Qo zfLJUUv}QIddEMe~OFFuzS*;v=&17pB?0?6cZq4u;PWUNjTQk))vcyBrcFpjcz;-CY zES>PiV?XqM4Ch<7!sVjYgCCydL)T2SPP%*O8P<*29EPhT$qi1d@RjX_a zt7dZY{Q&Jj!O?6XcZJK0nDgaAWo8+TVY0QP_5}cH>$NS1-lOC5l~{a@l4tWRT7QKM zSF(N&vFODGH3h*kZPi@>@{lWBHs7Y}ozbe@@2=M}3U^!8Lqc)}5>3IkOt1L49+zeF zU9;ZR`p?g7v8yNBaHaP_-XCR^n-K8FCfg4RfQKQ`Gg|P+Bh)j)Hk#l%rTbR9H8YI| zTjaW_H(LMW9^=OW%zYl|RW^@%=6~)UoJU`^mNiSCwcC6KC~W!U)~%$@nDkkRQfp`1 zY=w8-sK>s?OO~*Dv9;E^yQ8KjImfkYS-t!Pi{s~dkQXMpeqGxl*r(sfYWAy_INISa z^oowAzj~1~_UA8jH)qz+e7RrfzD_S;^8Qd zcO$efyZu34dpXNub|Qy=Ieh*8?6Bx|ip6bC-Cl8Y{O6ND9-bDJMx^N9Twiy4gVkVJ z>6EcrNE|LloN@VFvX#pjZGYwRfv7)dU6mqCUdia&V)R`R;kv*6=s(sKzlZy-6fcXNRUhtr=pZGyX%EgTC#>9pJyY_T*!j@8>XsPs8U~j+pdi2d$95}VtM~0< zuR9+)J^JOQ)uVnnZ2S76|4V!S`XcsjuLs@zDL<_JIpk6{%@=04c`u6u?S9t9-CR>I zqm;>`qWI96c#;hx6o2~TFob^{IlYbIAJzsXAYD@jsS{gM-IQ;d`Hg*3HTe5f z#dzKItL7l2BnzB^un-^$p%cmIxpOHw!4o)!kibhvOmnA{1ukrq5;PBTan-|PUcc(< zUG1v>T)tbn>P*|_7ya(=?cwXQqPj-I4|jJ)Eq9(j?|;!nzPBGepu!1!x>#Qql%glE%CD-=_9( zJtb%ueYTuHETb(CP7zl75?6%c;Gh_h9Dg0ZJ3KwxDbD_ISiUQYl@a?p?<-5N@WJJr07-xNpRy_moz5Aotd=kWN=iw~U)n76Nvzki?a;)p@o zX@5fJ9COx7odOxI&{~NhSP+f?RX~sErP5p}mjVDrp@B=|DR_=a8)hG#&aFO^%gJA} z^R~O!h%RDClre}1Rs@0+owgPzD6{=)GJ0Xy1Mu7<>!A0x%!_&ppP+Cte zM_lKGU@igIkdBVo)`oOcS!shNjw*3$e1DYQ0_R4mq*0TOR{0P?Gsa>{S~yG`wBiI5 z8FS=T(NU`c_avfaTtbKlUIB2%xD1jR?UD@wH6@C`H51$ksa1+B7-kThX--F*9;q?< z8j0E&cO9O5_xj|`;a3#41ci;@Q;ZQ=g~yf`^34}YCMpS*o_cJ%fzBmUR#Pmj+2n%VH9r!-k#Sm9KD}I1}zZhIn zF@XNhMXCRR>OqAsp?7hUwM_d3bg+EWquA|X(Zb?WtKH6$MfVD_WYKQl7Jq>X_z10b zspIK($_D@I-N|ur4Y%zs;Il@o5EsM^XK2x2NQQf7BNIMJ6CD?pyzrJetGoay5e-v` zLv)O5qg)c=aap0dEg3UIqkFlW5vplkl8^OJ@;W7L$bkzn1PMVh(p*{c7?{X^p_e=u z1`fUF+yG;wNXkH-5_e3YG6!J2^qOyWGIqy z+$ahbqm?A0CdWKSLjkyBcVv-jA-4gO^pyl`N&=QLQ^kl56g6aEYkxxU<-PVbdnf^5PyV(2}}|?$rKn^u4Q8Qw4C9lI^DF{UJK0G6y}WPdqC64s^cf8e;&oTNdo zgAUR`a+rif@WCnV@0t61V%G1=Mc$9a)IAADcLxl<1ZhjfIQJwSadOJBkd@CyBol;C zi8Jes1XLzQjaFHyj1dwUp~AqF+mfeioYC>wmYeP7m`F=v|Zz%G-0EFUI6ob{@(tUsNBCx_&W*Z^|Ywi=7Jb*N9?e z>+eoZ%b|vih93_v@J**f{lR4Vd&9?XPfmUqV}(5Q%K~~mxXsI@4(9Ls`^7Nl>h8|Y z&TslVJ3IUN(=lquxFC1)Vrrh{KmYb`Sgnzi1o3i%Dv0ENKVUMrXCi3OW5HS#bk=zo zPfE0tJTaH-EKjYK6%g6I@I-m;iz&7aRp8NOOh; zlN8QHNJjZ!5@^LpSS1jhqUhkhby4&Aq&MeV_E&tej$L{>RaTXRZX=#&^;c^%&2A3mcSpzHXE}3-MWA;vHL)qNvu36> z!sq?Y>g4U4MzdND^Nf8b8#Uc-1{crB%zuj#bL#9j6BqgwcHU+~x0m=IMBBQWr}k1i zhfJZ=&SEe|p#qYcDKdhXG)butxy*iOnVTf9tWGleXhBMNLRDmisW#F^)?>S78c9Qh zl6#bBgH+sj8Axl$9^IaTwce({Ba0zA1Wrz7Z$8C9B823ldqRz51*+|olFMMW9e*F~ zvtKlFMph!(sEDLjo{Qjw&VGCny~_@GXo5qGi6s_73?O0v9mGnrw3pzM45*2PED}16 zmLxJyE&>ad%cOE=NXoE4)*3=xTIDQavhq%f$z@<#qn!LoNWT)&uY~lG3F$VP?|O7* zQ3sv07Qsp(qYQzx6&eiZJY{=60e=b1PTNwT_JW*~N?5KH0cgK^ZUj6><;p9lpWkA|3(I^(7b&+u&v~#S`pI)8*wfN)VU-$FN>4@^1n--R#O~>!w zzAfKE9CUl5SLKr&hg7Bro;(_+N-V7u`8toolE%GcE5>;XbJNtK-teoLaepc!M5*f- zx8M2Kf8QydKQEuneqALmEFGmYX&JR(v)^68ujeor(AD*zzwoWlvmgIVx8=mjA63dV z7!+fF^ufPOzX?+xj}sHLI;mSMPPYyHsXRH0J=VEZV z`!n6{6~8SsmwPpD=fIr5d^rP=-(~7n)6Zl->$<2u8pC0)_+3#==l$`~|9*d1 zl+*ms*_$_-tv;&RSawP|O+l-3@onsNI|u#2ZJSCf8B%I>TA4%ZKQ9R4Ws&V`{cHHt z=@(`59Hc+qiT$E`oj)!uU;WsW8-c-Mf!DXvIH%6d#x089H?u#M|4!84j=oj5j9l|2q z7W6B`LHoAoc4*6mkFS4a$?O%ME=TrDteM_^(Icq*XaCoaXT>mhynw;5O^#mAr+>3a zOfXKK#bNoY*zNW%zJDDMTg|e&b9(vD-o&9lS)rO~Ij)K=fdg#pY3Hl{DA0Lwv6V4#0y9_+Sfk$sf zFfIqnBd;E?fkMpMXo3w7Ax_Q7QuBh;YAI@s2sI(Xb2jQ}QEFb2nh~SwM5qP%saAHH2u@3+ zrg@QRPF|Xy@}%Pxa?^65X@$hpP+VFeE3Frl)_+JzGa}OGE+9R)i1axNNuPsW)-r+&hkuIS0{OWF{w(3{Dy+Qb;lAs4O!iRgzH!X|&Aom^rtbB7X_u ze(l|>%Go}YhjA0rd(o6)J(aut z9EaASCJBi1sI&KrouxalvtQH@?Fq@=FLXnKjY)Bg!Dq9H7LiMbULZ(7Lm5g$7PU{( zaSO@$6zMqW6ItwShK#?U8MWWo8h} z7`LqC=Zht{rPy0Ssblh5CL?T)@=+q^)k2g(W~+pg?->|>x>=?5ry$u+M1h+DqYd7n zh*3nc&TuZd#m693GsTC$5~|rU(w`TZnw{IK!#OcaH=-sM9PI?@XI!x_cnX6m`^5a?6>L)^G&LLQqmL z?+GYK5FnlN!T}`7EP*Cc0a2q!QY5=N1Wzb8Op_jy+Z!STZFTjRlK~tnf9s;}|Ai>k z)-c*MPyM|Ru(F%8lQ$>D75q#^|E5<)Kvf+V(77$HyX{uqtznw7R=+4a`R|ijNkh!C zU+Ek6i(v@eelZM)TklC#TZU;3`j$}=Mhh(Sw1CVf9g+kvNs=%?XtLGBS-{AAwg<>) zBQVc6BDVpbiU@9hI%+B73+Z7letG8|$g#PblgJz^f9?06hOCYvoo5_)&&$3W>OE(M z$hyv9hn2PL-g~ctULUCUQTXh`^gar)eKD+Sxt_(a#E&*gU=FG!hdKXi5)PeC;8f&H8JU@N zZXik(f0#+$#3lyXrs%aXVqKylFeD<6Fb@-q5;ze|lLM^ZkW9XJ0w$^}4++!=PLJ^GSx; z(CxMfI>q;g$A_n{&JN!cuipOn>aXvzJIC(sjQ3@p&R+5A_)W1pp80QQ7P7ZjygNHN zJ$y4e{VysudiIO?^O`#|YWU^BLGh~HKIrxiI^Dr#t8?+Za*(9fAUo_v=;bUX1r+I~ zf8D-47`tPSF3HqwsQSwREB{&;MLOK1m(uL>kSI2JzWm(WZ;mpmhZUYTuUgx*c_0rX|i!+P&`0VCZQ-f6<`b z?a;F+*z7L3<>NE*vRDAz_~kYc=;N@8f8Rs=`J&go=?uN%%T1mNqiAJdX9AI(aJ#*t z)j7ECbuY4OY`LnthMTXBpx7w1yg{r)eT2k41dp$Xg{TbkBRA@mF%Z-l`ujJC6d(D& zPe@MX13!FF3@$V3S{VC9LBHHUyFZ~{*`fRL<_bColdp5<`vtaA%C+u?j>??^e=evq z`1aKgM+X^uLL8K0YM@JKr|eb?MJ|WlWZsR!HJ&z_z5HvQQkM?g-Ro+g>9^(N%4rYR z_M#ZiVs2l{SzXZJe6I7|De6BJBDc2zJWIx53Zmuj>mH@nuO)Q%iwxc}i_d@3FO+WQ<=v`7*URCxTb_V}cnPge@u}76P;c+q zaR1A*TZhNX(|-vcspxd;MtfEqcL(`QfA`1YKmO1EosJ9*1e#TdgV-J%b3ng*`_a4YcN62{i*%EzP#<{crdp z|3Re}nRB?q0#LCN_-2U51Bw43e`%mq#>wNH&XcIC@g%Cz z8d91`q}5@$k9ku^k6#qS(5lYORmdcxchf0StCuUiA|<$K4+_?3EtP0cjsm@QD;J9` zvA}o}`6otLaxLXif65ZTWCoYeF9Kzn zIQ*Qj=y!_*jR08}I@Y?nxtb3j3zWK9KYU3eLKwT9ehW+bd`-P8=ujtfDG6F_YJ{yD zN0hzDMB`(5YDRBp7(rJehZ6DRTed!9$0QVKC|;HOTK?~f`hB>dQP>d`SKa;~2boc4 zP_1bMj7DRu3Thi)f3N9yw$fE~lN=z80@$j7O8PUzA$gB-6kES(@{b`)JS|b?p}wyY zIcDtCzPai=F9y9E8m_N8AUBfDUz(LTiuKM{m3# zFj)bQS?!**(V!9r#-#T`TN?qmArT%;<$jl(7u%VWa}_R0fpU#>N+2+)U}Z8XcyKaT zTen<vDX)5ZPew^-B!E~LDu)d4Ol09R`NmZ<(d$8;LaHGV~H){Q%*=9ts& zHO&sDR zeK`uPr&DQtRcbwmQtPW)Yg4t>XQbGAI@Q+WD7PL@f4%k83a&>{aXqP$>*>^7kE7^% zJXP1@DZ8Ff-L=GRae+E^i#5rX|gD%R~i=DhXJe%lyPJS#C5jCI9a8Y-h%~U6KvzZU9=ZuYjS;li* zbuo{9KDZe6{rki3PEHRiPt06CrY6*W%QPN!+gR*23JfF3fh7kjFuGkx0!CkeNFJyH!ce&Y{&2q8J_ns}wQ!*TJsUc#&dcWda?Cj6?QFouG zQkM)r7jonsw?3X*n4GkZi(qm=-Ke$UT5D~YGFo_lt8B`pnxhSoghwxuC&9s~WK^WY zR+%}p#pqm4QYNEOOlXeAXhj@FAn$U1Zxgr!!=evfImd!d(xPWUVDh4YnPch2Y(#)o zT!$(fX*`c5bZ0V;=9Fi2)<>jfe8%TdQPZq0%P7r~fU7{!horb90Ww7f zi8C*MwT{~8oPpmc?_`u(>Kxq@Bsd0~Su2670O3B_(^8S0^&+{-;ZP$HyEmP|<1wpG z!*VWL_X9JaP3kvaoG+bR;~b#X$sX1foI26N-ulIqp=dt~T*LmW>V$uO_4fVYyJGkF z@W*G<-|_N&DdQb~_x30kJDjpNRqVjY=q|l~J3JfJBpeS=yeuY@cs6 zN@ch+DUw^ma%#qwg77?R>dFAM5^T$oJB|38d4BW9KO7zxyK|5!?OT<#Z*On90(?DF zx?+1@lxM2jY2TJ6tzNdnek%1v*e!+q%~v}_Oq11)P~CUahU5*_UbFZ&NAJ##j$faD z4TB#~-oAM@ZHFx5A67x^&LCUME^5wQ3cywJmOMC>)2^zB!H`ozCeA={9wZB3wV_~a z(o!WZZH{J>_KDc}rK@g`gRj}KsAIl2>t}eGmiDs|eruLjH(4gUPbLbm8W04_DXPf@ z(jf+tR^;3r(gUf;b7W$f8qmbV9k?Wa!xQoTOE+`78qTYrIG2KVzu2i}F?^9V_5={QqyX>zAdw}38&L=L9;AQw~f>9Gl%mqFj#WW11gAGd8Y&jR6%0$OcU^&%iIBx za)jW4IFf;!xQdW_ND550gWRWo09hqX9omXM4-?1AN)pALCF7?NSZCn;q-K&j@NP87 zItVuvlS{46Iix{$ks9KzyB5jVnNGyJm}md}+qdfqWz=UepPuAqg6F%lS7(RWxVm0Q zs)fgkwfuBY3YFC|qScIX0w*AmvC$f!oa|qzXq6PCtPI4Y);WuL&MPW^1&i*fgrnID z(J(9d3~)A*Sr&oYOj?n`co#&_s7w&73^qu?ND++=Cck7Dh0{jsvL2_1DFpAl!@$9- zr;=7?p}Vb!QU|pA6HdlgBY$ycUVi>~)N<{M3tHgURbzIYxI{;ZaUrFnU{Mhyl}!!Y z3(F*FZzKno3^yt`qm;vcn4%`nLPWOO{-D65gjyn6Ns)V(RT|g`t8et6=#1qJgy}0VS2A&YQFjvRa0CyyV4ux_)Rf8 zR5^{q<2S|8chfk3>E!L3P0Sbzh-ukgpO=ouIj!D=m8{8NwnmRXySPnl7%R@$ud;GA}II;5pZhQ1B3) z4oL^ktxPNznR_Bued)^AbM5O-#aa`_`fIeAB(SW%X7WIP%(?po_c2+65z)Bdgz{cT z4@{F$#tN@wqFkenP-CumCn%b%O(k$oBo8dz!fi!^kxozg2fz(FQgPOO7#3F*(wKqk%w_l4Fc1>nx%pWiw~1Sqw~=oP5|S0?soB z9L37Qk6J5#jl!YFB!djXNk~GN-%a8aP zQRpkL_O!g(cJ`G9bY>;5R%?lvvovgAR#?ZOZOnc>g;nKYmcK;yR+1AsGRRzF5v8&c zO|+0qF2rM4(82=hU{nOmMSTKy#w6pp*Kw+X@%FNR_oyPpxE#+goa}LmsXdk8y?W7) zCUwtMpc%T1LaQ^Z?@^g~XAk-NcSpzH7h%vNnl6hpod4d?==}Qa`!|O<@5prGj=eFS zEp}&Oqh6MAP?$ygQPDw9Z8D6oJ%<8%2nyod!~zM9|zbq4QhyxI)O9-PycC z&m5GO6y>SOoz$S)+0AI)b(4~-T65)@Q`QN81d(0BQy^|7qBPNEKgNVwYtSK~N)Tce z8@W)HwB>cCr0Lpr*1PDP^hi-_$+Y28c%eafDaZv#-U;ax4<;F9ypmjz$OQ+va>zEV z@-w#ui->Fyc`Zez6$-1tV2$wTrem)dZwba+R*xt+A+x8H2qc{)Qb>Y13c;fj3_aO@ zC@jMq>x9fp#S$6ej7Fd9YvDaI9FJZQ@2Q!MM=5#NpwQi#8duvCwKL=#gT#^vxj+ny zig_J5MD0jMZa6q*K}8OpJSGt`+VD9co0KuACnCLfDPXr<**a~}QebGDVcZ)hbUiJNQ?vrm zCuf`>ACs0Ynp_h;B@a$-N2oTqpiAx~O6w#?9DfOtc6@cP79g9@Q{c@GS*pgG1pj6Jm$m8 zShk0HX;?H-kwzzkqeSTBLzIBqsPG%1Gl9v38l+P)#)Lw9tGIAcMX-Q>E};{kjh3F6 zVG42r5sO|~6n7`^m3`!kD*rY!=X);#HxBHnE0#7kBg~hDn=!R5t3}qddRU`EO{00{ zyjcIcO#F961IP&rb5<;fKMyl}m6Ph1*|72;IS*EUkj*kV^;ek-H?IzdD_x(1eq{ce z%R=~mm`ttwT_ps(%|n%cti^_1PuT-!lF^M)^_KUm)!C}=m08jz9hMm*t2|<5LRJ{W z%Z+LR%ni78vb5oJW>coEPC;al2O%LDuOUT@SygVh%P~DJapWfEV!Z(f62z2v3ej)@ z$W6{F@~BDrXD{+5srsvyjXY;`GBU?&D~UX-G?ex-X&%9WV@x`KpEOxA(puv!^TZ`N z2$Ezr{_98eoS6m4Hfj5-9yS-gEu(x^G_xibX!i;fSgs9%XArecQUP+3Rx|SCgG*L0 zDg;H&4N6tcBcG zMuOU|mYWK;=2?g+Xr>bd%Rq8tTn49i-Z&eA3toAitp*wDkc10{<+Q%2lL^L%l{|}a zOxvttchYfx9eMPg6DO&24#^~qR7Qw^!CH{gGVW7!CPeL|Gs=0rg~4F!YrVwoA1Nen z(Sk>S+G*1Kn^9>!R51=1OSGd+E@p4A$oRi1m!a}Av`&y`RvRIONr1^ z85KqcejC$*vXXla-X}x^?tyWm43B}LvJP^N3)CWiTCajvQ6}Rca_`Ehq%aJ8M1e;y5IB9S`=fBV%awG~RmPjy^3>3pa$vMd-Bgm!; zDG}WR86lP;&Ly}~2FWEiTp}bblXTiEO<;`ULI~v(w?QOhHIj(YqBD-8k;ro93G23G z7pBs}WT&}t1(W>tGbw#A{CZ|)kH#^q0?*TbRy|k_l}177i>s-MY_>Xsp&E{Uy>9hx zhef+SLVHqck{6fVn_hosCUkuvhIcW5y&P`x;q25)MDo)@JZDj~e;#Y~rbDO>4oYoK zRhVknQ(0E9-<=dx8e~V%POc3!EFIeV7b-5PM-y~7j5zwIGU#|Qldv-6`7S)qu-spKYlY6j#Y(6{WKdkRSn%rMQgBAl@gkiEQ*&Df1Z8WyXg#D z9etN;qg@gXtLaQuTh)*p1yoN>VH&=FQwFxGAjBBfbNTIypQdBQ#%cE|NH3YfblBML zPjmXoQpbF7P;@c}IJK-15;!?6W}5u&WwBdU#V8J@uEE}-oFa ze>i;e{_WwLovMSW7`XX;g`vV4dcBF}xGcDum7=-YW;>UK%s0A7?bgNRpora%`Em?p zg;xA|ztnRNE~)DAXSyXUZm!GhC%K&2ry@e9)6JEVQjhwV1&kEDz3!*kgHjcIsyiAp zsbY#@_~Obl(XKla?Q)3Gq4=49ZfBwg+jFO&n#Ln+LzOJ1L!XA#F!L;1LoIY!>}sUn z&wlFt?oF+?xX@cVX<32?$Z@?2CdjHpg#c`V2OtI@>B*?R%NUD`q=8W zVO0IJfBrMwo>zcdmS?jv#23@0_^NQ>QGL(q=eVHb{(KZ_^fXH6f911_+t!zbhAWNg{~1h-{fN*WYpdAM(fb?{2_ z-WU;0X2NEQR=+IKNgZBVs0KIsa4zoppN5z1rx8a`h3if;G&sZ{Kjo`mas^d2x!FP( zqiFR0&GmJzo=jNvSXN!^L8pJ+?F~xiXg5DfSzk12RFwCTaehu*e^DD$5-7X*T|SC95?)=(9s+*k=O0 zU-xo&l3$;^dVBcp_2KSM&+;MfFY*j~&3U=$fb*X=s&KGcLt{}81jdr*D20)MMHe+% z#?krUWZ;Pfr+LnQ<>?=1 z(Zd+s$1b#~A8B_EE+h0`O#RrK<2Y6|pHP;WpQA3Oh{Vz^CZucT=Uxq?v#Og}D!A}y z9;=l&?bY#L#|NWgCQ35io=Zk}oy$Ui!|1(_iASLYA+y59kVpnYM1YU1)tkF%8|n0B+m?!{d^Kmn za$22%n*O_4smPOPIVgYh&4F+`HQ!dS+)nq0+KF6~gi#_dav~i9ut5i=bO0H#41rc0 z1t!vhB@r=~ws1UHunbq}{yYc?-Ydnmm5!LiI!0R0Gri~4;P0(8u&X;1J)$;84+Ps|PT$s+S zFwrhd-O}mToVlFVcu{6F#V%Cr*G>(P|aRLw5C+Oo~WetKc|io--t_gaN^%(jHw* z!82s^$c$Sp+_vQ3)&p=mZq0C|3$7oY@xYR{eEE*i?wb>z!@UnC`okYo6CdHt%Q7Ivi5XnnpUEn(Cz@v}ChQ!baC2~FpsezE8 zj1lgS33*&q-Deq=xCAPnLwrGlu=wS7yN-Lg1BDV?(i4BGDB#hD4?9iBoH)4KrHP(>5rb1-Wrd;QpWnU@Fr12^xrj6GzX9N$q zT65yjb8R>xX+@IUlQ2JbXL%-nK-!MM$%T{A=#Y}m;qb=Gpvgf>oFjMIiYODJKtNzl zP)?eO)*By9w4D4(brkctI8@Fy<#J&QBfPgd*kB=IbipdpJ^@GRl*K4*V%%vh$%Buc zTe=Shr-rS$3-9t33Em9}Zp|@y?|9-&pkT(Sm=6g7nOsPLR7$ztZ!nsF2PSL~&L(Sh zN(_{vXwhhXcaG_rdRNdXGaH>JXtf`Sb*fKM`T1E!Yr*pwZbbzbG_9 z@=_%$kaHg*c@w0JT6vIvM46^xuHFvOSjmV$v0#PBdE-?e|ET;?{Sn)QQL61>V!Ud~ zIXMV68Q{cijKMRK(YXjp015~^&KS|qYMk!DFMeixpq9$=f}#D})Y{nx;VYwTPs zVMx(=qdlW!Y|c6!Sb&r(mI+JJD{v5$)fN@$B&ADS%FKR8O;X;SVY;Bfqb9U?aue6W zfQIpxovU%fqgbN=JW=^qbI&yOk({5hxl=`2Q5e%0f- zWW8`zvv;_PMgc!l)g$M+Tk!9lMkEV+vxrX?-Kk5gG~aT6Z|D$y`sVPo`2An!91&i< zd%ZtKYp>qDF1PaNPe*6NfQm`O$+vIyXcy%`|*~(K2trlY{6` zJBLnzN-8m7RM0tPFi6dFZs4Gz64Ge$!ZC7S1wJaDx(KbUc+@pu+m1h#!b%Q~Vltcp z3r1y)TMXoxwdkxCJUC{hlSyVzKW))DVXe~A3lm^}rBRbH9TEu?0z{JBv7nK08=}vZ zyn+v9c{6Pc&lz>C&>AgqZ*xv9L3c80E&^*S3b&5DE$XgT6r%r1-@ekf|BUo)jr5_N z(As+x*(pbO(mq0bBsn9$1M9a&+&(T=v@{XHNbLU5S)E_+F7nM>Esi>>FwRu)@x0UhG~+YHG^XFr6N^RAEiD8+MGlq+JdVQaXFvFD zu{(s+Onm*`e2sdghYwTR9FxUcdJBU4&<2@OvfMrOpds=S_%lvapsIr-sIBq zETH4jIwz$^h(626jw=@LDhh8a0Pi>Bkd~!tx~p{jl{@;%9ew4FzH&$ZrMZ8jbwP{< z9jBD60sybA6Uw-R7%fl|nvl2y=91BhVnoR~<0d7;c*?0k?jhU9$o~HU3KsS0HFjVg zB42FWz<2=*T*C~RuJiBMTvzh$?DS};cCOQ!)KU8}J>S9!WY+g%E^_;#M=+rNpx7;0 zhY^n5EB?9IrC+aivm^0f;)=UpaJIL9_xyS3*Ya=wR_qjgiro(OclMq?AF)fTU47?W zYwMJ?6ZA18FiV4%R%>!A#NF$Dde#tm*QLvw$KeYZ7-!d4zrwkkR&#iKc81ew_m(?3 zPbJK|gB6xB5m85G1G5~tG{!QWtg}3TcgQR$prn{&5`u>>h-$LUdj#mw;~ z-9*kDHJXZu+`vE3AU95M$%5v_8{>jZK~p3dAq1(SDzkwY^jvzcg_9Av#At){S}J%{ z5pxWmtz^vQfZ9&H3>0*Z?j#>WG}dq~$VJA`8f%e5Kr-B>Y#_;LL6S}Y9Hao@!OA)V z|8%As0WwJt!hq%?CPZ&hamhn}5K$|we3DFA!Mx_tN|G!olNIi+YS zH4X{HD&g@fk@!j^?t@6IGiBOD4j_`$oD*8%0w6f&nGTUl^cHd|q)Z8-%r#h}a1oQV znA1;1dv{(tcCW6>x_e(EPL_h@i#2P`u42KP`c45PRb{-Dil)ywoaPP|8; z;FzFfNf~BiaAmy;>#VeYo(qjaqURlUj=yoOfE}@JFkZH^M#{ z6LWVSW*Su6u`x-1CZD{p-WdhJtXCw_rlhS%MrrAslS&B0WQ%Z?jf1V%h zk4xv}?9Q!!PU^1wG9#3-=+}HGfBAOKVQcY&mE|0S9!_{!!3b8qo>UaNxoP3z5EvHT zP3`2fn(fOWGEIY-mpJyXEIasdda!b4)#GvLr0UspqN(9BkP_Uq2c=H4OqfaiT*PCi zKQ7Hwl!xp^(e1Qvt2%!DVKp3xgQ7dC8W!jh+9?-*|9}EpDN$LzaNNl*J-c{TT=%*k zTe<(TLfEi^PcDYkhC%A~t`54NI^*I$q178)_Vtz>leB>~{Kt9^?e^Js4c(%W?^wyn4KedFe2M+D!)rgI+7#lz1brbDa1C1PsUGc%9EA#)+RlFS2us`k2hC@E&rK;{r>dxg(Vfm0+y^P zJvNOsryh=zg^X*9O)%l!)OR(jU$*|KPG=M73q9RqbsTEvGOhr?Dur70&rDIbpJ`H0 zvbL;SUA?u#zr23?{>|Z=XUn#|m@2$B{&@Z@%&~^!?`okmS8ZVYaqT&oCsI@2FN5}f zR857+gy)Xf3|qSp_PW&lbdGVKVcv61wW+InltvXU`!_KX<(g%?RT0N-uPV!yt;oar ztOULG?WlQGesy<9V@9#Evo$40%=2*Anc|W2=wq1Fra1UrMwmS+u5S9d?q&g%* zQ5+>oui2ouNTBk)d|UGwkm2gn89#o{izK;rD)VU-iAtEz2o zB$7)~c6^82XFoKDq=v8ILlWbE+anFw2H9ObQ`L{E>Z)IPr?i$}O-3(mfgXk^MF=uT z^4_HwLr4d8$}MwsFVEaFv3Ea5p3DU@E`vG~S7#bV|J^OZ2=03CuGSf&3+li&J=WWCxnDODvdTJ6-mjMU9&B<<-{ zM;rBMqaJP4qm6pBQI9t2(MCPms7D*M-A3J9prBf^Qr85pIt$7ss}-f_REP|Qg;G`r zNfr_Xv>~A7QWwLL2l9k}XtIz_>SW9_Kwu4^JDU7+TU7Ul5#KHe=62M(g@@7mA`OHQ z(@WiGw%(#16bWlTzC7<$Wg9o<4cFg)_w^s&R&W0O%{M>1eNzt9XYmY_OSZ;;8U}1t zcB|%#ZLGDtQjVzv5GC@YAdpSE zK<$(=30@o)DP~UzZSD8D9g}{V_h-F`7JNV#)#==0o?cX^llgMGrGE%Q#2{ocFsh22N=XN=9b-sFMoC&xski#{BYURg+;_Tvx6=^#+ez-=Qx4`sp0BMh zvFj$``*W@mq*;K z>N9!4lA1T(hJD5gpo9x|eI8cBQE$LhmA7g>>h-vL zRsDB$b22CobaGwYoV@zdR*?cr?iNqmSnO3T_9Cmq74e|+q1dleN8|8)@8OQBQAyh!QyNo%VRduz(EX{~ z``EoX8C!|NpE~`i*zUPII=^ZOz-ph_XUh>)a}h?9lVWXTe-U~E@@mZHDMHueyU9@X zZ_UPkHLF+rFdT8es?oKYh*Z|Is{B+s`4H~yy9$_rqRl>in zmL^p#dZ=zr>Yr{-ihHi=3}>YJ10HhO_It&H@Ub)GYH6BZ&3qS&k6oSCbWhg=+RpeP zr=E;|`+g;vddxX8%~HZk?aOl5zBR$8KjpuFF#ohmmt7v`zSkYS-^c_f7X3L}%$Uh+ zzJ6T|_#OY;za94PI-{`$HyFUDv(cb)e^yVH^NZ^A=4SYt(}pX0-01d>yYEKt&!*Ha zDtTVLu3Y0G7njKO>)F_Ul|TM=+Gu{cG#jb9Q7z!MQEmIEr{Z*r(d2BjH(McxSKp6+ z$GL1Qjl!|vY_ZKh$Ls3!)#>u8mW#pIE4uzqb^4z-H?O|DxuM_u^XdG%mMx_${)aRi z4LaR-XN#T37K*N+4(6xFw6x_}O{Va2#l!S_!%lrWdvRKj<`(FmS6@}4%;3j%m+RZj zZ8_gQ+>JW@a=K17?Q(t7Dc((dU){rh2;Uc9V=b`j3de^?(FH25ua4^Nr-hjlIsLBl zbI}&Y&95sMRrkGNx%EP>`aD=1X(ogDnX*QFll8YKzxlp9%5T2&PV%c>t$w0qA!$t&SO$i&yNoW>E zNR8Hsk}NF1OHZDx6fDsm)DO~s&b__DT()52ejzL-UGsp zpo26erCw7E0ss_7%#4|JcCEQ+DrOnAl+NcU!A9eZA@tUwNfei(@6Xh+@usQVvv|D*1I)cudT|55k<$JG6Q*T)t-s{d=s z6c{0ccMh$z&TG(^Rpw|YB+W`Itq`S0U+z>ZgR_vZrj7en+Xl$HbaTf*~aBV1I&-dte*G~T{FJijxn23*X}nsQ;mge9frVA8pc z!k8Bp8dSIj-Ml0 zpxzkw%@TNWj=;R=7m^3or3_4eTgTd^-g8WT{B;An9~-V^{7a-S%sNj*Qy)<0$3%wb zNo1IzozEIBEP?jag+{YAXKMfAY-EpjeqZO6K3Z$tjY#W?z=Uv8JWqYAp8W^{^c zg8%+dzAj?x@UWcF>EXUVK6a-f+ z6n0Ai*(vd3#UZe`@vq+OmpAy?UiRTw{2`p(%2(^y=3Ltxb9qRKNHuVhg+S6 z8_vNO<=mR$VvqaG4mb=g`7PTZksD)KCXOj(S%QO|NNiZ6jXGv@B3Prr35p>(EO4jD zEHXu($QhRoj>51H_QGONcr)&}VLh)y2ADQo{Qp+B*2EzT;iQvEN=u|E#YhmN#0;rG z--B@>lv*->5Oh{92;p6HfxOHb^}zudo&>}2Qk>rJVq1h#dpJ2FHqvFURMbYGLvmIt z&V~}%03lgV*&z9#ll0E%EWA-DTZ_ahk%dquB$u+4Ap{b@!lHE$$Ym750wEZ-A&IDS zVD`dkWF()Xcq%r-lMxyA##h+rc6tE>hRrUf4gRHn?VU^8c$S_E5n?ACh@)5O=v6v; zm5yGeqgUzZRXTc=j$WmsSLx_gI(n6kUZtmcm7Wnf1yoX57qmi~q*BV5QohbuYd}ho z*jtlJpi>SB1z-*WOKCOX&>1P^3nHidig+oX4NA(vu~GI#MA^lQv=+-@tuJXWm=u&! zMF#|bR!JbzLTOD|1}2nBOCJ?#5rv5|Ymgwc%#ay9s$>c2Gl5BYesGi*7S(=;DT1<1Q8OskV1$irOcsVX+%$-2V%+#;H12)w4NV41q;-T zFP9Bcu}DHHFG{)8$V5?iAS&hSO_nj*ED?-6an?glp@iXI5~qBy^I`^b|Hvtl@a>+z*AZvQ`!Wy1edbIj<+USIe=Hl$xEjg zy&`Wy^d6B!3YL>mDH1p-oKMO4=s-A-*}EjNBI%xfeh1j;oUG9e;yN&nY?J|*ny|&IL#Y};Pj6p`5SPL5j8D}Cvsf`&yc-95l z%OM2RMhgbvm=BA{*5J!t*Sd=g+V3hh#_V)*$wa|9v!;L;z4hfr7KQiVVt|}8$LO>o z&N)G{p0g3A94Ay8R z?NhE|8{C=OL$@t%S#O=Y-T@xB&tHA}+)(h6QG#;W+DJkR7FHXUN?JxIrO3fJMP9Sa zF&M8{tE3YpLoTIM^6j6lN{xZBZXdcumbY~^&*8C#c^h~B&(<8ZE%mZ$t1W|S- zAPJe2k^%soL5HX@DeD}dF+qoD!8%8n9f0#b8skfiA`z@Os1metChVdPts-zXnLb<9 z9}-D94geMr0(lVGWsDkZ6vgv`!G(xAGfU+R$HW?46q!gcL~+>qLo2y|wU?6A# { await pg.query( `INSERT INTO metaschema_modules_public.catalog_module (database_id, schema_id, functions_table_id, - domains_table_id, apis_table_id, sites_table_id, namespaces_table_id, + domains_table_id, managed_domains_table_id, + apis_table_id, sites_table_id, namespaces_table_id, resources_table_id, resource_definitions_table_id, resource_installations_table_id, apps_table_id, buckets_table_id, sites_web_config_table_id, sites_error_pages_table_id, sites_app_links_table_id, sites_deep_links_table_id, - redirects_table_id, bindings_table_id, images_table_id, scope) - VALUES ($1, $2, $3, $3, $4, $3, $3, $3, $3, $3, $3, $5, $3, $3, $3, $3, $3, $6, $3, 'database')`, + images_table_id, redirects_table_id, + app_store_identities_table_id, bindings_table_id, scope) + VALUES ($1, $2, $3, $3, $3, $3, $4, $3, $3, $3, $3, $3, $3, $5, $3, $3, $3, $3, $3, $3, $6, 'database')`, [ TENANT_DB, catFunctions.schemaId, diff --git a/packages/function-resolution/__tests__/catalog-fastpath.test.ts b/packages/function-resolution/__tests__/catalog-fastpath.test.ts index 1ed54cd9d..56f03f29e 100644 --- a/packages/function-resolution/__tests__/catalog-fastpath.test.ts +++ b/packages/function-resolution/__tests__/catalog-fastpath.test.ts @@ -195,13 +195,15 @@ describe('function-resolution catalog fast-path', () => { await pg.query( `INSERT INTO metaschema_modules_public.catalog_module (database_id, schema_id, functions_table_id, - domains_table_id, apis_table_id, sites_table_id, namespaces_table_id, + domains_table_id, managed_domains_table_id, + apis_table_id, sites_table_id, namespaces_table_id, resources_table_id, resource_definitions_table_id, resource_installations_table_id, apps_table_id, buckets_table_id, sites_web_config_table_id, sites_error_pages_table_id, sites_app_links_table_id, sites_deep_links_table_id, - redirects_table_id, bindings_table_id, images_table_id, scope) - VALUES ($1, $2, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, 'app')`, + images_table_id, redirects_table_id, + app_store_identities_table_id, bindings_table_id, scope) + VALUES ($1, $2, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, 'app')`, [TENANT_DB, catSchema.id, catTable.id] ); diff --git a/packages/function-resolution/__tests__/published-capability-catalog.test.ts b/packages/function-resolution/__tests__/published-capability-catalog.test.ts index 3b56d8b3f..fabdce162 100644 --- a/packages/function-resolution/__tests__/published-capability-catalog.test.ts +++ b/packages/function-resolution/__tests__/published-capability-catalog.test.ts @@ -237,14 +237,15 @@ describe('capability resolution against the published catalog planes', () => { await pg.query( `INSERT INTO metaschema_modules_public.catalog_module (database_id, schema_id, functions_table_id, - domains_table_id, apis_table_id, sites_table_id, namespaces_table_id, + domains_table_id, managed_domains_table_id, + apis_table_id, sites_table_id, namespaces_table_id, resources_table_id, resource_definitions_table_id, resource_installations_table_id, apps_table_id, buckets_table_id, - bindings_table_id, sites_web_config_table_id, + app_store_identities_table_id, bindings_table_id, sites_web_config_table_id, sites_error_pages_table_id, sites_app_links_table_id, sites_deep_links_table_id, - redirects_table_id, images_table_id, scope) - VALUES ($1, $2, $3, $3, $4, $3, $3, $3, $3, $3, $3, $5, $6, $3, $3, $3, $3, $3, $3, 'database')`, + images_table_id, redirects_table_id, scope) + VALUES ($1, $2, $3, $3, $3, $3, $4, $3, $3, $3, $3, $3, $3, $5, $6, $3, $3, $3, $3, $3, $3, 'database')`, [dbId, schemaId, bucketsTableId, apisTableId, bucketsTableId, bindingsTableId] ); // Label kept for readability of the fixture rows above. diff --git a/packages/function-resolution/__tests__/published-catalog.test.ts b/packages/function-resolution/__tests__/published-catalog.test.ts index b491689d7..32fc17c30 100644 --- a/packages/function-resolution/__tests__/published-catalog.test.ts +++ b/packages/function-resolution/__tests__/published-catalog.test.ts @@ -142,13 +142,15 @@ describe('function-resolution against the published catalog plane', () => { await pg.query( `INSERT INTO metaschema_modules_public.catalog_module (database_id, schema_id, functions_table_id, - domains_table_id, apis_table_id, sites_table_id, namespaces_table_id, + domains_table_id, managed_domains_table_id, + apis_table_id, sites_table_id, namespaces_table_id, resources_table_id, resource_definitions_table_id, resource_installations_table_id, apps_table_id, buckets_table_id, sites_web_config_table_id, sites_error_pages_table_id, sites_app_links_table_id, sites_deep_links_table_id, - redirects_table_id, bindings_table_id, images_table_id, scope) - VALUES ($1, $2, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, 'app')`, + images_table_id, redirects_table_id, + app_store_identities_table_id, bindings_table_id, scope) + VALUES ($1, $2, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, 'app')`, [dbId, schemaId, tableId] ); diff --git a/packages/function-resolution/__tests__/resolution.test.ts b/packages/function-resolution/__tests__/resolution.test.ts index 6fdaf4ddf..a64984563 100644 --- a/packages/function-resolution/__tests__/resolution.test.ts +++ b/packages/function-resolution/__tests__/resolution.test.ts @@ -8,6 +8,7 @@ const PLATFORM_DB = '11111111-1111-1111-1111-111111111111'; const TENANT_DB = '22222222-2222-2222-2222-222222222222'; const ORG_ID = '33333333-3333-3333-3333-333333333333'; const PLATFORM_ORG_ID = '44444444-4444-4444-4444-444444444444'; +const ACTOR_ID = '55555555-5555-5555-5555-555555555555'; // Ids captured during seeding. const ids: Record = {}; @@ -155,13 +156,15 @@ describe('function-resolution end-to-end (format-based, no AST)', () => { await pg.query( `INSERT INTO metaschema_modules_public.catalog_module (database_id, schema_id, functions_table_id, - domains_table_id, apis_table_id, sites_table_id, namespaces_table_id, + domains_table_id, managed_domains_table_id, + apis_table_id, sites_table_id, namespaces_table_id, resources_table_id, resource_definitions_table_id, resource_installations_table_id, apps_table_id, buckets_table_id, sites_web_config_table_id, sites_error_pages_table_id, sites_app_links_table_id, sites_deep_links_table_id, - redirects_table_id, bindings_table_id, images_table_id, scope) - VALUES ($1, $2, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, 'app')`, + images_table_id, redirects_table_id, + app_store_identities_table_id, bindings_table_id, scope) + VALUES ($1, $2, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, $3, 'app')`, [TENANT_DB, catSchema.id, catTable.id] ); }); @@ -323,10 +326,17 @@ describe('function-resolution end-to-end (format-based, no AST)', () => { }); it('enqueue(): full portable path resolves, routes, and inserts a job', async () => { - // enqueue reads the execution database from jwt_private.current_database_id() + // enqueue reads the execution database from jwt_private.require_database_id() // (the jwt.claims.database_id GUC), set transaction-locally around the call. + // app_jobs.add_job attributes the job from the same claims, and refuses a job + // with neither an actor nor an entity, so an actor claim is part of the frame. await pg.begin(); - await pg.query(`SELECT set_config('jwt.claims.database_id', $1, true)`, [TENANT_DB]); + await pg.query( + `SELECT set_config('jwt.claims.database_id', $1, true), + set_config('jwt.claims.user_id', $2, true), + set_config('jwt.claims.principal_id', $2, true)`, + [TENANT_DB, ACTOR_ID] + ); const job = await pg.one( `SELECT * FROM function_resolution.enqueue( task_identifier := 'email:send', @@ -346,7 +356,12 @@ describe('function-resolution end-to-end (format-based, no AST)', () => { it('enqueue(): NULL scope is a hard error', async () => { await pg.begin(); - await pg.query(`SELECT set_config('jwt.claims.database_id', $1, true)`, [TENANT_DB]); + await pg.query( + `SELECT set_config('jwt.claims.database_id', $1, true), + set_config('jwt.claims.user_id', $2, true), + set_config('jwt.claims.principal_id', $2, true)`, + [TENANT_DB, ACTOR_ID] + ); await expect( pg.one(`SELECT * FROM function_resolution.enqueue(task_identifier := 'email:send')`) ).rejects.toThrow(/ENQUEUE_SCOPE_REQUIRED/); diff --git a/packages/function-resolution/deploy/schemas/function_resolution/procedures/enqueue.sql b/packages/function-resolution/deploy/schemas/function_resolution/procedures/enqueue.sql index 711691c7b..665c8ee41 100644 --- a/packages/function-resolution/deploy/schemas/function_resolution/procedures/enqueue.sql +++ b/packages/function-resolution/deploy/schemas/function_resolution/procedures/enqueue.sql @@ -4,7 +4,9 @@ -- requires: schemas/function_resolution/procedures/routing -- requires: pgpm-app-scope:schemas/app_scope/procedures/frames -- requires: pgpm-database-jobs:schemas/app_jobs/procedures/add_job --- requires: pgpm-jwt-claims:schemas/jwt_private/procedures/current_database_id +-- requires: pgpm-jwt-claims:schemas/jwt_private/procedures/require_database_id +-- requires: pgpm-jwt-claims:schemas/jwt_public/procedures/current_user_id +-- requires: pgpm-jwt-claims:schemas/jwt_public/procedures/current_principal_id BEGIN; @@ -62,7 +64,9 @@ CREATE FUNCTION function_resolution.enqueue( should_resolve boolean DEFAULT true, resolution_scope text DEFAULT NULL, resolution_key uuid DEFAULT NULL, - db_id uuid DEFAULT jwt_private.current_database_id() + db_id uuid DEFAULT jwt_private.require_database_id(), + actor_id uuid DEFAULT jwt_public.current_user_id(), + principal_id uuid DEFAULT jwt_public.current_principal_id() ) RETURNS app_jobs.jobs AS $$ DECLARE v_database_id uuid; @@ -143,12 +147,14 @@ BEGIN entity_type := entity_type, function_definition_id := v_fn_id, definition_scope := v_def_scope, - db_id := v_database_id + db_id := v_database_id, + actor_id := enqueue.actor_id, + principal_id := enqueue.principal_id ); END; $$ LANGUAGE plpgsql VOLATILE SECURITY DEFINER; -COMMENT ON FUNCTION function_resolution.enqueue(text, json, text, uuid, uuid, text, text, text, timestamptz, integer, integer, uuid, text, boolean, text, uuid, uuid) IS +COMMENT ON FUNCTION function_resolution.enqueue(text, json, text, uuid, uuid, text, text, text, timestamptz, integer, integer, uuid, text, boolean, text, uuid, uuid, uuid, uuid) IS 'Resolver-aware job enqueue: resolves (or trusts a supplied) function definition for the execution (database, scope, entity, task_identifier), stamps the (function_definition_id, definition_scope) pair and the definition''s queue routing, then delegates the insert to app_jobs.add_job. The single enqueue path for function jobs; definition-less tasks enqueue with a NULL pair. Portable: built only on app_scope + the metaschema catalog + app_jobs, no AST/deparser runtime.'; COMMIT; diff --git a/packages/function-resolution/deploy/schemas/function_resolution/procedures/install_mantra.sql b/packages/function-resolution/deploy/schemas/function_resolution/procedures/install_mantra.sql new file mode 100644 index 000000000..139c7f840 --- /dev/null +++ b/packages/function-resolution/deploy/schemas/function_resolution/procedures/install_mantra.sql @@ -0,0 +1,107 @@ +-- Deploy schemas/function_resolution/procedures/install_mantra to pg +-- requires: schemas/function_resolution/schema +-- requires: schemas/function_resolution/procedures/install_route_bindings + +BEGIN; + +-- install_mantra: install the platform's Mantra page set onto one site. +-- +-- Mantra is not a new site backing and not a new routing mode: every page it +-- serves is an ordinary route row whose target is a function +-- (target_function_id -> the functions catalog), which the routing resolver +-- already answers on the function lane, emitting the definition's +-- task_identifier for the sync gateway. +-- +-- So there is nothing Mantra-specific left in the install itself, and this is +-- now the narrowest possible wrapper over the general engine +-- (install_route_bindings): it holds the Mantra document contract — a JSON array +-- of {path, task_identifier}, which the generated verb reads from the platform's +-- own preset catalog (metaschema_generators.content_preset_definition of kind +-- 'route_bindings', slug 'mantra' by default) — and declares every entry a +-- function target. Everything else (the one scope read off the sites plane's own +-- registration, the routes plane, the ownership key, resolution, idempotency) +-- belongs to the engine, so the platform's page set and a deployment graph +-- binding its own paths cannot drift apart. +-- +-- WHICH paths get installed stays data, and it is not this module's data: the +-- caller passes the document. That keeps this module portable (it depends on +-- app-scope and the module registry, not on the generator layer that owns the +-- catalog) and keeps the page set a versioned row a deployment can fork, never a +-- list inside a function. +-- +-- The sites plane arrives BY REFERENCE, as a regclass rather than a pair of name +-- strings. This entry point is called from GENERATED SQL, and a generated body +-- that spelled its own schema in a bare string literal carried the physical +-- schema name of the database it was generated in: the platform export renames +-- schemas to their logical names through an AST rewrite, which routes a reg* +-- cast structurally and deliberately leaves a bare literal alone, so the baked +-- name survived into the published module and matched no registration. It is the +-- same reason every generated verify asserts through a reg* cast. +CREATE FUNCTION function_resolution.install_mantra( + database_id uuid, + sites_plane regclass, + site_id uuid, + bindings jsonb, + entity_id uuid DEFAULT NULL +) RETURNS jsonb AS $$ +DECLARE + function_bindings jsonb; + sites_schema text; + sites_table text; +BEGIN + IF install_mantra.sites_plane IS NULL THEN + RAISE EXCEPTION 'MANTRA_SITES_PLANE_REQUIRED: sites_plane is required' + USING ERRCODE = 'FR050'; + END IF; + + -- The plane's names, read off the catalog entry the reference itself proves + -- exists, and handed to the engine as the registry records them. + SELECT n.nspname, c.relname + INTO sites_schema, sites_table + FROM pg_catalog.pg_class AS c + JOIN pg_catalog.pg_namespace AS n ON n.oid = c.relnamespace + WHERE c.oid = install_mantra.sites_plane; + + -- The Mantra document contract, checked here because it is stricter than the + -- engine's: every entry names a task, and none of them may name a target + -- kind — a preset row that has grown a service binding is a broken preset, + -- not a mixed install. + IF install_mantra.bindings IS NULL + OR jsonb_typeof(install_mantra.bindings) <> 'array' + OR jsonb_array_length(install_mantra.bindings) = 0 THEN + RAISE EXCEPTION 'MANTRA_BINDINGS_INVALID: bindings must be a non-empty JSON array of {path, task_identifier}, got %', + coalesce(jsonb_typeof(install_mantra.bindings), 'null') + USING ERRCODE = 'FR060'; + END IF; + + IF EXISTS ( + SELECT 1 + FROM jsonb_array_elements(install_mantra.bindings) AS b + WHERE jsonb_typeof(b) <> 'object' + OR coalesce(b ->> 'path', '') = '' + OR coalesce(b ->> 'task_identifier', '') = '' + OR b ? 'target' + ) THEN + RAISE EXCEPTION 'MANTRA_BINDINGS_INVALID: every binding must carry a non-empty path and task_identifier, and no target kind' + USING ERRCODE = 'FR060'; + END IF; + + SELECT jsonb_agg(b || jsonb_build_object('target', 'function')) + INTO function_bindings + FROM jsonb_array_elements(install_mantra.bindings) AS b; + + RETURN function_resolution.install_route_bindings( + install_mantra.database_id, + sites_schema, + sites_table, + install_mantra.site_id, + function_bindings, + install_mantra.entity_id + ); +END; +$$ LANGUAGE plpgsql VOLATILE; + +COMMENT ON FUNCTION function_resolution.install_mantra(uuid, regclass, uuid, jsonb, uuid) IS +'Install the Mantra page set (a JSON array of {path, task_identifier}, which the generated verb reads from the content_presets catalog at kind ''route_bindings'') onto one site as ordinary function-target routes. The sites plane arrives by reference as a regclass, so a generated caller never spells a schema name in a bare string literal the platform export''s AST rename cannot follow. A thin wrapper holding the Mantra document contract — every entry names a task, none names a target kind — over function_resolution.install_route_bindings, which owns the one-scope install: scope and ownership key read from the sites plane''s own registration, the routes plane from app_scope.routing_tables, resolution at that same (scope, entity), idempotent per (domain_id, path).'; + +COMMIT; diff --git a/packages/function-resolution/deploy/schemas/function_resolution/procedures/install_route_bindings.sql b/packages/function-resolution/deploy/schemas/function_resolution/procedures/install_route_bindings.sql new file mode 100644 index 000000000..0a7489182 --- /dev/null +++ b/packages/function-resolution/deploy/schemas/function_resolution/procedures/install_route_bindings.sql @@ -0,0 +1,506 @@ +-- Deploy schemas/function_resolution/procedures/install_route_bindings to pg +-- requires: schemas/function_resolution/schema +-- requires: schemas/function_resolution/procedures/resolve +-- requires: metaschema-modules:schemas/metaschema_modules_public/tables/site_surface_module/table +-- requires: metaschema-modules:schemas/metaschema_modules_public/tables/route_module/table +-- requires: metaschema-modules:schemas/metaschema_modules_public/tables/resource_module/table +-- requires: pgpm-app-scope:schemas/app_scope/procedures/routing_tables + +BEGIN; + +-- install_route_bindings: install a set of route bindings onto one site, at ONE +-- scope, for ONE entity. +-- +-- This is the one engine behind every "point these paths at what we just +-- deployed" verb: the platform's own Mantra page set (function targets, through +-- function_resolution.install_mantra) and a deployment graph binding the paths +-- of a service it just released (service targets). A binding is ordinary route +-- data either way — a row in the scope's routes plane whose typed target column +-- carries the id the routing resolver already answers — so this is a pure data +-- write and there is nothing to reconcile here: the routes plane's own job +-- trigger enqueues http_route:reconcile. +-- +-- The bindings document is a JSON array whose every entry NAMES ITS TARGET KIND: +-- +-- {"path": "/login", "target": "function", "task_identifier": "mantra:signin"} +-- {"path": "/app", "target": "service", "service_id": ""} +-- +-- The kind is never inferred from which key happens to be present, and an entry +-- carrying keys for two kinds is a malformed document rather than a precedence +-- question: guessing is how a deployment silently binds /app to the wrong plane. +-- +-- The single-scope invariant, unchanged from the function-only engine it +-- generalises. Nothing here takes a scope literal from a caller or bakes one +-- into generated SQL. The caller names the physical sites plane it belongs to; +-- the scope and the ownership key column are then read off that plane's own +-- module registration (site_surface_module.scope / .entity_field, stamped at +-- provisioning time by metaschema_generators.scope_key_column), and that one +-- (scope, key value) is what every subsequent step uses: +-- +-- * app_scope.routing_tables(database_id, scope) names the routes plane +-- serving that scope — so the routes written are the ones that scope's +-- resolver reads, never another frame's plane; +-- * the site row and its existing route are read pinned to the key value, so +-- a SECURITY DEFINER wrapper cannot reach another tenant's or another +-- entity's site; +-- * function_resolution.resolve starts at that same (scope, entity), so a +-- nearer frame publishing a task shadows an outer one FOR THAT ENTITY and +-- nobody else; +-- * a service target is proved to exist in the SAME-SCOPE resources plane — +-- the one this database's resource_module registers AT THAT SCOPE, which is +-- the source plane route_module gives target_service_id its FK against — +-- pinned to that plane's own recorded key, so a binding cannot point a +-- route at another tenant's or another entity's service; +-- * every inserted route stamps that same key value. +-- +-- The serving site is stamped the same registration-driven way. A routing plane +-- may carry a column saying which site's surface a route renders as, and its +-- route_module registration records that column's name (serving_site_field, +-- stamped by the generator that created the field). When it is recorded, every +-- route this install touches — inserted or already present, since a path the +-- document also claims is still a path this site serves — carries the site the +-- bindings were installed onto; when it is not, nothing is stamped. So the fact +-- travels with the plane rather than living in whatever verb happens to call +-- this, and this module still knows no column name of its own. +-- +-- Ownership tiers, from the plane's recorded entity_field: +-- entity_field IS NULL global tier — ownerless rows, no key stamped +-- entity_field = database_id database tier — keyed by the executing database +-- entity_field = _id entity tier — keyed by the entity_id argument, +-- which is then required +-- +-- Idempotent by construction: each insert carries a NOT EXISTS guard on the +-- route's (domain_id, path) key, so re-running after the document grows installs +-- only what is missing and never repoints a route a tenant has since edited. +-- +-- Fail-loud throughout: an unregistered plane, a scope with no routes plane, a +-- missing entity key, an unknown site, a site with no route to layer onto, a +-- malformed bindings document, an unknown or ambiguous target kind, an +-- unresolvable service, and an unpublished task each raise. Nothing is skipped +-- silently. +-- +-- The dynamic SQL is the same case as resolve_api's: the relation is named by +-- the caller's plane and proved to exist by an ordinary catalog join first, and +-- no value is ever interpolated — only verified relation and column names. +CREATE FUNCTION function_resolution.install_route_bindings( + database_id uuid, + sites_schema text, + sites_table text, + site_id uuid, + bindings jsonb, + entity_id uuid DEFAULT NULL +) RETURNS jsonb AS $$ +DECLARE + -- plane_scope, not scope: the module registrations this reads all carry a + -- scope column, and a local named scope makes every one of those predicates + -- ambiguous. + plane_scope text; + sites_key text; + routes_schema text; + routes_table text; + routes_key text; + -- The routes column carrying the serving site, from the plane's own + -- registration. NULL for a plane that has none, which makes the stamp a + -- no-op rather than an error. + routes_serving_site_key text; + stamped int := 0; + -- The same-scope resources plane a service binding must resolve within, and + -- its own recorded ownership key. NULL when this scope carries no resources + -- plane, which makes a service binding a hard error rather than an + -- unchecked insert. + resources_schema text; + resources_table text; + resources_key text; + -- The one ownership key value the whole install is keyed by (NULL at the + -- global tier), and the entity the resolver starts its frame walk at. + key_value uuid; + resolution_entity uuid; + site_found boolean; + domain_id uuid; + -- Per-binding state: the entry, its declared kind, and the id the route's + -- typed target column gets, together with the column that carries it. + entry jsonb; + entry_path text; + entry_target text; + entry_task text; + entry_service uuid; + target_column text; + target_id uuid; + service_found boolean; + inserted int; + installed jsonb := '[]'::jsonb; + skipped jsonb := '[]'::jsonb; + query text; +BEGIN + IF install_route_bindings.database_id IS NULL THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_DATABASE_REQUIRED: database_id is required' + USING ERRCODE = 'FR050'; + END IF; + + IF install_route_bindings.sites_schema IS NULL OR install_route_bindings.sites_table IS NULL THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_SITES_PLANE_REQUIRED: sites_schema and sites_table are required' + USING ERRCODE = 'FR050'; + END IF; + + IF install_route_bindings.site_id IS NULL THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_SITE_REQUIRED: site_id is required' + USING ERRCODE = 'FR050'; + END IF; + + -- The bindings document, validated before anything is written: an empty or + -- wrongly-shaped document is a broken install, not a no-op. + IF install_route_bindings.bindings IS NULL + OR jsonb_typeof(install_route_bindings.bindings) <> 'array' + OR jsonb_array_length(install_route_bindings.bindings) = 0 THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_INVALID: bindings must be a non-empty JSON array of {path, target, …}, got %', + coalesce(jsonb_typeof(install_route_bindings.bindings), 'null') + USING ERRCODE = 'FR060'; + END IF; + + IF EXISTS ( + SELECT 1 + FROM jsonb_array_elements(install_route_bindings.bindings) AS b + WHERE jsonb_typeof(b) <> 'object' + OR coalesce(b ->> 'path', '') = '' + OR coalesce(b ->> 'target', '') = '' + ) THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_INVALID: every binding must carry a non-empty path and target' + USING ERRCODE = 'FR060'; + END IF; + + IF EXISTS ( + SELECT 1 + FROM jsonb_array_elements(install_route_bindings.bindings) AS b + WHERE (b ->> 'target') NOT IN ('function', 'service') + ) THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_TARGET_UNKNOWN: target must be "function" or "service"' + USING ERRCODE = 'FR060'; + END IF; + + -- A binding carrying the key of a kind it did not declare is ambiguous, and + -- resolving it by precedence would let a typo repoint a path at a different + -- plane than the document reads like. + IF EXISTS ( + SELECT 1 + FROM jsonb_array_elements(install_route_bindings.bindings) AS b + WHERE (b ? 'task_identifier') AND (b ? 'service_id') + OR (b ->> 'target') = 'function' AND (b ? 'service_id') + OR (b ->> 'target') = 'service' AND (b ? 'task_identifier') + ) THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_TARGET_AMBIGUOUS: a binding must carry only the key of the target kind it declares' + USING ERRCODE = 'FR060'; + END IF; + + IF EXISTS ( + SELECT 1 + FROM jsonb_array_elements(install_route_bindings.bindings) AS b + WHERE (b ->> 'target') = 'function' AND coalesce(b ->> 'task_identifier', '') = '' + OR (b ->> 'target') = 'service' AND coalesce(b ->> 'service_id', '') = '' + ) THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_INVALID: a function binding needs a task_identifier and a service binding needs a service_id' + USING ERRCODE = 'FR060'; + END IF; + + -- A service_id is cast to uuid before it is looked up, and a bare cast + -- failure reports "invalid input syntax for type uuid" with no hint of which + -- binding carried it — so the document is rejected by name instead. + IF EXISTS ( + SELECT 1 + FROM jsonb_array_elements(install_route_bindings.bindings) AS b + WHERE (b ->> 'target') = 'service' + AND (b ->> 'service_id') !~* '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' + ) THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_SERVICE_INVALID: a service binding''s service_id must be a uuid' + USING ERRCODE = 'FR060'; + END IF; + + -- Idempotency is keyed by (domain_id, path), so two entries claiming one + -- path would install the first and report the second as already-there — + -- a document that reads like it bound both. It is malformed instead. + IF EXISTS ( + SELECT 1 + FROM jsonb_array_elements(install_route_bindings.bindings) AS b + GROUP BY b ->> 'path' + HAVING count(*) > 1 + ) THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_PATH_DUPLICATED: a path may be bound once per document' + USING ERRCODE = 'FR060'; + END IF; + + -- 1. The plane's own registration decides the scope. This is the whole + -- point: the scope is a recorded fact about the sites plane being + -- installed onto, not a caller-supplied string and not a literal frozen + -- into a generated body. + -- + -- The registration is looked up ALONG THE CALLER'S FRAMES, the same walk + -- step 2 resolves the routes plane with, because a sites plane serving a + -- scope is not necessarily registered in the database consuming it: the + -- database-scope serving planes are shared, hosted by an outer frame's + -- database and keyed per tenant, so a tenant installing onto the plane it + -- is actually served by has no registration of its own to find. Keying is + -- unaffected — the scope comes from the registration and the key value from + -- the caller (step 3), so a tenant's rows stay stamped with the tenant. + -- Nearest frame wins, so a plane a database does register still resolves to + -- its own registration. + SELECT sm.scope, sm.entity_field + INTO plane_scope, sites_key + FROM app_scope.frames(install_route_bindings.database_id, 'database') WITH ORDINALITY AS f + JOIN metaschema_modules_public.site_surface_module AS sm + ON sm.scope = f.scope + AND sm.database_id = f.lookup_database_id + JOIN metaschema_public."table" AS t ON t.id = sm.sites_table_id + JOIN metaschema_public.schema AS s ON s.id = t.schema_id + WHERE s.schema_name = install_route_bindings.sites_schema + AND t.name = install_route_bindings.sites_table + ORDER BY f.ordinality + LIMIT 1; + + IF plane_scope IS NULL THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_SITES_PLANE_NOT_REGISTERED: no site_surface_module on any frame of database % registers %.% as its sites plane', + install_route_bindings.database_id, install_route_bindings.sites_schema, install_route_bindings.sites_table + USING ERRCODE = 'FR051'; + END IF; + + -- 2. The routes plane serving THAT scope, resolved the one way every + -- scope-aware consumer resolves it. + SELECT r.routes_schema, r.routes_table + INTO routes_schema, routes_table + FROM app_scope.routing_tables(install_route_bindings.database_id, plane_scope) AS r; + + IF routes_schema IS NULL OR routes_table IS NULL THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_ROUTES_PLANE_NOT_FOUND: scope "%" has no routes plane provisioned on any frame of database %', + plane_scope, install_route_bindings.database_id + USING ERRCODE = 'FR051'; + END IF; + + -- The routes plane's own registration carries its ownership key and, when + -- the plane has one, the column naming the site a route renders as. + SELECT rm.entity_field, rm.serving_site_field + INTO routes_key, routes_serving_site_key + FROM metaschema_modules_public.route_module AS rm + JOIN metaschema_public."table" AS t ON t.id = rm.routes_table_id + JOIN metaschema_public.schema AS s ON s.id = t.schema_id + WHERE s.schema_name = routes_schema + AND t.name = routes_table; + + -- Both planes are at the same scope, so scope_key_column gave them the same + -- ownership key. A mismatch means the registrations disagree about who owns + -- the rows, which is exactly the cross-scope write this function exists to + -- make impossible. + IF routes_key IS DISTINCT FROM sites_key THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_PLANE_KEY_MISMATCH: sites plane %.% is keyed by % but routes plane %.% is keyed by %', + install_route_bindings.sites_schema, install_route_bindings.sites_table, coalesce(sites_key, ''), + routes_schema, routes_table, coalesce(routes_key, '') + USING ERRCODE = 'FR051'; + END IF; + + -- The resources plane serving that same scope, from its own module + -- registration: target_service_id FKs this scope's source resources table, + -- so this is the plane the constraint would check a service binding against. + SELECT s.schema_name, t.name, rem.entity_field + INTO resources_schema, resources_table, resources_key + FROM metaschema_modules_public.resource_module AS rem + JOIN metaschema_public."table" AS t ON t.id = rem.resources_table_id + JOIN metaschema_public.schema AS s ON s.id = t.schema_id + WHERE rem.database_id = install_route_bindings.database_id + AND rem.scope = plane_scope; + + -- 3. One key value for the whole operation. + IF sites_key IS NULL THEN + key_value := NULL; + resolution_entity := NULL; + ELSIF sites_key = 'database_id' THEN + key_value := install_route_bindings.database_id; + resolution_entity := NULL; + ELSE + IF install_route_bindings.entity_id IS NULL THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_ENTITY_REQUIRED: scope "%" is keyed by %, so entity_id is required', + plane_scope, sites_key + USING ERRCODE = 'FR052'; + END IF; + key_value := install_route_bindings.entity_id; + resolution_entity := install_route_bindings.entity_id; + END IF; + + -- 4. The site, pinned to that key value. + -- pgsql-lint-disable-next-line no-dynamic-sql -- lookup-only: the sites plane is named by the caller and proved by the registration join above + query := format( + 'SELECT EXISTS (SELECT 1 FROM %I.%I AS s WHERE s.id = $1%s)', + install_route_bindings.sites_schema, + install_route_bindings.sites_table, + CASE WHEN sites_key IS NULL + THEN ' AND $2 IS NULL' + ELSE format(' AND s.%I = $2', sites_key) + END + ); + + EXECUTE query INTO site_found USING install_route_bindings.site_id, key_value; + + IF NOT site_found THEN + RAISE EXCEPTION 'SITE_NOT_FOUND: no site % in %.% owned by %', + install_route_bindings.site_id, install_route_bindings.sites_schema, install_route_bindings.sites_table, + coalesce(key_value::text, plane_scope) + USING ERRCODE = 'FR053'; + END IF; + + -- 5. The hostname the site already serves. Bindings layer onto an existing + -- site route rather than claiming a hostname of their own: the root-route + -- guard auto-creates '/' carrying the target of a hostname's FIRST route, + -- so installing onto a bare hostname would make '/' the first binding's + -- target. A site that is not routed yet is a hard error, not a silently + -- empty install. + -- pgsql-lint-disable-next-line no-dynamic-sql -- lookup-only: the routes plane is named by app_scope.routing_tables + query := format( + 'SELECT r.domain_id FROM %I.%I AS r WHERE r.target_site_id = $1%s ORDER BY r.path LIMIT 1', + routes_schema, + routes_table, + CASE WHEN routes_key IS NULL + THEN ' AND $2 IS NULL' + ELSE format(' AND r.%I = $2', routes_key) + END + ); + + EXECUTE query INTO domain_id USING install_route_bindings.site_id, key_value; + + IF domain_id IS NULL THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_SITE_NOT_ROUTED: site % serves no hostname in %.%, so there is nowhere to install the bindings', + install_route_bindings.site_id, routes_schema, routes_table + USING ERRCODE = 'FR054'; + END IF; + + -- 6. Per binding: resolve the declared target at THIS (scope, entity) and + -- install the route if it is not already there. + FOR entry IN SELECT * FROM jsonb_array_elements(install_route_bindings.bindings) + LOOP + entry_path := entry ->> 'path'; + entry_target := entry ->> 'target'; + + IF entry_target = 'function' THEN + entry_task := entry ->> 'task_identifier'; + + -- Fail-loud by the resolver's own contract: an unpublished task + -- raises FUNCTION_DEFINITION_NOT_FOUND rather than installing a + -- route that would 404 at request time. + SELECT fr.function_definition_id + INTO target_id + FROM function_resolution.resolve( + install_route_bindings.database_id, + plane_scope, + resolution_entity, + entry_task + ) AS fr; + + target_column := 'target_function_id'; + ELSE + entry_service := (entry ->> 'service_id')::uuid; + + IF resources_schema IS NULL THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_SERVICE_PLANE_NOT_FOUND: database % has no resource_module at scope "%", so there is no plane a service target could live in', + install_route_bindings.database_id, plane_scope + USING ERRCODE = 'FR055'; + END IF; + + -- pgsql-lint-disable-next-line no-dynamic-sql -- lookup-only: the resources plane is named by this scope's own resource_module registration + query := format( + 'SELECT EXISTS (SELECT 1 FROM %I.%I AS r WHERE r.id = $1%s)', + resources_schema, + resources_table, + CASE WHEN resources_key IS NULL + THEN ' AND $2 IS NULL' + ELSE format(' AND r.%I = $2', resources_key) + END + ); + + EXECUTE query INTO service_found USING entry_service, key_value; + + IF NOT service_found THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_SERVICE_NOT_FOUND: no service % in %.% owned by %', + entry_service, resources_schema, resources_table, + coalesce(key_value::text, plane_scope) + USING ERRCODE = 'FR056'; + END IF; + + target_id := entry_service; + target_column := 'target_service_id'; + END IF; + + -- pgsql-lint-disable-next-line no-dynamic-sql -- write-only: insert into the routes plane named by app_scope.routing_tables; every value is a bound parameter + query := format( + 'INSERT INTO %I.%I (%s%sdomain_id, path, %I) + SELECT %s%s$1, $2, $3 + WHERE NOT EXISTS ( + SELECT 1 FROM %I.%I AS x + WHERE x.domain_id = $1 AND x.path = $2%s)', + routes_schema, + routes_table, + CASE WHEN routes_key IS NULL THEN '' ELSE format('%I, ', routes_key) END, + CASE WHEN routes_serving_site_key IS NULL THEN '' ELSE format('%I, ', routes_serving_site_key) END, + target_column, + CASE WHEN routes_key IS NULL THEN '' ELSE '$4, ' END, + CASE WHEN routes_serving_site_key IS NULL THEN '' ELSE '$5, ' END, + routes_schema, + routes_table, + CASE WHEN routes_key IS NULL + THEN ' AND $4 IS NULL' + ELSE format(' AND x.%I = $4', routes_key) + END + ); + + EXECUTE query USING domain_id, entry_path, target_id, key_value, + install_route_bindings.site_id; + GET DIAGNOSTICS inserted = ROW_COUNT; + + IF inserted > 0 THEN + installed := installed || jsonb_build_array(entry_path); + ELSE + skipped := skipped || jsonb_build_array(entry_path); + END IF; + END LOOP; + + -- Backfill the serving site onto the paths that were already there: the + -- insert above stamps what it writes, and a route the document claims but + -- did not create is still a route this site serves. IS DISTINCT FROM keeps a + -- repeat install a no-op write rather than a row version per path, and the + -- ownership pin repeats here so a SECURITY DEFINER caller cannot stamp a row + -- another owner holds even if a domain were ever shared across planes. + IF routes_serving_site_key IS NOT NULL AND jsonb_array_length(skipped) > 0 THEN + -- pgsql-lint-disable-next-line no-dynamic-sql -- write-only: update the routes plane named by app_scope.routing_tables; every value is a bound parameter + query := format( + 'UPDATE %I.%I AS r + SET %I = $1 + WHERE r.domain_id = $2 + AND $3 @> to_jsonb(r.path) + AND r.%I IS DISTINCT FROM $1%s', + routes_schema, + routes_table, + routes_serving_site_key, + routes_serving_site_key, + CASE WHEN routes_key IS NULL + THEN ' AND $4 IS NULL' + ELSE format(' AND r.%I = $4', routes_key) + END + ); + + EXECUTE query USING install_route_bindings.site_id, domain_id, skipped, key_value; + GET DIAGNOSTICS stamped = ROW_COUNT; + END IF; + + RETURN jsonb_build_object( + 'site_id', install_route_bindings.site_id, + 'scope', plane_scope, + 'entity_id', install_route_bindings.entity_id, + 'domain_id', domain_id, + 'routes_schema', routes_schema, + 'routes_table', routes_table, + 'installed', installed, + 'skipped', skipped, + 'serving_site_field', routes_serving_site_key, + 'serving_site_backfilled', stamped + ); +END; +$$ LANGUAGE plpgsql VOLATILE; + +COMMENT ON FUNCTION function_resolution.install_route_bindings(uuid, text, text, uuid, jsonb, uuid) IS +'Install a set of route bindings — a JSON array of {path, target, …} entries, each NAMING its target kind ("function" with a task_identifier, or "service" with a service_id) — onto one site as ordinary route rows, at ONE scope for ONE entity. The scope and ownership key are read from the named sites plane''s own site_surface_module registration, located along the caller''s frames (nearest first) so a shared serving plane hosted by an outer frame''s database resolves for the tenant consuming it — never a caller-supplied or generated literal — and that one (scope, key) then names the routes plane (app_scope.routing_tables), pins the site, route and service reads, starts function_resolution.resolve''s frame walk, and stamps every inserted row; a service target is proved to exist in the same-scope resources plane the routes plane''s registration records, which is the plane target_service_id FKs. Idempotent per (domain_id, path); raises on a malformed document, an unknown or ambiguous target kind, an unregistered plane, a scope with no routes plane, a missing entity key, an unknown site, an unrouted site, a scope with no resources plane, an unresolvable service, or an unpublished task.'; + +COMMIT; diff --git a/packages/function-resolution/pgpm.plan b/packages/function-resolution/pgpm.plan index cce588290..abe69283e 100644 --- a/packages/function-resolution/pgpm.plan +++ b/packages/function-resolution/pgpm.plan @@ -7,7 +7,7 @@ schemas/function_resolution/procedures/definitions_location [schemas/function_re schemas/function_resolution/procedures/routing [schemas/function_resolution/schema] 2017-08-11T08:11:51Z constructive # queue routing for a definition schemas/function_resolution/procedures/resolve [schemas/function_resolution/schema] 2017-08-11T08:11:51Z constructive # cross-scope resolver schemas/function_resolution/procedures/resolve_invocation [schemas/function_resolution/schema schemas/function_resolution/procedures/resolve] 2017-08-11T08:11:51Z constructive # invocation-lane resolver -schemas/function_resolution/procedures/enqueue [schemas/function_resolution/schema schemas/function_resolution/procedures/routing schemas/function_resolution/procedures/resolve] 2017-08-11T08:11:51Z constructive # resolver-aware enqueue entry point +schemas/function_resolution/procedures/enqueue [schemas/function_resolution/schema schemas/function_resolution/procedures/routing schemas/function_resolution/procedures/resolve pgpm-jwt-claims:schemas/jwt_private/procedures/require_database_id] 2017-08-11T08:11:51Z constructive # resolver-aware enqueue entry point schemas/function_resolution/procedures/frame_candidates [schemas/function_resolution/schema] 2017-08-11T08:11:51Z constructive # frames expanded into catalog probe candidates schemas/function_resolution/procedures/bucket_matches [schemas/function_resolution/schema schemas/function_resolution/procedures/frame_candidates] 2017-08-11T08:11:51Z constructive # buckets a selector matches in the nearest frame 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 @@ -23,3 +23,5 @@ schemas/function_resolution/procedures/resolve_payload_refs [schemas/function_re schemas/function_resolution/procedures/resolve_capabilities [schemas/function_resolution/schema schemas/function_resolution/procedures/definitions_location schemas/function_resolution/procedures/frame_candidates schemas/function_resolution/procedures/resolve_bucket schemas/function_resolution/procedures/bucket_catalog_row schemas/function_resolution/procedures/bound_bucket_id schemas/function_resolution/procedures/resolve_api schemas/function_resolution/procedures/resolve_payload_refs] 2017-08-11T08:11:51Z constructive # resolve-before-dispatch capability bundle schemas/function_resolution/procedures/validate_capabilities [schemas/function_resolution/schema schemas/function_resolution/procedures/resolve_capabilities] 2017-08-11T08:11:51Z constructive # prove declarations resolvable schemas/function_resolution/procedures/image_catalog_row [schemas/function_resolution/schema schemas/function_resolution/procedures/frame_candidates] 2017-08-11T08:11:51Z constructive # reachable image by name (nearest frame wins) +schemas/function_resolution/procedures/install_route_bindings [schemas/function_resolution/schema schemas/function_resolution/procedures/resolve metaschema-modules:schemas/metaschema_modules_public/tables/site_surface_module/table metaschema-modules:schemas/metaschema_modules_public/tables/route_module/table metaschema-modules:schemas/metaschema_modules_public/tables/resource_module/table pgpm-app-scope:schemas/app_scope/procedures/routing_tables] 2026-08-19T22:00:01Z devin # install a set of route bindings (function or service targets) onto a site at one scope for one entity +schemas/function_resolution/procedures/install_mantra [schemas/function_resolution/schema schemas/function_resolution/procedures/install_route_bindings] 2026-08-19T22:00:02Z devin # the mantra page set as function-target bindings over the general engine diff --git a/packages/function-resolution/revert/schemas/function_resolution/procedures/enqueue.sql b/packages/function-resolution/revert/schemas/function_resolution/procedures/enqueue.sql index ee5771693..d0d6dbc2d 100644 --- a/packages/function-resolution/revert/schemas/function_resolution/procedures/enqueue.sql +++ b/packages/function-resolution/revert/schemas/function_resolution/procedures/enqueue.sql @@ -2,6 +2,6 @@ BEGIN; -DROP FUNCTION function_resolution.enqueue(text, json, text, uuid, uuid, text, text, text, timestamptz, int4, int4, uuid, text, bool, text, uuid, uuid); +DROP FUNCTION function_resolution.enqueue(text, json, text, uuid, uuid, text, text, text, timestamptz, int4, int4, uuid, text, bool, text, uuid, uuid, uuid, uuid); COMMIT; diff --git a/packages/function-resolution/revert/schemas/function_resolution/procedures/install_mantra.sql b/packages/function-resolution/revert/schemas/function_resolution/procedures/install_mantra.sql new file mode 100644 index 000000000..dd51f11d3 --- /dev/null +++ b/packages/function-resolution/revert/schemas/function_resolution/procedures/install_mantra.sql @@ -0,0 +1,7 @@ +-- Revert schemas/function_resolution/procedures/install_mantra from pg + +BEGIN; + +DROP FUNCTION function_resolution.install_mantra(uuid, regclass, uuid, jsonb, uuid); + +COMMIT; diff --git a/packages/function-resolution/revert/schemas/function_resolution/procedures/install_route_bindings.sql b/packages/function-resolution/revert/schemas/function_resolution/procedures/install_route_bindings.sql new file mode 100644 index 000000000..58bfc97f6 --- /dev/null +++ b/packages/function-resolution/revert/schemas/function_resolution/procedures/install_route_bindings.sql @@ -0,0 +1,7 @@ +-- Revert schemas/function_resolution/procedures/install_route_bindings from pg + +BEGIN; + +DROP FUNCTION function_resolution.install_route_bindings(uuid, text, text, uuid, jsonb, uuid); + +COMMIT; diff --git a/packages/function-resolution/sql/pgpm-function-resolution--0.43.2.bundle.tar.gz b/packages/function-resolution/sql/pgpm-function-resolution--0.43.2.bundle.tar.gz index 1a1c1dd464b25c24ac50b199d51b87ae8b3be0ff..420c21ec5d859571404488bf0e3264871762cf92 100644 GIT binary patch literal 40462 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 StKS^`dzTgwl7z60gz$ezdR->~ delta 31056 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 M7`#}Zg#G}PQ4~{H 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 diff --git a/packages/function-resolution/sql/pgpm-function-resolution--0.43.2.sql b/packages/function-resolution/sql/pgpm-function-resolution--0.43.2.sql index 56d079ddf..3dc9bd258 100644 --- a/packages/function-resolution/sql/pgpm-function-resolution--0.43.2.sql +++ b/packages/function-resolution/sql/pgpm-function-resolution--0.43.2.sql @@ -309,7 +309,9 @@ CREATE FUNCTION function_resolution.enqueue( should_resolve boolean DEFAULT true, resolution_scope text DEFAULT NULL, resolution_key uuid DEFAULT NULL, - db_id uuid DEFAULT jwt_private.current_database_id() + db_id uuid DEFAULT jwt_private.require_database_id(), + actor_id uuid DEFAULT jwt_public.current_user_id(), + principal_id uuid DEFAULT jwt_public.current_principal_id() ) RETURNS app_jobs.jobs AS $EOFCODE$ DECLARE v_database_id uuid; @@ -390,12 +392,14 @@ BEGIN entity_type := entity_type, function_definition_id := v_fn_id, definition_scope := v_def_scope, - db_id := v_database_id + db_id := v_database_id, + actor_id := enqueue.actor_id, + principal_id := enqueue.principal_id ); END; $EOFCODE$ LANGUAGE plpgsql VOLATILE SECURITY DEFINER; -COMMENT ON FUNCTION function_resolution.enqueue(text, pg_catalog.json, text, uuid, uuid, text, text, text, timestamptz, int, int, uuid, text, boolean, text, uuid, uuid) IS 'Resolver-aware job enqueue: resolves (or trusts a supplied) function definition for the execution (database, scope, entity, task_identifier), stamps the (function_definition_id, definition_scope) pair and the definition''s queue routing, then delegates the insert to app_jobs.add_job. The single enqueue path for function jobs; definition-less tasks enqueue with a NULL pair. Portable: built only on app_scope + the metaschema catalog + app_jobs, no AST/deparser runtime.'; +COMMENT ON FUNCTION function_resolution.enqueue(text, pg_catalog.json, text, uuid, uuid, text, text, text, timestamptz, int, int, uuid, text, boolean, text, uuid, uuid, uuid, uuid) IS 'Resolver-aware job enqueue: resolves (or trusts a supplied) function definition for the execution (database, scope, entity, task_identifier), stamps the (function_definition_id, definition_scope) pair and the definition''s queue routing, then delegates the insert to app_jobs.add_job. The single enqueue path for function jobs; definition-less tasks enqueue with a NULL pair. Portable: built only on app_scope + the metaschema catalog + app_jobs, no AST/deparser runtime.'; CREATE FUNCTION function_resolution.frame_candidates( database_id uuid, @@ -1647,4 +1651,490 @@ BEGIN ORDER BY cand.ord LIMIT 1; END; -$EOFCODE$ LANGUAGE plpgsql STABLE SECURITY DEFINER; \ No newline at end of file +$EOFCODE$ LANGUAGE plpgsql STABLE SECURITY DEFINER; + +CREATE FUNCTION function_resolution.install_route_bindings( + database_id uuid, + sites_schema text, + sites_table text, + site_id uuid, + bindings jsonb, + entity_id uuid DEFAULT NULL +) RETURNS jsonb AS $EOFCODE$ +DECLARE + -- plane_scope, not scope: the module registrations this reads all carry a + -- scope column, and a local named scope makes every one of those predicates + -- ambiguous. + plane_scope text; + sites_key text; + routes_schema text; + routes_table text; + routes_key text; + -- The routes column carrying the serving site, from the plane's own + -- registration. NULL for a plane that has none, which makes the stamp a + -- no-op rather than an error. + routes_serving_site_key text; + stamped int := 0; + -- The same-scope resources plane a service binding must resolve within, and + -- its own recorded ownership key. NULL when this scope carries no resources + -- plane, which makes a service binding a hard error rather than an + -- unchecked insert. + resources_schema text; + resources_table text; + resources_key text; + -- The one ownership key value the whole install is keyed by (NULL at the + -- global tier), and the entity the resolver starts its frame walk at. + key_value uuid; + resolution_entity uuid; + site_found boolean; + domain_id uuid; + -- Per-binding state: the entry, its declared kind, and the id the route's + -- typed target column gets, together with the column that carries it. + entry jsonb; + entry_path text; + entry_target text; + entry_task text; + entry_service uuid; + target_column text; + target_id uuid; + service_found boolean; + inserted int; + installed jsonb := '[]'::jsonb; + skipped jsonb := '[]'::jsonb; + query text; +BEGIN + IF install_route_bindings.database_id IS NULL THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_DATABASE_REQUIRED: database_id is required' + USING ERRCODE = 'FR050'; + END IF; + + IF install_route_bindings.sites_schema IS NULL OR install_route_bindings.sites_table IS NULL THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_SITES_PLANE_REQUIRED: sites_schema and sites_table are required' + USING ERRCODE = 'FR050'; + END IF; + + IF install_route_bindings.site_id IS NULL THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_SITE_REQUIRED: site_id is required' + USING ERRCODE = 'FR050'; + END IF; + + -- The bindings document, validated before anything is written: an empty or + -- wrongly-shaped document is a broken install, not a no-op. + IF install_route_bindings.bindings IS NULL + OR jsonb_typeof(install_route_bindings.bindings) <> 'array' + OR jsonb_array_length(install_route_bindings.bindings) = 0 THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_INVALID: bindings must be a non-empty JSON array of {path, target, …}, got %', + coalesce(jsonb_typeof(install_route_bindings.bindings), 'null') + USING ERRCODE = 'FR060'; + END IF; + + IF EXISTS ( + SELECT 1 + FROM jsonb_array_elements(install_route_bindings.bindings) AS b + WHERE jsonb_typeof(b) <> 'object' + OR coalesce(b ->> 'path', '') = '' + OR coalesce(b ->> 'target', '') = '' + ) THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_INVALID: every binding must carry a non-empty path and target' + USING ERRCODE = 'FR060'; + END IF; + + IF EXISTS ( + SELECT 1 + FROM jsonb_array_elements(install_route_bindings.bindings) AS b + WHERE (b ->> 'target') NOT IN ('function', 'service') + ) THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_TARGET_UNKNOWN: target must be "function" or "service"' + USING ERRCODE = 'FR060'; + END IF; + + -- A binding carrying the key of a kind it did not declare is ambiguous, and + -- resolving it by precedence would let a typo repoint a path at a different + -- plane than the document reads like. + IF EXISTS ( + SELECT 1 + FROM jsonb_array_elements(install_route_bindings.bindings) AS b + WHERE (b ? 'task_identifier') AND (b ? 'service_id') + OR (b ->> 'target') = 'function' AND (b ? 'service_id') + OR (b ->> 'target') = 'service' AND (b ? 'task_identifier') + ) THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_TARGET_AMBIGUOUS: a binding must carry only the key of the target kind it declares' + USING ERRCODE = 'FR060'; + END IF; + + IF EXISTS ( + SELECT 1 + FROM jsonb_array_elements(install_route_bindings.bindings) AS b + WHERE (b ->> 'target') = 'function' AND coalesce(b ->> 'task_identifier', '') = '' + OR (b ->> 'target') = 'service' AND coalesce(b ->> 'service_id', '') = '' + ) THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_INVALID: a function binding needs a task_identifier and a service binding needs a service_id' + USING ERRCODE = 'FR060'; + END IF; + + -- A service_id is cast to uuid before it is looked up, and a bare cast + -- failure reports "invalid input syntax for type uuid" with no hint of which + -- binding carried it — so the document is rejected by name instead. + IF EXISTS ( + SELECT 1 + FROM jsonb_array_elements(install_route_bindings.bindings) AS b + WHERE (b ->> 'target') = 'service' + AND (b ->> 'service_id') !~* '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' + ) THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_SERVICE_INVALID: a service binding''s service_id must be a uuid' + USING ERRCODE = 'FR060'; + END IF; + + -- Idempotency is keyed by (domain_id, path), so two entries claiming one + -- path would install the first and report the second as already-there — + -- a document that reads like it bound both. It is malformed instead. + IF EXISTS ( + SELECT 1 + FROM jsonb_array_elements(install_route_bindings.bindings) AS b + GROUP BY b ->> 'path' + HAVING count(*) > 1 + ) THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_PATH_DUPLICATED: a path may be bound once per document' + USING ERRCODE = 'FR060'; + END IF; + + -- 1. The plane's own registration decides the scope. This is the whole + -- point: the scope is a recorded fact about the sites plane being + -- installed onto, not a caller-supplied string and not a literal frozen + -- into a generated body. + -- + -- The registration is looked up ALONG THE CALLER'S FRAMES, the same walk + -- step 2 resolves the routes plane with, because a sites plane serving a + -- scope is not necessarily registered in the database consuming it: the + -- database-scope serving planes are shared, hosted by an outer frame's + -- database and keyed per tenant, so a tenant installing onto the plane it + -- is actually served by has no registration of its own to find. Keying is + -- unaffected — the scope comes from the registration and the key value from + -- the caller (step 3), so a tenant's rows stay stamped with the tenant. + -- Nearest frame wins, so a plane a database does register still resolves to + -- its own registration. + SELECT sm.scope, sm.entity_field + INTO plane_scope, sites_key + FROM app_scope.frames(install_route_bindings.database_id, 'database') WITH ORDINALITY AS f + JOIN metaschema_modules_public.site_surface_module AS sm + ON sm.scope = f.scope + AND sm.database_id = f.lookup_database_id + JOIN metaschema_public."table" AS t ON t.id = sm.sites_table_id + JOIN metaschema_public.schema AS s ON s.id = t.schema_id + WHERE s.schema_name = install_route_bindings.sites_schema + AND t.name = install_route_bindings.sites_table + ORDER BY f.ordinality + LIMIT 1; + + IF plane_scope IS NULL THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_SITES_PLANE_NOT_REGISTERED: no site_surface_module on any frame of database % registers %.% as its sites plane', + install_route_bindings.database_id, install_route_bindings.sites_schema, install_route_bindings.sites_table + USING ERRCODE = 'FR051'; + END IF; + + -- 2. The routes plane serving THAT scope, resolved the one way every + -- scope-aware consumer resolves it. + SELECT r.routes_schema, r.routes_table + INTO routes_schema, routes_table + FROM app_scope.routing_tables(install_route_bindings.database_id, plane_scope) AS r; + + IF routes_schema IS NULL OR routes_table IS NULL THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_ROUTES_PLANE_NOT_FOUND: scope "%" has no routes plane provisioned on any frame of database %', + plane_scope, install_route_bindings.database_id + USING ERRCODE = 'FR051'; + END IF; + + -- The routes plane's own registration carries its ownership key and, when + -- the plane has one, the column naming the site a route renders as. + SELECT rm.entity_field, rm.serving_site_field + INTO routes_key, routes_serving_site_key + FROM metaschema_modules_public.route_module AS rm + JOIN metaschema_public."table" AS t ON t.id = rm.routes_table_id + JOIN metaschema_public.schema AS s ON s.id = t.schema_id + WHERE s.schema_name = routes_schema + AND t.name = routes_table; + + -- Both planes are at the same scope, so scope_key_column gave them the same + -- ownership key. A mismatch means the registrations disagree about who owns + -- the rows, which is exactly the cross-scope write this function exists to + -- make impossible. + IF routes_key IS DISTINCT FROM sites_key THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_PLANE_KEY_MISMATCH: sites plane %.% is keyed by % but routes plane %.% is keyed by %', + install_route_bindings.sites_schema, install_route_bindings.sites_table, coalesce(sites_key, ''), + routes_schema, routes_table, coalesce(routes_key, '') + USING ERRCODE = 'FR051'; + END IF; + + -- The resources plane serving that same scope, from its own module + -- registration: target_service_id FKs this scope's source resources table, + -- so this is the plane the constraint would check a service binding against. + SELECT s.schema_name, t.name, rem.entity_field + INTO resources_schema, resources_table, resources_key + FROM metaschema_modules_public.resource_module AS rem + JOIN metaschema_public."table" AS t ON t.id = rem.resources_table_id + JOIN metaschema_public.schema AS s ON s.id = t.schema_id + WHERE rem.database_id = install_route_bindings.database_id + AND rem.scope = plane_scope; + + -- 3. One key value for the whole operation. + IF sites_key IS NULL THEN + key_value := NULL; + resolution_entity := NULL; + ELSIF sites_key = 'database_id' THEN + key_value := install_route_bindings.database_id; + resolution_entity := NULL; + ELSE + IF install_route_bindings.entity_id IS NULL THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_ENTITY_REQUIRED: scope "%" is keyed by %, so entity_id is required', + plane_scope, sites_key + USING ERRCODE = 'FR052'; + END IF; + key_value := install_route_bindings.entity_id; + resolution_entity := install_route_bindings.entity_id; + END IF; + + -- 4. The site, pinned to that key value. + -- pgsql-lint-disable-next-line no-dynamic-sql -- lookup-only: the sites plane is named by the caller and proved by the registration join above + query := format( + 'SELECT EXISTS (SELECT 1 FROM %I.%I AS s WHERE s.id = $1%s)', + install_route_bindings.sites_schema, + install_route_bindings.sites_table, + CASE WHEN sites_key IS NULL + THEN ' AND $2 IS NULL' + ELSE format(' AND s.%I = $2', sites_key) + END + ); + + EXECUTE query INTO site_found USING install_route_bindings.site_id, key_value; + + IF NOT site_found THEN + RAISE EXCEPTION 'SITE_NOT_FOUND: no site % in %.% owned by %', + install_route_bindings.site_id, install_route_bindings.sites_schema, install_route_bindings.sites_table, + coalesce(key_value::text, plane_scope) + USING ERRCODE = 'FR053'; + END IF; + + -- 5. The hostname the site already serves. Bindings layer onto an existing + -- site route rather than claiming a hostname of their own: the root-route + -- guard auto-creates '/' carrying the target of a hostname's FIRST route, + -- so installing onto a bare hostname would make '/' the first binding's + -- target. A site that is not routed yet is a hard error, not a silently + -- empty install. + -- pgsql-lint-disable-next-line no-dynamic-sql -- lookup-only: the routes plane is named by app_scope.routing_tables + query := format( + 'SELECT r.domain_id FROM %I.%I AS r WHERE r.target_site_id = $1%s ORDER BY r.path LIMIT 1', + routes_schema, + routes_table, + CASE WHEN routes_key IS NULL + THEN ' AND $2 IS NULL' + ELSE format(' AND r.%I = $2', routes_key) + END + ); + + EXECUTE query INTO domain_id USING install_route_bindings.site_id, key_value; + + IF domain_id IS NULL THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_SITE_NOT_ROUTED: site % serves no hostname in %.%, so there is nowhere to install the bindings', + install_route_bindings.site_id, routes_schema, routes_table + USING ERRCODE = 'FR054'; + END IF; + + -- 6. Per binding: resolve the declared target at THIS (scope, entity) and + -- install the route if it is not already there. + FOR entry IN SELECT * FROM jsonb_array_elements(install_route_bindings.bindings) + LOOP + entry_path := entry ->> 'path'; + entry_target := entry ->> 'target'; + + IF entry_target = 'function' THEN + entry_task := entry ->> 'task_identifier'; + + -- Fail-loud by the resolver's own contract: an unpublished task + -- raises FUNCTION_DEFINITION_NOT_FOUND rather than installing a + -- route that would 404 at request time. + SELECT fr.function_definition_id + INTO target_id + FROM function_resolution.resolve( + install_route_bindings.database_id, + plane_scope, + resolution_entity, + entry_task + ) AS fr; + + target_column := 'target_function_id'; + ELSE + entry_service := (entry ->> 'service_id')::uuid; + + IF resources_schema IS NULL THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_SERVICE_PLANE_NOT_FOUND: database % has no resource_module at scope "%", so there is no plane a service target could live in', + install_route_bindings.database_id, plane_scope + USING ERRCODE = 'FR055'; + END IF; + + -- pgsql-lint-disable-next-line no-dynamic-sql -- lookup-only: the resources plane is named by this scope's own resource_module registration + query := format( + 'SELECT EXISTS (SELECT 1 FROM %I.%I AS r WHERE r.id = $1%s)', + resources_schema, + resources_table, + CASE WHEN resources_key IS NULL + THEN ' AND $2 IS NULL' + ELSE format(' AND r.%I = $2', resources_key) + END + ); + + EXECUTE query INTO service_found USING entry_service, key_value; + + IF NOT service_found THEN + RAISE EXCEPTION 'ROUTE_BINDINGS_SERVICE_NOT_FOUND: no service % in %.% owned by %', + entry_service, resources_schema, resources_table, + coalesce(key_value::text, plane_scope) + USING ERRCODE = 'FR056'; + END IF; + + target_id := entry_service; + target_column := 'target_service_id'; + END IF; + + -- pgsql-lint-disable-next-line no-dynamic-sql -- write-only: insert into the routes plane named by app_scope.routing_tables; every value is a bound parameter + query := format( + 'INSERT INTO %I.%I (%s%sdomain_id, path, %I) + SELECT %s%s$1, $2, $3 + WHERE NOT EXISTS ( + SELECT 1 FROM %I.%I AS x + WHERE x.domain_id = $1 AND x.path = $2%s)', + routes_schema, + routes_table, + CASE WHEN routes_key IS NULL THEN '' ELSE format('%I, ', routes_key) END, + CASE WHEN routes_serving_site_key IS NULL THEN '' ELSE format('%I, ', routes_serving_site_key) END, + target_column, + CASE WHEN routes_key IS NULL THEN '' ELSE '$4, ' END, + CASE WHEN routes_serving_site_key IS NULL THEN '' ELSE '$5, ' END, + routes_schema, + routes_table, + CASE WHEN routes_key IS NULL + THEN ' AND $4 IS NULL' + ELSE format(' AND x.%I = $4', routes_key) + END + ); + + EXECUTE query USING domain_id, entry_path, target_id, key_value, + install_route_bindings.site_id; + GET DIAGNOSTICS inserted = ROW_COUNT; + + IF inserted > 0 THEN + installed := installed || jsonb_build_array(entry_path); + ELSE + skipped := skipped || jsonb_build_array(entry_path); + END IF; + END LOOP; + + -- Backfill the serving site onto the paths that were already there: the + -- insert above stamps what it writes, and a route the document claims but + -- did not create is still a route this site serves. IS DISTINCT FROM keeps a + -- repeat install a no-op write rather than a row version per path, and the + -- ownership pin repeats here so a SECURITY DEFINER caller cannot stamp a row + -- another owner holds even if a domain were ever shared across planes. + IF routes_serving_site_key IS NOT NULL AND jsonb_array_length(skipped) > 0 THEN + -- pgsql-lint-disable-next-line no-dynamic-sql -- write-only: update the routes plane named by app_scope.routing_tables; every value is a bound parameter + query := format( + 'UPDATE %I.%I AS r + SET %I = $1 + WHERE r.domain_id = $2 + AND $3 @> to_jsonb(r.path) + AND r.%I IS DISTINCT FROM $1%s', + routes_schema, + routes_table, + routes_serving_site_key, + routes_serving_site_key, + CASE WHEN routes_key IS NULL + THEN ' AND $4 IS NULL' + ELSE format(' AND r.%I = $4', routes_key) + END + ); + + EXECUTE query USING install_route_bindings.site_id, domain_id, skipped, key_value; + GET DIAGNOSTICS stamped = ROW_COUNT; + END IF; + + RETURN jsonb_build_object( + 'site_id', install_route_bindings.site_id, + 'scope', plane_scope, + 'entity_id', install_route_bindings.entity_id, + 'domain_id', domain_id, + 'routes_schema', routes_schema, + 'routes_table', routes_table, + 'installed', installed, + 'skipped', skipped, + 'serving_site_field', routes_serving_site_key, + 'serving_site_backfilled', stamped + ); +END; +$EOFCODE$ LANGUAGE plpgsql VOLATILE; + +COMMENT ON FUNCTION function_resolution.install_route_bindings(uuid, text, text, uuid, jsonb, uuid) IS 'Install a set of route bindings — a JSON array of {path, target, …} entries, each NAMING its target kind ("function" with a task_identifier, or "service" with a service_id) — onto one site as ordinary route rows, at ONE scope for ONE entity. The scope and ownership key are read from the named sites plane''s own site_surface_module registration, located along the caller''s frames (nearest first) so a shared serving plane hosted by an outer frame''s database resolves for the tenant consuming it — never a caller-supplied or generated literal — and that one (scope, key) then names the routes plane (app_scope.routing_tables), pins the site, route and service reads, starts function_resolution.resolve''s frame walk, and stamps every inserted row; a service target is proved to exist in the same-scope resources plane the routes plane''s registration records, which is the plane target_service_id FKs. Idempotent per (domain_id, path); raises on a malformed document, an unknown or ambiguous target kind, an unregistered plane, a scope with no routes plane, a missing entity key, an unknown site, an unrouted site, a scope with no resources plane, an unresolvable service, or an unpublished task.'; + +CREATE FUNCTION function_resolution.install_mantra( + database_id uuid, + sites_plane regclass, + site_id uuid, + bindings jsonb, + entity_id uuid DEFAULT NULL +) RETURNS jsonb AS $EOFCODE$ +DECLARE + function_bindings jsonb; + sites_schema text; + sites_table text; +BEGIN + IF install_mantra.sites_plane IS NULL THEN + RAISE EXCEPTION 'MANTRA_SITES_PLANE_REQUIRED: sites_plane is required' + USING ERRCODE = 'FR050'; + END IF; + + -- The plane's names, read off the catalog entry the reference itself proves + -- exists, and handed to the engine as the registry records them. + SELECT n.nspname, c.relname + INTO sites_schema, sites_table + FROM pg_catalog.pg_class AS c + JOIN pg_catalog.pg_namespace AS n ON n.oid = c.relnamespace + WHERE c.oid = install_mantra.sites_plane; + + -- The Mantra document contract, checked here because it is stricter than the + -- engine's: every entry names a task, and none of them may name a target + -- kind — a preset row that has grown a service binding is a broken preset, + -- not a mixed install. + IF install_mantra.bindings IS NULL + OR jsonb_typeof(install_mantra.bindings) <> 'array' + OR jsonb_array_length(install_mantra.bindings) = 0 THEN + RAISE EXCEPTION 'MANTRA_BINDINGS_INVALID: bindings must be a non-empty JSON array of {path, task_identifier}, got %', + coalesce(jsonb_typeof(install_mantra.bindings), 'null') + USING ERRCODE = 'FR060'; + END IF; + + IF EXISTS ( + SELECT 1 + FROM jsonb_array_elements(install_mantra.bindings) AS b + WHERE jsonb_typeof(b) <> 'object' + OR coalesce(b ->> 'path', '') = '' + OR coalesce(b ->> 'task_identifier', '') = '' + OR b ? 'target' + ) THEN + RAISE EXCEPTION 'MANTRA_BINDINGS_INVALID: every binding must carry a non-empty path and task_identifier, and no target kind' + USING ERRCODE = 'FR060'; + END IF; + + SELECT jsonb_agg(b || jsonb_build_object('target', 'function')) + INTO function_bindings + FROM jsonb_array_elements(install_mantra.bindings) AS b; + + RETURN function_resolution.install_route_bindings( + install_mantra.database_id, + sites_schema, + sites_table, + install_mantra.site_id, + function_bindings, + install_mantra.entity_id + ); +END; +$EOFCODE$ LANGUAGE plpgsql VOLATILE; + +COMMENT ON FUNCTION function_resolution.install_mantra(uuid, regclass, uuid, jsonb, uuid) IS 'Install the Mantra page set (a JSON array of {path, task_identifier}, which the generated verb reads from the content_presets catalog at kind ''route_bindings'') onto one site as ordinary function-target routes. The sites plane arrives by reference as a regclass, so a generated caller never spells a schema name in a bare string literal the platform export''s AST rename cannot follow. A thin wrapper holding the Mantra document contract — every entry names a task, none names a target kind — over function_resolution.install_route_bindings, which owns the one-scope install: scope and ownership key read from the sites plane''s own registration, the routes plane from app_scope.routing_tables, resolution at that same (scope, entity), idempotent per (domain_id, path).'; \ No newline at end of file diff --git a/packages/function-resolution/verify/schemas/function_resolution/procedures/enqueue.sql b/packages/function-resolution/verify/schemas/function_resolution/procedures/enqueue.sql index 46f383df3..27475e85d 100644 --- a/packages/function-resolution/verify/schemas/function_resolution/procedures/enqueue.sql +++ b/packages/function-resolution/verify/schemas/function_resolution/procedures/enqueue.sql @@ -2,6 +2,6 @@ BEGIN; -SELECT assert_function('function_resolution.enqueue(text, json, text, uuid, uuid, text, text, text, timestamptz, int4, int4, uuid, text, bool, text, uuid, uuid)'::regprocedure); +SELECT assert_function('function_resolution.enqueue(text, json, text, uuid, uuid, text, text, text, timestamptz, int4, int4, uuid, text, bool, text, uuid, uuid, uuid, uuid)'::regprocedure); ROLLBACK; diff --git a/packages/function-resolution/verify/schemas/function_resolution/procedures/install_mantra.sql b/packages/function-resolution/verify/schemas/function_resolution/procedures/install_mantra.sql new file mode 100644 index 000000000..5f9b8de87 --- /dev/null +++ b/packages/function-resolution/verify/schemas/function_resolution/procedures/install_mantra.sql @@ -0,0 +1,7 @@ +-- Verify schemas/function_resolution/procedures/install_mantra on pg + +BEGIN; + +SELECT assert_function('function_resolution.install_mantra(uuid, regclass, uuid, jsonb, uuid)'::regprocedure); + +ROLLBACK; diff --git a/packages/function-resolution/verify/schemas/function_resolution/procedures/install_route_bindings.sql b/packages/function-resolution/verify/schemas/function_resolution/procedures/install_route_bindings.sql new file mode 100644 index 000000000..c0841cdf3 --- /dev/null +++ b/packages/function-resolution/verify/schemas/function_resolution/procedures/install_route_bindings.sql @@ -0,0 +1,7 @@ +-- Verify schemas/function_resolution/procedures/install_route_bindings on pg + +BEGIN; + +SELECT assert_function('function_resolution.install_route_bindings(uuid, text, text, uuid, jsonb, uuid)'::regprocedure); + +ROLLBACK; diff --git a/packages/jwt-claims/__tests__/jwt.test.ts b/packages/jwt-claims/__tests__/jwt.test.ts index ee5da7149..48aef4b5f 100644 --- a/packages/jwt-claims/__tests__/jwt.test.ts +++ b/packages/jwt-claims/__tests__/jwt.test.ts @@ -5,7 +5,9 @@ let teardown: () => Promise; const jwt = { user_id: 'b9d22af1-62c7-43a5-b8c4-50630bbd4962', - database_id: '44744c94-93cf-425a-b524-ce6f1466e327' + database_id: '44744c94-93cf-425a-b524-ce6f1466e327', + entity_id: 'f12f1f0d-6f62-4f4b-93f2-72ee5d2a5b8e', + entity_type: 'org' }; beforeAll(async () => { @@ -66,10 +68,266 @@ it('current_database_id returns the claim when it is set', async () => { expect(database_id).toEqual(jwt.database_id); }); -it('current_database_id raises DATABASE_CLAIM_REQUIRED when the claim is absent', async () => { +it('require_database_id raises DATABASE_CLAIM_REQUIRED when the claim is absent', async () => { await pg.any(`BEGIN`); await expect( - pg.one(`select jwt_private.current_database_id()`) + pg.one(`select jwt_private.require_database_id()`) ).rejects.toThrow('DATABASE_CLAIM_REQUIRED'); await pg.any(`ROLLBACK`); }); + +it('require_user_id raises ACTOR_CLAIM_REQUIRED when the claim is absent', async () => { + await pg.any(`BEGIN`); + await expect( + pg.one(`select jwt_public.require_user_id()`) + ).rejects.toThrow('ACTOR_CLAIM_REQUIRED'); + await pg.any(`ROLLBACK`); +}); + +it.each([ + ['unset', null], + ['empty', ''], + ['malformed', 'not-a-uuid'] +])('current_database_id returns NULL for a %s claim', async (_label, value) => { + await pg.any(`BEGIN`); + if (value === null) { + await pg.any(`RESET "jwt.claims.database_id"`); + } else { + await pg.any( + `SELECT set_config('jwt.claims.database_id', $1, true)`, + [value] + ); + } + const { database_id } = await pg.one( + `select jwt_private.current_database_id() as database_id` + ); + await pg.any(`ROLLBACK`); + + expect(database_id).toBeNull(); +}); + +describe('entity claim readers', () => { + it.each([ + ['unset', null, null], + ['malformed', 'not-a-uuid', null], + ['valid', jwt.entity_id, jwt.entity_id] + ])('current_entity_id returns %s', async (_label, value, expected) => { + await pg.any(`BEGIN`); + if (value === null) { + await pg.any(`RESET "jwt.claims.entity_id"`); + } else { + await pg.any( + `SELECT set_config('jwt.claims.entity_id', $1, true)`, + [value] + ); + } + const { entity_id } = await pg.one( + `select jwt_private.current_entity_id() as entity_id` + ); + await pg.any(`ROLLBACK`); + + expect(entity_id).toEqual(expected); + }); + + it('current_entity_id returns NULL for transaction-local residue after COMMIT', async () => { + try { + await pg.any(`BEGIN`); + await pg.any( + `SELECT set_config('jwt.claims.entity_id', $1, true)`, + [jwt.entity_id] + ); + await pg.any(`COMMIT`); + + const { entity_id } = await pg.one( + `select jwt_private.current_entity_id() as entity_id` + ); + expect(entity_id).toBeNull(); + } finally { + await pg.any(`SELECT set_config('jwt.claims.entity_id', NULL, false)`); + } + }); + + it.each([ + ['unset', null, null], + ['valid', jwt.entity_type, jwt.entity_type] + ])('current_entity_type returns %s', async (_label, value, expected) => { + await pg.any(`BEGIN`); + if (value === null) { + await pg.any(`RESET "jwt.claims.entity_type"`); + } else { + await pg.any( + `SELECT set_config('jwt.claims.entity_type', $1, true)`, + [value] + ); + } + const { entity_type } = await pg.one( + `select jwt_private.current_entity_type() as entity_type` + ); + await pg.any(`ROLLBACK`); + + expect(entity_type).toEqual(expected); + }); + + it('current_entity_type returns NULL for transaction-local residue after COMMIT', async () => { + try { + await pg.any(`BEGIN`); + await pg.any( + `SELECT set_config('jwt.claims.entity_type', $1, true)`, + [jwt.entity_type] + ); + await pg.any(`COMMIT`); + + const { entity_type } = await pg.one( + `select jwt_private.current_entity_type() as entity_type` + ); + expect(entity_type).toBeNull(); + } finally { + await pg.any(`SELECT set_config('jwt.claims.entity_type', NULL, false)`); + } + }); + + it('require_database_id raises for a malformed claim', async () => { + await pg.any(`BEGIN`); + await pg.any( + `SELECT set_config('jwt.claims.database_id', $1, true)`, + ['not-a-uuid'] + ); + await expect( + pg.one(`select jwt_private.require_database_id()`) + ).rejects.toThrow('DATABASE_CLAIM_REQUIRED'); + await pg.any(`ROLLBACK`); + }); + + it('require_entity_id raises for an absent claim', async () => { + await pg.any(`BEGIN`); + await expect( + pg.one(`select jwt_private.require_entity_id()`) + ).rejects.toThrow('ENTITY_CLAIM_REQUIRED'); + await pg.any(`ROLLBACK`); + }); + + it('require_entity_type raises for an absent claim', async () => { + await pg.any(`BEGIN`); + await expect( + pg.one(`select jwt_private.require_entity_type()`) + ).rejects.toThrow('ENTITY_TYPE_CLAIM_REQUIRED'); + await pg.any(`ROLLBACK`); + }); +}); + +describe('attribution assertion', () => { + it('raises on incomplete attribution by default', async () => { + await pg.any(`BEGIN`); + try { + await pg.one(`select jwt_private.assert_attribution(NULL, NULL, NULL)`); + throw new Error('assert_attribution did not raise'); + } catch (err) { + expect(err).toMatchObject({ code: 'P0001' }); + } finally { + await pg.any(`ROLLBACK`); + } + }); + + it('warns and allows incomplete attribution when strict mode is disabled', async () => { + const notices: Array<{ message?: string }> = []; + const onNotice = (notice: { message?: string }) => notices.push(notice); + pg.client.on('notice', onNotice); + await pg.any(`BEGIN`); + await pg.any( + `SELECT set_config('jwt.strict_attribution', 'false', true)` + ); + try { + await pg.one(`select jwt_private.assert_attribution(NULL, NULL, NULL)`); + await pg.one( + `select jwt_private.assert_attribution(NULL, $1::uuid, NULL)`, + [jwt.entity_id] + ); + await pg.one( + `select jwt_private.assert_attribution($1::uuid, NULL, $2::text)`, + [jwt.user_id, jwt.entity_type] + ); + await pg.any(`CREATE TEMP TABLE attribution_probe(value integer)`); + await pg.any(`INSERT INTO attribution_probe VALUES (1)`); + const [{ value }] = await pg.any( + `SELECT value FROM attribution_probe` + ); + expect(value).toBe(1); + } finally { + await pg.any(`ROLLBACK`); + pg.client.off('notice', onNotice); + } + + expect(notices.map(({ message }) => message)).toEqual( + expect.arrayContaining([ + expect.stringContaining('ATTRIBUTION_REQUIRED'), + expect.stringContaining('ENTITY_TYPE_REQUIRED'), + expect.stringContaining('ENTITY_ID_REQUIRED'), + ]) + ); + }); + + it('raises when neither actor nor entity is present', async () => { + await pg.any(`BEGIN`); + await pg.any(`SELECT set_config('jwt.strict_attribution', 'true', true)`); + try { + await pg.one(`select jwt_private.assert_attribution(NULL, NULL, NULL)`); + throw new Error('assert_attribution did not raise'); + } catch (err) { + expect(err).toMatchObject({ code: 'P0001' }); + const detail = JSON.parse((err as { detail: string }).detail) as { + code: string; + context: { + arguments: string[]; + claims: string[]; + }; + }; + expect(detail.code).toBe('ATTRIBUTION_REQUIRED'); + expect(detail.context).toEqual({ + arguments: ['actor_id', 'entity_id'], + claims: ['jwt.claims.user_id', 'jwt.claims.entity_id'], + }); + } + await pg.any(`ROLLBACK`); + }); + + it('raises when an entity has no entity type', async () => { + await pg.any(`BEGIN`); + await pg.any(`SELECT set_config('jwt.strict_attribution', 'true', true)`); + await expect( + pg.one( + `select jwt_private.assert_attribution(NULL, $1::uuid, NULL)`, + [jwt.entity_id] + ) + ).rejects.toThrow('ENTITY_TYPE_REQUIRED'); + await pg.any(`ROLLBACK`); + }); + + it('raises when an entity type has no entity', async () => { + await pg.any(`BEGIN`); + await pg.any(`SELECT set_config('jwt.strict_attribution', 'true', true)`); + await expect( + pg.one( + `select jwt_private.assert_attribution($1::uuid, NULL, $2::text)`, + [jwt.user_id, jwt.entity_type] + ) + ).rejects.toThrow('ENTITY_ID_REQUIRED'); + await pg.any(`ROLLBACK`); + }); + + it('allows actor-only attribution', async () => { + await expect( + pg.one(`select jwt_private.assert_attribution($1::uuid, NULL, NULL)`, [ + jwt.user_id + ]) + ).resolves.toBeDefined(); + }); + + it('allows row-derived entity attribution with its type', async () => { + await expect( + pg.one( + `select jwt_private.assert_attribution(NULL, $1::uuid, $2::text)`, + [jwt.entity_id, jwt.entity_type] + ) + ).resolves.toBeDefined(); + }); +}); diff --git a/packages/jwt-claims/deploy/schemas/ctx/procedures/ip_address.sql b/packages/jwt-claims/deploy/schemas/ctx/procedures/ip_address.sql index 864bb146f..cc152738c 100644 --- a/packages/jwt-claims/deploy/schemas/ctx/procedures/ip_address.sql +++ b/packages/jwt-claims/deploy/schemas/ctx/procedures/ip_address.sql @@ -12,7 +12,6 @@ CREATE FUNCTION ctx.ip_address() AS $$ SELECT nullif(current_setting('jwt.claims.ip_address', true), '')::inet; $$ -LANGUAGE 'sql' STABLE; +LANGUAGE 'sql' STABLE LEAKPROOF PARALLEL SAFE; COMMIT; - diff --git a/packages/jwt-claims/deploy/schemas/ctx/procedures/origin.sql b/packages/jwt-claims/deploy/schemas/ctx/procedures/origin.sql index 5c23772f9..ece7b9189 100644 --- a/packages/jwt-claims/deploy/schemas/ctx/procedures/origin.sql +++ b/packages/jwt-claims/deploy/schemas/ctx/procedures/origin.sql @@ -12,7 +12,6 @@ CREATE FUNCTION ctx.origin() AS $$ SELECT nullif(current_setting('jwt.claims.origin', true), '')::origin; $$ -LANGUAGE 'sql' STABLE; +LANGUAGE 'sql' STABLE LEAKPROOF PARALLEL SAFE; COMMIT; - diff --git a/packages/jwt-claims/deploy/schemas/ctx/procedures/uagent.sql b/packages/jwt-claims/deploy/schemas/ctx/procedures/uagent.sql index cf4423f55..1e7df4b5d 100644 --- a/packages/jwt-claims/deploy/schemas/ctx/procedures/uagent.sql +++ b/packages/jwt-claims/deploy/schemas/ctx/procedures/uagent.sql @@ -12,7 +12,6 @@ CREATE FUNCTION ctx.uagent() AS $$ SELECT nullif(current_setting('jwt.claims.user_agent', true), ''); $$ -LANGUAGE 'sql' STABLE; +LANGUAGE 'sql' STABLE LEAKPROOF PARALLEL SAFE; COMMIT; - diff --git a/packages/jwt-claims/deploy/schemas/ctx/procedures/uid.sql b/packages/jwt-claims/deploy/schemas/ctx/procedures/uid.sql index ed2bca9a9..367672cc3 100644 --- a/packages/jwt-claims/deploy/schemas/ctx/procedures/uid.sql +++ b/packages/jwt-claims/deploy/schemas/ctx/procedures/uid.sql @@ -12,7 +12,6 @@ CREATE FUNCTION ctx.uid() AS $$ SELECT nullif(current_setting('jwt.claims.user_id', true), '')::uuid; $$ -LANGUAGE 'sql' STABLE; +LANGUAGE 'sql' STABLE LEAKPROOF PARALLEL SAFE; COMMIT; - diff --git a/packages/jwt-claims/deploy/schemas/jwt_private/procedures/assert_attribution.sql b/packages/jwt-claims/deploy/schemas/jwt_private/procedures/assert_attribution.sql new file mode 100644 index 000000000..195e148c5 --- /dev/null +++ b/packages/jwt-claims/deploy/schemas/jwt_private/procedures/assert_attribution.sql @@ -0,0 +1,77 @@ +-- Deploy schemas/jwt_private/procedures/assert_attribution to pg +-- Raises on incomplete attribution by default; warns only when strict mode is +-- explicitly disabled. Set jwt.strict_attribution = 'false' at a boundary that +-- must tolerate incomplete attribution while its writers are being fixed. + +-- requires: schemas/jwt_private/schema + +BEGIN; + +CREATE FUNCTION jwt_private.assert_attribution( + actor_id uuid, + entity_id uuid, + entity_type text +) + RETURNS void +AS $$ +DECLARE + strict_attribution boolean := + COALESCE( + NULLIF(current_setting('jwt.strict_attribution', true), ''), + 'true' + ) <> 'false'; + context jsonb; +BEGIN + IF actor_id IS NULL AND entity_id IS NULL THEN + context := jsonb_build_object( + 'arguments', jsonb_build_array('actor_id', 'entity_id'), + 'claims', jsonb_build_array('jwt.claims.user_id', 'jwt.claims.entity_id') + ); + IF strict_attribution THEN + PERFORM errors.raise_error('ATTRIBUTION_REQUIRED', context, 'internal'); + ELSE + RAISE WARNING '%', + jsonb_build_object( + 'code', 'ATTRIBUTION_REQUIRED', + 'class', 'internal', + 'context', context + ); + END IF; + ELSIF entity_id IS NOT NULL AND entity_type IS NULL THEN + context := jsonb_build_object( + 'argument', 'entity_type', + 'claim', 'jwt.claims.entity_type', + 'entity_id', entity_id + ); + IF strict_attribution THEN + PERFORM errors.raise_error('ENTITY_TYPE_REQUIRED', context, 'internal'); + ELSE + RAISE WARNING '%', + jsonb_build_object( + 'code', 'ENTITY_TYPE_REQUIRED', + 'class', 'internal', + 'context', context + ); + END IF; + ELSIF entity_type IS NOT NULL AND entity_id IS NULL THEN + context := jsonb_build_object( + 'argument', 'entity_id', + 'claim', 'jwt.claims.entity_id', + 'entity_type', entity_type + ); + IF strict_attribution THEN + PERFORM errors.raise_error('ENTITY_ID_REQUIRED', context, 'internal'); + ELSE + RAISE WARNING '%', + jsonb_build_object( + 'code', 'ENTITY_ID_REQUIRED', + 'class', 'internal', + 'context', context + ); + END IF; + END IF; +END; +$$ +LANGUAGE 'plpgsql' STABLE PARALLEL SAFE; + +COMMIT; diff --git a/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_api_id.sql b/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_api_id.sql index 5f426bd7b..6a2ffa178 100644 --- a/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_api_id.sql +++ b/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_api_id.sql @@ -16,6 +16,6 @@ AS $$ THEN current_setting('jwt.claims.api_id', TRUE)::uuid END; $$ -LANGUAGE 'sql' STABLE LEAKPROOF; +LANGUAGE 'sql' STABLE LEAKPROOF PARALLEL SAFE; COMMIT; diff --git a/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_database_id.sql b/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_database_id.sql index 92c3ff7e0..13a826546 100644 --- a/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_database_id.sql +++ b/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_database_id.sql @@ -5,41 +5,18 @@ BEGIN; --- Returns the current database UUID from the JWT claims --- Used for multi-tenant database isolation --- --- The claim is set at every session boundary (API, worker, provisioning, --- platform deploy/seed), so a missing or malformed claim is never a --- legitimate state: it means a scoped write is about to run unattributed. --- Fails loudly instead of returning NULL, so the omission surfaces at the --- statement that needed the identity rather than as a NULL that travels. --- -- pg_input_is_valid() validates without raising, so no EXCEPTION block is -- needed: an exception handler would open a subtransaction on every call --- --- LEAKPROOF like its sibling claim readers: it takes no arguments, so it can --- leak nothing about the row a policy is testing, and the planner may push the --- claim comparison down instead of treating every policy that reads a claim as --- a barrier. Its one error path depends on the session, never on a value. +-- LEAKPROOF is safe here: with no arguments, this function cannot reveal +-- anything about the row a policy is testing, so claim comparisons can be pushed down. CREATE FUNCTION jwt_private.current_database_id() RETURNS uuid AS $$ -DECLARE - database_id uuid; -BEGIN - IF pg_input_is_valid(current_setting('jwt.claims.database_id', TRUE), 'uuid') THEN - database_id = current_setting('jwt.claims.database_id', TRUE)::uuid; - END IF; - IF database_id IS NULL THEN - PERFORM errors.raise_error( - 'DATABASE_CLAIM_REQUIRED', - jsonb_build_object('claim', 'jwt.claims.database_id'), - 'internal' - ); - END IF; - RETURN database_id; -END; + SELECT CASE + WHEN pg_input_is_valid(current_setting('jwt.claims.database_id', TRUE), 'uuid') + THEN current_setting('jwt.claims.database_id', TRUE)::uuid + END; $$ -LANGUAGE 'plpgsql' STABLE LEAKPROOF; +LANGUAGE 'sql' STABLE LEAKPROOF PARALLEL SAFE; COMMIT; diff --git a/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_entity_id.sql b/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_entity_id.sql new file mode 100644 index 000000000..1d05f0d95 --- /dev/null +++ b/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_entity_id.sql @@ -0,0 +1,19 @@ +-- Deploy schemas/jwt_private/procedures/current_entity_id to pg +-- Retrieves the current entity ID from JWT claims (private/internal use) + +-- requires: schemas/jwt_private/schema + +BEGIN; + +-- Returns NULL if the claim is not set, empty, or not a valid UUID +CREATE FUNCTION jwt_private.current_entity_id() + RETURNS uuid +AS $$ + SELECT CASE + WHEN pg_input_is_valid(current_setting('jwt.claims.entity_id', TRUE), 'uuid') + THEN current_setting('jwt.claims.entity_id', TRUE)::uuid + END; +$$ +LANGUAGE 'sql' STABLE LEAKPROOF PARALLEL SAFE; + +COMMIT; diff --git a/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_entity_type.sql b/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_entity_type.sql new file mode 100644 index 000000000..f5320f075 --- /dev/null +++ b/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_entity_type.sql @@ -0,0 +1,15 @@ +-- Deploy schemas/jwt_private/procedures/current_entity_type to pg +-- Retrieves the current entity type from JWT claims (private/internal use) + +-- requires: schemas/jwt_private/schema + +BEGIN; + +CREATE FUNCTION jwt_private.current_entity_type() + RETURNS text +AS $$ + SELECT NULLIF(current_setting('jwt.claims.entity_type', TRUE), ''); +$$ +LANGUAGE 'sql' STABLE LEAKPROOF PARALLEL SAFE; + +COMMIT; diff --git a/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_graph_execution_id.sql b/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_graph_execution_id.sql index 87f2c59d9..4cf912072 100644 --- a/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_graph_execution_id.sql +++ b/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_graph_execution_id.sql @@ -16,6 +16,6 @@ AS $$ THEN current_setting('jwt.claims.graph_execution_id', TRUE)::uuid END; $$ -LANGUAGE 'sql' STABLE LEAKPROOF; +LANGUAGE 'sql' STABLE LEAKPROOF PARALLEL SAFE; COMMIT; diff --git a/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_session_id.sql b/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_session_id.sql index ccf41ae0a..f83e5bf0e 100644 --- a/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_session_id.sql +++ b/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_session_id.sql @@ -13,6 +13,6 @@ CREATE FUNCTION jwt_private.current_session_id() AS $$ SELECT nullif(current_setting('jwt.claims.session_id', true), '')::uuid; $$ -LANGUAGE 'sql' STABLE; +LANGUAGE 'sql' STABLE LEAKPROOF PARALLEL SAFE; COMMIT; diff --git a/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_token_id.sql b/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_token_id.sql index 67d0cba98..f4b828b7c 100644 --- a/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_token_id.sql +++ b/packages/jwt-claims/deploy/schemas/jwt_private/procedures/current_token_id.sql @@ -12,7 +12,6 @@ CREATE FUNCTION jwt_private.current_token_id() AS $$ SELECT nullif(current_setting('jwt.claims.token_id', true), '')::uuid; $$ -LANGUAGE 'sql' STABLE; +LANGUAGE 'sql' STABLE LEAKPROOF PARALLEL SAFE; COMMIT; - diff --git a/packages/jwt-claims/deploy/schemas/jwt_private/procedures/require_database_id.sql b/packages/jwt-claims/deploy/schemas/jwt_private/procedures/require_database_id.sql new file mode 100644 index 000000000..ec886a0e2 --- /dev/null +++ b/packages/jwt-claims/deploy/schemas/jwt_private/procedures/require_database_id.sql @@ -0,0 +1,31 @@ +-- Deploy schemas/jwt_private/procedures/require_database_id to pg +-- Retrieves the required database ID from JWT claims (private/internal use) + +-- requires: schemas/jwt_private/schema + +BEGIN; + +-- This strict reader is deliberately not LEAKPROOF so PostgreSQL cannot push +-- its raising claim check into an RLS security barrier. +CREATE FUNCTION jwt_private.require_database_id() + RETURNS uuid +AS $$ +DECLARE + database_id uuid; +BEGIN + IF pg_input_is_valid(current_setting('jwt.claims.database_id', TRUE), 'uuid') THEN + database_id = current_setting('jwt.claims.database_id', TRUE)::uuid; + END IF; + IF database_id IS NULL THEN + PERFORM errors.raise_error( + 'DATABASE_CLAIM_REQUIRED', + jsonb_build_object('claim', 'jwt.claims.database_id'), + 'internal' + ); + END IF; + RETURN database_id; +END; +$$ +LANGUAGE 'plpgsql' STABLE PARALLEL SAFE; + +COMMIT; diff --git a/packages/jwt-claims/deploy/schemas/jwt_private/procedures/require_entity_id.sql b/packages/jwt-claims/deploy/schemas/jwt_private/procedures/require_entity_id.sql new file mode 100644 index 000000000..56cb850c6 --- /dev/null +++ b/packages/jwt-claims/deploy/schemas/jwt_private/procedures/require_entity_id.sql @@ -0,0 +1,31 @@ +-- Deploy schemas/jwt_private/procedures/require_entity_id to pg +-- Retrieves the required entity ID from JWT claims (private/internal use) + +-- requires: schemas/jwt_private/schema + +BEGIN; + +-- This strict reader is deliberately not LEAKPROOF so PostgreSQL cannot push +-- its raising claim check into an RLS security barrier. +CREATE FUNCTION jwt_private.require_entity_id() + RETURNS uuid +AS $$ +DECLARE + entity_id uuid; +BEGIN + IF pg_input_is_valid(current_setting('jwt.claims.entity_id', TRUE), 'uuid') THEN + entity_id = current_setting('jwt.claims.entity_id', TRUE)::uuid; + END IF; + IF entity_id IS NULL THEN + PERFORM errors.raise_error( + 'ENTITY_CLAIM_REQUIRED', + jsonb_build_object('claim', 'jwt.claims.entity_id'), + 'internal' + ); + END IF; + RETURN entity_id; +END; +$$ +LANGUAGE 'plpgsql' STABLE PARALLEL SAFE; + +COMMIT; diff --git a/packages/jwt-claims/deploy/schemas/jwt_private/procedures/require_entity_type.sql b/packages/jwt-claims/deploy/schemas/jwt_private/procedures/require_entity_type.sql new file mode 100644 index 000000000..513de2879 --- /dev/null +++ b/packages/jwt-claims/deploy/schemas/jwt_private/procedures/require_entity_type.sql @@ -0,0 +1,29 @@ +-- Deploy schemas/jwt_private/procedures/require_entity_type to pg +-- Retrieves the required entity type from JWT claims (private/internal use) + +-- requires: schemas/jwt_private/schema + +BEGIN; + +-- This strict reader is deliberately not LEAKPROOF so PostgreSQL cannot push +-- its raising claim check into an RLS security barrier. +CREATE FUNCTION jwt_private.require_entity_type() + RETURNS text +AS $$ +DECLARE + entity_type text; +BEGIN + entity_type = NULLIF(current_setting('jwt.claims.entity_type', TRUE), ''); + IF entity_type IS NULL THEN + PERFORM errors.raise_error( + 'ENTITY_TYPE_CLAIM_REQUIRED', + jsonb_build_object('claim', 'jwt.claims.entity_type'), + 'internal' + ); + END IF; + RETURN entity_type; +END; +$$ +LANGUAGE 'plpgsql' STABLE PARALLEL SAFE; + +COMMIT; diff --git a/packages/jwt-claims/deploy/schemas/jwt_public/procedures/current_ip_address.sql b/packages/jwt-claims/deploy/schemas/jwt_public/procedures/current_ip_address.sql index b5f93f106..54a710dc3 100644 --- a/packages/jwt-claims/deploy/schemas/jwt_public/procedures/current_ip_address.sql +++ b/packages/jwt-claims/deploy/schemas/jwt_public/procedures/current_ip_address.sql @@ -17,6 +17,6 @@ AS $$ THEN trim(current_setting('jwt.claims.ip_address', TRUE))::inet END; $$ -LANGUAGE 'sql' STABLE; +LANGUAGE 'sql' STABLE LEAKPROOF PARALLEL SAFE; COMMIT; diff --git a/packages/jwt-claims/deploy/schemas/jwt_public/procedures/current_origin.sql b/packages/jwt-claims/deploy/schemas/jwt_public/procedures/current_origin.sql index 99f33ccaa..431b80216 100644 --- a/packages/jwt-claims/deploy/schemas/jwt_public/procedures/current_origin.sql +++ b/packages/jwt-claims/deploy/schemas/jwt_public/procedures/current_origin.sql @@ -12,7 +12,6 @@ CREATE FUNCTION jwt_public.current_origin() AS $$ SELECT nullif(current_setting('jwt.claims.origin', true), '')::origin; $$ -LANGUAGE 'sql' STABLE; +LANGUAGE 'sql' STABLE LEAKPROOF PARALLEL SAFE; COMMIT; - diff --git a/packages/jwt-claims/deploy/schemas/jwt_public/procedures/current_principal_id.sql b/packages/jwt-claims/deploy/schemas/jwt_public/procedures/current_principal_id.sql index ffac56624..a5f2ffd79 100644 --- a/packages/jwt-claims/deploy/schemas/jwt_public/procedures/current_principal_id.sql +++ b/packages/jwt-claims/deploy/schemas/jwt_public/procedures/current_principal_id.sql @@ -17,6 +17,6 @@ AS $$ THEN current_setting('jwt.claims.principal_id', TRUE)::uuid END; $$ -LANGUAGE 'sql' STABLE LEAKPROOF; +LANGUAGE 'sql' STABLE LEAKPROOF PARALLEL SAFE; COMMIT; diff --git a/packages/jwt-claims/deploy/schemas/jwt_public/procedures/current_role_type.sql b/packages/jwt-claims/deploy/schemas/jwt_public/procedures/current_role_type.sql index e846ec60d..a00057369 100644 --- a/packages/jwt-claims/deploy/schemas/jwt_public/procedures/current_role_type.sql +++ b/packages/jwt-claims/deploy/schemas/jwt_public/procedures/current_role_type.sql @@ -13,6 +13,6 @@ CREATE FUNCTION jwt_public.current_role_type() AS $$ SELECT coalesce(nullif(current_setting('jwt.claims.role_type', TRUE), ''), 'user'); $$ -LANGUAGE 'sql' STABLE LEAKPROOF; +LANGUAGE 'sql' STABLE LEAKPROOF PARALLEL SAFE; COMMIT; diff --git a/packages/jwt-claims/deploy/schemas/jwt_public/procedures/current_user_agent.sql b/packages/jwt-claims/deploy/schemas/jwt_public/procedures/current_user_agent.sql index 514903621..5b50b7629 100644 --- a/packages/jwt-claims/deploy/schemas/jwt_public/procedures/current_user_agent.sql +++ b/packages/jwt-claims/deploy/schemas/jwt_public/procedures/current_user_agent.sql @@ -13,6 +13,6 @@ CREATE FUNCTION jwt_public.current_user_agent() AS $$ SELECT current_setting('jwt.claims.user_agent', TRUE); $$ -LANGUAGE 'sql' STABLE; +LANGUAGE 'sql' STABLE LEAKPROOF PARALLEL SAFE; COMMIT; diff --git a/packages/jwt-claims/deploy/schemas/jwt_public/procedures/current_user_id.sql b/packages/jwt-claims/deploy/schemas/jwt_public/procedures/current_user_id.sql index 56d166913..e5cfbb646 100644 --- a/packages/jwt-claims/deploy/schemas/jwt_public/procedures/current_user_id.sql +++ b/packages/jwt-claims/deploy/schemas/jwt_public/procedures/current_user_id.sql @@ -16,6 +16,6 @@ AS $$ THEN current_setting('jwt.claims.user_id', TRUE)::uuid END; $$ -LANGUAGE 'sql' STABLE LEAKPROOF; +LANGUAGE 'sql' STABLE LEAKPROOF PARALLEL SAFE; COMMIT; diff --git a/packages/jwt-claims/deploy/schemas/jwt_public/procedures/require_user_id.sql b/packages/jwt-claims/deploy/schemas/jwt_public/procedures/require_user_id.sql new file mode 100644 index 000000000..6470f0541 --- /dev/null +++ b/packages/jwt-claims/deploy/schemas/jwt_public/procedures/require_user_id.sql @@ -0,0 +1,31 @@ +-- Deploy schemas/jwt_public/procedures/require_user_id to pg +-- Retrieves the required user ID from JWT claims + +-- requires: schemas/jwt_public/schema + +BEGIN; + +-- This strict reader is deliberately not LEAKPROOF so PostgreSQL cannot push +-- its raising claim check into an RLS security barrier. +CREATE FUNCTION jwt_public.require_user_id() + RETURNS uuid +AS $$ +DECLARE + user_id uuid; +BEGIN + IF pg_input_is_valid(current_setting('jwt.claims.user_id', TRUE), 'uuid') THEN + user_id = current_setting('jwt.claims.user_id', TRUE)::uuid; + END IF; + IF user_id IS NULL THEN + PERFORM errors.raise_error( + 'ACTOR_CLAIM_REQUIRED', + jsonb_build_object('claim', 'jwt.claims.user_id'), + 'internal' + ); + END IF; + RETURN user_id; +END; +$$ +LANGUAGE 'plpgsql' STABLE PARALLEL SAFE; + +COMMIT; diff --git a/packages/jwt-claims/pgpm.plan b/packages/jwt-claims/pgpm.plan index bec8a1749..4852bb805 100644 --- a/packages/jwt-claims/pgpm.plan +++ b/packages/jwt-claims/pgpm.plan @@ -17,7 +17,14 @@ schemas/jwt_public/procedures/current_principal_id [schemas/jwt_public/schema] 2 schemas/jwt_public/procedures/current_role_type [schemas/jwt_public/schema] 2026-06-28T08:00:00Z Dan Lynch # add schemas/jwt_public/procedures/current_role_type schemas/jwt_private/schema 2020-12-17T06:47:34Z Dan Lynch # add schemas/jwt_private/schema schemas/jwt_private/procedures/current_database_id [schemas/jwt_private/schema] 2020-12-17T23:22:28Z Dan Lynch # add schemas/jwt_private/procedures/current_database_id +schemas/jwt_private/procedures/require_database_id [schemas/jwt_private/schema] 2026-08-23T03:39:19Z Dan Lynch # add schemas/jwt_private/procedures/require_database_id schemas/jwt_private/procedures/current_token_id [schemas/jwt_private/schema] 2017-08-11T08:11:51Z skitch # add schemas/jwt_private/procedures/current_token_id schemas/jwt_private/procedures/current_session_id [schemas/jwt_private/schema] 2026-01-28T05:44:00Z Dan Lynch # add schemas/jwt_private/procedures/current_session_id schemas/jwt_private/procedures/current_api_id [schemas/jwt_private/schema] 2026-07-12T13:40:27Z Dan Lynch # add schemas/jwt_private/procedures/current_api_id schemas/jwt_private/procedures/current_graph_execution_id [schemas/jwt_private/schema] 2026-07-16T09:17:00Z Dan Lynch # add schemas/jwt_private/procedures/current_graph_execution_id +schemas/jwt_private/procedures/current_entity_id [schemas/jwt_private/schema] 2026-07-17T00:00:00Z Dan Lynch # add schemas/jwt_private/procedures/current_entity_id +schemas/jwt_private/procedures/current_entity_type [schemas/jwt_private/schema] 2026-07-17T00:00:00Z Dan Lynch # add schemas/jwt_private/procedures/current_entity_type +schemas/jwt_public/procedures/require_user_id [schemas/jwt_public/schema] 2026-08-23T11:44:08Z devin # add strict user claim reader +schemas/jwt_private/procedures/require_entity_id [schemas/jwt_private/schema] 2026-08-23T11:44:08Z devin # add strict entity claim reader +schemas/jwt_private/procedures/require_entity_type [schemas/jwt_private/schema] 2026-08-23T11:44:08Z devin # add strict entity type claim reader +schemas/jwt_private/procedures/assert_attribution [schemas/jwt_private/schema] 2026-08-23T11:44:08Z devin # enforce actor or entity attribution diff --git a/packages/jwt-claims/revert/schemas/jwt_private/procedures/assert_attribution.sql b/packages/jwt-claims/revert/schemas/jwt_private/procedures/assert_attribution.sql new file mode 100644 index 000000000..87ce0847d --- /dev/null +++ b/packages/jwt-claims/revert/schemas/jwt_private/procedures/assert_attribution.sql @@ -0,0 +1,7 @@ +-- Revert schemas/jwt_private/procedures/assert_attribution from pg + +BEGIN; + +DROP FUNCTION jwt_private.assert_attribution(uuid, uuid, text); + +COMMIT; diff --git a/packages/jwt-claims/revert/schemas/jwt_private/procedures/current_entity_id.sql b/packages/jwt-claims/revert/schemas/jwt_private/procedures/current_entity_id.sql new file mode 100644 index 000000000..9fe9a9a6a --- /dev/null +++ b/packages/jwt-claims/revert/schemas/jwt_private/procedures/current_entity_id.sql @@ -0,0 +1,7 @@ +-- Revert schemas/jwt_private/procedures/current_entity_id from pg + +BEGIN; + +DROP FUNCTION jwt_private.current_entity_id(); + +COMMIT; diff --git a/packages/jwt-claims/revert/schemas/jwt_private/procedures/current_entity_type.sql b/packages/jwt-claims/revert/schemas/jwt_private/procedures/current_entity_type.sql new file mode 100644 index 000000000..182e5a161 --- /dev/null +++ b/packages/jwt-claims/revert/schemas/jwt_private/procedures/current_entity_type.sql @@ -0,0 +1,7 @@ +-- Revert schemas/jwt_private/procedures/current_entity_type from pg + +BEGIN; + +DROP FUNCTION jwt_private.current_entity_type(); + +COMMIT; diff --git a/packages/jwt-claims/revert/schemas/jwt_private/procedures/require_database_id.sql b/packages/jwt-claims/revert/schemas/jwt_private/procedures/require_database_id.sql new file mode 100644 index 000000000..8570994f2 --- /dev/null +++ b/packages/jwt-claims/revert/schemas/jwt_private/procedures/require_database_id.sql @@ -0,0 +1,7 @@ +-- Revert schemas/jwt_private/procedures/require_database_id from pg + +BEGIN; + +DROP FUNCTION jwt_private.require_database_id(); + +COMMIT; diff --git a/packages/jwt-claims/revert/schemas/jwt_private/procedures/require_entity_id.sql b/packages/jwt-claims/revert/schemas/jwt_private/procedures/require_entity_id.sql new file mode 100644 index 000000000..eb7ade47f --- /dev/null +++ b/packages/jwt-claims/revert/schemas/jwt_private/procedures/require_entity_id.sql @@ -0,0 +1,7 @@ +-- Revert schemas/jwt_private/procedures/require_entity_id from pg + +BEGIN; + +DROP FUNCTION jwt_private.require_entity_id(); + +COMMIT; diff --git a/packages/jwt-claims/revert/schemas/jwt_private/procedures/require_entity_type.sql b/packages/jwt-claims/revert/schemas/jwt_private/procedures/require_entity_type.sql new file mode 100644 index 000000000..0aa7b4a7d --- /dev/null +++ b/packages/jwt-claims/revert/schemas/jwt_private/procedures/require_entity_type.sql @@ -0,0 +1,7 @@ +-- Revert schemas/jwt_private/procedures/require_entity_type from pg + +BEGIN; + +DROP FUNCTION jwt_private.require_entity_type(); + +COMMIT; diff --git a/packages/jwt-claims/revert/schemas/jwt_public/procedures/require_user_id.sql b/packages/jwt-claims/revert/schemas/jwt_public/procedures/require_user_id.sql new file mode 100644 index 000000000..886988f9c --- /dev/null +++ b/packages/jwt-claims/revert/schemas/jwt_public/procedures/require_user_id.sql @@ -0,0 +1,7 @@ +-- Revert schemas/jwt_public/procedures/require_user_id from pg + +BEGIN; + +DROP FUNCTION jwt_public.require_user_id(); + +COMMIT; diff --git a/packages/jwt-claims/sql/pgpm-jwt-claims--0.43.1.bundle.tar.gz b/packages/jwt-claims/sql/pgpm-jwt-claims--0.43.1.bundle.tar.gz index 91f73aa12486c8c418a04dcb26224772c5238486..50d0045b0800746becf730604afd5120998e0d33 100644 GIT binary patch literal 10917 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 diff --git a/packages/jwt-claims/sql/pgpm-jwt-claims--0.43.1.sql b/packages/jwt-claims/sql/pgpm-jwt-claims--0.43.1.sql index 258a44bb7..ca5a41a59 100644 --- a/packages/jwt-claims/sql/pgpm-jwt-claims--0.43.1.sql +++ b/packages/jwt-claims/sql/pgpm-jwt-claims--0.43.1.sql @@ -8,19 +8,19 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA ctx CREATE FUNCTION ctx.ip_address() RETURNS inet AS $EOFCODE$ SELECT nullif(current_setting('jwt.claims.ip_address', true), '')::inet; -$EOFCODE$ LANGUAGE sql STABLE; +$EOFCODE$ LANGUAGE sql STABLE LEAKPROOF PARALLEL safe; CREATE FUNCTION ctx.origin() RETURNS origin AS $EOFCODE$ SELECT nullif(current_setting('jwt.claims.origin', true), '')::origin; -$EOFCODE$ LANGUAGE sql STABLE; +$EOFCODE$ LANGUAGE sql STABLE LEAKPROOF PARALLEL safe; CREATE FUNCTION ctx.uagent() RETURNS text AS $EOFCODE$ SELECT nullif(current_setting('jwt.claims.user_agent', true), ''); -$EOFCODE$ LANGUAGE sql STABLE; +$EOFCODE$ LANGUAGE sql STABLE LEAKPROOF PARALLEL safe; CREATE FUNCTION ctx.uid() RETURNS uuid AS $EOFCODE$ SELECT nullif(current_setting('jwt.claims.user_id', true), '')::uuid; -$EOFCODE$ LANGUAGE sql STABLE; +$EOFCODE$ LANGUAGE sql STABLE LEAKPROOF PARALLEL safe; DO $EOFCODE$ DECLARE @@ -52,33 +52,33 @@ CREATE FUNCTION jwt_public.current_user_id() RETURNS uuid AS $EOFCODE$ WHEN pg_input_is_valid(current_setting('jwt.claims.user_id', TRUE), 'uuid') THEN current_setting('jwt.claims.user_id', TRUE)::uuid END; -$EOFCODE$ LANGUAGE sql STABLE LEAKPROOF; +$EOFCODE$ LANGUAGE sql STABLE LEAKPROOF PARALLEL safe; CREATE FUNCTION jwt_public.current_ip_address() RETURNS inet AS $EOFCODE$ SELECT CASE WHEN pg_input_is_valid(trim(current_setting('jwt.claims.ip_address', TRUE)), 'inet') THEN trim(current_setting('jwt.claims.ip_address', TRUE))::inet END; -$EOFCODE$ LANGUAGE sql STABLE; +$EOFCODE$ LANGUAGE sql STABLE LEAKPROOF PARALLEL safe; CREATE FUNCTION jwt_public.current_user_agent() RETURNS text AS $EOFCODE$ SELECT current_setting('jwt.claims.user_agent', TRUE); -$EOFCODE$ LANGUAGE sql STABLE; +$EOFCODE$ LANGUAGE sql STABLE LEAKPROOF PARALLEL safe; CREATE FUNCTION jwt_public.current_origin() RETURNS origin AS $EOFCODE$ SELECT nullif(current_setting('jwt.claims.origin', true), '')::origin; -$EOFCODE$ LANGUAGE sql STABLE; +$EOFCODE$ LANGUAGE sql STABLE LEAKPROOF PARALLEL safe; CREATE FUNCTION jwt_public.current_principal_id() RETURNS uuid AS $EOFCODE$ SELECT CASE WHEN pg_input_is_valid(current_setting('jwt.claims.principal_id', TRUE), 'uuid') THEN current_setting('jwt.claims.principal_id', TRUE)::uuid END; -$EOFCODE$ LANGUAGE sql STABLE LEAKPROOF; +$EOFCODE$ LANGUAGE sql STABLE LEAKPROOF PARALLEL safe; CREATE FUNCTION jwt_public.current_role_type() RETURNS text AS $EOFCODE$ SELECT coalesce(nullif(current_setting('jwt.claims.role_type', TRUE), ''), 'user'); -$EOFCODE$ LANGUAGE sql STABLE LEAKPROOF; +$EOFCODE$ LANGUAGE sql STABLE LEAKPROOF PARALLEL safe; CREATE SCHEMA jwt_private; @@ -88,6 +88,13 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA jwt_private GRANT EXECUTE ON FUNCTIONS TO authenticated; CREATE FUNCTION jwt_private.current_database_id() RETURNS uuid AS $EOFCODE$ + SELECT CASE + WHEN pg_input_is_valid(current_setting('jwt.claims.database_id', TRUE), 'uuid') + THEN current_setting('jwt.claims.database_id', TRUE)::uuid + END; +$EOFCODE$ LANGUAGE sql STABLE LEAKPROOF PARALLEL safe; + +CREATE FUNCTION jwt_private.require_database_id() RETURNS uuid AS $EOFCODE$ DECLARE database_id uuid; BEGIN @@ -103,26 +110,153 @@ BEGIN END IF; RETURN database_id; END; -$EOFCODE$ LANGUAGE plpgsql STABLE LEAKPROOF; +$EOFCODE$ LANGUAGE plpgsql STABLE PARALLEL safe; CREATE FUNCTION jwt_private.current_token_id() RETURNS uuid AS $EOFCODE$ SELECT nullif(current_setting('jwt.claims.token_id', true), '')::uuid; -$EOFCODE$ LANGUAGE sql STABLE; +$EOFCODE$ LANGUAGE sql STABLE LEAKPROOF PARALLEL safe; CREATE FUNCTION jwt_private.current_session_id() RETURNS uuid AS $EOFCODE$ SELECT nullif(current_setting('jwt.claims.session_id', true), '')::uuid; -$EOFCODE$ LANGUAGE sql STABLE; +$EOFCODE$ LANGUAGE sql STABLE LEAKPROOF PARALLEL safe; CREATE FUNCTION jwt_private.current_api_id() RETURNS uuid AS $EOFCODE$ SELECT CASE WHEN pg_input_is_valid(current_setting('jwt.claims.api_id', TRUE), 'uuid') THEN current_setting('jwt.claims.api_id', TRUE)::uuid END; -$EOFCODE$ LANGUAGE sql STABLE LEAKPROOF; +$EOFCODE$ LANGUAGE sql STABLE LEAKPROOF PARALLEL safe; CREATE FUNCTION jwt_private.current_graph_execution_id() RETURNS uuid AS $EOFCODE$ SELECT CASE WHEN pg_input_is_valid(current_setting('jwt.claims.graph_execution_id', TRUE), 'uuid') THEN current_setting('jwt.claims.graph_execution_id', TRUE)::uuid END; -$EOFCODE$ LANGUAGE sql STABLE LEAKPROOF; \ No newline at end of file +$EOFCODE$ LANGUAGE sql STABLE LEAKPROOF PARALLEL safe; + +CREATE FUNCTION jwt_private.current_entity_id() RETURNS uuid AS $EOFCODE$ + SELECT CASE + WHEN pg_input_is_valid(current_setting('jwt.claims.entity_id', TRUE), 'uuid') + THEN current_setting('jwt.claims.entity_id', TRUE)::uuid + END; +$EOFCODE$ LANGUAGE sql STABLE LEAKPROOF PARALLEL safe; + +CREATE FUNCTION jwt_private.current_entity_type() RETURNS text AS $EOFCODE$ + SELECT NULLIF(current_setting('jwt.claims.entity_type', TRUE), ''); +$EOFCODE$ LANGUAGE sql STABLE LEAKPROOF PARALLEL safe; + +CREATE FUNCTION jwt_public.require_user_id() RETURNS uuid AS $EOFCODE$ +DECLARE + user_id uuid; +BEGIN + IF pg_input_is_valid(current_setting('jwt.claims.user_id', TRUE), 'uuid') THEN + user_id = current_setting('jwt.claims.user_id', TRUE)::uuid; + END IF; + IF user_id IS NULL THEN + PERFORM errors.raise_error( + 'ACTOR_CLAIM_REQUIRED', + jsonb_build_object('claim', 'jwt.claims.user_id'), + 'internal' + ); + END IF; + RETURN user_id; +END; +$EOFCODE$ LANGUAGE plpgsql STABLE PARALLEL safe; + +CREATE FUNCTION jwt_private.require_entity_id() RETURNS uuid AS $EOFCODE$ +DECLARE + entity_id uuid; +BEGIN + IF pg_input_is_valid(current_setting('jwt.claims.entity_id', TRUE), 'uuid') THEN + entity_id = current_setting('jwt.claims.entity_id', TRUE)::uuid; + END IF; + IF entity_id IS NULL THEN + PERFORM errors.raise_error( + 'ENTITY_CLAIM_REQUIRED', + jsonb_build_object('claim', 'jwt.claims.entity_id'), + 'internal' + ); + END IF; + RETURN entity_id; +END; +$EOFCODE$ LANGUAGE plpgsql STABLE PARALLEL safe; + +CREATE FUNCTION jwt_private.require_entity_type() RETURNS text AS $EOFCODE$ +DECLARE + entity_type text; +BEGIN + entity_type = NULLIF(current_setting('jwt.claims.entity_type', TRUE), ''); + IF entity_type IS NULL THEN + PERFORM errors.raise_error( + 'ENTITY_TYPE_CLAIM_REQUIRED', + jsonb_build_object('claim', 'jwt.claims.entity_type'), + 'internal' + ); + END IF; + RETURN entity_type; +END; +$EOFCODE$ LANGUAGE plpgsql STABLE PARALLEL safe; + +CREATE FUNCTION jwt_private.assert_attribution( + actor_id uuid, + entity_id uuid, + entity_type text +) RETURNS void AS $EOFCODE$ +DECLARE + strict_attribution boolean := + COALESCE( + NULLIF(current_setting('jwt.strict_attribution', true), ''), + 'true' + ) <> 'false'; + context jsonb; +BEGIN + IF actor_id IS NULL AND entity_id IS NULL THEN + context := jsonb_build_object( + 'arguments', jsonb_build_array('actor_id', 'entity_id'), + 'claims', jsonb_build_array('jwt.claims.user_id', 'jwt.claims.entity_id') + ); + IF strict_attribution THEN + PERFORM errors.raise_error('ATTRIBUTION_REQUIRED', context, 'internal'); + ELSE + RAISE WARNING '%', + jsonb_build_object( + 'code', 'ATTRIBUTION_REQUIRED', + 'class', 'internal', + 'context', context + ); + END IF; + ELSIF entity_id IS NOT NULL AND entity_type IS NULL THEN + context := jsonb_build_object( + 'argument', 'entity_type', + 'claim', 'jwt.claims.entity_type', + 'entity_id', entity_id + ); + IF strict_attribution THEN + PERFORM errors.raise_error('ENTITY_TYPE_REQUIRED', context, 'internal'); + ELSE + RAISE WARNING '%', + jsonb_build_object( + 'code', 'ENTITY_TYPE_REQUIRED', + 'class', 'internal', + 'context', context + ); + END IF; + ELSIF entity_type IS NOT NULL AND entity_id IS NULL THEN + context := jsonb_build_object( + 'argument', 'entity_id', + 'claim', 'jwt.claims.entity_id', + 'entity_type', entity_type + ); + IF strict_attribution THEN + PERFORM errors.raise_error('ENTITY_ID_REQUIRED', context, 'internal'); + ELSE + RAISE WARNING '%', + jsonb_build_object( + 'code', 'ENTITY_ID_REQUIRED', + 'class', 'internal', + 'context', context + ); + END IF; + END IF; +END; +$EOFCODE$ LANGUAGE plpgsql STABLE PARALLEL safe; \ No newline at end of file diff --git a/packages/jwt-claims/verify/schemas/jwt_private/procedures/assert_attribution.sql b/packages/jwt-claims/verify/schemas/jwt_private/procedures/assert_attribution.sql new file mode 100644 index 000000000..b6491e870 --- /dev/null +++ b/packages/jwt-claims/verify/schemas/jwt_private/procedures/assert_attribution.sql @@ -0,0 +1,7 @@ +-- Verify schemas/jwt_private/procedures/assert_attribution on pg + +BEGIN; + +SELECT assert_function('jwt_private.assert_attribution(uuid, uuid, text)'::regprocedure); + +ROLLBACK; diff --git a/packages/jwt-claims/verify/schemas/jwt_private/procedures/current_entity_id.sql b/packages/jwt-claims/verify/schemas/jwt_private/procedures/current_entity_id.sql new file mode 100644 index 000000000..fe0ad1026 --- /dev/null +++ b/packages/jwt-claims/verify/schemas/jwt_private/procedures/current_entity_id.sql @@ -0,0 +1,14 @@ +-- Verify schemas/jwt_private/procedures/current_entity_id on pg + +BEGIN; + +SELECT 1 / CASE WHEN EXISTS ( + SELECT 1 + FROM pg_proc p + JOIN pg_namespace n ON n.oid = p.pronamespace + WHERE n.nspname = 'jwt_private' + AND p.proname = 'current_entity_id' + AND p.proargtypes = '' +) THEN 1 ELSE 0 END; + +ROLLBACK; diff --git a/packages/jwt-claims/verify/schemas/jwt_private/procedures/current_entity_type.sql b/packages/jwt-claims/verify/schemas/jwt_private/procedures/current_entity_type.sql new file mode 100644 index 000000000..2a61caea3 --- /dev/null +++ b/packages/jwt-claims/verify/schemas/jwt_private/procedures/current_entity_type.sql @@ -0,0 +1,14 @@ +-- Verify schemas/jwt_private/procedures/current_entity_type on pg + +BEGIN; + +SELECT 1 / CASE WHEN EXISTS ( + SELECT 1 + FROM pg_proc p + JOIN pg_namespace n ON n.oid = p.pronamespace + WHERE n.nspname = 'jwt_private' + AND p.proname = 'current_entity_type' + AND p.proargtypes = '' +) THEN 1 ELSE 0 END; + +ROLLBACK; diff --git a/packages/jwt-claims/verify/schemas/jwt_private/procedures/require_database_id.sql b/packages/jwt-claims/verify/schemas/jwt_private/procedures/require_database_id.sql new file mode 100644 index 000000000..756d16d24 --- /dev/null +++ b/packages/jwt-claims/verify/schemas/jwt_private/procedures/require_database_id.sql @@ -0,0 +1,7 @@ +-- Verify schemas/jwt_private/procedures/require_database_id on pg + +BEGIN; + +SELECT assert_function('jwt_private.require_database_id()'::regprocedure); + +ROLLBACK; diff --git a/packages/jwt-claims/verify/schemas/jwt_private/procedures/require_entity_id.sql b/packages/jwt-claims/verify/schemas/jwt_private/procedures/require_entity_id.sql new file mode 100644 index 000000000..a031721dc --- /dev/null +++ b/packages/jwt-claims/verify/schemas/jwt_private/procedures/require_entity_id.sql @@ -0,0 +1,7 @@ +-- Verify schemas/jwt_private/procedures/require_entity_id on pg + +BEGIN; + +SELECT assert_function('jwt_private.require_entity_id()'::regprocedure); + +ROLLBACK; diff --git a/packages/jwt-claims/verify/schemas/jwt_private/procedures/require_entity_type.sql b/packages/jwt-claims/verify/schemas/jwt_private/procedures/require_entity_type.sql new file mode 100644 index 000000000..321411d4d --- /dev/null +++ b/packages/jwt-claims/verify/schemas/jwt_private/procedures/require_entity_type.sql @@ -0,0 +1,7 @@ +-- Verify schemas/jwt_private/procedures/require_entity_type on pg + +BEGIN; + +SELECT assert_function('jwt_private.require_entity_type()'::regprocedure); + +ROLLBACK; diff --git a/packages/jwt-claims/verify/schemas/jwt_public/procedures/require_user_id.sql b/packages/jwt-claims/verify/schemas/jwt_public/procedures/require_user_id.sql new file mode 100644 index 000000000..671213a9c --- /dev/null +++ b/packages/jwt-claims/verify/schemas/jwt_public/procedures/require_user_id.sql @@ -0,0 +1,7 @@ +-- Verify schemas/jwt_public/procedures/require_user_id on pg + +BEGIN; + +SELECT assert_function('jwt_public.require_user_id()'::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 38ded3134..8feb32b7b 100644 --- a/packages/metaschema-modules/__tests__/__snapshots__/modules.test.ts.snap +++ b/packages/metaschema-modules/__tests__/__snapshots__/modules.test.ts.snap @@ -158,7 +158,7 @@ exports[`db_meta_modules should verify module table structures have database_id exports[`db_meta_modules should verify module tables have proper foreign key relationships 1`] = ` { - "constraintCount": 551, + "constraintCount": 558, "foreignTables": [ "catalog_module", "database", 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 ddd06ca70..c4326dd5a 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 @@ -30,6 +30,7 @@ CREATE TABLE metaschema_modules_public.agent_module ( agent_table_id uuid DEFAULT NULL, persona_table_id uuid DEFAULT NULL, resource_table_id uuid DEFAULT NULL, + resource_repository_table_id uuid DEFAULT NULL, run_table_id uuid DEFAULT NULL, event_table_id uuid DEFAULT NULL, workspace_table_id uuid DEFAULT NULL, @@ -43,6 +44,7 @@ CREATE TABLE metaschema_modules_public.agent_module ( agent_table_name text NOT NULL DEFAULT 'agent', persona_table_name text NOT NULL DEFAULT 'agent_persona', resource_table_name text NOT NULL DEFAULT 'agent_resource', + resource_repository_table_name text NOT NULL DEFAULT 'agent_resource_repository', run_table_name text NOT NULL DEFAULT 'agent_run', event_table_name text NOT NULL DEFAULT 'agent_event', workspace_table_name text NOT NULL DEFAULT 'agent_run_workspace', @@ -51,6 +53,12 @@ CREATE TABLE metaschema_modules_public.agent_module ( has_plans boolean NOT NULL DEFAULT false, has_resources boolean NOT NULL DEFAULT false, has_agents boolean NOT NULL DEFAULT false, + -- Attaches resources to the repositories they apply to, through a junction + -- table. Requires a repository module at this same scope and has_resources: + -- with no repository catalog to point at there is nothing to attach to, and + -- the junction is simply not created. Same scope only, as everywhere else — a + -- pointer into another scope's catalog is not adjudicable by any policy. + has_repository_resources boolean NOT NULL DEFAULT false, -- The coding-agent execution surface: runs and their append-only transcripts. -- Off by default, so a conversation-only install provisions exactly what it did -- before this flag existed. @@ -111,6 +119,7 @@ CREATE TABLE metaschema_modules_public.agent_module ( CONSTRAINT agent_module_agent_table_fkey FOREIGN KEY (agent_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT agent_module_persona_table_fkey FOREIGN KEY (persona_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT agent_module_resource_table_fkey FOREIGN KEY (resource_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT agent_module_resource_repository_table_fkey FOREIGN KEY (resource_repository_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT agent_module_run_table_fkey FOREIGN KEY (run_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT agent_module_event_table_fkey FOREIGN KEY (event_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT agent_module_workspace_table_fkey FOREIGN KEY (workspace_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, @@ -126,6 +135,7 @@ CREATE INDEX agent_module_persona_table_id_idx ON metaschema_modules_public.agen CREATE INDEX agent_module_plan_table_id_idx ON metaschema_modules_public.agent_module ( plan_table_id ); CREATE INDEX agent_module_prompts_table_id_idx ON metaschema_modules_public.agent_module ( prompts_table_id ); CREATE INDEX agent_module_resource_table_id_idx ON metaschema_modules_public.agent_module ( resource_table_id ); +CREATE INDEX agent_module_resource_repository_table_id_idx ON metaschema_modules_public.agent_module ( resource_repository_table_id ); CREATE INDEX agent_module_run_table_id_idx ON metaschema_modules_public.agent_module ( run_table_id ); CREATE INDEX agent_module_event_table_id_idx ON metaschema_modules_public.agent_module ( event_table_id ); CREATE INDEX agent_module_workspace_table_id_idx ON metaschema_modules_public.agent_module ( workspace_table_id ); @@ -144,6 +154,7 @@ COMMENT ON COLUMN metaschema_modules_public.agent_module.persona_table_id IS '@m COMMENT ON COLUMN metaschema_modules_public.agent_module.plan_table_id IS '@module_table'; COMMENT ON COLUMN metaschema_modules_public.agent_module.prompts_table_id IS '@module_table'; COMMENT ON COLUMN metaschema_modules_public.agent_module.resource_table_id IS '@module_table'; +COMMENT ON COLUMN metaschema_modules_public.agent_module.resource_repository_table_id IS '@module_table'; COMMENT ON COLUMN metaschema_modules_public.agent_module.task_table_id IS '@module_table'; COMMENT ON COLUMN metaschema_modules_public.agent_module.thread_table_id IS '@module_table'; COMMENT ON COLUMN metaschema_modules_public.agent_module.run_table_id IS '@module_table'; 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 6f5b8ef4b..85db607c8 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 @@ -33,6 +33,10 @@ CREATE TABLE metaschema_modules_public.app_module ( -- Table names (input to the generator) apps_table_name text NOT NULL DEFAULT 'apps', app_components_table_name text NOT NULL DEFAULT 'app_components', + -- Mobile-store identity owned by the app (platform, bundle id / package + -- name, team id, signing fingerprints, store URL). A site's app-link + -- association names a row here rather than restating it per host. + app_store_identities_table_name text NOT NULL DEFAULT 'app_store_identities', -- API routing (get-or-create: if set, schema is added to this API) api_name text, diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/billing_provider_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/billing_provider_module/table.sql index 68558e4f6..1caad4c3d 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/billing_provider_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/billing_provider_module/table.sql @@ -43,6 +43,9 @@ CREATE TABLE metaschema_modules_public.billing_provider_module ( billing_invoices_table_id uuid NOT NULL DEFAULT uuid_nil(), billing_invoices_table_name text NOT NULL DEFAULT '', + billing_disputes_table_id uuid NOT NULL DEFAULT uuid_nil(), + billing_disputes_table_name text NOT NULL DEFAULT '', + -- Generated functions process_billing_event_function text NOT NULL DEFAULT '', record_refund_function text NOT NULL DEFAULT '', @@ -51,6 +54,27 @@ CREATE TABLE metaschema_modules_public.billing_provider_module ( -- the provider's subscription item, which only this module knows about. list_pending_usage_sync_function text NOT NULL DEFAULT '', mark_usage_synced_function text NOT NULL DEFAULT '', + -- Reconcile seams: the system-only read/write path the billing sync workers + -- use to mirror provider objects into the mapping tables. + get_billing_customer_function text NOT NULL DEFAULT '', + upsert_billing_customer_function text NOT NULL DEFAULT '', + get_billing_product_function text NOT NULL DEFAULT '', + upsert_billing_product_function text NOT NULL DEFAULT '', + get_billing_price_function text NOT NULL DEFAULT '', + upsert_billing_price_function text NOT NULL DEFAULT '', + get_billing_subscription_function text NOT NULL DEFAULT '', + upsert_billing_subscription_function text NOT NULL DEFAULT '', + sweep_overdue_subscriptions_function text NOT NULL DEFAULT '', + -- Read seams over the plan tables the module points at: which active pricing + -- a plan bills through, and which plan an overdue entity falls back to. + get_active_plan_pricing_function text NOT NULL DEFAULT '', + get_fallback_free_plan_function text NOT NULL DEFAULT '', + -- Disputes are recorded separately from refunds: the bank opens them, they + -- are adjudicated, and credits are clawed back only on a lost outcome. + record_dispute_function text NOT NULL DEFAULT '', + -- The webhook side of a completed purchase: the system-only seam that moves + -- an entity onto the plan a verified provider event names. + activate_plan_subscription_function text NOT NULL DEFAULT '', prefix text NULL, @@ -68,6 +92,7 @@ CREATE TABLE metaschema_modules_public.billing_provider_module ( CONSTRAINT billing_webhook_events_table_fkey FOREIGN KEY (billing_webhook_events_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT billing_refunds_table_fkey FOREIGN KEY (billing_refunds_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT billing_invoices_table_fkey FOREIGN KEY (billing_invoices_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT billing_disputes_table_fkey FOREIGN KEY (billing_disputes_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT products_table_fkey FOREIGN KEY (products_table_id) REFERENCES metaschema_public.table (id) ON DELETE SET NULL, CONSTRAINT prices_table_fkey FOREIGN KEY (prices_table_id) REFERENCES metaschema_public.table (id) ON DELETE SET NULL, CONSTRAINT subscriptions_table_fkey FOREIGN KEY (subscriptions_table_id) REFERENCES metaschema_public.table (id) ON DELETE SET NULL, @@ -81,6 +106,7 @@ CREATE INDEX billing_provider_module_billing_subscriptions_table_id_idx ON metas CREATE INDEX billing_provider_module_billing_webhook_events_table_id_idx ON metaschema_modules_public.billing_provider_module ( billing_webhook_events_table_id ); CREATE INDEX billing_provider_module_billing_refunds_table_id_idx ON metaschema_modules_public.billing_provider_module ( billing_refunds_table_id ); CREATE INDEX billing_provider_module_billing_invoices_table_id_idx ON metaschema_modules_public.billing_provider_module ( billing_invoices_table_id ); +CREATE INDEX billing_provider_module_billing_disputes_table_id_idx ON metaschema_modules_public.billing_provider_module ( billing_disputes_table_id ); CREATE INDEX billing_provider_module_prices_table_id_idx ON metaschema_modules_public.billing_provider_module ( prices_table_id ); CREATE INDEX billing_provider_module_products_table_id_idx ON metaschema_modules_public.billing_provider_module ( products_table_id ); CREATE INDEX billing_provider_module_subscriptions_table_id_idx ON metaschema_modules_public.billing_provider_module ( subscriptions_table_id ); @@ -92,6 +118,7 @@ CREATE INDEX billing_provider_module_schema_id_idx ON metaschema_modules_public. -- metaschema_modules_private.tg_module_install_provenance attributes to this -- install, keyed by the role name in the column. COMMENT ON COLUMN metaschema_modules_public.billing_provider_module.billing_customers_table_id IS '@module_table'; +COMMENT ON COLUMN metaschema_modules_public.billing_provider_module.billing_disputes_table_id IS '@module_table'; COMMENT ON COLUMN metaschema_modules_public.billing_provider_module.billing_invoices_table_id IS '@module_table'; COMMENT ON COLUMN metaschema_modules_public.billing_provider_module.billing_prices_table_id IS '@module_table'; COMMENT ON COLUMN metaschema_modules_public.billing_provider_module.billing_products_table_id IS '@module_table'; 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 1fad9c05a..a347b912e 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 @@ -5,7 +5,7 @@ BEGIN; -- Typed catalog module configuration: one row per database installs the typed --- catalog tables (catalog_private.domains / apis / sites / namespaces / +-- catalog tables (catalog_private.domains / managed_domains / apis / sites / namespaces / -- functions / resources / resource_definitions / resource_installations / -- apps / buckets / images / redirects / bindings). The catalog is -- a system projection surface holding ALL scopes of each type; scoped source @@ -23,6 +23,7 @@ CREATE TABLE metaschema_modules_public.catalog_module ( -- Generated table IDs (populated by the generator) domains_table_id uuid NOT NULL DEFAULT uuid_nil(), + managed_domains_table_id uuid NOT NULL DEFAULT uuid_nil(), apis_table_id uuid NOT NULL DEFAULT uuid_nil(), sites_table_id uuid NOT NULL DEFAULT uuid_nil(), namespaces_table_id uuid NOT NULL DEFAULT uuid_nil(), @@ -38,10 +39,12 @@ CREATE TABLE metaschema_modules_public.catalog_module ( sites_error_pages_table_id uuid NOT NULL DEFAULT uuid_nil(), sites_app_links_table_id uuid NOT NULL DEFAULT uuid_nil(), sites_deep_links_table_id uuid NOT NULL DEFAULT uuid_nil(), + app_store_identities_table_id uuid NOT NULL DEFAULT uuid_nil(), redirects_table_id uuid NOT NULL DEFAULT uuid_nil(), -- Table names (inputs to the generator; stable load-bearing contracts) domains_table_name text NOT NULL DEFAULT 'domains', + managed_domains_table_name text NOT NULL DEFAULT 'managed_domains', apis_table_name text NOT NULL DEFAULT 'apis', sites_table_name text NOT NULL DEFAULT 'sites', namespaces_table_name text NOT NULL DEFAULT 'namespaces', @@ -57,6 +60,7 @@ CREATE TABLE metaschema_modules_public.catalog_module ( sites_error_pages_table_name text NOT NULL DEFAULT 'sites_error_pages', sites_app_links_table_name text NOT NULL DEFAULT 'sites_app_links', sites_deep_links_table_name text NOT NULL DEFAULT 'sites_deep_links', + app_store_identities_table_name text NOT NULL DEFAULT 'app_store_identities', redirects_table_name text NOT NULL DEFAULT 'redirects', -- API routing (get-or-create: if set, schema is added to this API) @@ -91,6 +95,10 @@ CREATE TABLE metaschema_modules_public.catalog_module ( FOREIGN KEY (domains_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT catalog_module_managed_domains_table_fkey + FOREIGN KEY (managed_domains_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, CONSTRAINT catalog_module_apis_table_fkey FOREIGN KEY (apis_table_id) REFERENCES metaschema_public.table (id) @@ -151,6 +159,10 @@ CREATE TABLE metaschema_modules_public.catalog_module ( FOREIGN KEY (sites_deep_links_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT catalog_module_app_store_identities_table_fkey + FOREIGN KEY (app_store_identities_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, CONSTRAINT catalog_module_redirects_table_fkey FOREIGN KEY (redirects_table_id) REFERENCES metaschema_public.table (id) @@ -174,8 +186,10 @@ CREATE INDEX catalog_module_sites_web_config_table_id_idx ON metaschema_modules_ CREATE INDEX catalog_module_sites_error_pages_table_id_idx ON metaschema_modules_public.catalog_module ( sites_error_pages_table_id ); CREATE INDEX catalog_module_sites_app_links_table_id_idx ON metaschema_modules_public.catalog_module ( sites_app_links_table_id ); CREATE INDEX catalog_module_sites_deep_links_table_id_idx ON metaschema_modules_public.catalog_module ( sites_deep_links_table_id ); +CREATE INDEX catalog_module_app_store_identities_table_id_idx ON metaschema_modules_public.catalog_module ( app_store_identities_table_id ); CREATE INDEX catalog_module_redirects_table_id_idx ON metaschema_modules_public.catalog_module ( redirects_table_id ); CREATE INDEX catalog_module_domains_table_id_idx ON metaschema_modules_public.catalog_module ( domains_table_id ); +CREATE INDEX catalog_module_managed_domains_table_id_idx ON metaschema_modules_public.catalog_module ( managed_domains_table_id ); CREATE INDEX catalog_module_entity_table_id_idx ON metaschema_modules_public.catalog_module ( entity_table_id ); CREATE INDEX catalog_module_functions_table_id_idx ON metaschema_modules_public.catalog_module ( functions_table_id ); CREATE INDEX catalog_module_namespaces_table_id_idx ON metaschema_modules_public.catalog_module ( namespaces_table_id ); @@ -194,6 +208,7 @@ COMMENT ON COLUMN metaschema_modules_public.catalog_module.apps_table_id IS '@mo COMMENT ON COLUMN metaschema_modules_public.catalog_module.bindings_table_id IS '@module_table'; COMMENT ON COLUMN metaschema_modules_public.catalog_module.buckets_table_id IS '@module_table'; COMMENT ON COLUMN metaschema_modules_public.catalog_module.domains_table_id IS '@module_table'; +COMMENT ON COLUMN metaschema_modules_public.catalog_module.managed_domains_table_id IS '@module_table'; COMMENT ON COLUMN metaschema_modules_public.catalog_module.functions_table_id IS '@module_table'; COMMENT ON COLUMN metaschema_modules_public.catalog_module.images_table_id IS '@module_table'; COMMENT ON COLUMN metaschema_modules_public.catalog_module.namespaces_table_id IS '@module_table'; @@ -203,6 +218,7 @@ COMMENT ON COLUMN metaschema_modules_public.catalog_module.redirects_table_id IS COMMENT ON COLUMN metaschema_modules_public.catalog_module.resources_table_id IS '@module_table'; COMMENT ON COLUMN metaschema_modules_public.catalog_module.sites_app_links_table_id IS '@module_table'; COMMENT ON COLUMN metaschema_modules_public.catalog_module.sites_deep_links_table_id IS '@module_table'; +COMMENT ON COLUMN metaschema_modules_public.catalog_module.app_store_identities_table_id IS '@module_table'; COMMENT ON COLUMN metaschema_modules_public.catalog_module.sites_error_pages_table_id IS '@module_table'; COMMENT ON COLUMN metaschema_modules_public.catalog_module.sites_table_id IS '@module_table'; COMMENT ON COLUMN metaschema_modules_public.catalog_module.sites_web_config_table_id IS '@module_table'; 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 33b04c3cf..42bcc5ccc 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 @@ -52,6 +52,11 @@ CREATE TABLE metaschema_modules_public.function_module ( -- Naming-only: instances are unique per (database_id, scope). prefix text NOT NULL DEFAULT '', + -- Which of the scope's storage planes a capability binding may target + -- (bucket_id FKs that plane's buckets table). Storage is the one plane kind + -- a scope may carry several of, so the binding names its key. + storage_key text NOT NULL DEFAULT 'default', + -- Entity table for RLS (NULL for app-level functions, entity table for entity-scoped functions) entity_table_id uuid NULL, diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/pages_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/pages_module/table.sql index 7fc4515b5..be1580a77 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/pages_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/pages_module/table.sql @@ -34,6 +34,25 @@ CREATE TABLE metaschema_modules_public.pages_module ( -- metadata, themes — so it is named for the site, not for pages. store_name_prefix text NOT NULL DEFAULT 'site:', + -- Generated release-manifest reader function name (populated by the + -- generator when the site surface provisions a releases head; NULL + -- otherwise). Read as a fact — never re-derived from the prefix. + release_manifest_function_name text, + + -- Generated preview-commit reader function name (populated by the + -- generator when the site surface provisions a releases head; NULL + -- otherwise). This is the two-argument reader (site_id, name). + -- Read as a fact — never re-derived from the prefix. + preview_commit_function_name text, + + -- Generated preview setter function name (populated by the generator + -- when the site surface provisions a releases head; NULL otherwise). + -- This is the three-argument writer (site_id, name, commit_id). + -- Read as a fact — never re-derived from the prefix. + preview_set_function_name text, + preview_token_mint_function_name text, + preview_token_verifier_function_name text, + api_name text, private_api_name text, entity_table_id uuid NULL, diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/repository_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/repository_module/table.sql index dd90361e3..24cf4f375 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/repository_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/repository_module/table.sql @@ -24,6 +24,7 @@ CREATE TABLE metaschema_modules_public.repository_module ( -- Generated table IDs (populated by the generator) repositories_table_id uuid NOT NULL DEFAULT uuid_nil(), repository_events_table_id uuid NOT NULL DEFAULT uuid_nil(), + repository_required_checks_table_id uuid NOT NULL DEFAULT uuid_nil(), workflows_table_id uuid NOT NULL DEFAULT uuid_nil(), builds_table_id uuid NOT NULL DEFAULT uuid_nil(), build_steps_table_id uuid NOT NULL DEFAULT uuid_nil(), @@ -36,6 +37,7 @@ CREATE TABLE metaschema_modules_public.repository_module ( -- Table names (input to the generator) repositories_table_name text NOT NULL DEFAULT 'repositories', repository_events_table_name text NOT NULL DEFAULT 'repository_events', + repository_required_checks_table_name text NOT NULL DEFAULT 'repository_required_checks', workflows_table_name text NOT NULL DEFAULT 'repository_workflows', builds_table_name text NOT NULL DEFAULT 'builds', build_steps_table_name text NOT NULL DEFAULT 'build_steps', @@ -94,6 +96,7 @@ CREATE TABLE metaschema_modules_public.repository_module ( CONSTRAINT repository_module_private_schema_fkey FOREIGN KEY (private_schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, CONSTRAINT repository_module_repositories_table_fkey FOREIGN KEY (repositories_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT repository_module_events_table_fkey FOREIGN KEY (repository_events_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT repository_module_required_checks_table_fkey FOREIGN KEY (repository_required_checks_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT repository_module_workflows_table_fkey FOREIGN KEY (workflows_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT repository_module_builds_table_fkey FOREIGN KEY (builds_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT repository_module_build_steps_table_fkey FOREIGN KEY (build_steps_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, @@ -112,6 +115,7 @@ CREATE UNIQUE INDEX repository_module_unique_scope ON metaschema_modules_public. CREATE INDEX repository_module_entity_table_id_idx ON metaschema_modules_public.repository_module ( entity_table_id ); CREATE INDEX repository_module_repositories_table_id_idx ON metaschema_modules_public.repository_module ( repositories_table_id ); CREATE INDEX repository_module_repository_events_table_id_idx ON metaschema_modules_public.repository_module ( repository_events_table_id ); +CREATE INDEX repository_module_repository_required_checks_table_id_idx ON metaschema_modules_public.repository_module ( repository_required_checks_table_id ); CREATE INDEX repository_module_workflows_table_id_idx ON metaschema_modules_public.repository_module ( workflows_table_id ); CREATE INDEX repository_module_builds_table_id_idx ON metaschema_modules_public.repository_module ( builds_table_id ); CREATE INDEX repository_module_build_steps_table_id_idx ON metaschema_modules_public.repository_module ( build_steps_table_id ); 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 82c9711ba..362c0cc3d 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 @@ -36,6 +36,10 @@ CREATE TABLE metaschema_modules_public.resource_module ( -- than by namespace_module because its keys are the namespace and the -- installed registry, which only exist together in this scope. registry_bindings_table_id uuid NOT NULL DEFAULT uuid_nil(), + -- Which BuildKit installation a namespace builds on, per lane. Generated + -- here because its namespace FK is local while the installation may be + -- owned by another resource scope. + builder_bindings_table_id uuid NOT NULL DEFAULT uuid_nil(), -- Table names (input to the generator — bare names without scope prefix). -- The trigger prepends the scope prefix automatically. @@ -47,6 +51,7 @@ CREATE TABLE metaschema_modules_public.resource_module ( resource_usage_summary_table_name text NOT NULL DEFAULT 'resource_usage_summary', resource_installations_table_name text NOT NULL DEFAULT 'resource_installations', registry_bindings_table_name text NOT NULL DEFAULT 'registry_bindings', + builder_bindings_table_name text NOT NULL DEFAULT 'builder_bindings', -- Generated functions (populated by the generator) rollup_resource_usage_summary_function text NOT NULL DEFAULT '', @@ -105,6 +110,7 @@ CREATE TABLE metaschema_modules_public.resource_module ( CONSTRAINT resource_module_usage_summary_table_fkey FOREIGN KEY (resource_usage_summary_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT resource_module_installations_table_fkey FOREIGN KEY (resource_installations_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT resource_module_registry_bindings_table_fkey FOREIGN KEY (registry_bindings_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT resource_module_builder_bindings_table_fkey FOREIGN KEY (builder_bindings_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT resource_module_merkle_store_module_fkey FOREIGN KEY (merkle_store_module_id) REFERENCES metaschema_modules_public.merkle_store_module (id) ON DELETE SET NULL, CONSTRAINT resource_module_entity_table_fkey FOREIGN KEY (entity_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT resource_module_namespace_module_fkey FOREIGN KEY (namespace_module_id) REFERENCES metaschema_modules_public.namespace_module (id) ON DELETE SET NULL @@ -116,6 +122,7 @@ CREATE INDEX resource_module_resource_definitions_table_id_idx ON metaschema_mod CREATE INDEX resource_module_entity_table_id_idx ON metaschema_modules_public.resource_module ( entity_table_id ); CREATE INDEX resource_module_resource_events_table_id_idx ON metaschema_modules_public.resource_module ( resource_events_table_id ); CREATE INDEX resource_module_resource_installations_table_id_idx ON metaschema_modules_public.resource_module ( resource_installations_table_id ); +CREATE INDEX resource_module_builder_bindings_table_id_idx ON metaschema_modules_public.resource_module ( builder_bindings_table_id ); CREATE INDEX resource_module_registry_bindings_table_id_idx ON metaschema_modules_public.resource_module ( registry_bindings_table_id ); CREATE INDEX resource_module_resources_table_id_idx ON metaschema_modules_public.resource_module ( resources_table_id ); CREATE INDEX resource_module_resource_status_checks_table_id_idx ON metaschema_modules_public.resource_module ( resource_status_checks_table_id ); @@ -134,6 +141,7 @@ COMMENT ON COLUMN metaschema_modules_public.resource_module.resource_definitions COMMENT ON COLUMN metaschema_modules_public.resource_module.resource_events_table_id IS '@module_table'; COMMENT ON COLUMN metaschema_modules_public.resource_module.resource_installations_table_id IS '@module_table'; COMMENT ON COLUMN metaschema_modules_public.resource_module.registry_bindings_table_id IS '@module_table'; +COMMENT ON COLUMN metaschema_modules_public.resource_module.builder_bindings_table_id IS '@module_table'; COMMENT ON COLUMN metaschema_modules_public.resource_module.resource_status_checks_table_id IS '@module_table'; COMMENT ON COLUMN metaschema_modules_public.resource_module.resource_usage_log_table_id IS '@module_table'; COMMENT ON COLUMN metaschema_modules_public.resource_module.resource_usage_summary_table_id IS '@module_table'; 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 1166b42cf..ccae8aca1 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 @@ -14,6 +14,14 @@ CREATE TABLE metaschema_modules_public.route_module ( -- trigger via metaschema_generators.scope_key_column(scope, key). entity_field text, + -- Column on the generated routes table carrying the site whose surface a + -- route serves, recorded by the generator when it creates that field. It is + -- read the same way entity_field is: a portable install engine consults the + -- registration and stamps the column it names, so the fact "a route can say + -- which site it renders as" travels with the plane instead of living in the + -- generated verbs. NULL for a routing plane that carries no such column. + serving_site_field text, + -- Schema references (if uuid_nil, resolved from schema name or default) schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), @@ -64,6 +72,13 @@ CREATE TABLE metaschema_modules_public.route_module ( -- Table name prefix. Auto-derived from scope by the trigger when empty. prefix text NOT NULL DEFAULT '', + -- Which of the scope's storage planes a route may target + -- (routes.target_bucket_id FKs that plane's buckets table). A scope may + -- carry several storage planes, each with its own semantic key, so the + -- binding is named rather than guessed; 'default' is the plane a blueprint + -- installs when it declares no storage keys. + storage_key text NOT NULL DEFAULT 'default', + -- Entity table for RLS (NULL for non-entity scopes) entity_table_id uuid NULL, diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/secure_table_provision/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/secure_table_provision/table.sql index ca5b36e8b..59d1efc8e 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/secure_table_provision/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/secure_table_provision/table.sql @@ -15,6 +15,10 @@ CREATE TABLE metaschema_modules_public.secure_table_provision ( table_name text DEFAULT NULL, + module jsonb DEFAULT NULL, + + owns jsonb NOT NULL DEFAULT '[]', + nodes jsonb NOT NULL DEFAULT '[]', use_rls boolean NOT NULL DEFAULT true, @@ -33,7 +37,7 @@ CREATE TABLE metaschema_modules_public.secure_table_provision ( ); COMMENT ON TABLE metaschema_modules_public.secure_table_provision IS - 'Provisions security, fields, grants, and policies onto a table. Each row can independently: (1) create fields via nodes[] array (supporting multiple Data* modules per row), (2) grant privileges via grants[] array (supporting per-role privilege targeting), (3) create RLS policies via policies[] array (supporting multiple Authz* policies per row). Multiple rows can target the same table to compose different concerns. All three concerns are optional and independent.'; + 'Provisions security, fields, grants, and policies onto a table. Each row can independently: (1) create fields via nodes[] array (supporting multiple Data* modules per row), (2) grant privileges via grants[] array (supporting per-role privilege targeting), (3) create RLS policies via policies[] array (supporting multiple Authz* policies per row). Multiple rows can target the same table to compose different concerns. All three concerns are optional and independent. The target table is addressed by table_id, by table_name, or symbolically by a module reference in module. A row that lists a concern in owns[] replaces that concern on the target table instead of composing with what is already there.'; COMMENT ON COLUMN metaschema_modules_public.secure_table_provision.id IS 'Unique identifier for this provision row.'; @@ -50,6 +54,12 @@ COMMENT ON COLUMN metaschema_modules_public.secure_table_provision.table_id IS COMMENT ON COLUMN metaschema_modules_public.secure_table_provision.table_name IS 'Name of the target table. Used to create or look up the table when table_id is not provided. If omitted, it is backfilled from the resolved table.'; +COMMENT ON COLUMN metaschema_modules_public.secure_table_provision.module IS + 'Module reference naming a module-generated target table symbolically, instead of by table_id or table_name: a jsonb object with keys "type" (text, required — the module type, e.g. "image"), "table" (text, required — the module''s own table key, e.g. "registries"), "scope" (text, optional — the install scope) and "prefix" (text, optional — disambiguates multiple installs of the same module). Resolved through metaschema_modules_private.resolve_module_table(), so it raises the same errors blueprint module references do (BLUEPRINT_MODULE_REF_INVALID, BLUEPRINT_MODULE_NOT_INSTALLED, BLUEPRINT_MODULE_REF_AMBIGUOUS, BLUEPRINT_MODULE_TABLE_UNKNOWN). Mutually exclusive with table_name and with an explicit table_id. Example: {"type":"image","scope":"org","table":"registries"}. Defaults to NULL.'; + +COMMENT ON COLUMN metaschema_modules_public.secure_table_provision.owns IS + 'Security concerns this row owns on the target table, as a jsonb array of "grants" and/or "policies". A listed concern is replaced: the target table''s existing grants (or its non-derived policies) are dropped before this row''s grants[] (or policies[]) are applied, so the row''s array is the table''s whole set — this is how a module-generated table''s default security is superseded rather than layered on. An unlisted concern composes, which is the default and the historical behavior. A concern may only be owned when this row supplies a non-empty array for it; owning a concern with nothing to install would leave the table with RLS enabled and no policy, and raises instead. Example: ["policies","grants"]. Defaults to ''[]'' (compose everything).'; + COMMENT ON COLUMN metaschema_modules_public.secure_table_provision.nodes IS 'Array of node objects to apply to the table. Each element is a jsonb object with a required "$type" key (one of: DataId, DataDirectOwner, DataEntityMembership, DataOwnershipInEntity, DataTimestamps, DataPeoplestamps, DataPublishable, DataSoftDelete, DataEmbedding, DataFullTextSearch, DataSlug, etc.) and an optional "data" key containing generator-specific configuration. Supports multiple nodes per row, matching the blueprint definition format. Example: [{"$type": "DataId"}, {"$type": "DataTimestamps"}, {"$type": "DataDirectOwner", "data": {"owner_field_name": "author_id"}}]. Defaults to ''[]'' (no node processing).'; 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 d3e542c12..abc3b0493 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 @@ -32,6 +32,7 @@ CREATE TABLE metaschema_modules_public.site_surface_module ( site_metadata_table_id uuid NOT NULL DEFAULT uuid_nil(), site_modules_table_id uuid NOT NULL DEFAULT uuid_nil(), site_themes_table_id uuid NOT NULL DEFAULT uuid_nil(), + site_releases_table_id uuid NOT NULL DEFAULT uuid_nil(), site_app_links_table_id uuid NOT NULL DEFAULT uuid_nil(), site_deep_links_table_id uuid NOT NULL DEFAULT uuid_nil(), -- Serving-config companions (the distribution tier's typed behavior): @@ -45,6 +46,7 @@ CREATE TABLE metaschema_modules_public.site_surface_module ( site_metadata_table_name text NOT NULL DEFAULT 'site_metadata', site_modules_table_name text NOT NULL DEFAULT 'site_modules', site_themes_table_name text NOT NULL DEFAULT 'site_themes', + site_releases_table_name text NOT NULL DEFAULT 'site_releases', site_app_links_table_name text NOT NULL DEFAULT 'site_app_links', site_deep_links_table_name text NOT NULL DEFAULT 'site_deep_links', site_web_config_table_name text NOT NULL DEFAULT 'site_web_config', @@ -60,6 +62,13 @@ CREATE TABLE metaschema_modules_public.site_surface_module ( -- Table name prefix. Auto-derived from scope by the trigger when empty. prefix text NOT NULL DEFAULT '', + -- Which of the scope's storage planes backs a site (sites.bucket_id FKs + -- that plane's buckets table). A scope may carry several storage planes, + -- each with its own semantic key, so the binding is named rather than + -- guessed; 'default' is the plane a blueprint installs when it declares no + -- storage keys. + storage_key text NOT NULL DEFAULT 'default', + -- Entity table for RLS (NULL for non-entity scopes) entity_table_id uuid NULL, @@ -104,6 +113,10 @@ CREATE TABLE metaschema_modules_public.site_surface_module ( FOREIGN KEY (site_themes_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT site_module_site_releases_table_fkey + FOREIGN KEY (site_releases_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, CONSTRAINT site_module_site_app_links_table_fkey FOREIGN KEY (site_app_links_table_id) REFERENCES metaschema_public.table (id) @@ -132,6 +145,7 @@ CREATE INDEX site_surface_module_entity_table_id_idx ON metaschema_modules_publi CREATE INDEX site_surface_module_site_metadata_table_id_idx ON metaschema_modules_public.site_surface_module ( site_metadata_table_id ); CREATE INDEX site_surface_module_site_modules_table_id_idx ON metaschema_modules_public.site_surface_module ( site_modules_table_id ); CREATE INDEX site_surface_module_site_themes_table_id_idx ON metaschema_modules_public.site_surface_module ( site_themes_table_id ); +CREATE INDEX site_surface_module_site_releases_table_id_idx ON metaschema_modules_public.site_surface_module ( site_releases_table_id ); CREATE INDEX site_surface_module_site_app_links_table_id_idx ON metaschema_modules_public.site_surface_module ( site_app_links_table_id ); CREATE INDEX site_surface_module_site_deep_links_table_id_idx ON metaschema_modules_public.site_surface_module ( site_deep_links_table_id ); CREATE INDEX site_surface_module_site_web_config_table_id_idx ON metaschema_modules_public.site_surface_module ( site_web_config_table_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 602577895..a0c819c12 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 @@ -35,9 +35,18 @@ CREATE TABLE metaschema_modules_public.storage_module ( scope text NOT NULL, -- Table name prefix. Auto-derived from scope by the trigger when empty. - -- Override to create multiple module instances at the same scope. + -- Naming only: it decides whether the tables are `buckets` or `org_buckets`. prefix text NOT NULL DEFAULT '', + -- Semantic identity of this plane WITHIN its scope, and the only thing a + -- consumer binds to. Storage is the one plane kind a scope may legitimately + -- have several of (a blueprint's entity type declares storage keys, e.g. + -- `default` plus `media`), so a consumer that references "the scope's + -- buckets table" — sites.bucket_id, routes.target_bucket_id — must name + -- WHICH plane it means. It names this key, never the prefix, which is a + -- table-naming artifact. + key text NOT NULL DEFAULT 'default', + -- Configurable security policies (NULL = use defaults based on scope). -- When provided, replaces the default policy set in apply_storage_security. -- Accepts a JSON array of policy objects: @@ -125,7 +134,14 @@ CREATE TABLE metaschema_modules_public.storage_module ( CONSTRAINT file_events_table_fkey FOREIGN KEY (file_events_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE ); --- Unique constraint: one storage module per database per scope per prefix. +-- Semantic identity: one storage plane per (database, scope, key). Every +-- sibling plane module (api_surface/site_surface/resource/function/domain/ +-- route/app) is UNIQUE (database_id, scope) because a scope has exactly one of +-- them; storage is the exception, so it carries an explicit key and consumers +-- bind to it. This is what makes the typed bucket references unambiguous +-- instead of "whichever plane the lookup happened to read first". +CREATE UNIQUE INDEX storage_module_unique_key ON metaschema_modules_public.storage_module ( database_id, scope, key ); +-- Physical identity: two planes in a scope cannot claim the same table names. CREATE UNIQUE INDEX storage_module_unique_scope ON metaschema_modules_public.storage_module ( database_id, scope, prefix ); CREATE INDEX storage_module_buckets_table_id_idx ON metaschema_modules_public.storage_module ( buckets_table_id ); CREATE INDEX storage_module_entity_table_id_idx ON metaschema_modules_public.storage_module ( entity_table_id ); diff --git a/packages/metaschema-modules/sql/metaschema-modules--0.43.2.bundle.tar.gz b/packages/metaschema-modules/sql/metaschema-modules--0.43.2.bundle.tar.gz index bbf50c33907468aa01cb9a399433f63598424032..ad5a66ed76051fa8eab7878bdfac0efcdee0ce2e 100644 GIT binary patch delta 102226 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

W&ZP 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 delta 98799 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�{J-Lms9d#IF0*Uf-I``7+BMfE(~J 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 { expect(database.name).toBe('test-db'); expect(database.id).toBeDefined(); }); + + describe('trigger customer attachment shape', () => { + let database_id: string; + let table_id: string; + let function_id: string; + + beforeEach(async () => { + const owner_id = '07281002-1699-4762-57e3-ab1b92243120'; + ({ id: database_id } = await pg.one( + `INSERT INTO metaschema_public.database (owner_id, name) + VALUES ($1, $2) RETURNING id`, + [owner_id, 'trigger-db'] + )); + const { id: schema_id } = await pg.one( + `INSERT INTO metaschema_public.schema (database_id, name, schema_name) + VALUES ($1, $2, $3) RETURNING id`, + [database_id, 'app_public', 'trigger_test_app_public'] + ); + ({ id: table_id } = await pg.one( + `INSERT INTO metaschema_public.table (database_id, schema_id, name) + VALUES ($1, $2, $3) RETURNING id`, + [database_id, schema_id, 'orders'] + )); + ({ id: function_id } = await pg.one( + `INSERT INTO metaschema_public.function (database_id, schema_id, name, kind, returns, volatility, body_ast) + VALUES ($1, $2, $3, 'trigger', '{"type":{"name":"trigger"}}', 'VOLATILE', '{}') RETURNING id`, + [database_id, schema_id, 'orders_audit_tg'] + )); + }); + + const insertTrigger = (name: string, columns: Record) => { + const extra = Object.keys(columns); + const cols = ['database_id', 'table_id', 'name', ...extra]; + const params = [database_id, table_id, name, ...extra.map((k) => columns[k])]; + const placeholders = params.map((_, i) => `$${i + 1}`); + return pg.one( + `INSERT INTO metaschema_public.trigger (${cols.join(', ')}) + VALUES (${placeholders.join(', ')}) RETURNING *`, + params + ); + }; + + it('still accepts existing generated rows unchanged', async () => { + const row = await insertTrigger('generated_tg', { + event: 'INSERT', + function_name: 'app_hidden.some_generated_fn' + }); + expect(row.kind).toBe('reservation'); + expect(row.function_id).toBeNull(); + expect(row.timing).toBeNull(); + expect(row.events).toBeNull(); + expect(row.for_each_row).toBeNull(); + expect(row.when_ast).toBeNull(); + }); + + it('accepts a reservation whose events round-tripped as an empty array', async () => { + const row = await insertTrigger('seeded_tg', { + function_name: 'app_hidden.some_generated_fn', + events: [] + }); + expect(row.kind).toBe('reservation'); + expect(row.events).toEqual([]); + }); + + it('accepts the AFTER ... FOR EACH ROW customer shape', async () => { + const row = await insertTrigger('customer_tg', { + kind: 'attachment', + function_id, + timing: 'AFTER', + events: ['INSERT', 'UPDATE', 'DELETE'], + for_each_row: true + }); + expect(row.kind).toBe('attachment'); + expect(row.function_id).toBe(function_id); + expect(row.events).toEqual(['INSERT', 'UPDATE', 'DELETE']); + }); + + it('rejects an attachment kind without a function', async () => { + await expect( + insertTrigger('kindless_tg', { + kind: 'attachment', + timing: 'AFTER', + events: ['INSERT'], + for_each_row: true + }) + ).rejects.toThrow(/trigger_kind_matches_attachment/); + }); + + it('rejects a function on a reservation row', async () => { + await expect( + insertTrigger('reserved_with_fn_tg', { + function_id, + timing: 'AFTER', + events: ['INSERT'], + for_each_row: true + }) + ).rejects.toThrow(/trigger_kind_matches_attachment/); + }); + + it('rejects an attachment definition on a reservation row', async () => { + await expect( + insertTrigger('reserved_with_timing_tg', { + timing: 'AFTER', + events: ['INSERT'], + for_each_row: true + }) + ).rejects.toThrow(/trigger_reservation_has_no_definition/); + }); + + it('rejects an unknown kind', async () => { + await expect( + insertTrigger('bogus_kind_tg', { kind: 'physical' }) + ).rejects.toThrow(/trigger_kind_valid/); + }); + + it('refuses to delete a function that is still attached', async () => { + await insertTrigger('attached_tg', { + kind: 'attachment', + function_id, + timing: 'AFTER', + events: ['INSERT'], + for_each_row: true + }); + await expect( + pg.any(`DELETE FROM metaschema_public.function WHERE id = $1`, [ + function_id + ]) + ).rejects.toThrow(/function_fkey/); + }); + + it('rejects BEFORE when function_id is set', async () => { + await expect( + insertTrigger('before_tg', { + kind: 'attachment', + function_id, + timing: 'BEFORE', + events: ['INSERT'], + for_each_row: true + }) + ).rejects.toThrow(/trigger_customer_attachment_shape/); + }); + + it('rejects statement-level triggers when function_id is set', async () => { + await expect( + insertTrigger('stmt_tg', { + kind: 'attachment', + function_id, + timing: 'AFTER', + events: ['INSERT'], + for_each_row: false + }) + ).rejects.toThrow(/trigger_customer_attachment_shape/); + }); + + it('rejects TRUNCATE events when function_id is set', async () => { + await expect( + insertTrigger('truncate_tg', { + kind: 'attachment', + function_id, + timing: 'AFTER', + events: ['TRUNCATE'], + for_each_row: true + }) + ).rejects.toThrow(/trigger_customer_attachment_shape/); + }); + + it('rejects empty events when function_id is set', async () => { + await expect( + insertTrigger('empty_events_tg', { + kind: 'attachment', + function_id, + timing: 'AFTER', + events: [], + for_each_row: true + }) + ).rejects.toThrow(/trigger_customer_attachment_shape/); + }); + + it('rejects missing events when function_id is set', async () => { + await expect( + insertTrigger('null_events_tg', { + kind: 'attachment', + function_id, + timing: 'AFTER', + events: null, + for_each_row: true + }) + ).rejects.toThrow(/trigger_customer_attachment_shape/); + }); + }); }); diff --git a/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/function/table.sql b/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/function/table.sql index 8f5980c1d..4929ccc88 100644 --- a/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/function/table.sql +++ b/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/function/table.sql @@ -3,6 +3,7 @@ -- requires: schemas/metaschema_public/schema -- requires: schemas/metaschema_public/tables/database/table -- requires: schemas/metaschema_public/tables/schema/table +-- requires: schemas/metaschema_public/types/object_category BEGIN; @@ -13,7 +14,122 @@ CREATE TABLE metaschema_public.function ( name text NOT NULL, + -- What this row is: + -- 'reservation' — name-only. The function is emitted by a generator and + -- this row exists to hand out a stable, FK-able id and to + -- reserve (schema_id, name) against a customer-defined + -- function taking the same name. + -- 'sql' — this row owns the definition of a pure SQL function. + -- 'plpgsql' — this row owns the definition of a PL/pgSQL function. + -- 'trigger' — this row owns the definition of a PL/pgSQL trigger + -- function: no arguments, RETURNS trigger, never callable + -- through the API. It fires inside whatever transaction + -- writes the table it is attached to, so it is gated, + -- validated and emitted separately from a plain function. + -- For a definition row the kind is also the function's LANGUAGE ('trigger' + -- being PL/pgSQL), so the two can never disagree. + kind text NOT NULL DEFAULT 'reservation', + + -- Signature (definition rows only). + -- arguments: [{ "name": "user_id", "type": { "name": "uuid" }, "mode": "in", "default": ... }, …] + -- returns: { "type": { "name": "boolean" } } | { "setof": true, "type": … } | { "table": [ … ] } + arguments jsonb NOT NULL DEFAULT '[]', + returns jsonb, + + volatility text, + is_strict boolean NOT NULL DEFAULT false, + + -- SECURITY DEFINER is deliberately not expressible: a customer-owned + -- function runs as its invoker and stays subject to RLS. + security_invoker boolean NOT NULL DEFAULT true, + + -- Tier A authoring: a typed Function* node type plus its parameters, the + -- same shape metaschema_public.view uses for View* types. The body AST is + -- derived from these at generation time. + function_type text, + data jsonb DEFAULT '{}', + + -- Tier B authoring: the validated native AST. For 'sql' a statement AST, + -- for 'plpgsql' the complete PLpgSQL_function AST including its top-level + -- `datums` array — never a body fragment, because NEW/OLD field references + -- resolve by datum index. + body_ast jsonb, + + smart_tags jsonb, + + -- Whether this function is published to the generated API. -- + -- PostGraphile exposes every function it finds in a published schema, so a + -- customer function landing in app_public would become a query or mutation + -- field by existing. Publishing executable code is a separate decision from + -- authoring it, and the safe answer when nobody made that decision is no: + -- the default is false, and provisioning both denies every API behavior on + -- the emitted function and withholds EXECUTE until this is turned on. + api_exposed boolean NOT NULL DEFAULT false, + + category metaschema_public.object_category NOT NULL DEFAULT 'app', + + tags citext[] NOT NULL DEFAULT '{}', + + -- + CONSTRAINT function_kind_valid CHECK (kind IN ('reservation', 'sql', 'plpgsql', 'trigger')), + + CONSTRAINT function_volatility_valid CHECK ( + volatility IS NULL OR volatility IN ('IMMUTABLE', 'STABLE', 'VOLATILE') + ), + + CONSTRAINT function_security_invoker_only CHECK (security_invoker), + + -- A reservation is a name held for a generated function, whose own + -- generator decides its API surface; the flag would describe nothing. + CONSTRAINT function_reservation_not_api_exposed CHECK ( + kind <> 'reservation' OR NOT api_exposed + ), + + -- A reservation row carries no executable definition. + CONSTRAINT function_reservation_has_no_definition CHECK ( + kind <> 'reservation' + OR ( + arguments = '[]'::jsonb + AND returns IS NULL + AND volatility IS NULL + AND function_type IS NULL + AND body_ast IS NULL + ) + ), + + -- A trigger function takes no arguments and returns the trigger + -- pseudo-type: PostgreSQL calls it with the row in NEW/OLD rather than + -- through a signature, and any other shape cannot be attached at all. + CONSTRAINT function_trigger_signature CHECK ( + kind <> 'trigger' + OR ( + arguments = '[]'::jsonb + AND returns = '{"type": {"name": "trigger"}}'::jsonb + AND volatility = 'VOLATILE' + AND function_type IS NULL + AND body_ast IS NOT NULL + ) + ), + + -- A trigger function is reached by firing, not by calling: it needs no + -- EXECUTE grant, and exposing it would publish a function whose only + -- argument type cannot be spelled in a GraphQL field. + CONSTRAINT function_trigger_not_api_exposed CHECK ( + kind <> 'trigger' OR NOT api_exposed + ), + + -- A definition row carries a complete signature and at least one authoring + -- tier to generate the body from. + CONSTRAINT function_definition_complete CHECK ( + kind = 'reservation' + OR ( + returns IS NOT NULL + AND volatility IS NOT NULL + AND (function_type IS NOT NULL OR body_ast IS NOT NULL) + ) + ), + CONSTRAINT db_fkey FOREIGN KEY (database_id) REFERENCES metaschema_public.database (id) ON DELETE CASCADE, CONSTRAINT schema_fkey FOREIGN KEY (schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, @@ -21,5 +137,6 @@ CREATE TABLE metaschema_public.function ( ); CREATE INDEX function_database_id_idx ON metaschema_public.function ( database_id ); +CREATE INDEX function_kind_idx ON metaschema_public.function ( kind ); COMMIT; diff --git a/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/identity_provider_registry/table.sql b/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/identity_provider_registry/table.sql new file mode 100644 index 000000000..0406e53d9 --- /dev/null +++ b/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/identity_provider_registry/table.sql @@ -0,0 +1,32 @@ +-- Deploy schemas/metaschema_public/tables/identity_provider_registry/table to pg + +-- requires: schemas/metaschema_public/schema + +BEGIN; + +-- Catalog of built-in identity providers. Each row is the protocol metadata +-- for one built-in IdP; provisioning reads this table and seeds a tenant +-- catalog row per provider (credentials NULL, enabled false) so nothing is +-- offered for sign-in until an admin configures it. Adding a provider or +-- correcting an endpoint is an INSERT/UPDATE here, not a code change. +CREATE TABLE metaschema_public.identity_provider_registry ( + slug text PRIMARY KEY, + -- 'oidc' rows carry issuer_url (endpoints resolve via discovery); + -- 'oauth2' rows carry the three explicit endpoint URLs. + kind text NOT NULL CHECK (kind IN ('oauth2', 'oidc')), + display_name text NOT NULL, + issuer_url text, + authorization_url text, + token_url text, + userinfo_url text, + scopes text[] NOT NULL DEFAULT '{}'::text[], + CHECK ( + (kind = 'oidc' AND issuer_url IS NOT NULL) + OR (kind = 'oauth2' + AND authorization_url IS NOT NULL + AND token_url IS NOT NULL + AND userinfo_url IS NOT NULL) + ) +); + +COMMIT; diff --git a/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/trigger/table.sql b/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/trigger/table.sql index 7cd4f7ab1..c82774fe0 100644 --- a/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/trigger/table.sql +++ b/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/trigger/table.sql @@ -2,6 +2,7 @@ -- requires: schemas/metaschema_public/schema -- requires: schemas/metaschema_public/tables/table/table +-- requires: schemas/metaschema_public/tables/function/table -- requires: schemas/metaschema_public/types/object_category BEGIN; @@ -17,6 +18,33 @@ CREATE TABLE metaschema_public.trigger ( event text, -- INSERT, UPDATE, DELETE, or TRUNCATE function_name text, + -- What this row is, mirroring metaschema_public.function.kind: + -- 'reservation' — name-only. The trigger itself is emitted by a generator, + -- or captured from the catalog by introspection, and this + -- row exists to hand out a stable, FK-able id and to + -- reserve (table_id, name) against a customer attachment + -- taking the same name. + -- 'attachment' — this row owns the definition of a customer trigger: which + -- customer function fires, on which events, under which + -- condition. The physical trigger is re-derived from the row. + kind text NOT NULL DEFAULT 'reservation', + + -- Customer trigger attachment. A row with function_id set attaches a + -- customer-defined trigger function (metaschema_public.function, + -- kind='trigger') to the target table. Generated/reservation rows leave all + -- of these NULL and keep their current lifecycle. + function_id uuid, + + -- Stored as readable enums, translated to the trigger type bitmask at + -- emission time. + timing text, + events text[], + for_each_row boolean, + + -- Optional WHEN condition. A condition is an AST, never a string or DSL; + -- it is validated and deparsed at emission time. + when_ast jsonb, + smart_tags jsonb, category metaschema_public.object_category NOT NULL DEFAULT 'app', @@ -28,11 +56,64 @@ CREATE TABLE metaschema_public.trigger ( 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, + -- RESTRICT rather than CASCADE: dropping a trigger function that is still + -- attached is a mistake to report, not an attachment to delete silently. An + -- attachment is removed by deleting the trigger row, which is the only path + -- that also drops the physical trigger. + CONSTRAINT function_fkey FOREIGN KEY (function_id) REFERENCES metaschema_public.function (id) ON DELETE RESTRICT, + + CONSTRAINT trigger_kind_valid CHECK (kind IN ('reservation', 'attachment')), + + -- The kind and the relation are two spellings of the same fact, so neither + -- can be set without the other: a reservation never names a function, and an + -- attachment is nothing without one. + CONSTRAINT trigger_kind_matches_attachment CHECK ( + (kind = 'attachment') = (function_id IS NOT NULL) + ), + + -- A reservation carries no attachment definition. An empty events array is + -- accepted alongside NULL because that is how the seed exporter round-trips a + -- null array, and both say the same thing: no events. + CONSTRAINT trigger_reservation_has_no_definition CHECK ( + kind <> 'reservation' + OR ( + timing IS NULL + AND coalesce(cardinality(events), 0) = 0 + AND for_each_row IS NULL + AND when_ast IS NULL + ) + ), + + -- event and function_name are the generated-trigger spelling: a single event + -- and a physical function name, filled in by whichever generator emitted the + -- trigger. An attachment says the same things in function_id and events, and + -- resolves the physical name from the function row, so carrying both would be + -- two answers to one question. + CONSTRAINT trigger_attachment_has_no_legacy_definition CHECK ( + kind <> 'attachment' + OR (event IS NULL AND function_name IS NULL) + ), + + -- The customer attachment posture: AFTER, FOR EACH ROW, on a non-empty + -- subset of {INSERT, UPDATE, DELETE}. BEFORE (a body could rewrite NEW + -- before RLS/checks see it), TRUNCATE, statement-level, constraint/ + -- deferrable triggers and transition relations are not representable. + CONSTRAINT trigger_customer_attachment_shape CHECK ( + function_id IS NULL + OR ( + timing = 'AFTER' + AND for_each_row + AND events IS NOT NULL + AND cardinality(events) > 0 + AND events <@ ARRAY['INSERT', 'UPDATE', 'DELETE'] + ) + ), UNIQUE(table_id, name) ); CREATE INDEX trigger_database_id_idx ON metaschema_public.trigger ( database_id ); +CREATE INDEX trigger_function_id_idx ON metaschema_public.trigger ( function_id ); COMMIT; diff --git a/packages/metaschema-schema/pgpm.plan b/packages/metaschema-schema/pgpm.plan index cf67a3381..5314cf8c5 100644 --- a/packages/metaschema-schema/pgpm.plan +++ b/packages/metaschema-schema/pgpm.plan @@ -23,7 +23,6 @@ schemas/metaschema_public/tables/schema_grant/table [schemas/metaschema_public/s schemas/metaschema_public/tables/table_grant/table [schemas/metaschema_public/schema schemas/metaschema_public/tables/table/table] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_public/tables/table_grant/table schemas/metaschema_public/tables/table/indexes/databases_table_unique_name_idx [schemas/metaschema_public/schema schemas/metaschema_private/schema schemas/metaschema_public/tables/table/table] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_public/tables/table/indexes/databases_table_unique_name_idx schemas/metaschema_public/tables/trigger_function/table [schemas/metaschema_public/schema schemas/metaschema_public/tables/database/table] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_public/tables/trigger_function/table -schemas/metaschema_public/tables/trigger/table [schemas/metaschema_public/schema schemas/metaschema_public/tables/table/table] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_public/tables/trigger/table schemas/metaschema_public/tables/unique_constraint/table [schemas/metaschema_public/schema schemas/metaschema_public/tables/database/table schemas/metaschema_public/tables/table/table] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_public/tables/unique_constraint/table schemas/metaschema_public/tables/view/table [schemas/metaschema_public/schema schemas/metaschema_public/tables/schema/table schemas/metaschema_public/tables/table/table schemas/metaschema_public/tables/database/table schemas/metaschema_public/types/object_category] 2026-01-23T00:00:00Z devin # add schemas/metaschema_public/tables/view/table schemas/metaschema_public/tables/view_table/table [schemas/metaschema_public/schema schemas/metaschema_public/tables/view/table schemas/metaschema_public/tables/table/table] 2026-01-23T00:00:00Z devin # add schemas/metaschema_public/tables/view_table/table @@ -35,6 +34,7 @@ schemas/metaschema_public/tables/embedding_chunks/table [schemas/metaschema_publ schemas/metaschema_public/tables/spatial_relation/table [schemas/metaschema_public/schema schemas/metaschema_public/tables/database/table schemas/metaschema_public/tables/table/table schemas/metaschema_public/tables/field/table schemas/metaschema_public/types/object_category] 2026-04-17T00:00:00Z devin # add schemas/metaschema_public/tables/spatial_relation/table schemas/metaschema_public/tables/node_type_registry/table [schemas/metaschema_public/schema] 2026-04-30T00:00:00Z Constructive # add schemas/metaschema_public/tables/node_type_registry/table schemas/metaschema_public/tables/function/table [schemas/metaschema_public/schema schemas/metaschema_public/tables/database/table schemas/metaschema_public/tables/schema/table] 2026-05-09T00:00:00Z devin # add metaschema_public.function table for tracking generated SQL functions +schemas/metaschema_public/tables/trigger/table [schemas/metaschema_public/schema schemas/metaschema_public/tables/table/table schemas/metaschema_public/tables/function/table] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_public/tables/trigger/table schemas/metaschema_public/tables/partition/table [schemas/metaschema_public/schema schemas/metaschema_public/tables/database/table schemas/metaschema_public/tables/table/table schemas/metaschema_public/tables/field/table] 2026-05-26T00:00:00Z Constructive # add metaschema_public.partition table for pg_partman lifecycle config schemas/metaschema_public/tables/composite_type/table [schemas/metaschema_public/schema schemas/metaschema_public/tables/database/table schemas/metaschema_public/tables/schema/table schemas/metaschema_public/types/object_category] 2026-05-29T00:00:00Z devin # add metaschema_public.composite_type table for generated composite types schemas/metaschema_public/tables/domain_type/table [schemas/metaschema_public/schema schemas/metaschema_public/tables/database/table schemas/metaschema_public/tables/schema/table schemas/metaschema_public/types/object_category] 2026-07-22T00:00:00Z devin # add metaschema_public.domain_type table for declarative CREATE DOMAIN support @@ -46,3 +46,4 @@ schemas/metaschema_public/tables/field_behavior/table [schemas/metaschema_public schemas/metaschema_public/tables/view_behavior/table [schemas/metaschema_public/schema schemas/metaschema_public/tables/database/table schemas/metaschema_public/tables/view/table] 2026-08-01T00:00:00Z devin # add metaschema_public.view_behavior: one row per PostGraphile v5 behavior fragment schemas/metaschema_public/tables/foreign_key_constraint_behavior/table [schemas/metaschema_public/schema schemas/metaschema_public/tables/database/table schemas/metaschema_public/tables/foreign_key_constraint/table] 2026-08-01T00:00:00Z devin # add metaschema_public.foreign_key_constraint_behavior: one row per PostGraphile v5 behavior fragment schemas/metaschema_public/tables/unique_constraint_behavior/table [schemas/metaschema_public/schema schemas/metaschema_public/tables/database/table schemas/metaschema_public/tables/unique_constraint/table] 2026-08-01T00:00:00Z devin # add metaschema_public.unique_constraint_behavior: one row per PostGraphile v5 behavior fragment +schemas/metaschema_public/tables/identity_provider_registry/table [schemas/metaschema_public/schema] 2026-08-21T00:00:00Z devin # built-in identity provider catalog (google, github, apple, facebook, microsoft) — protocol metadata read at provision time to seed tenant rows diff --git a/packages/metaschema-schema/revert/schemas/metaschema_public/tables/identity_provider_registry/table.sql b/packages/metaschema-schema/revert/schemas/metaschema_public/tables/identity_provider_registry/table.sql new file mode 100644 index 000000000..e921d9c63 --- /dev/null +++ b/packages/metaschema-schema/revert/schemas/metaschema_public/tables/identity_provider_registry/table.sql @@ -0,0 +1,7 @@ +-- Revert schemas/metaschema_public/tables/identity_provider_registry/table from pg + +BEGIN; + +DROP TABLE IF EXISTS metaschema_public.identity_provider_registry; + +COMMIT; diff --git a/packages/metaschema-schema/sql/metaschema-schema--0.43.2.bundle.tar.gz b/packages/metaschema-schema/sql/metaschema-schema--0.43.2.bundle.tar.gz index 85c36980ef39506b5a1caae3b4d8e39bca0b433c..614c2dd54b024941ef9e222b0b9af80ff830a14d 100644 GIT binary patch delta 33989 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


v`AbvSD(#TXAxS&$?a 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 diff --git a/packages/metaschema-schema/sql/metaschema-schema--0.43.2.sql b/packages/metaschema-schema/sql/metaschema-schema--0.43.2.sql index f366efeff..4780454af 100644 --- a/packages/metaschema-schema/sql/metaschema-schema--0.43.2.sql +++ b/packages/metaschema-schema/sql/metaschema-schema--0.43.2.sql @@ -798,31 +798,6 @@ CREATE TABLE metaschema_public.trigger_function ( UNIQUE (database_id, name) ); -CREATE TABLE metaschema_public.trigger ( - id uuid PRIMARY KEY DEFAULT uuidv7(), - database_id uuid NOT NULL DEFAULT uuid_nil(), - table_id uuid NOT NULL, - name text NOT NULL, - event text, - function_name text, - smart_tags jsonb, - category metaschema_public.object_category NOT NULL DEFAULT 'app', - tags citext[] NOT NULL DEFAULT '{}', - created_at timestamptz DEFAULT now(), - updated_at timestamptz DEFAULT now(), - 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, - UNIQUE (table_id, name) -); - -CREATE INDEX trigger_database_id_idx ON metaschema_public.trigger (database_id); - CREATE TABLE metaschema_public.unique_constraint ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL DEFAULT uuid_nil(), @@ -1149,6 +1124,64 @@ CREATE TABLE metaschema_public.function ( database_id uuid NOT NULL, schema_id uuid NOT NULL, name text NOT NULL, + kind text NOT NULL DEFAULT 'reservation', + arguments jsonb NOT NULL DEFAULT '[]', + returns jsonb, + volatility text, + is_strict boolean NOT NULL DEFAULT false, + security_invoker boolean NOT NULL DEFAULT true, + function_type text, + data jsonb DEFAULT '{}', + body_ast jsonb, + smart_tags jsonb, + api_exposed boolean NOT NULL DEFAULT false, + category metaschema_public.object_category NOT NULL DEFAULT 'app', + tags citext[] NOT NULL DEFAULT '{}', + CONSTRAINT function_kind_valid + CHECK (kind IN ('reservation', 'sql', 'plpgsql', 'trigger')), + CONSTRAINT function_volatility_valid + CHECK ( + volatility IS NULL + OR volatility IN ('IMMUTABLE', 'STABLE', 'VOLATILE') + ), + CONSTRAINT function_security_invoker_only + CHECK (security_invoker), + CONSTRAINT function_reservation_not_api_exposed + CHECK ( + kind <> 'reservation' + OR NOT (api_exposed) + ), + CONSTRAINT function_reservation_has_no_definition + CHECK ( + kind <> 'reservation' + OR (arguments = '[]'::jsonb + AND returns IS NULL + AND volatility IS NULL + AND function_type IS NULL + AND body_ast IS NULL) + ), + CONSTRAINT function_trigger_signature + CHECK ( + kind <> 'trigger' + OR (arguments = '[]'::jsonb + AND returns = '{"type": {"name": "trigger"}}'::jsonb + AND volatility = 'VOLATILE' + AND function_type IS NULL + AND body_ast IS NOT NULL) + ), + CONSTRAINT function_trigger_not_api_exposed + CHECK ( + kind <> 'trigger' + OR NOT (api_exposed) + ), + CONSTRAINT function_definition_complete + CHECK ( + kind = 'reservation' + OR (returns IS NOT NULL + AND volatility IS NOT NULL + AND (function_type IS NOT NULL + OR body_ast IS NOT NULL)) + ), CONSTRAINT db_fkey FOREIGN KEY(database_id) REFERENCES metaschema_public.database (id) @@ -1162,6 +1195,72 @@ CREATE TABLE metaschema_public.function ( CREATE INDEX function_database_id_idx ON metaschema_public.function (database_id); +CREATE INDEX function_kind_idx ON metaschema_public.function (kind); + +CREATE TABLE metaschema_public.trigger ( + id uuid PRIMARY KEY DEFAULT uuidv7(), + database_id uuid NOT NULL DEFAULT uuid_nil(), + table_id uuid NOT NULL, + name text NOT NULL, + event text, + function_name text, + kind text NOT NULL DEFAULT 'reservation', + function_id uuid, + timing text, + events text[], + for_each_row boolean, + when_ast jsonb, + smart_tags jsonb, + category metaschema_public.object_category NOT NULL DEFAULT 'app', + tags citext[] NOT NULL DEFAULT '{}', + created_at timestamptz DEFAULT now(), + updated_at timestamptz DEFAULT now(), + 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 function_fkey + FOREIGN KEY(function_id) + REFERENCES metaschema_public.function (id) + ON DELETE RESTRICT, + CONSTRAINT trigger_kind_valid + CHECK (kind IN ('reservation', 'attachment')), + CONSTRAINT trigger_kind_matches_attachment + CHECK ((kind = 'attachment') = (function_id IS NOT NULL)), + CONSTRAINT trigger_reservation_has_no_definition + CHECK ( + kind <> 'reservation' + OR (timing IS NULL + AND (COALESCE(cardinality(events), 0)) = 0 + AND for_each_row IS NULL + AND when_ast IS NULL) + ), + CONSTRAINT trigger_attachment_has_no_legacy_definition + CHECK ( + kind <> 'attachment' + OR (event IS NULL + AND function_name IS NULL) + ), + CONSTRAINT trigger_customer_attachment_shape + CHECK ( + function_id IS NULL + OR (timing = 'AFTER' + AND for_each_row + AND events IS NOT NULL + AND cardinality(events) > 0 + AND events <@ ARRAY['INSERT', 'UPDATE', 'DELETE']) + ), + UNIQUE (table_id, name) +); + +CREATE INDEX trigger_database_id_idx ON metaschema_public.trigger (database_id); + +CREATE INDEX trigger_function_id_idx ON metaschema_public.trigger (function_id); + CREATE TABLE metaschema_public.partition ( id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), database_id uuid NOT NULL, @@ -1447,4 +1546,23 @@ CREATE TABLE metaschema_public.unique_constraint_behavior ( UNIQUE (unique_constraint_id, scope) ); -CREATE INDEX unique_constraint_behavior_database_id_idx ON metaschema_public.unique_constraint_behavior (database_id); \ No newline at end of file +CREATE INDEX unique_constraint_behavior_database_id_idx ON metaschema_public.unique_constraint_behavior (database_id); + +CREATE TABLE metaschema_public.identity_provider_registry ( + slug text PRIMARY KEY, + kind text NOT NULL CHECK (kind IN ('oauth2', 'oidc')), + display_name text NOT NULL, + issuer_url text, + authorization_url text, + token_url text, + userinfo_url text, + scopes text[] NOT NULL DEFAULT CAST('{}' AS text[]), + CHECK ( + (kind = 'oidc' + AND issuer_url IS NOT NULL) + OR (kind = 'oauth2' + AND authorization_url IS NOT NULL + AND token_url IS NOT NULL + AND userinfo_url IS NOT NULL) + ) +); \ No newline at end of file diff --git a/packages/metaschema-schema/verify/schemas/metaschema_public/tables/function/table.sql b/packages/metaschema-schema/verify/schemas/metaschema_public/tables/function/table.sql index ae36d2213..2fbe82dbb 100644 --- a/packages/metaschema-schema/verify/schemas/metaschema_public/tables/function/table.sql +++ b/packages/metaschema-schema/verify/schemas/metaschema_public/tables/function/table.sql @@ -2,6 +2,10 @@ BEGIN; -SELECT assert_table('metaschema_public.function'::regclass); +SELECT id, database_id, schema_id, name, kind, arguments, returns, volatility, + is_strict, security_invoker, function_type, data, body_ast, smart_tags, + api_exposed, category, tags +FROM metaschema_public.function +WHERE FALSE; ROLLBACK; diff --git a/packages/metaschema-schema/verify/schemas/metaschema_public/tables/identity_provider_registry/table.sql b/packages/metaschema-schema/verify/schemas/metaschema_public/tables/identity_provider_registry/table.sql new file mode 100644 index 000000000..3bf2024f9 --- /dev/null +++ b/packages/metaschema-schema/verify/schemas/metaschema_public/tables/identity_provider_registry/table.sql @@ -0,0 +1,9 @@ +-- Verify schemas/metaschema_public/tables/identity_provider_registry/table on pg + +BEGIN; + +SELECT slug, kind, display_name, issuer_url, authorization_url, token_url, userinfo_url, scopes +FROM metaschema_public.identity_provider_registry +WHERE FALSE; + +ROLLBACK;