@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,151 @@
1
+ -- deployment-mode: maintenance
2
+ -- Foundation only: new binaries must opt acquisition writers into the guard.
3
+ -- The runtime table/grant contract changes; old processes must be drained.
4
+ SET LOCAL lock_timeout = '5s';
5
+ SET LOCAL statement_timeout = '5min';
6
+ DO $drain$
7
+ DECLARE roles jsonb := nullif(current_setting('opengeni.migration_application_roles', true), '')::jsonb;
8
+ BEGIN
9
+ IF roles IS NULL OR jsonb_typeof(roles) <> 'array' OR jsonb_array_length(roles) NOT BETWEEN 1 AND 16 THEN
10
+ RAISE EXCEPTION 'integration policy migration requires application database roles' USING ERRCODE = '55000';
11
+ END IF;
12
+ IF EXISTS (SELECT 1 FROM jsonb_array_elements(roles) item WHERE jsonb_typeof(item) <> 'string'
13
+ OR btrim(item #>> '{}') = '' OR item #>> '{}' <> btrim(item #>> '{}') OR octet_length(item #>> '{}') > 63)
14
+ OR EXISTS (SELECT 1 FROM pg_stat_activity a JOIN jsonb_array_elements_text(roles) r ON r = a.usename
15
+ WHERE a.datname = current_database() AND a.pid <> pg_backend_pid()) THEN
16
+ RAISE EXCEPTION 'integration policy migration requires valid stopped application roles' USING ERRCODE = '55000';
17
+ END IF;
18
+ END $drain$;
19
+
20
+ CREATE TABLE organization_integration_policies (
21
+ account_id uuid PRIMARY KEY REFERENCES managed_accounts(id) ON DELETE CASCADE,
22
+ mode text NOT NULL CHECK (mode IN ('unrestricted', 'restricted')),
23
+ allowed_integration_keys jsonb NOT NULL CHECK (jsonb_typeof(allowed_integration_keys) = 'array'),
24
+ revision bigint NOT NULL CHECK (revision BETWEEN 1 AND 9007199254740991)
25
+ );
26
+ CREATE TABLE organization_integration_policy_operations (
27
+ account_id uuid NOT NULL REFERENCES managed_accounts(id) ON DELETE CASCADE,
28
+ operation_id uuid NOT NULL,
29
+ actor_subject_id text NOT NULL,
30
+ request jsonb NOT NULL,
31
+ result jsonb NOT NULL,
32
+ PRIMARY KEY (account_id, operation_id)
33
+ );
34
+ ALTER TABLE organization_integration_policies ENABLE ROW LEVEL SECURITY;
35
+ ALTER TABLE organization_integration_policies FORCE ROW LEVEL SECURITY;
36
+ ALTER TABLE organization_integration_policy_operations ENABLE ROW LEVEL SECURITY;
37
+ ALTER TABLE organization_integration_policy_operations FORCE ROW LEVEL SECURITY;
38
+ REVOKE ALL ON organization_integration_policies, organization_integration_policy_operations FROM PUBLIC;
39
+ CREATE POLICY organization_integration_policy_account ON organization_integration_policies
40
+ USING (account_id = opengeni_private.current_account_id())
41
+ WITH CHECK (account_id = opengeni_private.current_account_id());
42
+ CREATE POLICY organization_integration_policy_operation_account ON organization_integration_policy_operations
43
+ USING (account_id = opengeni_private.current_account_id())
44
+ WITH CHECK (account_id = opengeni_private.current_account_id());
45
+
46
+ -- This is a live fence, not authentication. The application must verify canonical
47
+ -- administrator provenance before selecting the subject, including on replay.
48
+ CREATE FUNCTION opengeni_private.assert_organization_integration_policy_administrator(
49
+ p_account uuid, p_actor text
50
+ ) RETURNS void LANGUAGE plpgsql SECURITY DEFINER SET search_path FROM CURRENT AS $body$
51
+ DECLARE
52
+ previous_marker text := current_setting('opengeni.organization_tenancy_lifecycle', true);
53
+ permissions_value jsonb; member_id uuid;
54
+ BEGIN
55
+ IF p_account IS NULL OR p_actor IS NULL
56
+ OR p_account IS DISTINCT FROM opengeni_private.current_account_id()
57
+ OR p_actor IS DISTINCT FROM opengeni_private.current_subject_id() THEN
58
+ RAISE EXCEPTION 'integration policy scope invalid' USING ERRCODE = '42501';
59
+ END IF;
60
+ IF p_actor LIKE 'api_key:%' THEN
61
+ SELECT permissions INTO permissions_value FROM api_keys
62
+ WHERE account_id = p_account AND 'api_key:' || id::text = p_actor
63
+ AND credential_kind = 'organization' AND workspace_id IS NULL AND revoked_at IS NULL
64
+ AND (expires_at IS NULL OR expires_at > clock_timestamp()) FOR SHARE;
65
+ IF NOT coalesce(permissions_value ? 'workspace:admin', false) THEN
66
+ RAISE EXCEPTION 'organization administrator required' USING ERRCODE = '42501';
67
+ END IF;
68
+ ELSE
69
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', 'organization_private_session_settings', true);
70
+ SELECT id INTO member_id FROM organization_memberships
71
+ WHERE account_id = p_account AND subject_id = p_actor AND status = 'active' AND role IN ('owner','admin') FOR SHARE;
72
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(previous_marker, ''), true);
73
+ IF member_id IS NULL THEN RAISE EXCEPTION 'organization administrator required' USING ERRCODE = '42501'; END IF;
74
+ END IF;
75
+ END $body$;
76
+ REVOKE ALL ON FUNCTION opengeni_private.assert_organization_integration_policy_administrator(uuid,text) FROM PUBLIC;
77
+
78
+ CREATE FUNCTION opengeni_private.update_organization_integration_policy(
79
+ p_account uuid, p_actor text, p_request jsonb
80
+ ) RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path FROM CURRENT AS $body$
81
+ DECLARE
82
+ prior organization_integration_policy_operations%ROWTYPE;
83
+ current_revision bigint; expected_revision bigint; operation_value uuid; result_value jsonb;
84
+ policy_lock bigint := hashtextextended('organization-integration-policy:' || p_account::text, 0);
85
+ BEGIN
86
+ IF p_account IS NULL OR p_actor IS NULL
87
+ OR p_account IS DISTINCT FROM opengeni_private.current_account_id()
88
+ OR p_actor IS DISTINCT FROM opengeni_private.current_subject_id() THEN
89
+ RAISE EXCEPTION 'integration policy scope invalid' USING ERRCODE = '42501';
90
+ END IF;
91
+ IF jsonb_typeof(p_request) IS DISTINCT FROM 'object'
92
+ OR p_request ->> 'mode' IS NULL OR p_request ->> 'mode' NOT IN ('unrestricted', 'restricted')
93
+ OR jsonb_typeof(p_request -> 'allowedIntegrationKeys') IS DISTINCT FROM 'array'
94
+ OR jsonb_typeof(p_request -> 'expectedRevision') IS DISTINCT FROM 'number'
95
+ OR coalesce(p_request ->> 'expectedRevision', '') !~ '^[0-9]+$'
96
+ OR jsonb_typeof(p_request -> 'operationId') IS DISTINCT FROM 'string'
97
+ OR p_request - ARRAY['mode','allowedIntegrationKeys','expectedRevision','operationId'] <> '{}'::jsonb THEN
98
+ RAISE EXCEPTION 'integration policy request invalid' USING ERRCODE = '22023';
99
+ END IF;
100
+ expected_revision := (p_request ->> 'expectedRevision')::bigint;
101
+ operation_value := (p_request ->> 'operationId')::uuid;
102
+ IF expected_revision NOT BETWEEN 0 AND 9007199254740990
103
+ OR jsonb_array_length(p_request -> 'allowedIntegrationKeys') > 10000
104
+ OR EXISTS (SELECT 1 FROM jsonb_array_elements(p_request -> 'allowedIntegrationKeys') k
105
+ WHERE jsonb_typeof(k) <> 'string' OR length(k #>> '{}') NOT BETWEEN 1 AND 200
106
+ OR k #>> '{}' !~ '^[a-z0-9][a-z0-9._:-]*$')
107
+ OR (SELECT count(*) <> count(DISTINCT k) FROM jsonb_array_elements(p_request -> 'allowedIntegrationKeys') k) THEN
108
+ RAISE EXCEPTION 'integration policy request invalid' USING ERRCODE = '22023';
109
+ END IF;
110
+ -- Never upgrade a shared acquisition fence: two upgrading acquisitions deadlock.
111
+ -- This also detects a shared fence retained by a released nested savepoint.
112
+ IF EXISTS (SELECT 1 FROM pg_locks WHERE locktype = 'advisory' AND pid = pg_backend_pid()
113
+ AND mode = 'ShareLock' AND granted AND objsubid = 1
114
+ AND classid = ((policy_lock >> 32) & 4294967295)::oid
115
+ AND objid = (policy_lock & 4294967295)::oid) THEN
116
+ RAISE EXCEPTION 'policy mutation cannot upgrade an acquisition fence' USING ERRCODE = '55000';
117
+ END IF;
118
+ -- Policy BEFORE membership: acquisition callbacks such as personal OAuth take
119
+ -- membership themselves. The inverse order cycles with their shared policy lock.
120
+ -- No workspace row is required or locked by this organization-only operation.
121
+ PERFORM pg_advisory_xact_lock(policy_lock);
122
+ PERFORM pg_advisory_xact_lock(hashtextextended('organization-membership:' || p_account::text, 0));
123
+ PERFORM opengeni_private.assert_organization_integration_policy_administrator(p_account, p_actor);
124
+ SELECT * INTO prior FROM organization_integration_policy_operations
125
+ WHERE account_id = p_account AND operation_id = operation_value;
126
+ IF FOUND THEN
127
+ IF prior.actor_subject_id <> p_actor OR prior.request <> p_request THEN
128
+ RAISE EXCEPTION 'integration policy operation reused' USING ERRCODE = '23505';
129
+ END IF;
130
+ RETURN prior.result;
131
+ END IF;
132
+ SELECT revision INTO current_revision FROM organization_integration_policies WHERE account_id = p_account;
133
+ IF coalesce(current_revision, 0) <> expected_revision THEN
134
+ RAISE EXCEPTION 'integration policy revision conflict' USING ERRCODE = '40001';
135
+ END IF;
136
+ result_value := jsonb_build_object('mode', p_request -> 'mode', 'allowedIntegrationKeys', p_request -> 'allowedIntegrationKeys', 'revision', expected_revision + 1);
137
+ INSERT INTO organization_integration_policies (account_id, mode, allowed_integration_keys, revision)
138
+ VALUES (p_account, p_request ->> 'mode', p_request -> 'allowedIntegrationKeys', expected_revision + 1)
139
+ ON CONFLICT (account_id) DO UPDATE SET mode = excluded.mode,
140
+ allowed_integration_keys = excluded.allowed_integration_keys, revision = excluded.revision;
141
+ INSERT INTO organization_integration_policy_operations (account_id, operation_id, actor_subject_id, request, result)
142
+ VALUES (p_account, operation_value, p_actor, p_request, result_value);
143
+ RETURN result_value;
144
+ END $body$;
145
+ REVOKE ALL ON FUNCTION opengeni_private.update_organization_integration_policy(uuid,text,jsonb) FROM PUBLIC;
146
+ -- Explicit pg_temp LAST prevents temporary relation shadowing in the definer.
147
+ DO $safe_path$
148
+ BEGIN
149
+ EXECUTE format('ALTER FUNCTION opengeni_private.update_organization_integration_policy(uuid,text,jsonb) SET search_path = pg_catalog, %I, pg_temp', current_schema());
150
+ EXECUTE format('ALTER FUNCTION opengeni_private.assert_organization_integration_policy_administrator(uuid,text) SET search_path = pg_catalog, %I, pg_temp', current_schema());
151
+ END $safe_path$;
@@ -0,0 +1,4 @@
1
+ -- deployment-mode: rolling
2
+ -- opengeni:concurrent-index lock-timeout=5s
3
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS "usage_events_account_recent_idx"
4
+ ON "usage_events" ("account_id", "occurred_at" DESC, "recorded_at" DESC);
@@ -0,0 +1,4 @@
1
+ -- deployment-mode: rolling
2
+ -- opengeni:concurrent-index lock-timeout=5s
3
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS "usage_events_workspace_recent_idx"
4
+ ON "usage_events" ("account_id", "workspace_id", "occurred_at" DESC, "recorded_at" DESC);
@@ -0,0 +1,237 @@
1
+ -- deployment-mode: rolling
2
+ -- Exact account-scoped reporting, retaining FORCE RLS and actual actor scope.
3
+ -- Only this aggregate function may mint the short-lived owner capability. It
4
+ -- never returns raw facts and does not change any write/tenant/session policy.
5
+ SET LOCAL lock_timeout = '5s';
6
+ SET LOCAL statement_timeout = '10min';
7
+
8
+ CREATE TABLE opengeni_private.organization_usage_read_capabilities (
9
+ backend_pid integer NOT NULL,
10
+ transaction_id xid8 NOT NULL,
11
+ account_id uuid NOT NULL,
12
+ subject_id text,
13
+ initiating_human_subject_id text,
14
+ PRIMARY KEY (backend_pid, transaction_id)
15
+ );
16
+ REVOKE ALL ON TABLE opengeni_private.organization_usage_read_capabilities FROM PUBLIC;
17
+
18
+ -- Strip hostile owner defaults, not merely the nominal application role.
19
+ DO $table_acl$
20
+ DECLARE role_name text;
21
+ BEGIN
22
+ FOR role_name IN
23
+ SELECT DISTINCT role_row.rolname
24
+ FROM pg_catalog.pg_class relation
25
+ CROSS JOIN LATERAL pg_catalog.aclexplode(coalesce(relation.relacl,
26
+ pg_catalog.acldefault('r', relation.relowner))) privilege
27
+ JOIN pg_catalog.pg_roles role_row ON role_row.oid = privilege.grantee
28
+ WHERE relation.oid = 'opengeni_private.organization_usage_read_capabilities'::regclass
29
+ AND privilege.grantee <> relation.relowner
30
+ LOOP
31
+ EXECUTE format('REVOKE ALL ON TABLE opengeni_private.organization_usage_read_capabilities FROM %I', role_name);
32
+ END LOOP;
33
+ END
34
+ $table_acl$;
35
+
36
+ DO $functions$
37
+ DECLARE data_schema text := pg_catalog.current_schema();
38
+ BEGIN
39
+ EXECUTE format($ddl$
40
+ CREATE FUNCTION %1$I.organization_usage_policy_capability_active(p_actor text)
41
+ RETURNS boolean LANGUAGE sql STABLE SECURITY DEFINER
42
+ SET search_path = pg_catalog
43
+ AS $fn$
44
+ SELECT p_actor = pg_catalog.pg_get_userbyid((
45
+ SELECT relowner FROM pg_catalog.pg_class
46
+ WHERE oid = %2$L::pg_catalog.regclass
47
+ )) AND nullif(pg_catalog.current_setting('opengeni.workspace_id', true), '') IS NULL
48
+ AND EXISTS (
49
+ SELECT 1 FROM opengeni_private.organization_usage_read_capabilities capability
50
+ WHERE capability.backend_pid = pg_catalog.pg_backend_pid()
51
+ AND capability.transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
52
+ AND capability.account_id::text IS NOT DISTINCT FROM nullif(pg_catalog.current_setting('opengeni.account_id', true), '')
53
+ AND capability.subject_id IS NOT DISTINCT FROM nullif(pg_catalog.current_setting('opengeni.subject_id', true), '')
54
+ AND capability.initiating_human_subject_id IS NOT DISTINCT FROM nullif(pg_catalog.current_setting('opengeni.initiating_human_subject_id', true), '')
55
+ )
56
+ $fn$;
57
+ REVOKE ALL ON FUNCTION %1$I.organization_usage_policy_capability_active(text) FROM PUBLIC;
58
+ -- RLS plans check function ACLs before short circuiting. This reveals only
59
+ -- a same-backend boolean; callers cannot insert its capability row.
60
+ GRANT EXECUTE ON FUNCTION %1$I.organization_usage_policy_capability_active(text) TO PUBLIC;
61
+
62
+ CREATE FUNCTION opengeni_private.organization_usage_summary(
63
+ p_account_id uuid, p_since timestamptz, p_until timestamptz,
64
+ p_granularity text, p_after_workspace_id uuid, p_include_period boolean
65
+ ) RETURNS jsonb LANGUAGE plpgsql VOLATILE SECURITY DEFINER
66
+ SET search_path = pg_catalog, %1$I, opengeni_private, pg_temp
67
+ SET plan_cache_mode = force_custom_plan
68
+ AS $fn$
69
+ DECLARE
70
+ context_account_id uuid;
71
+ context_subject_id text;
72
+ context_human_id text;
73
+ page_ids uuid[];
74
+ next_cursor uuid;
75
+ response jsonb;
76
+ BEGIN
77
+ PERFORM opengeni_private.session_variable_set_attachments_protocol_v1_active();
78
+ BEGIN
79
+ context_account_id := nullif(current_setting('opengeni.account_id', true), '')::uuid;
80
+ EXCEPTION WHEN invalid_text_representation THEN
81
+ RAISE EXCEPTION 'Organization usage account context is malformed' USING ERRCODE = '42501';
82
+ END;
83
+ context_subject_id := nullif(current_setting('opengeni.subject_id', true), '');
84
+ context_human_id := nullif(current_setting('opengeni.initiating_human_subject_id', true), '');
85
+ IF context_account_id IS NULL OR context_account_id IS DISTINCT FROM p_account_id
86
+ OR nullif(current_setting('opengeni.workspace_id', true), '') IS NOT NULL
87
+ THEN
88
+ RAISE EXCEPTION 'Organization usage requires the exact account-only context' USING ERRCODE = '42501';
89
+ END IF;
90
+ IF p_since IS NULL OR p_until IS NULL OR NOT isfinite(p_since) OR NOT isfinite(p_until)
91
+ OR p_until < p_since OR p_until - p_since > interval '366 days'
92
+ OR p_granularity IS NULL OR p_granularity NOT IN ('hour', 'day')
93
+ OR (p_granularity = 'hour' AND p_until - p_since > interval '1 day')
94
+ OR p_include_period IS NULL
95
+ THEN
96
+ RAISE EXCEPTION 'Organization usage window or granularity is invalid' USING ERRCODE = '22023';
97
+ END IF;
98
+ -- Match listSharedWorkspacesForAccount: canonical membership pointers
99
+ -- exclude EVERY Personal workspace before lookahead/cursor selection.
100
+ -- Names, kind guesses and caller-owned Personal exceptions are forbidden.
101
+ -- Period totals remain actor-visible accounting across the account;
102
+ -- this shared-only inventory deliberately need not sum to those totals.
103
+ SELECT coalesce(array_agg(id ORDER BY id), '{}'::uuid[]) INTO page_ids
104
+ FROM (SELECT id FROM %1$I.workspaces
105
+ WHERE account_id = context_account_id
106
+ AND id IN (SELECT workspace_id FROM %1$I.list_organization_workspace_ids(context_account_id))
107
+ AND (p_after_workspace_id IS NULL OR id > p_after_workspace_id)
108
+ ORDER BY id LIMIT 51) page;
109
+ IF cardinality(page_ids) > 50 THEN
110
+ next_cursor := page_ids[50];
111
+ page_ids := page_ids[1:50];
112
+ END IF;
113
+
114
+ INSERT INTO opengeni_private.organization_usage_read_capabilities
115
+ (backend_pid, transaction_id, account_id, subject_id, initiating_human_subject_id)
116
+ VALUES (pg_backend_pid(), pg_current_xact_id(), context_account_id, context_subject_id, context_human_id);
117
+ BEGIN
118
+ WITH visible_sessions AS MATERIALIZED (
119
+ -- Sessions retain ordinary FORCE RLS. Private actor checks occur
120
+ -- once per session here, never once per fact in the aggregate scan.
121
+ SELECT id, account_id, workspace_id FROM %1$I.sessions session_row
122
+ WHERE account_id = context_account_id
123
+ AND (p_include_period OR workspace_id = ANY(page_ids))
124
+ AND (context_subject_id IS NULL OR visibility = 'workspace_shared'
125
+ OR %1$I.session_private_actor_visible(account_id, workspace_id,
126
+ owner_organization_membership_id, owner_subject_id))
127
+ ), visible AS MATERIALIZED (
128
+ SELECT usage_row.workspace_id, usage_row.event_type, usage_row.unit, usage_row.quantity,
129
+ CASE WHEN p_include_period THEN to_char(date_trunc(p_granularity, usage_row.occurred_at AT TIME ZONE 'UTC'),
130
+ CASE WHEN p_granularity = 'hour' THEN 'YYYY-MM-DD"T"HH24:00' ELSE 'YYYY-MM-DD' END) END AS bucket
131
+ FROM %1$I.usage_events usage_row
132
+ LEFT JOIN visible_sessions session_row
133
+ ON session_row.id = usage_row.session_id
134
+ AND session_row.account_id = usage_row.account_id
135
+ AND session_row.workspace_id = usage_row.workspace_id
136
+ WHERE usage_row.account_id = context_account_id
137
+ AND usage_row.occurred_at >= p_since AND usage_row.occurred_at < p_until
138
+ AND (p_include_period OR usage_row.workspace_id = ANY(page_ids))
139
+ AND (usage_row.session_id IS NULL OR session_row.id IS NOT NULL)
140
+ ), aggregates AS MATERIALIZED (
141
+ SELECT workspace_id, bucket, grouping(workspace_id) AS all_workspaces,
142
+ grouping(bucket) AS all_buckets,
143
+ jsonb_build_object('eventType', event_type, 'unit', unit,
144
+ 'quantity', sum(quantity)::text, 'eventCount', count(*)::text) AS total
145
+ FROM visible GROUP BY GROUPING SETS
146
+ ((event_type, unit), (bucket, event_type, unit), (workspace_id, event_type, unit))
147
+ ), bucket_rows AS (
148
+ SELECT bucket, jsonb_agg(total ORDER BY total->>'eventType', total->>'unit') AS totals
149
+ FROM aggregates WHERE all_buckets = 0 AND p_include_period GROUP BY bucket
150
+ ), workspace_rows AS (
151
+ SELECT workspace_id, jsonb_agg(total ORDER BY total->>'eventType', total->>'unit') AS totals
152
+ FROM aggregates WHERE all_workspaces = 0 AND workspace_id = ANY(page_ids) GROUP BY workspace_id
153
+ )
154
+ SELECT jsonb_build_object(
155
+ 'totals', CASE WHEN p_include_period THEN coalesce((SELECT jsonb_agg(total ORDER BY total->>'eventType', total->>'unit')
156
+ FROM aggregates WHERE all_workspaces = 1 AND all_buckets = 1), '[]'::jsonb) ELSE NULL END,
157
+ 'buckets', CASE WHEN p_include_period THEN coalesce((SELECT jsonb_agg(jsonb_build_object('bucket', bucket, 'totals', totals) ORDER BY bucket)
158
+ FROM bucket_rows), '[]'::jsonb) ELSE NULL END,
159
+ 'workspaces', coalesce((SELECT jsonb_agg(jsonb_build_object('workspaceId', w.id, 'name', w.name,
160
+ 'totals', coalesce(r.totals, '[]'::jsonb)) ORDER BY w.id)
161
+ FROM %1$I.workspaces w LEFT JOIN workspace_rows r ON r.workspace_id = w.id
162
+ WHERE w.account_id = context_account_id AND w.id = ANY(page_ids)), '[]'::jsonb),
163
+ 'nextWorkspaceCursor', next_cursor
164
+ ) INTO response;
165
+
166
+ DELETE FROM opengeni_private.organization_usage_read_capabilities
167
+ WHERE backend_pid = pg_backend_pid() AND transaction_id = pg_current_xact_id_if_assigned();
168
+ RETURN response;
169
+ EXCEPTION WHEN OTHERS THEN
170
+ DELETE FROM opengeni_private.organization_usage_read_capabilities
171
+ WHERE backend_pid = pg_backend_pid() AND transaction_id = pg_current_xact_id_if_assigned();
172
+ RAISE;
173
+ END;
174
+ END
175
+ $fn$;
176
+ REVOKE ALL ON FUNCTION opengeni_private.organization_usage_summary(uuid,timestamptz,timestamptz,text,uuid,boolean) FROM PUBLIC;
177
+ $ddl$, data_schema, format('%I.usage_events', data_schema));
178
+ END
179
+ $functions$;
180
+
181
+ DO $policy$
182
+ DECLARE data_schema text := current_schema(); installed record; expected_expression text;
183
+ BEGIN
184
+ SELECT p.polpermissive, p.polcmd, pg_get_expr(p.polqual, p.polrelid) AS expression INTO installed
185
+ FROM pg_policy p WHERE p.polrelid = format('%I.usage_events', data_schema)::regclass
186
+ AND p.polname = 'session_visibility_isolation';
187
+ IF NOT FOUND OR installed.polpermissive OR installed.polcmd <> 'r'
188
+ OR installed.expression IS NULL
189
+ THEN
190
+ RAISE EXCEPTION 'Organization usage refuses unknown fact visibility policy' USING ERRCODE = '55000';
191
+ END IF;
192
+ -- Compare parsed policy trees, not substring heuristics. This temporary,
193
+ -- equally restrictive policy is dropped before the atomic migration ends.
194
+ -- A future visibility change must be reconciled explicitly rather than
195
+ -- silently replaced by a capability for a different authorization rule.
196
+ EXECUTE format('CREATE POLICY organization_usage_expected_visibility ON %1$I.usage_events AS RESTRICTIVE FOR SELECT USING (
197
+ CASE WHEN %1$I.insights_fact_read_policy_capability_active(current_user,
198
+ pg_catalog.pg_get_userbyid((SELECT relation.relowner FROM pg_catalog.pg_class relation WHERE relation.oid = %2$L::pg_catalog.regclass)),
199
+ ''usage_events'') THEN true ELSE %1$I.session_reference_visible(account_id, workspace_id, session_id) END)',
200
+ data_schema, format('%I.usage_events', data_schema));
201
+ SELECT pg_get_expr(p.polqual, p.polrelid) INTO expected_expression FROM pg_policy p
202
+ WHERE p.polrelid = format('%I.usage_events', data_schema)::regclass
203
+ AND p.polname = 'organization_usage_expected_visibility';
204
+ EXECUTE format('DROP POLICY organization_usage_expected_visibility ON %I.usage_events', data_schema);
205
+ IF installed.expression IS DISTINCT FROM expected_expression THEN
206
+ RAISE EXCEPTION 'Organization usage refuses changed fact visibility semantics' USING ERRCODE = '55000';
207
+ END IF;
208
+ -- The cheap owner-capability initPlan comes first; normal callers retain the
209
+ -- complete existing expression. Only SELECT changes; all writes stay intact.
210
+ EXECUTE format('ALTER POLICY session_visibility_isolation ON %I.usage_events USING (CASE WHEN (SELECT %I.organization_usage_policy_capability_active(current_user)) THEN true ELSE (%s) END)',
211
+ data_schema, data_schema, installed.expression);
212
+ END
213
+ $policy$;
214
+
215
+ DO $function_acl$
216
+ DECLARE role_name text; data_schema text := current_schema();
217
+ BEGIN
218
+ FOR role_name IN
219
+ SELECT DISTINCT r.rolname FROM pg_proc p
220
+ CROSS JOIN LATERAL aclexplode(coalesce(p.proacl, acldefault('f', p.proowner))) privilege
221
+ JOIN pg_roles r ON r.oid = privilege.grantee
222
+ WHERE p.oid = 'opengeni_private.organization_usage_summary(uuid,timestamptz,timestamptz,text,uuid,boolean)'::regprocedure
223
+ AND privilege.grantee <> p.proowner
224
+ LOOP
225
+ EXECUTE format('REVOKE ALL ON FUNCTION opengeni_private.organization_usage_summary(uuid,timestamptz,timestamptz,text,uuid,boolean) FROM %I', role_name);
226
+ END LOOP;
227
+ FOR role_name IN
228
+ SELECT r.rolname FROM jsonb_array_elements_text(current_setting('opengeni.migration_application_roles')::jsonb) configured(value)
229
+ JOIN pg_roles r ON r.rolname = configured.value
230
+ WHERE has_table_privilege(r.rolname, format('%I.usage_events', data_schema), 'SELECT')
231
+ AND has_table_privilege(r.rolname, format('%I.sessions', data_schema), 'SELECT')
232
+ LOOP
233
+ EXECUTE format('GRANT USAGE ON SCHEMA opengeni_private TO %I', role_name);
234
+ EXECUTE format('GRANT EXECUTE ON FUNCTION opengeni_private.organization_usage_summary(uuid,timestamptz,timestamptz,text,uuid,boolean) TO %I', role_name);
235
+ END LOOP;
236
+ END
237
+ $function_acl$;
@@ -0,0 +1,156 @@
1
+ -- deployment-mode: rolling
2
+ -- Rolling-compatible storage fences. Existing non-report goals are unchanged.
3
+ -- No caller boolean can waive a persisted requirement, including old binaries.
4
+ -- Trust boundary: authenticated agent/API callers can supply receipt IDs, never
5
+ -- receipt bodies. Trusted application code records proof after native query and
6
+ -- decode. The runtime SQL role can INSERT these rows and is inside that trust
7
+ -- boundary; these guards are retention/integrity checks, not an attestation
8
+ -- against compromised application-role SQL or database administrators.
9
+ CREATE FUNCTION session_goal_report_requirements_before_write()
10
+ RETURNS trigger LANGUAGE plpgsql AS $$
11
+ DECLARE requirements jsonb := coalesce(NEW.metadata -> 'reportRequirementsV1', '[]'::jsonb);
12
+ DECLARE previous_requirements jsonb;
13
+ DECLARE deliveries jsonb;
14
+ DECLARE requirement jsonb;
15
+ DECLARE delivery jsonb;
16
+ DECLARE artifact record;
17
+ BEGIN
18
+ IF jsonb_typeof(requirements) <> 'array' OR jsonb_array_length(requirements) > 16 THEN
19
+ RAISE EXCEPTION 'Invalid goal report requirements' USING ERRCODE = '23514';
20
+ END IF;
21
+ FOR requirement IN SELECT value FROM jsonb_array_elements(requirements) LOOP
22
+ IF jsonb_typeof(requirement) <> 'object'
23
+ OR (SELECT count(*) FROM jsonb_object_keys(requirement)) <> 2
24
+ OR jsonb_typeof(requirement -> 'id') IS DISTINCT FROM 'string'
25
+ OR (requirement ->> 'id') !~ '^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$'
26
+ OR jsonb_typeof(requirement -> 'title') IS DISTINCT FROM 'string'
27
+ OR octet_length(requirement ->> 'title') NOT BETWEEN 1 AND 512
28
+ OR btrim(requirement ->> 'title') = ''
29
+ THEN RAISE EXCEPTION 'Invalid goal report requirement' USING ERRCODE = '23514'; END IF;
30
+ END LOOP;
31
+ IF (SELECT count(DISTINCT value ->> 'id') FROM jsonb_array_elements(requirements)) <> jsonb_array_length(requirements) THEN
32
+ RAISE EXCEPTION 'Duplicate goal report requirement IDs' USING ERRCODE = '23514';
33
+ END IF;
34
+ IF TG_OP = 'UPDATE' AND NOT (OLD.status = 'completed' AND NEW.status = 'active') THEN
35
+ previous_requirements := coalesce(OLD.metadata -> 'reportRequirementsV1', '[]'::jsonb);
36
+ IF NOT requirements @> previous_requirements THEN
37
+ RAISE EXCEPTION 'Goal report requirements are append-only until completed-goal replacement' USING ERRCODE = '23514';
38
+ END IF;
39
+ IF OLD.status = 'completed' AND (requirements <> previous_requirements OR NEW.metadata -> 'reportDeliveriesV1' IS DISTINCT FROM OLD.metadata -> 'reportDeliveriesV1') THEN
40
+ RAISE EXCEPTION 'Completed report requirements and deliveries are immutable' USING ERRCODE = '23514';
41
+ END IF;
42
+ END IF;
43
+ IF TG_OP = 'UPDATE' AND OLD.status = 'completed' AND NEW.status = 'active' THEN
44
+ -- Old rolling writers retain metadata on replacement. They must never
45
+ -- reuse a previous goal's verified delivery to complete the new goal.
46
+ NEW.metadata := jsonb_set(NEW.metadata, '{reportDeliveriesV1}', '[]'::jsonb);
47
+ END IF;
48
+ IF NEW.status = 'completed' AND (TG_OP = 'INSERT' OR OLD.status <> 'completed') AND jsonb_array_length(requirements) > 0 THEN
49
+ deliveries := coalesce(NEW.metadata -> 'reportDeliveriesV1', '[]'::jsonb);
50
+ IF jsonb_typeof(deliveries) <> 'array' OR jsonb_array_length(deliveries) <> jsonb_array_length(requirements)
51
+ OR (SELECT count(DISTINCT value ->> 'requirementId') FROM jsonb_array_elements(deliveries)) <> jsonb_array_length(requirements)
52
+ THEN RAISE EXCEPTION 'Missing report deliveries' USING ERRCODE = '23514'; END IF;
53
+ -- Same no-wait lock policy as the application: artifact writers already
54
+ -- lock the aggregate before checking their actor's session authority.
55
+ FOR delivery IN SELECT value FROM jsonb_array_elements(deliveries) ORDER BY value ->> 'artifactId' LOOP
56
+ IF NOT EXISTS (SELECT 1 FROM jsonb_array_elements(requirements) r WHERE r ->> 'id' = delivery ->> 'requirementId') THEN
57
+ RAISE EXCEPTION 'Unknown report requirement' USING ERRCODE = '23514';
58
+ END IF;
59
+ SELECT head_sequence, state_hash, authorization_revision INTO artifact FROM editable_artifacts
60
+ WHERE account_id = NEW.account_id AND workspace_id = NEW.workspace_id
61
+ AND id = delivery ->> 'artifactId' AND modality = 'document' AND lifecycle_state = 'active'
62
+ FOR SHARE NOWAIT;
63
+ IF NOT FOUND THEN RAISE EXCEPTION 'Report document unavailable' USING ERRCODE = '23514'; END IF;
64
+ IF NOT EXISTS (
65
+ SELECT 1 FROM session_command_receipts receipt
66
+ WHERE receipt.id::text = delivery ->> 'inspectionReceiptId'
67
+ AND receipt.account_id = NEW.account_id AND receipt.workspace_id = NEW.workspace_id
68
+ AND receipt.target_session_id = NEW.session_id
69
+ AND receipt.actor_type = 'agent_attempt' AND receipt.action = 'artifact.document.inspect'
70
+ AND receipt.result ->> 'version' = 'native-document-inspection.v1'
71
+ AND receipt.result ->> 'queryKind' = 'body'
72
+ AND receipt.result ->> 'artifactId' = delivery ->> 'artifactId'
73
+ AND receipt.result ->> 'headSequence' = artifact.head_sequence::text
74
+ AND receipt.result ->> 'stateHash' = artifact.state_hash
75
+ AND receipt.result ->> 'authorizationRevision' = artifact.authorization_revision::text
76
+ ) THEN RAISE EXCEPTION 'Missing or stale document inspection proof' USING ERRCODE = '23514'; END IF;
77
+ END LOOP;
78
+ END IF;
79
+ RETURN NEW;
80
+ END;
81
+ $$;
82
+
83
+ CREATE TRIGGER session_goal_report_requirements_before_write_trigger
84
+ BEFORE INSERT OR UPDATE OF metadata, status ON session_goals
85
+ FOR EACH ROW EXECUTE FUNCTION session_goal_report_requirements_before_write();
86
+
87
+ -- Runs after the existing snapshot freeze trigger, without rewriting old turns.
88
+ CREATE FUNCTION session_goal_freeze_report_requirements()
89
+ RETURNS trigger LANGUAGE plpgsql AS $$
90
+ DECLARE requirements jsonb;
91
+ BEGIN
92
+ IF NEW.goal_snapshot ->> 'state' <> 'none' THEN
93
+ SELECT coalesce(metadata -> 'reportRequirementsV1', '[]'::jsonb) INTO requirements
94
+ FROM session_goals WHERE workspace_id = NEW.workspace_id AND session_id = NEW.session_id;
95
+ NEW.goal_snapshot := NEW.goal_snapshot || jsonb_build_object('reportRequirements', coalesce(requirements, '[]'::jsonb));
96
+ END IF;
97
+ RETURN NEW;
98
+ END;
99
+ $$;
100
+
101
+ CREATE TRIGGER zz_session_goal_freeze_report_requirements_trigger
102
+ BEFORE INSERT ON session_turns
103
+ FOR EACH ROW EXECUTE FUNCTION session_goal_freeze_report_requirements();
104
+
105
+ CREATE FUNCTION session_document_inspection_receipt_immutable()
106
+ RETURNS trigger LANGUAGE plpgsql AS $$
107
+ BEGIN
108
+ IF (OLD.action = 'artifact.document.inspect' OR NEW.action = 'artifact.document.inspect') AND NEW IS DISTINCT FROM OLD THEN
109
+ RAISE EXCEPTION 'Native document inspection receipts are immutable' USING ERRCODE = '23514';
110
+ END IF;
111
+ RETURN NEW;
112
+ END;
113
+ $$;
114
+
115
+ CREATE TRIGGER session_document_inspection_receipt_immutable_trigger
116
+ BEFORE UPDATE ON session_command_receipts
117
+ FOR EACH ROW EXECUTE FUNCTION session_document_inspection_receipt_immutable();
118
+
119
+ CREATE FUNCTION session_document_inspection_receipt_retained()
120
+ RETURNS trigger LANGUAGE plpgsql AS $$
121
+ BEGIN
122
+ -- Keep proof while its session exists. FK retention cascades remove the
123
+ -- session first and must remain able to delete its receipts. Checking parent
124
+ -- existence also prevents using an unrelated nested trigger to bypass this.
125
+ IF OLD.action = 'artifact.document.inspect' AND EXISTS (
126
+ SELECT 1 FROM sessions WHERE account_id = OLD.account_id
127
+ AND workspace_id = OLD.workspace_id AND id = OLD.target_session_id
128
+ ) THEN
129
+ RAISE EXCEPTION 'Native document inspection receipts are retained with their session' USING ERRCODE = '23514';
130
+ END IF;
131
+ RETURN OLD;
132
+ END;
133
+ $$;
134
+
135
+ CREATE TRIGGER session_document_inspection_receipt_retained_trigger
136
+ BEFORE DELETE ON session_command_receipts
137
+ FOR EACH ROW EXECUTE FUNCTION session_document_inspection_receipt_retained();
138
+
139
+ CREATE FUNCTION session_goal_report_requirements_before_delete()
140
+ RETURNS trigger LANGUAGE plpgsql AS $$
141
+ BEGIN
142
+ -- Parent session/workspace deletion remains cancellation, not completion.
143
+ -- Ordinary goal clearing must carry the exact trusted API cancellation stamp.
144
+ IF pg_trigger_depth() = 1 AND OLD.status <> 'completed'
145
+ AND jsonb_array_length(coalesce(OLD.metadata -> 'reportRequirementsV1', '[]'::jsonb)) > 0
146
+ AND current_setting('opengeni.goal_report_clear_id', true) IS DISTINCT FROM OLD.id::text
147
+ THEN
148
+ RAISE EXCEPTION 'Pending report requirements require explicit API cancellation' USING ERRCODE = '23514';
149
+ END IF;
150
+ RETURN OLD;
151
+ END;
152
+ $$;
153
+
154
+ CREATE TRIGGER session_goal_report_requirements_before_delete_trigger
155
+ BEFORE DELETE ON session_goals
156
+ FOR EACH ROW EXECUTE FUNCTION session_goal_report_requirements_before_delete();
@@ -0,0 +1,15 @@
1
+ -- deployment-mode: rolling
2
+ -- Session identity determines workspace and account. Independent column
3
+ -- estimates otherwise multiply the same scope selectivity several times.
4
+ -- No index, payload, pagination boundary, grant or planner setting changes.
5
+ SET LOCAL lock_timeout = '5s';
6
+
7
+ CREATE STATISTICS session_events_scope_statistics (dependencies, mcv)
8
+ ON account_id, workspace_id, session_id FROM session_events;
9
+ ALTER STATISTICS session_events_scope_statistics SET STATISTICS 1000;
10
+
11
+ -- Populate existing installations immediately; autovacuum ANALYZE maintains
12
+ -- this statistics object afterward. Only scope and cursor columns are sampled.
13
+ -- The policy DDL is deliberately in 0476's separate transaction so its lock
14
+ -- cannot be retained across this scan.
15
+ ANALYZE session_events (account_id, workspace_id, session_id, sequence);
@@ -0,0 +1,29 @@
1
+ -- deployment-mode: rolling
2
+ -- Statistics were populated in 0475's separate transaction, so this policy
3
+ -- DDL transaction does not retain its table lock across the ANALYZE scan.
4
+ -- Keep the migration-owner capability exactly as introduced by 0353. In
5
+ -- particular, ownership is resolved dynamically, not captured as a policy role.
6
+ -- Express the owner-only branch as one conditional: treating its owner and
7
+ -- capability terms as independent selectivity estimates can badly undercount
8
+ -- ordinary scoped reads and favor scanning/sorting an entire session history.
9
+ SET LOCAL lock_timeout = '5s';
10
+
11
+ ALTER POLICY session_events_automatic_title_quarantine_v1 ON session_events
12
+ USING (
13
+ CASE WHEN current_user = (
14
+ SELECT pg_catalog.pg_get_userbyid(relation.relowner)
15
+ FROM pg_catalog.pg_class relation
16
+ WHERE relation.oid = 'session_events'::regclass
17
+ ) THEN pg_catalog.current_setting(
18
+ 'opengeni.automatic_session_title_quarantine_v1', true
19
+ ) = '1' ELSE false END
20
+ )
21
+ WITH CHECK (
22
+ CASE WHEN current_user = (
23
+ SELECT pg_catalog.pg_get_userbyid(relation.relowner)
24
+ FROM pg_catalog.pg_class relation
25
+ WHERE relation.oid = 'session_events'::regclass
26
+ ) THEN pg_catalog.current_setting(
27
+ 'opengeni.automatic_session_title_quarantine_v1', true
28
+ ) = '1' ELSE false END
29
+ );