@opengeni/db 5.0.1 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/dist/automations.d.ts +0 -2
  2. package/dist/canonical-human-identities.d.ts +4 -0
  3. package/dist/canonical-human-identities.js +3 -3
  4. package/dist/capability-components.d.ts +5 -13
  5. package/dist/capability-integrations.d.ts +23 -8
  6. package/dist/capability-reconciliation.d.ts +5 -0
  7. package/dist/capability-reconciliation.js +21 -0
  8. package/dist/capability-reconciliation.js.map +1 -0
  9. package/dist/{chunk-6T2GGYWE.js → chunk-4UJCXBR2.js} +4 -5800
  10. package/dist/chunk-4UJCXBR2.js.map +1 -0
  11. package/dist/{chunk-MWMXWHJD.js → chunk-7JCDSMX2.js} +2 -2
  12. package/dist/chunk-AJA2ZIUW.js +219 -0
  13. package/dist/chunk-AJA2ZIUW.js.map +1 -0
  14. package/dist/chunk-CDAT6EKW.js +247 -0
  15. package/dist/chunk-CDAT6EKW.js.map +1 -0
  16. package/dist/{chunk-DPONPYJD.js → chunk-CJI6G254.js} +70 -18
  17. package/dist/chunk-CJI6G254.js.map +1 -0
  18. package/dist/chunk-G32NVMRJ.js +276 -0
  19. package/dist/chunk-G32NVMRJ.js.map +1 -0
  20. package/dist/chunk-GABXCG3N.js +5811 -0
  21. package/dist/chunk-GABXCG3N.js.map +1 -0
  22. package/dist/{chunk-ERKVSPDX.js → chunk-LVY33EWL.js} +3 -3
  23. package/dist/{chunk-45CP2YKY.js → chunk-MAMBI2HR.js} +9 -3
  24. package/dist/{chunk-45CP2YKY.js.map → chunk-MAMBI2HR.js.map} +1 -1
  25. package/dist/{chunk-F73UKJY6.js → chunk-MN5PZMEK.js} +91 -108
  26. package/dist/chunk-MN5PZMEK.js.map +1 -0
  27. package/dist/chunk-PJI4MUZ7.js +131 -0
  28. package/dist/chunk-PJI4MUZ7.js.map +1 -0
  29. package/dist/{chunk-T6BWIGWJ.js → chunk-RIYHP2WU.js} +2 -2
  30. package/dist/{chunk-7HMK3B45.js → chunk-RL2MUXM3.js} +16 -7
  31. package/dist/chunk-RL2MUXM3.js.map +1 -0
  32. package/dist/{chunk-HQDVI2DB.js → chunk-SYKHLPTK.js} +4 -3
  33. package/dist/{chunk-HQDVI2DB.js.map → chunk-SYKHLPTK.js.map} +1 -1
  34. package/dist/{chunk-T6YLHJLS.js → chunk-XIXVVBW2.js} +198 -51
  35. package/dist/chunk-XIXVVBW2.js.map +1 -0
  36. package/dist/chunk-ZPJ6V2VG.js +156 -0
  37. package/dist/chunk-ZPJ6V2VG.js.map +1 -0
  38. package/dist/codex-capacity-recovery.d.ts +12 -0
  39. package/dist/codex-failure-eligibility.d.ts +9 -0
  40. package/dist/company-profile-schema.d.ts +4 -2
  41. package/dist/connect-attempts.d.ts +15 -0
  42. package/dist/connect-attempts.js +25 -0
  43. package/dist/connect-attempts.js.map +1 -0
  44. package/dist/connection-authority.d.ts +7 -63
  45. package/dist/connection-refresh-lock.d.ts +4 -0
  46. package/dist/connection-token-resolver.d.ts +8 -43
  47. package/dist/database-timing.d.ts +10 -0
  48. package/dist/database.d.ts +4 -0
  49. package/dist/editable-artifact-durable-export.js +2 -2
  50. package/dist/editable-artifacts.d.ts +9 -0
  51. package/dist/editable-artifacts.js +7 -5
  52. package/dist/index.d.ts +447 -78
  53. package/dist/index.js +12705 -12872
  54. package/dist/index.js.map +1 -1
  55. package/dist/insights-usage-bundle.d.ts +3 -0
  56. package/dist/integration-bindings.d.ts +1 -1
  57. package/dist/integration-facets.d.ts +18 -1
  58. package/dist/knowledge-entries-schema.d.ts +2 -0
  59. package/dist/knowledge-entries.d.ts +1 -0
  60. package/dist/knowledge-indexing.d.ts +1 -0
  61. package/dist/live-session-attempt.d.ts +35 -137
  62. package/dist/managed-auth-session-sets.js +3 -3
  63. package/dist/mcp-account-bindings.d.ts +4 -0
  64. package/dist/mcp-oauth.d.ts +11 -0
  65. package/dist/mcp-operations.js +2 -2
  66. package/dist/migrate.js +1 -1
  67. package/dist/organization-integration-policy.d.ts +51 -0
  68. package/dist/organization-integration-policy.js +18 -0
  69. package/dist/organization-integration-policy.js.map +1 -0
  70. package/dist/organization-usage.d.ts +20 -0
  71. package/dist/plugin-packages.d.ts +9 -12
  72. package/dist/pr-review.d.ts +0 -11
  73. package/dist/preference-registry-schema.d.ts +17 -0
  74. package/dist/provision-roles.js +1 -1
  75. package/dist/retained-provider-commands.d.ts +124 -6
  76. package/dist/retained-provider-commands.js +29 -97
  77. package/dist/retained-provider-commands.js.map +1 -1
  78. package/dist/retired-session-create-metadata.d.ts +4 -0
  79. package/dist/runtime-posture.d.ts +7 -5
  80. package/dist/schema.d.ts +876 -656
  81. package/dist/schema.js +5 -7
  82. package/dist/session-admission-block.d.ts +32 -0
  83. package/dist/session-attempt-writers.d.ts +4 -0
  84. package/dist/session-background-commands.js +5 -4
  85. package/dist/session-command-output.js +8 -126
  86. package/dist/session-command-output.js.map +1 -1
  87. package/dist/session-control.d.ts +5 -1
  88. package/dist/session-event-slices.d.ts +10 -0
  89. package/dist/session-event-slices.js +8 -222
  90. package/dist/session-event-slices.js.map +1 -1
  91. package/dist/session-file-attachments.d.ts +28 -0
  92. package/dist/session-goal-reports.d.ts +43 -0
  93. package/dist/session-mcp-approval.d.ts +4 -0
  94. package/dist/session-mcp-credential-rotation.js +5 -4
  95. package/dist/session-mcp-credential-rotation.js.map +1 -1
  96. package/dist/session-message-search.d.ts +22 -0
  97. package/dist/session-queue-commands.d.ts +2 -6
  98. package/dist/session-retry.d.ts +36 -0
  99. package/dist/session-tenancy.js +3 -3
  100. package/dist/skill-source-release-impact.d.ts +18 -0
  101. package/dist/skill-source-release.d.ts +9 -0
  102. package/dist/skills.d.ts +7 -1
  103. package/dist/user-resource-authority.d.ts +0 -8
  104. package/dist/video-generation.js +3 -3
  105. package/dist/workspace-tool-defaults.js +5 -4
  106. package/dist/workspace-tool-defaults.js.map +1 -1
  107. package/drizzle/0468_knowledge_relationship_projection.sql +30 -0
  108. package/drizzle/0469_knowledge_source_discovery.sql +85 -0
  109. package/drizzle/0470_organization_integration_policy.sql +151 -0
  110. package/drizzle/0471_usage_events_account_recent_index.sql +4 -0
  111. package/drizzle/0472_usage_events_workspace_recent_index.sql +4 -0
  112. package/drizzle/0473_organization_usage_analytical_capability.sql +237 -0
  113. package/drizzle/0474_goal_report_requirements.sql +156 -0
  114. package/drizzle/0475_session_event_history_statistics.sql +15 -0
  115. package/drizzle/0476_session_event_history_policy_planning.sql +29 -0
  116. package/drizzle/0477_managed_sign_in_methods.sql +303 -0
  117. package/drizzle/0478_sender_owned_connections.sql +2772 -0
  118. package/drizzle/0479_connection_create_idempotency.sql +16 -0
  119. package/drizzle/0480_private_session_organization_key_administration.sql +154 -0
  120. package/drizzle/0481_personal_connection_visibility_cleanup.sql +45 -0
  121. package/drizzle/0482_remove_packs.sql +417 -0
  122. package/drizzle/0483_preclaim_admission_block.sql +85 -0
  123. package/drizzle/0484_insights_usage_projection.sql +211 -0
  124. package/drizzle/0485_inherited_mcp_approval_policies.sql +6 -0
  125. package/drizzle/0486_reasoning_configuration_message_forks.sql +35 -0
  126. package/drizzle/0487_skill_catalog_message_forks.sql +26 -0
  127. package/drizzle/0488_permanent_skill_removal.sql +207 -0
  128. package/drizzle/0489_modal_command_byte_offsets.sql +17 -0
  129. package/drizzle/0490_periodic_capture_attempt_cadence.sql +6 -0
  130. package/drizzle/0491_warm_capture_holder_reclamation.sql +57 -0
  131. package/drizzle/0492_codex_accepted_source_authority.sql +249 -0
  132. package/drizzle/0493_stopping_command_error_containment.sql +58 -0
  133. package/drizzle/0494_mcp_account_bindings.sql +334 -0
  134. package/drizzle/0495_consented_sandbox_recovery.sql +220 -0
  135. package/drizzle/0496_supervised_command_settlement.sql +386 -0
  136. package/drizzle/0497_modal_inventory_owner_read_capability.sql +112 -0
  137. package/drizzle/0498_enrollment_membership_fence.sql +87 -0
  138. package/drizzle/0499_session_attachment_access.sql +224 -0
  139. package/drizzle/0500_session_execution_epoch.sql +37 -0
  140. package/drizzle/0501_session_sharing_execution.sql +591 -0
  141. package/drizzle/0502_active_message_boundary_forks.sql +30 -0
  142. package/package.json +17 -5
  143. package/src/automations.ts +17 -73
  144. package/src/canonical-human-identities.ts +17 -5
  145. package/src/capability-components.ts +7 -24
  146. package/src/capability-integrations.ts +301 -21
  147. package/src/capability-reconciliation.ts +19 -0
  148. package/src/codex-capacity-recovery.ts +47 -0
  149. package/src/codex-failure-eligibility.ts +93 -0
  150. package/src/connect-attempts.ts +49 -18
  151. package/src/connection-authority.ts +12 -158
  152. package/src/connection-refresh-lock.ts +22 -0
  153. package/src/connection-token-resolver.ts +58 -530
  154. package/src/database-timing.ts +43 -0
  155. package/src/database.ts +61 -26
  156. package/src/editable-artifacts.ts +1 -1
  157. package/src/index.ts +3578 -1273
  158. package/src/insights-model-bundle.ts +19 -7
  159. package/src/insights-usage-bundle.ts +16 -19
  160. package/src/integration-bindings.ts +2 -7
  161. package/src/integration-facets.ts +73 -6
  162. package/src/knowledge-entries.ts +8 -1
  163. package/src/live-session-attempt.ts +0 -22
  164. package/src/mcp-account-bindings.ts +12 -0
  165. package/src/mcp-oauth.ts +26 -0
  166. package/src/organization-integration-policy.ts +235 -0
  167. package/src/organization-usage.ts +83 -0
  168. package/src/plugin-packages.ts +226 -42
  169. package/src/pr-review.ts +7 -69
  170. package/src/preference-registry-schema.ts +1 -0
  171. package/src/provision-roles.ts +50 -37
  172. package/src/retained-provider-commands.ts +286 -9
  173. package/src/retired-session-create-metadata.ts +16 -0
  174. package/src/runtime-posture.ts +217 -13
  175. package/src/schema.ts +104 -120
  176. package/src/session-admission-block.ts +29 -0
  177. package/src/session-attempt-writers.ts +48 -0
  178. package/src/session-control.ts +21 -5
  179. package/src/session-event-slices.ts +76 -26
  180. package/src/session-file-attachments.ts +66 -0
  181. package/src/session-goal-reports.ts +255 -0
  182. package/src/session-mcp-approval.ts +30 -0
  183. package/src/session-message-search.ts +348 -0
  184. package/src/session-queue-commands.ts +60 -36
  185. package/src/session-retry.ts +319 -0
  186. package/src/skill-config-migration.ts +11 -2
  187. package/src/skill-source-release-impact.ts +33 -0
  188. package/src/skill-source-release.ts +67 -26
  189. package/src/skills.ts +44 -6
  190. package/src/user-resource-authority.ts +0 -56
  191. package/dist/chunk-6T2GGYWE.js.map +0 -1
  192. package/dist/chunk-7HMK3B45.js.map +0 -1
  193. package/dist/chunk-DPONPYJD.js.map +0 -1
  194. package/dist/chunk-F73UKJY6.js.map +0 -1
  195. package/dist/chunk-T6YLHJLS.js.map +0 -1
  196. package/dist/host-mcp-binding-match.d.ts +0 -5
  197. package/dist/host-mcp-bindings.d.ts +0 -79
  198. package/dist/host-mcp-resolvers.d.ts +0 -47
  199. package/dist/host-mcp-task-authority.d.ts +0 -43
  200. package/dist/host-selection-identity.d.ts +0 -6
  201. package/dist/pack-components.d.ts +0 -83
  202. package/dist/pack-installations.d.ts +0 -86
  203. package/src/host-mcp-binding-match.ts +0 -34
  204. package/src/host-mcp-bindings.ts +0 -777
  205. package/src/host-mcp-resolvers.ts +0 -266
  206. package/src/host-mcp-task-authority.ts +0 -384
  207. package/src/host-selection-identity.ts +0 -29
  208. package/src/pack-components.ts +0 -980
  209. package/src/pack-installations.ts +0 -950
  210. /package/dist/{chunk-MWMXWHJD.js.map → chunk-7JCDSMX2.js.map} +0 -0
  211. /package/dist/{chunk-ERKVSPDX.js.map → chunk-LVY33EWL.js.map} +0 -0
  212. /package/dist/{chunk-T6BWIGWJ.js.map → chunk-RIYHP2WU.js.map} +0 -0
@@ -0,0 +1,16 @@
1
+ -- deployment-mode: rolling
2
+ -- Creation receipts are internal and survive token refresh and disconnect.
3
+ ALTER TABLE connections
4
+ ADD COLUMN create_operation_id text,
5
+ ADD COLUMN create_request_digest text;
6
+
7
+ ALTER TABLE connections ADD CONSTRAINT connections_create_operation_pair_check
8
+ CHECK (
9
+ (create_operation_id IS NULL AND create_request_digest IS NULL)
10
+ OR (create_operation_id IS NOT NULL AND create_request_digest IS NOT NULL
11
+ AND created_by_subject_id IS NOT NULL)
12
+ );
13
+
14
+ CREATE UNIQUE INDEX connections_create_operation_uq
15
+ ON connections (workspace_id, created_by_subject_id, create_operation_id)
16
+ WHERE create_operation_id IS NOT NULL;
@@ -0,0 +1,154 @@
1
+ -- deployment-mode: rolling
2
+ -- Organization administrators may configure the product gate with a live
3
+ -- organization key. This grants no access to private session contents.
4
+ -- Existing human command receipts and replay semantics remain valid.
5
+ SET LOCAL lock_timeout = '5s';
6
+ SET LOCAL statement_timeout = '5min';
7
+
8
+ ALTER TABLE organization_private_session_setting_events
9
+ ALTER COLUMN actor_membership_id DROP NOT NULL,
10
+ ADD COLUMN actor_subject_id text,
11
+ ADD CONSTRAINT organization_private_session_setting_events_actor_check CHECK (
12
+ actor_membership_id IS NOT NULL OR
13
+ (actor_subject_id IS NOT NULL AND actor_subject_id LIKE 'api_key:%')
14
+ );
15
+
16
+ -- Share the existing live administrator fence, not its policy-specific name.
17
+ -- The authenticated API caller establishes provenance; this is a live DB fence.
18
+ CREATE FUNCTION opengeni_private.assert_organization_administrator(
19
+ p_account uuid, p_actor text
20
+ ) RETURNS void LANGUAGE plpgsql SECURITY DEFINER SET search_path FROM CURRENT AS $body$
21
+ DECLARE
22
+ previous_marker text := current_setting('opengeni.organization_tenancy_lifecycle', true);
23
+ permissions_value jsonb;
24
+ member_id uuid;
25
+ BEGIN
26
+ IF p_account IS NULL OR p_actor IS NULL
27
+ OR p_account IS DISTINCT FROM opengeni_private.current_account_id()
28
+ OR p_actor IS DISTINCT FROM opengeni_private.current_subject_id() THEN
29
+ RAISE EXCEPTION 'organization administration scope invalid' USING ERRCODE = '42501';
30
+ END IF;
31
+ IF p_actor LIKE 'api_key:%' THEN
32
+ SELECT permissions INTO permissions_value FROM api_keys
33
+ WHERE account_id = p_account AND 'api_key:' || id::text = p_actor
34
+ AND credential_kind = 'organization' AND workspace_id IS NULL AND revoked_at IS NULL
35
+ AND (expires_at IS NULL OR expires_at > clock_timestamp()) FOR SHARE;
36
+ IF NOT coalesce(permissions_value ? 'workspace:admin', false) THEN
37
+ RAISE EXCEPTION 'organization administrator required' USING ERRCODE = '42501';
38
+ END IF;
39
+ ELSE
40
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', 'organization_private_session_settings', true);
41
+ SELECT id INTO member_id FROM organization_memberships
42
+ WHERE account_id = p_account AND subject_id = p_actor AND status = 'active' AND role IN ('owner','admin') FOR SHARE;
43
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(previous_marker, ''), true);
44
+ IF member_id IS NULL THEN RAISE EXCEPTION 'organization administrator required' USING ERRCODE = '42501'; END IF;
45
+ END IF;
46
+ END $body$;
47
+ REVOKE ALL ON FUNCTION opengeni_private.assert_organization_administrator(uuid,text) FROM PUBLIC;
48
+ -- Replace in place to preserve existing runtime EXECUTE grants during rollout.
49
+ CREATE OR REPLACE FUNCTION opengeni_private.assert_organization_integration_policy_administrator(
50
+ p_account uuid, p_actor text
51
+ ) RETURNS void LANGUAGE sql SECURITY DEFINER SET search_path FROM CURRENT AS $body$
52
+ SELECT opengeni_private.assert_organization_administrator(p_account, p_actor)
53
+ $body$;
54
+
55
+ CREATE OR REPLACE FUNCTION get_organization_private_session_settings(
56
+ p_account_id uuid, p_actor_subject_id text
57
+ ) RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path FROM CURRENT AS $body$
58
+ DECLARE
59
+ setting organization_private_session_settings%ROWTYPE;
60
+ account_created_at timestamptz;
61
+ BEGIN
62
+ PERFORM opengeni_private.assert_organization_administrator(p_account_id, p_actor_subject_id);
63
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', 'organization_private_session_settings', true);
64
+ SELECT created_at INTO account_created_at FROM managed_accounts WHERE id = p_account_id;
65
+ IF NOT FOUND THEN RAISE EXCEPTION 'organization not found' USING ERRCODE = 'P0002'; END IF;
66
+ SELECT * INTO setting FROM organization_private_session_settings WHERE account_id = p_account_id;
67
+ RETURN jsonb_build_object(
68
+ 'organizationId', p_account_id,
69
+ 'enabled', coalesce(setting.enabled, false),
70
+ 'available', session_tenancy_product_activated(p_account_id, 1),
71
+ 'version', coalesce(setting.version, 0),
72
+ 'updatedAt', coalesce(setting.updated_at, account_created_at)
73
+ );
74
+ END $body$;
75
+
76
+ CREATE OR REPLACE FUNCTION update_organization_private_session_settings(
77
+ p_account_id uuid, p_actor_subject_id text, p_enabled boolean,
78
+ p_expected_version bigint, p_operation_id uuid
79
+ ) RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path FROM CURRENT AS $body$
80
+ DECLARE
81
+ actor_id uuid;
82
+ setting organization_private_session_settings%ROWTYPE;
83
+ prior organization_private_session_setting_events%ROWTYPE;
84
+ next_version bigint;
85
+ did_change boolean;
86
+ BEGIN
87
+ IF p_account_id IS NULL OR p_enabled IS NULL OR p_operation_id IS NULL
88
+ OR p_expected_version IS NULL OR p_expected_version < 0
89
+ OR p_actor_subject_id IS DISTINCT FROM opengeni_private.current_subject_id()
90
+ OR p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
91
+ THEN
92
+ RAISE EXCEPTION 'organization private-session settings request is invalid' USING ERRCODE = '22023';
93
+ END IF;
94
+ PERFORM pg_advisory_xact_lock(hashtextextended('organization-membership:' || p_account_id::text, 0));
95
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', 'organization_private_session_settings', true);
96
+ PERFORM 1 FROM managed_accounts WHERE id = p_account_id FOR KEY SHARE;
97
+ IF NOT FOUND THEN RAISE EXCEPTION 'organization not found' USING ERRCODE = 'P0002'; END IF;
98
+ -- Revoked/expired keys cannot replay old successful commands.
99
+ PERFORM opengeni_private.assert_organization_administrator(p_account_id, p_actor_subject_id);
100
+ SELECT id INTO actor_id FROM organization_memberships
101
+ WHERE account_id = p_account_id AND subject_id = p_actor_subject_id;
102
+ SELECT * INTO prior FROM organization_private_session_setting_events WHERE id = p_operation_id;
103
+ IF FOUND THEN
104
+ IF prior.account_id IS DISTINCT FROM p_account_id
105
+ OR prior.actor_membership_id IS DISTINCT FROM actor_id
106
+ OR (prior.actor_subject_id IS NOT NULL AND prior.actor_subject_id IS DISTINCT FROM p_actor_subject_id)
107
+ OR prior.requested_enabled IS DISTINCT FROM p_enabled
108
+ OR prior.expected_version IS DISTINCT FROM p_expected_version
109
+ THEN
110
+ RAISE EXCEPTION 'organization private-session operation key was reused' USING ERRCODE = '23505';
111
+ END IF;
112
+ RETURN jsonb_build_object(
113
+ 'organizationId', p_account_id, 'enabled', prior.result_enabled,
114
+ 'available', session_tenancy_product_activated(p_account_id, 1),
115
+ 'version', prior.result_version, 'updatedAt', prior.result_updated_at, 'changed', prior.changed
116
+ );
117
+ END IF;
118
+ SELECT * INTO setting FROM organization_private_session_settings WHERE account_id = p_account_id FOR UPDATE;
119
+ IF coalesce(setting.version, 0) IS DISTINCT FROM p_expected_version THEN
120
+ RAISE EXCEPTION 'organization private-session settings changed before update' USING ERRCODE = '40001';
121
+ END IF;
122
+ IF p_enabled AND NOT session_tenancy_product_activated(p_account_id, 1) THEN
123
+ RAISE EXCEPTION 'session tenancy product surface is not available for this organization' USING ERRCODE = '55000';
124
+ END IF;
125
+ did_change := coalesce(setting.enabled, false) IS DISTINCT FROM p_enabled;
126
+ next_version := CASE WHEN setting.account_id IS NULL THEN 1 ELSE setting.version + 1 END;
127
+ INSERT INTO organization_private_session_settings (
128
+ account_id, enabled, version, updated_by_membership_id, updated_at
129
+ ) VALUES (p_account_id, p_enabled, next_version, actor_id, clock_timestamp())
130
+ ON CONFLICT (account_id) DO UPDATE SET
131
+ enabled = excluded.enabled, version = excluded.version,
132
+ updated_by_membership_id = excluded.updated_by_membership_id, updated_at = excluded.updated_at
133
+ RETURNING * INTO setting;
134
+ INSERT INTO organization_private_session_setting_events (
135
+ id, account_id, actor_membership_id, actor_subject_id, requested_enabled,
136
+ expected_version, result_enabled, result_version, result_updated_at, changed
137
+ ) VALUES (
138
+ p_operation_id, p_account_id, actor_id, p_actor_subject_id, p_enabled,
139
+ p_expected_version, setting.enabled, setting.version, setting.updated_at, did_change
140
+ );
141
+ RETURN jsonb_build_object(
142
+ 'organizationId', p_account_id, 'enabled', setting.enabled,
143
+ 'available', session_tenancy_product_activated(p_account_id, 1),
144
+ 'version', setting.version, 'updatedAt', setting.updated_at, 'changed', did_change
145
+ );
146
+ END $body$;
147
+
148
+ DO $safe_path$
149
+ BEGIN
150
+ EXECUTE format('ALTER FUNCTION opengeni_private.assert_organization_administrator(uuid,text) SET search_path = pg_catalog, %I, pg_temp', current_schema());
151
+ EXECUTE format('ALTER FUNCTION opengeni_private.assert_organization_integration_policy_administrator(uuid,text) SET search_path = pg_catalog, %I, pg_temp', current_schema());
152
+ EXECUTE format('ALTER FUNCTION get_organization_private_session_settings(uuid,text) SET search_path = pg_catalog, %I, pg_temp', current_schema());
153
+ EXECUTE format('ALTER FUNCTION update_organization_private_session_settings(uuid,text,boolean,bigint,uuid) SET search_path = pg_catalog, %I, pg_temp', current_schema());
154
+ END $safe_path$;
@@ -0,0 +1,45 @@
1
+ -- deployment-mode: rolling
2
+ -- Visibility changes invalidate session-initial personal connection selections.
3
+ -- Permit that exact cleanup under the existing transaction-local lifecycle
4
+ -- capability; accepted turn authority and parent provenance remain immutable.
5
+ SET LOCAL lock_timeout = '5s';
6
+ SET LOCAL statement_timeout = '5min';
7
+
8
+ CREATE OR REPLACE FUNCTION opengeni_private.prevent_personal_connection_authority_mutation()
9
+ RETURNS trigger LANGUAGE plpgsql SET search_path FROM CURRENT AS $body$
10
+ BEGIN
11
+ IF TG_TABLE_NAME = 'sessions' THEN
12
+ IF NEW.parent_turn_id IS DISTINCT FROM OLD.parent_turn_id THEN
13
+ RAISE EXCEPTION 'session parent turn authority is immutable' USING ERRCODE = '23514';
14
+ END IF;
15
+ IF NEW.initial_personal_connection_delegations IS DISTINCT FROM OLD.initial_personal_connection_delegations THEN
16
+ IF NEW.initial_personal_connection_delegations IS DISTINCT FROM '[]'::jsonb
17
+ OR NEW.visibility IS NOT DISTINCT FROM OLD.visibility
18
+ OR NEW.authority_epoch IS DISTINCT FROM OLD.authority_epoch + 1
19
+ OR NEW.owner_subject_id IS DISTINCT FROM OLD.owner_subject_id
20
+ OR NEW.owner_organization_membership_id IS DISTINCT FROM OLD.owner_organization_membership_id
21
+ THEN
22
+ RAISE EXCEPTION 'session initial personal MCP authority is immutable' USING ERRCODE = '23514';
23
+ END IF;
24
+ -- A caller-set GUC is insufficient: only the existing protected capability
25
+ -- row minted by the native visibility transition permits this cleanup.
26
+ IF NOT EXISTS (
27
+ SELECT 1 FROM session_visibility_write_capabilities capability
28
+ WHERE capability.backend_pid = pg_backend_pid()
29
+ AND capability.transaction_id = pg_current_xact_id()
30
+ AND capability.capability_id = nullif(current_setting(
31
+ 'opengeni.session_visibility_write_capability', true
32
+ ), '')::uuid
33
+ ) THEN
34
+ RAISE EXCEPTION 'session personal authority cleanup requires the visibility lifecycle capability' USING ERRCODE = '42501';
35
+ END IF;
36
+ END IF;
37
+ ELSIF NEW.personal_connection_delegations IS DISTINCT FROM OLD.personal_connection_delegations THEN
38
+ RAISE EXCEPTION '% personal MCP authority is immutable', TG_TABLE_NAME USING ERRCODE = '23514';
39
+ END IF;
40
+ RETURN NEW;
41
+ END $body$;
42
+ DO $safe_path$
43
+ BEGIN
44
+ EXECUTE format('ALTER FUNCTION opengeni_private.prevent_personal_connection_authority_mutation() SET search_path = pg_catalog, %I, pg_temp', current_schema());
45
+ END $safe_path$;
@@ -0,0 +1,417 @@
1
+ -- deployment-mode: maintenance
2
+ -- Destructive feature removal. Drain all API/control/turn workers before this
3
+ -- cutover and start only matching binaries. No Pack data is migrated forward.
4
+ SET LOCAL lock_timeout = '5s';
5
+ SET LOCAL statement_timeout = '10min';
6
+
7
+ DO $drain$
8
+ DECLARE roles jsonb := nullif(current_setting('opengeni.migration_application_roles', true), '')::jsonb;
9
+ BEGIN
10
+ IF roles IS NULL OR jsonb_typeof(roles) <> 'array' OR jsonb_array_length(roles) NOT BETWEEN 1 AND 16
11
+ OR EXISTS (SELECT 1 FROM jsonb_array_elements(roles) r WHERE jsonb_typeof(r) <> 'string' OR length(btrim(r #>> '{}')) NOT BETWEEN 1 AND 63)
12
+ THEN RAISE EXCEPTION 'Pack removal requires explicit application database roles' USING ERRCODE='55000'; END IF;
13
+ IF EXISTS (SELECT 1 FROM pg_stat_activity a JOIN jsonb_array_elements_text(roles) r ON a.usename=r.value
14
+ WHERE a.datname=current_database() AND a.pid<>pg_backend_pid())
15
+ THEN RAISE EXCEPTION 'Pack removal requires drained application sessions' USING ERRCODE='55000'; END IF;
16
+ END $drain$;
17
+
18
+ -- The migration owner is NOSUPERUSER/NOBYPASSRLS. Temporarily lift FORCE,
19
+ -- never policies, only while the drained owner performs this atomic cutover.
20
+ LOCK TABLE workspace_packs, pack_installations, pack_installation_components,
21
+ capability_operations, capability_component_owners, capability_facet_installations,
22
+ capability_plugin_installations, integration_facet_binding_owners, integration_facet_bindings,
23
+ skill_source_bindings, preference_registry_preferences, preference_registry_revisions,
24
+ preference_registry_events, automation_sources, automation_triggers, automation_runs,
25
+ automation_trigger_revisions, automation_trigger_events, automation_run_event_links,
26
+ automation_webhook_endpoints
27
+ IN ACCESS EXCLUSIVE MODE;
28
+ ALTER TABLE workspace_packs NO FORCE ROW LEVEL SECURITY;
29
+ ALTER TABLE pack_installations NO FORCE ROW LEVEL SECURITY;
30
+ ALTER TABLE pack_installation_components NO FORCE ROW LEVEL SECURITY;
31
+ ALTER TABLE capability_operations NO FORCE ROW LEVEL SECURITY;
32
+ ALTER TABLE capability_component_owners NO FORCE ROW LEVEL SECURITY;
33
+ ALTER TABLE capability_facet_installations NO FORCE ROW LEVEL SECURITY;
34
+ ALTER TABLE capability_plugin_installations NO FORCE ROW LEVEL SECURITY;
35
+ ALTER TABLE integration_facet_binding_owners NO FORCE ROW LEVEL SECURITY;
36
+ ALTER TABLE integration_facet_bindings NO FORCE ROW LEVEL SECURITY;
37
+ ALTER TABLE skill_source_bindings NO FORCE ROW LEVEL SECURITY;
38
+ ALTER TABLE preference_registry_preferences NO FORCE ROW LEVEL SECURITY;
39
+ ALTER TABLE preference_registry_revisions NO FORCE ROW LEVEL SECURITY;
40
+ ALTER TABLE preference_registry_events NO FORCE ROW LEVEL SECURITY;
41
+ ALTER TABLE automation_sources NO FORCE ROW LEVEL SECURITY;
42
+ ALTER TABLE automation_triggers NO FORCE ROW LEVEL SECURITY;
43
+ ALTER TABLE automation_runs NO FORCE ROW LEVEL SECURITY;
44
+ ALTER TABLE automation_trigger_revisions NO FORCE ROW LEVEL SECURITY;
45
+ ALTER TABLE automation_trigger_events NO FORCE ROW LEVEL SECURITY;
46
+ ALTER TABLE automation_run_event_links NO FORCE ROW LEVEL SECURITY;
47
+
48
+ -- Capture only installations whose last owner is the removed feature. Shared
49
+ -- direct, Plugin and migration ownership remains untouched.
50
+ CREATE TEMP TABLE removed_pack_facets ON COMMIT DROP AS
51
+ SELECT fi.* FROM capability_facet_installations fi
52
+ WHERE EXISTS (SELECT 1 FROM capability_component_owners o WHERE o.facet_installation_id=fi.id AND o.owner_kind='pack')
53
+ AND NOT EXISTS (SELECT 1 FROM capability_component_owners o WHERE o.facet_installation_id=fi.id AND o.owner_kind<>'pack');
54
+ CREATE TEMP TABLE removed_pack_bindings ON COMMIT DROP AS
55
+ SELECT binding.id FROM integration_facet_bindings binding
56
+ WHERE EXISTS (SELECT 1 FROM integration_facet_binding_owners o WHERE o.binding_id=binding.id AND o.owner_kind='pack')
57
+ AND NOT EXISTS (SELECT 1 FROM integration_facet_binding_owners o WHERE o.binding_id=binding.id AND o.owner_kind<>'pack');
58
+ CREATE TEMP TABLE removed_pack_sources ON COMMIT DROP AS
59
+ SELECT id FROM automation_sources
60
+ WHERE pack_installation_id IS NOT NULL AND adapter_id<>'source-control.pull-request.v1';
61
+ CREATE TEMP TABLE removed_pack_triggers ON COMMIT DROP AS
62
+ SELECT trigger.id FROM automation_triggers trigger JOIN automation_sources source ON source.id=trigger.source_id
63
+ WHERE source.adapter_id<>'source-control.pull-request.v1'
64
+ AND (trigger.pack_installation_id IS NOT NULL OR source.id IN (SELECT id FROM removed_pack_sources));
65
+ CREATE TEMP TABLE removed_pack_events ON COMMIT DROP AS
66
+ SELECT event.id FROM automation_trigger_events event
67
+ WHERE event.source_id IN (SELECT id FROM removed_pack_sources)
68
+ OR EXISTS (SELECT 1 FROM jsonb_array_elements(event.matched_trigger_revisions) matched
69
+ JOIN removed_pack_triggers removed ON removed.id::text=matched->>'triggerId');
70
+
71
+ DO $preflight$
72
+ BEGIN
73
+ -- Historical rows can violate the former application-only source/trigger
74
+ -- ownership invariant. Remove exclusively Pack events, but never erase an
75
+ -- event that also records independent work. Refuse that ambiguous cutover.
76
+ IF EXISTS (SELECT 1 FROM automation_trigger_events event
77
+ WHERE event.id IN (SELECT id FROM removed_pack_events) AND (
78
+ EXISTS (SELECT 1 FROM jsonb_array_elements(event.matched_trigger_revisions) matched
79
+ WHERE NOT EXISTS (SELECT 1 FROM removed_pack_triggers removed WHERE removed.id::text=matched->>'triggerId'))
80
+ OR EXISTS (SELECT 1 FROM automation_runs run WHERE run.event_id=event.id
81
+ AND run.trigger_id NOT IN (SELECT id FROM removed_pack_triggers))
82
+ OR EXISTS (SELECT 1 FROM automation_run_event_links link JOIN automation_runs run ON run.id=link.run_id
83
+ WHERE link.event_id=event.id AND run.trigger_id NOT IN (SELECT id FROM removed_pack_triggers))))
84
+ THEN RAISE EXCEPTION 'Resolve mixed Pack and independent automation events before removal' USING ERRCODE='55000'; END IF;
85
+ IF EXISTS (SELECT 1 FROM capability_operations
86
+ WHERE status IN ('pending','running','outcome_unknown')
87
+ AND (target_kind='pack' OR (target_kind='facet_binding' AND target_id IN (SELECT id::text FROM removed_pack_bindings))))
88
+ THEN RAISE EXCEPTION 'Settle Pack-related capability operations before Pack removal' USING ERRCODE='55000'; END IF;
89
+ IF EXISTS (SELECT 1 FROM automation_runs
90
+ WHERE (source_id IN (SELECT id FROM removed_pack_sources) OR trigger_id IN (SELECT id FROM removed_pack_triggers))
91
+ AND status IN ('queued','dispatching'))
92
+ THEN RAISE EXCEPTION 'Settle Pack automation runs before Pack removal' USING ERRCODE='55000'; END IF;
93
+ END $preflight$;
94
+
95
+ -- This is maintenance deactivation, not an invented human approval. Keep all
96
+ -- immutable Skill revisions/events and preserve customized/re-scoped heads.
97
+ DO $release_skills$
98
+ DECLARE head record; next_event integer;
99
+ BEGIN
100
+ FOR head IN
101
+ SELECT DISTINCT p.* FROM preference_registry_preferences p
102
+ JOIN preference_registry_revisions r ON r.id=p.active_revision_id AND r.preference_id=p.id
103
+ JOIN skill_source_bindings b ON b.preference_id=p.id AND b.account_id=p.account_id
104
+ JOIN removed_pack_facets fi ON fi.facet_id=b.skill_facet_id AND fi.workspace_id=b.workspace_id AND fi.account_id=b.account_id
105
+ WHERE p.status='active' AND r.provenance_source='portable_skill'
106
+ AND p.scope='workspace' AND p.scope_workspace_id=b.workspace_id
107
+ AND NOT EXISTS (
108
+ SELECT 1 FROM skill_source_bindings other
109
+ JOIN capability_facet_installations installed ON installed.facet_id=other.skill_facet_id
110
+ AND installed.account_id=other.account_id AND installed.workspace_id=other.workspace_id
111
+ JOIN capability_component_owners owner ON owner.facet_installation_id=installed.id
112
+ WHERE other.preference_id=p.id AND owner.owner_kind<>'pack'
113
+ )
114
+ ORDER BY p.id
115
+ LOOP
116
+ PERFORM set_config('opengeni.preference_lifecycle_head_id',head.id::text,true);
117
+ PERFORM set_config('opengeni.preference_lifecycle_operation','deactivate',true);
118
+ UPDATE preference_registry_preferences SET status='inactive',active_revision_id=NULL,
119
+ active_revision=NULL,active_content_hash=NULL,activation_version=activation_version+1,updated_at=clock_timestamp()
120
+ WHERE id=head.id;
121
+ SELECT coalesce(max(version),0)+1 INTO next_event FROM preference_registry_events WHERE preference_id=head.id;
122
+ INSERT INTO preference_registry_events(account_id,preference_id,type,version,old_revision_id,actor_subject_id,reason)
123
+ VALUES(head.account_id,head.id,'deactivated',next_event,head.active_revision_id,
124
+ 'service:remove-packs:0482','Removed the last distribution owner during Pack removal');
125
+ END LOOP;
126
+ END $release_skills$;
127
+
128
+ DELETE FROM integration_facet_binding_owners WHERE owner_kind='pack';
129
+ DELETE FROM capability_component_owners WHERE owner_kind='pack';
130
+ DELETE FROM integration_facet_bindings WHERE id IN (SELECT id FROM removed_pack_bindings);
131
+ DELETE FROM skill_source_bindings binding USING removed_pack_facets removed
132
+ WHERE binding.skill_facet_id=removed.facet_id AND binding.account_id=removed.account_id
133
+ AND binding.workspace_id=removed.workspace_id
134
+ AND NOT EXISTS (SELECT 1 FROM capability_facet_installations surviving
135
+ WHERE surviving.facet_id=removed.facet_id AND surviving.account_id=removed.account_id
136
+ AND surviving.workspace_id=removed.workspace_id AND surviving.id NOT IN (SELECT id FROM removed_pack_facets));
137
+ DELETE FROM capability_facet_installations WHERE id IN (SELECT id FROM removed_pack_facets);
138
+ DELETE FROM capability_plugin_installations installation
139
+ WHERE installation.id IN (SELECT plugin_installation_id FROM removed_pack_facets)
140
+ AND NOT EXISTS (SELECT 1 FROM capability_facet_installations fi WHERE fi.plugin_installation_id=installation.id)
141
+ AND NOT EXISTS (SELECT 1 FROM capability_component_owners owner WHERE owner.owner_kind='plugin' AND owner.owner_id=installation.id::text)
142
+ AND NOT EXISTS (SELECT 1 FROM integration_facet_binding_owners owner WHERE owner.owner_kind='plugin' AND owner.owner_id=installation.id::text);
143
+
144
+ -- PR Review already uses the independent source/trigger/registration system.
145
+ -- Keep its IDs, credentials, revisions and history; remove only the obsolete
146
+ -- ownership columns. Other Pack automations are removed, not converted.
147
+ -- Remove completed Pack execution records in FK order. Sessions referenced by
148
+ -- those records are independent history and are never deleted here.
149
+ DELETE FROM automation_run_event_links WHERE run_id IN (
150
+ SELECT id FROM automation_runs WHERE source_id IN (SELECT id FROM removed_pack_sources)
151
+ OR trigger_id IN (SELECT id FROM removed_pack_triggers)
152
+ ) OR event_id IN (SELECT id FROM removed_pack_events);
153
+ DELETE FROM automation_runs WHERE source_id IN (SELECT id FROM removed_pack_sources)
154
+ OR trigger_id IN (SELECT id FROM removed_pack_triggers);
155
+ DELETE FROM automation_trigger_events WHERE id IN (SELECT id FROM removed_pack_events);
156
+ DELETE FROM automation_trigger_revisions WHERE trigger_id IN (SELECT id FROM removed_pack_triggers);
157
+ DELETE FROM automation_triggers WHERE id IN (SELECT id FROM removed_pack_triggers);
158
+ DELETE FROM automation_webhook_endpoints WHERE source_id IN (SELECT id FROM removed_pack_sources);
159
+ DELETE FROM automation_sources WHERE id IN (SELECT id FROM removed_pack_sources);
160
+ ALTER TABLE automation_sources DROP CONSTRAINT automation_sources_pack_installation_fk;
161
+ ALTER TABLE automation_sources DROP CONSTRAINT automation_sources_shape_chk;
162
+ ALTER TABLE automation_sources DROP COLUMN pack_installation_id, DROP COLUMN pack_connector_id;
163
+ ALTER TABLE automation_sources ADD CONSTRAINT automation_sources_shape_chk CHECK (
164
+ status IN ('active','disabled') AND version>0 AND octet_length(name) BETWEEN 1 AND 512
165
+ AND octet_length(adapter_id) BETWEEN 1 AND 128 AND octet_length(created_by_subject_id) BETWEEN 1 AND 4096
166
+ AND jsonb_typeof(configuration)='object'
167
+ );
168
+ ALTER TABLE automation_triggers DROP CONSTRAINT automation_triggers_pack_installation_fk;
169
+ ALTER TABLE automation_triggers DROP CONSTRAINT automation_triggers_shape_chk;
170
+ ALTER TABLE automation_triggers DROP COLUMN pack_installation_id, DROP COLUMN pack_template_id;
171
+ ALTER TABLE automation_triggers ADD CONSTRAINT automation_triggers_shape_chk CHECK (
172
+ status IN ('active','paused','disabled') AND current_revision>0
173
+ AND octet_length(name) BETWEEN 1 AND 512 AND octet_length(created_by_subject_id) BETWEEN 1 AND 4096
174
+ );
175
+
176
+ DELETE FROM capability_operations WHERE target_kind='pack';
177
+ ALTER TABLE capability_component_owners DROP CONSTRAINT capability_component_owners_kind_chk;
178
+ ALTER TABLE capability_component_owners ADD CONSTRAINT capability_component_owners_kind_chk CHECK (owner_kind IN ('direct','plugin','migration'));
179
+ ALTER TABLE integration_facet_binding_owners DROP CONSTRAINT integration_facet_binding_owners_kind_chk;
180
+ ALTER TABLE integration_facet_binding_owners ADD CONSTRAINT integration_facet_binding_owners_kind_chk CHECK (owner_kind IN ('direct','plugin','migration'));
181
+ ALTER TABLE capability_operations DROP CONSTRAINT capability_operations_target_chk;
182
+ ALTER TABLE capability_operations ADD CONSTRAINT capability_operations_target_chk CHECK (
183
+ target_kind IN ('plugin','integration','skill','facet_binding') AND length(target_id) BETWEEN 1 AND 512
184
+ );
185
+
186
+ -- Function definitions below retain Plugin finalization and tenant validation.
187
+ -- They are replaced before dropping tables so no active SQL body retains Pack
188
+ -- lookups. DROP without CASCADE refuses unaccounted dependencies.
189
+ CREATE OR REPLACE FUNCTION capability_v2_validate_component_owner()
190
+ RETURNS trigger
191
+ LANGUAGE plpgsql
192
+ AS $$
193
+ BEGIN
194
+ IF NOT EXISTS (
195
+ SELECT 1 FROM "capability_facet_installations" i
196
+ WHERE i."id" = NEW."facet_installation_id"
197
+ AND i."account_id" = NEW."account_id"
198
+ AND i."workspace_id" = NEW."workspace_id"
199
+ ) THEN
200
+ RAISE EXCEPTION 'component owner does not match its facet installation tenant'
201
+ USING ERRCODE = '23514';
202
+ END IF;
203
+ IF NEW."owner_kind" = 'plugin' THEN
204
+ IF NEW."owner_id" !~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' THEN
205
+ RAISE EXCEPTION 'plugin component owner belongs to another tenant or does not exist'
206
+ USING ERRCODE = '23514';
207
+ ELSIF NOT EXISTS (
208
+ SELECT 1 FROM "capability_plugin_installations" p
209
+ WHERE p."id" = NEW."owner_id"::uuid
210
+ AND p."account_id" = NEW."account_id"
211
+ AND p."workspace_id" = NEW."workspace_id"
212
+ ) THEN
213
+ RAISE EXCEPTION 'plugin component owner belongs to another tenant or does not exist'
214
+ USING ERRCODE = '23514';
215
+ END IF;
216
+ END IF;
217
+ RETURN NEW;
218
+ END;
219
+ $$;
220
+
221
+ CREATE OR REPLACE FUNCTION capability_v2_validate_facet_binding_owner()
222
+ RETURNS trigger
223
+ LANGUAGE plpgsql
224
+ AS $$
225
+ BEGIN
226
+ IF NOT EXISTS (
227
+ SELECT 1 FROM integration_facet_bindings binding
228
+ WHERE binding.id = NEW.binding_id
229
+ AND binding.account_id = NEW.account_id
230
+ AND binding.workspace_id = NEW.workspace_id
231
+ ) THEN
232
+ RAISE EXCEPTION 'Facet binding owner does not match its binding tenant'
233
+ USING ERRCODE = '23514';
234
+ END IF;
235
+ IF NEW.owner_kind = 'plugin' THEN
236
+ IF NEW.owner_id !~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' THEN
237
+ RAISE EXCEPTION 'Plugin Facet binding owner belongs to another tenant or does not exist'
238
+ USING ERRCODE = '23514';
239
+ ELSIF NOT EXISTS (
240
+ SELECT 1 FROM capability_plugin_installations plugin
241
+ WHERE plugin.id = NEW.owner_id::uuid
242
+ AND plugin.account_id = NEW.account_id
243
+ AND plugin.workspace_id = NEW.workspace_id
244
+ ) THEN
245
+ RAISE EXCEPTION 'Plugin Facet binding owner belongs to another tenant or does not exist'
246
+ USING ERRCODE = '23514';
247
+ END IF;
248
+ END IF;
249
+ RETURN NEW;
250
+ END;
251
+ $$;
252
+
253
+ CREATE OR REPLACE FUNCTION skill_source_has_effective_owner(p_account_id uuid,p_workspace_id uuid,p_facet_id uuid)
254
+ RETURNS boolean LANGUAGE sql STABLE SET search_path FROM CURRENT AS $$
255
+ SELECT EXISTS (
256
+ SELECT 1 FROM capability_facet_installations fi
257
+ JOIN capability_plugin_installations child ON child.id=fi.plugin_installation_id
258
+ JOIN capability_component_owners owner ON owner.facet_installation_id=fi.id
259
+ AND owner.account_id=fi.account_id AND owner.workspace_id=fi.workspace_id
260
+ WHERE fi.account_id=p_account_id AND fi.workspace_id=p_workspace_id AND fi.facet_id=p_facet_id
261
+ AND fi.status='active' AND child.status='active'
262
+ AND (owner.owner_kind IN ('direct','migration')
263
+ OR (owner.owner_kind='plugin' AND EXISTS(SELECT 1 FROM capability_plugin_installations parent
264
+ WHERE parent.id::text=owner.owner_id AND parent.account_id=p_account_id AND parent.workspace_id=p_workspace_id AND parent.status='active')))
265
+ )
266
+ $$;
267
+
268
+ CREATE OR REPLACE FUNCTION skill_publish_finalized_owner() RETURNS trigger
269
+ LANGUAGE plpgsql SECURITY DEFINER SET search_path FROM CURRENT AS $$
270
+ DECLARE
271
+ publication_owner_kind text := 'plugin';
272
+ pending record; head preference_registry_preferences%ROWTYPE; revision preference_registry_revisions%ROWTYPE;
273
+ publication_id uuid; publication_hash text; publication_operation uuid; expected_fingerprint text; event_id uuid;
274
+ next_event integer; result jsonb; disposition text; current_mode text; parent_target text;
275
+ publication_actor jsonb := jsonb_build_object('kind','service','subjectId','service:skill-publication','principalKind','service');
276
+ BEGIN
277
+ PERFORM pg_advisory_xact_lock(hashtextextended('skill-publication:'||NEW.workspace_id,0));
278
+ FOR pending IN
279
+ SELECT DISTINCT receipt.* FROM skill_write_receipts receipt
280
+ JOIN skill_source_bindings binding ON binding.preference_id=(receipt.receipt->>'skillId')::uuid
281
+ AND binding.account_id=receipt.account_id AND binding.workspace_id=receipt.workspace_id
282
+ JOIN capability_facet_installations fi ON fi.facet_id=binding.skill_facet_id
283
+ AND fi.account_id=binding.account_id AND fi.workspace_id=binding.workspace_id
284
+ JOIN capability_component_owners owner ON owner.facet_installation_id=fi.id
285
+ AND owner.account_id=fi.account_id AND owner.workspace_id=fi.workspace_id
286
+ WHERE receipt.account_id=NEW.account_id AND receipt.workspace_id=NEW.workspace_id
287
+ AND receipt.receipt ? 'deferredPublication' AND owner.owner_kind=publication_owner_kind AND owner.owner_id=NEW.id::text
288
+ ORDER BY receipt.created_at,receipt.operation_id
289
+ LOOP
290
+ publication_hash := md5('skill-publication:'||pending.operation_id);
291
+ publication_id := (substr(publication_hash,1,12)||'5'||substr(publication_hash,14,3)||'a'||substr(publication_hash,18,15))::uuid;
292
+ expected_fingerprint := encode(sha256(convert_to('skill-publication:'||pending.fingerprint,'UTF8')),'hex');
293
+ IF EXISTS(SELECT 1 FROM skill_write_receipts existing WHERE existing.workspace_id=NEW.workspace_id AND existing.operation_id=publication_id) THEN
294
+ IF NOT EXISTS(SELECT 1 FROM skill_write_receipts existing WHERE existing.workspace_id=NEW.workspace_id AND existing.operation_id=publication_id
295
+ AND existing.fingerprint=expected_fingerprint AND existing.receipt->>'sourceOperationId'=pending.operation_id::text) THEN
296
+ RAISE EXCEPTION 'Skill publication operation identity collision' USING ERRCODE='23505';
297
+ END IF;
298
+ CONTINUE;
299
+ END IF;
300
+ publication_operation := nullif(current_setting('opengeni.skill_publication_operation_id',true),'')::uuid;
301
+ SELECT plugin_key INTO parent_target FROM capability_plugins WHERE id=NEW.plugin_id;
302
+ IF publication_operation IS NULL OR NOT EXISTS(SELECT 1 FROM capability_operations operation
303
+ WHERE operation.id=publication_operation AND operation.account_id=NEW.account_id AND operation.workspace_id=NEW.workspace_id
304
+ AND operation.target_kind=publication_owner_kind AND operation.target_id=parent_target AND operation.status='running') THEN
305
+ RAISE EXCEPTION 'Skill publication requires a claimed parent finalization' USING ERRCODE='42501';
306
+ END IF;
307
+ SELECT * INTO head FROM preference_registry_preferences WHERE id=(pending.receipt->>'skillId')::uuid AND account_id=NEW.account_id FOR UPDATE;
308
+ SELECT * INTO revision FROM preference_registry_revisions WHERE id=(pending.receipt->>'revisionId')::uuid AND account_id=NEW.account_id;
309
+ disposition := 'preserved'; event_id := NULL; current_mode := 'automatic';
310
+ IF pending.actor->>'kind' <> 'human' THEN
311
+ -- Deferred publication uses its original accepted policy. Historical
312
+ -- receipts receive no new automatic authority during migration.
313
+ current_mode:=coalesce(pending.receipt#>>'{learningPolicy,effective,skills}','review_first');
314
+ IF current_mode='review_first' THEN current_mode:='suggest'; END IF;
315
+ END IF;
316
+ IF pending.actor->>'kind'='agent' THEN
317
+ PERFORM 1 FROM sessions session JOIN session_turns turn ON turn.id=session.active_turn_id AND turn.session_id=session.id
318
+ JOIN session_turn_attempts attempt ON attempt.id=turn.active_attempt_id AND attempt.turn_id=turn.id
319
+ WHERE session.id=(pending.actor->>'sessionId')::uuid AND session.account_id=NEW.account_id AND session.workspace_id=NEW.workspace_id
320
+ AND turn.id=(pending.actor->>'turnId')::uuid AND turn.account_id=NEW.account_id AND turn.workspace_id=NEW.workspace_id
321
+ AND turn.status IN ('running','requires_action','recovering','waiting_capacity')
322
+ AND attempt.id=(pending.actor->>'attemptId')::uuid AND attempt.account_id=NEW.account_id AND attempt.workspace_id=NEW.workspace_id
323
+ AND attempt.session_id=session.id AND attempt.execution_generation=(pending.actor->>'executionGeneration')::integer
324
+ AND turn.execution_generation=attempt.execution_generation AND attempt.state IN ('claimed','running')
325
+ AND NOT EXISTS(SELECT 1 FROM session_attempt_interruptions interruption WHERE interruption.workspace_id=NEW.workspace_id
326
+ AND interruption.attempt_id=attempt.id AND interruption.state IN ('pending','delivered','acknowledged'))
327
+ FOR SHARE OF session,turn,attempt;
328
+ IF NOT FOUND THEN current_mode := 'suggest'; END IF;
329
+ END IF;
330
+ IF head.status NOT IN ('rejected','superseded') AND head.scope='workspace' AND head.scope_workspace_id=NEW.workspace_id
331
+ AND head.scope_version=(pending.receipt->'deferredPublication'->>'expectedScopeVersion')::integer
332
+ AND head.active_revision_id IS NOT DISTINCT FROM (pending.receipt->'deferredPublication'->>'expectedRevisionId')::uuid
333
+ AND revision.revision=(SELECT max(r.revision) FROM preference_registry_revisions r WHERE r.preference_id=head.id)
334
+ AND (revision.expires_at IS NULL OR revision.expires_at>clock_timestamp())
335
+ AND EXISTS(SELECT 1 FROM skill_source_bindings binding WHERE binding.preference_id=head.id
336
+ AND binding.skill_facet_id=(pending.receipt->'deferredPublication'->>'sourceFacetId')::uuid)
337
+ AND skill_source_has_effective_owner(NEW.account_id,NEW.workspace_id,(pending.receipt->'deferredPublication'->>'sourceFacetId')::uuid)
338
+ THEN
339
+ IF current_mode='automatic' THEN
340
+ PERFORM set_config('opengeni.preference_lifecycle_head_id',head.id::text,true);
341
+ PERFORM set_config('opengeni.preference_lifecycle_operation','activate',true);
342
+ UPDATE preference_registry_preferences SET status='active',active_revision_id=revision.id,
343
+ active_revision=revision.revision,active_content_hash=revision.content_hash,activation_version=activation_version+1,updated_at=clock_timestamp()
344
+ WHERE id=head.id;
345
+ SELECT coalesce(max(version),0)+1 INTO next_event FROM preference_registry_events WHERE preference_id=head.id;
346
+ INSERT INTO preference_registry_events(account_id,preference_id,type,version,old_revision_id,new_revision_id,actor_subject_id,reason)
347
+ VALUES(NEW.account_id,head.id,'activated',next_event,head.active_revision_id,revision.id,'service:skill-publication',
348
+ 'Publish previously authorized Skill after composite owner finalization') RETURNING id INTO event_id;
349
+ disposition := 'applied';
350
+ ELSE disposition := 'pending'; END IF;
351
+ END IF;
352
+ result := jsonb_build_object('operationId',publication_id,'sourceOperationId',pending.operation_id,
353
+ 'skillId',pending.receipt->>'skillId','revisionId',pending.receipt->>'revisionId','outcome',disposition,
354
+ 'activationEventId',event_id,'replayed',false,'publicationOperationId',publication_operation);
355
+ IF disposition='pending' THEN result := result || jsonb_build_object('pendingReason','approval'); END IF;
356
+ INSERT INTO skill_write_receipts(account_id,workspace_id,operation_id,fingerprint,actor,receipt,activation_event_id)
357
+ VALUES(NEW.account_id,NEW.workspace_id,publication_id,expected_fingerprint,publication_actor,result,event_id);
358
+ END LOOP;
359
+ RETURN NEW;
360
+ END $$;
361
+
362
+ -- CREATE OR REPLACE resets function-local settings. Preserve the original
363
+ -- fixed namespace: planning nested source-owner checks must not inherit the
364
+ -- runtime caller's search path or lose the lifecycle guard's resolution.
365
+ DO $function_paths$
366
+ BEGIN
367
+ EXECUTE format('ALTER FUNCTION skill_source_has_effective_owner(uuid,uuid,uuid) SET search_path = %I, pg_catalog, pg_temp', current_schema());
368
+ EXECUTE format('ALTER FUNCTION skill_publish_finalized_owner() SET search_path = %I, pg_catalog, pg_temp', current_schema());
369
+ END $function_paths$;
370
+
371
+ -- Do not plan the restricted source-owner query for a new, inactive head.
372
+ -- A non-bypass migration owner exposes this distinction during runtime INSERT:
373
+ -- PostgreSQL may initialize an AND subplan even when active_revision_id is NULL.
374
+ -- Active revisions still pass the same source and files-bearing guards.
375
+ CREATE OR REPLACE FUNCTION skill_guard_legacy_activation() RETURNS trigger
376
+ LANGUAGE plpgsql SET search_path FROM CURRENT AS $$
377
+ BEGIN
378
+ IF NEW.active_revision_id IS NOT NULL THEN
379
+ IF (TG_OP='INSERT' OR NEW.active_revision_id IS DISTINCT FROM OLD.active_revision_id)
380
+ AND EXISTS(SELECT 1 FROM preference_registry_revisions target JOIN skill_source_bindings binding
381
+ ON binding.preference_id=target.preference_id AND binding.account_id=target.account_id
382
+ WHERE target.id=NEW.active_revision_id AND target.provenance_source='portable_skill'
383
+ AND NOT skill_source_has_effective_owner(binding.account_id,binding.workspace_id,target.provenance_source_id::uuid)) THEN
384
+ RAISE EXCEPTION 'Skill activation requires a finalized source owner' USING ERRCODE='42501';
385
+ END IF;
386
+ IF EXISTS(SELECT 1 FROM preference_registry_revisions target
387
+ WHERE target.account_id=NEW.account_id AND target.preference_id=NEW.id
388
+ AND target.id=NEW.active_revision_id AND target.skill_files IS NULL) THEN
389
+ RAISE EXCEPTION 'Skill folder activation requires a files-bearing revision; use unified restore' USING ERRCODE='55000';
390
+ END IF;
391
+ END IF;
392
+ RETURN NEW;
393
+ END $$;
394
+
395
+ SET CONSTRAINTS ALL IMMEDIATE;
396
+ DROP TABLE pack_installation_components;
397
+ DROP TABLE pack_installations;
398
+ DROP TABLE workspace_packs;
399
+ DROP FUNCTION pack_v2_validate_installation();
400
+ DROP FUNCTION pack_v2_validate_component();
401
+
402
+ ALTER TABLE capability_operations FORCE ROW LEVEL SECURITY;
403
+ ALTER TABLE capability_component_owners FORCE ROW LEVEL SECURITY;
404
+ ALTER TABLE capability_facet_installations FORCE ROW LEVEL SECURITY;
405
+ ALTER TABLE capability_plugin_installations FORCE ROW LEVEL SECURITY;
406
+ ALTER TABLE integration_facet_binding_owners FORCE ROW LEVEL SECURITY;
407
+ ALTER TABLE integration_facet_bindings FORCE ROW LEVEL SECURITY;
408
+ ALTER TABLE skill_source_bindings FORCE ROW LEVEL SECURITY;
409
+ ALTER TABLE preference_registry_preferences FORCE ROW LEVEL SECURITY;
410
+ ALTER TABLE preference_registry_revisions FORCE ROW LEVEL SECURITY;
411
+ ALTER TABLE preference_registry_events FORCE ROW LEVEL SECURITY;
412
+ ALTER TABLE automation_sources FORCE ROW LEVEL SECURITY;
413
+ ALTER TABLE automation_triggers FORCE ROW LEVEL SECURITY;
414
+ ALTER TABLE automation_runs FORCE ROW LEVEL SECURITY;
415
+ ALTER TABLE automation_trigger_revisions FORCE ROW LEVEL SECURITY;
416
+ ALTER TABLE automation_trigger_events FORCE ROW LEVEL SECURITY;
417
+ ALTER TABLE automation_run_event_links FORCE ROW LEVEL SECURITY;