@opengeni/db 3.2.2 → 3.3.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 (81) hide show
  1. package/dist/canonical-human-identities.js +3 -3
  2. package/dist/capability-reconciliation.js +2 -2
  3. package/dist/{chunk-V2X2NK7T.js → chunk-5MPQZI43.js} +2675 -2540
  4. package/dist/{chunk-V2X2NK7T.js.map → chunk-5MPQZI43.js.map} +1 -1
  5. package/dist/{chunk-L3NT43Z5.js → chunk-7K5AVQUZ.js} +3 -3
  6. package/dist/{chunk-JYPBOJ2Y.js → chunk-AAOKWNHE.js} +4 -4
  7. package/dist/{chunk-D7LTIG33.js → chunk-B4RMLA2X.js} +2 -2
  8. package/dist/{chunk-OXO32LTK.js → chunk-CEJ3NQ3B.js} +11 -1
  9. package/dist/chunk-CEJ3NQ3B.js.map +1 -0
  10. package/dist/{chunk-AYZC65QU.js → chunk-CGSINPLW.js} +2 -2
  11. package/dist/{chunk-VCJAYIPH.js → chunk-FX2PSMGH.js} +2 -2
  12. package/dist/{chunk-HMKUJ6NI.js → chunk-HQWXEF77.js} +57 -9
  13. package/dist/chunk-HQWXEF77.js.map +1 -0
  14. package/dist/{chunk-V22DUH4C.js → chunk-HWEHZKHA.js} +5 -5
  15. package/dist/{chunk-6UU7OVUN.js → chunk-JTBJGSUZ.js} +37 -7
  16. package/dist/chunk-JTBJGSUZ.js.map +1 -0
  17. package/dist/{chunk-YH4GWEDM.js → chunk-KPC7I5WG.js} +2 -2
  18. package/dist/{chunk-XQHABE23.js → chunk-OWM6XG55.js} +4 -4
  19. package/dist/{chunk-YWHG45TK.js → chunk-PNEJVKKV.js} +6 -6
  20. package/dist/{chunk-4BAKPTPG.js → chunk-S74WULCK.js} +3 -3
  21. package/dist/{chunk-K6NT66NP.js → chunk-SDKFRIAN.js} +4 -4
  22. package/dist/{chunk-RCDMYTLP.js → chunk-UWS5DJQ6.js} +2 -2
  23. package/dist/{chunk-LRRGVHAQ.js → chunk-VUR5U5B3.js} +2 -2
  24. package/dist/{chunk-IF63BST7.js → chunk-Y4ERASEM.js} +2 -2
  25. package/dist/{chunk-VBG4TW5C.js → chunk-ZZOEKHGU.js} +2 -2
  26. package/dist/connect-attempts.js +3 -3
  27. package/dist/database.d.ts +9 -0
  28. package/dist/editable-artifact-durable-export.js +2 -2
  29. package/dist/editable-artifacts.js +3 -3
  30. package/dist/index.d.ts +93 -22
  31. package/dist/index.js +31 -19
  32. package/dist/insights-site-usage.d.ts +10 -0
  33. package/dist/managed-auth-session-sets.js +3 -3
  34. package/dist/mcp-operations.js +2 -2
  35. package/dist/migrate.js +19 -19
  36. package/dist/organization-integration-policy.js +3 -3
  37. package/dist/provision-roles.js +1 -1
  38. package/dist/retained-provider-commands.js +6 -6
  39. package/dist/schema.d.ts +102 -0
  40. package/dist/schema.js +1 -1
  41. package/dist/session-background-commands.js +5 -5
  42. package/dist/session-command-output.js +5 -5
  43. package/dist/session-event-slices.js +3 -3
  44. package/dist/session-mcp-credential-rotation.js +6 -6
  45. package/dist/session-tenancy.js +6 -6
  46. package/dist/site-publishing.d.ts +11 -0
  47. package/dist/{subscription-core-codex-4G4CCOJU.js → subscription-core-codex-HIAP7CY2.js} +6 -6
  48. package/dist/{subscription-core-codex-compat-64ACIZVP.js → subscription-core-codex-compat-IBERN7DU.js} +8 -8
  49. package/dist/video-generation.js +3 -3
  50. package/dist/workspace-tool-defaults.js +5 -5
  51. package/drizzle/0793_private_site_sessions.sql +166 -0
  52. package/drizzle/0794_site_ai_insights.sql +175 -0
  53. package/drizzle/0795_site_lifecycle_cancellation.sql +121 -0
  54. package/package.json +9 -9
  55. package/src/database.ts +44 -3
  56. package/src/index.ts +142 -11
  57. package/src/insights-site-usage.ts +37 -0
  58. package/src/provision-roles.ts +15 -0
  59. package/src/runtime-posture.ts +76 -7
  60. package/src/schema.ts +10 -0
  61. package/src/site-publishing.ts +35 -0
  62. package/dist/chunk-6UU7OVUN.js.map +0 -1
  63. package/dist/chunk-HMKUJ6NI.js.map +0 -1
  64. package/dist/chunk-OXO32LTK.js.map +0 -1
  65. /package/dist/{chunk-L3NT43Z5.js.map → chunk-7K5AVQUZ.js.map} +0 -0
  66. /package/dist/{chunk-JYPBOJ2Y.js.map → chunk-AAOKWNHE.js.map} +0 -0
  67. /package/dist/{chunk-D7LTIG33.js.map → chunk-B4RMLA2X.js.map} +0 -0
  68. /package/dist/{chunk-AYZC65QU.js.map → chunk-CGSINPLW.js.map} +0 -0
  69. /package/dist/{chunk-VCJAYIPH.js.map → chunk-FX2PSMGH.js.map} +0 -0
  70. /package/dist/{chunk-V22DUH4C.js.map → chunk-HWEHZKHA.js.map} +0 -0
  71. /package/dist/{chunk-YH4GWEDM.js.map → chunk-KPC7I5WG.js.map} +0 -0
  72. /package/dist/{chunk-XQHABE23.js.map → chunk-OWM6XG55.js.map} +0 -0
  73. /package/dist/{chunk-YWHG45TK.js.map → chunk-PNEJVKKV.js.map} +0 -0
  74. /package/dist/{chunk-4BAKPTPG.js.map → chunk-S74WULCK.js.map} +0 -0
  75. /package/dist/{chunk-K6NT66NP.js.map → chunk-SDKFRIAN.js.map} +0 -0
  76. /package/dist/{chunk-RCDMYTLP.js.map → chunk-UWS5DJQ6.js.map} +0 -0
  77. /package/dist/{chunk-LRRGVHAQ.js.map → chunk-VUR5U5B3.js.map} +0 -0
  78. /package/dist/{chunk-IF63BST7.js.map → chunk-Y4ERASEM.js.map} +0 -0
  79. /package/dist/{chunk-VBG4TW5C.js.map → chunk-ZZOEKHGU.js.map} +0 -0
  80. /package/dist/{subscription-core-codex-4G4CCOJU.js.map → subscription-core-codex-HIAP7CY2.js.map} +0 -0
  81. /package/dist/{subscription-core-codex-compat-64ACIZVP.js.map → subscription-core-codex-compat-IBERN7DU.js.map} +0 -0
@@ -28,13 +28,13 @@ import {
28
28
  settleConnectedMachineSessionBackgroundCommandWithMutation,
29
29
  settleSessionBackgroundCommandForRetainedProcessInTransaction,
30
30
  unobservableRetainedProcessOutcomeSql
31
- } from "./chunk-V22DUH4C.js";
32
- import "./chunk-IF63BST7.js";
33
- import "./chunk-L3NT43Z5.js";
31
+ } from "./chunk-HWEHZKHA.js";
32
+ import "./chunk-Y4ERASEM.js";
33
+ import "./chunk-7K5AVQUZ.js";
34
34
  import "./chunk-4UJCXBR2.js";
35
- import "./chunk-6UU7OVUN.js";
35
+ import "./chunk-JTBJGSUZ.js";
36
36
  import "./chunk-SMCA7PNJ.js";
37
- import "./chunk-OXO32LTK.js";
37
+ import "./chunk-CEJ3NQ3B.js";
38
38
  import "./chunk-PZ5AY32C.js";
39
39
  export {
40
40
  COMMAND_OUTPUT_DEFAULT_BYTES,
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  appendSessionCommandOutput,
3
3
  commandOutputClientEventIdPrefix
4
- } from "./chunk-V22DUH4C.js";
5
- import "./chunk-IF63BST7.js";
6
- import "./chunk-L3NT43Z5.js";
4
+ } from "./chunk-HWEHZKHA.js";
5
+ import "./chunk-Y4ERASEM.js";
6
+ import "./chunk-7K5AVQUZ.js";
7
7
  import "./chunk-4UJCXBR2.js";
8
- import "./chunk-6UU7OVUN.js";
8
+ import "./chunk-JTBJGSUZ.js";
9
9
  import "./chunk-SMCA7PNJ.js";
10
- import "./chunk-OXO32LTK.js";
10
+ import "./chunk-CEJ3NQ3B.js";
11
11
  import "./chunk-PZ5AY32C.js";
12
12
  export {
13
13
  appendSessionCommandOutput,
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  listSessionEventSlices,
3
3
  readSessionMessageSliceInScope
4
- } from "./chunk-4BAKPTPG.js";
5
- import "./chunk-6UU7OVUN.js";
4
+ } from "./chunk-S74WULCK.js";
5
+ import "./chunk-JTBJGSUZ.js";
6
6
  import "./chunk-SMCA7PNJ.js";
7
- import "./chunk-OXO32LTK.js";
7
+ import "./chunk-CEJ3NQ3B.js";
8
8
  import "./chunk-PZ5AY32C.js";
9
9
  export {
10
10
  listSessionEventSlices,
@@ -1,25 +1,25 @@
1
1
  import {
2
2
  connectionMetadataMatchesBinding
3
- } from "./chunk-VCJAYIPH.js";
3
+ } from "./chunk-FX2PSMGH.js";
4
4
  import {
5
5
  lockSessionEventWriteRows,
6
6
  organizationApiKeyAllowsWorkspace
7
- } from "./chunk-V22DUH4C.js";
8
- import "./chunk-IF63BST7.js";
9
- import "./chunk-L3NT43Z5.js";
7
+ } from "./chunk-HWEHZKHA.js";
8
+ import "./chunk-Y4ERASEM.js";
9
+ import "./chunk-7K5AVQUZ.js";
10
10
  import "./chunk-4UJCXBR2.js";
11
11
  import {
12
12
  rawRows,
13
13
  setSubjectRlsContext,
14
14
  withRlsContext
15
- } from "./chunk-6UU7OVUN.js";
15
+ } from "./chunk-JTBJGSUZ.js";
16
16
  import "./chunk-SMCA7PNJ.js";
17
17
  import {
18
18
  apiKeys,
19
19
  sessionCommandReceipts,
20
20
  sessionMcpServers,
21
21
  sessions
22
- } from "./chunk-OXO32LTK.js";
22
+ } from "./chunk-CEJ3NQ3B.js";
23
23
  import "./chunk-PZ5AY32C.js";
24
24
 
25
25
  // src/session-mcp-credential-rotation.ts
@@ -19,14 +19,14 @@ import {
19
19
  sessionHasLiveAttempts,
20
20
  stopSessionWorkForVisibilityChange,
21
21
  transitionSessionVisibility
22
- } from "./chunk-K6NT66NP.js";
23
- import "./chunk-V22DUH4C.js";
24
- import "./chunk-IF63BST7.js";
25
- import "./chunk-L3NT43Z5.js";
22
+ } from "./chunk-SDKFRIAN.js";
23
+ import "./chunk-HWEHZKHA.js";
24
+ import "./chunk-Y4ERASEM.js";
25
+ import "./chunk-7K5AVQUZ.js";
26
26
  import "./chunk-4UJCXBR2.js";
27
- import "./chunk-6UU7OVUN.js";
27
+ import "./chunk-JTBJGSUZ.js";
28
28
  import "./chunk-SMCA7PNJ.js";
29
- import "./chunk-OXO32LTK.js";
29
+ import "./chunk-CEJ3NQ3B.js";
30
30
  import "./chunk-PZ5AY32C.js";
31
31
  export {
32
32
  EMPTY_SESSION_VISIBILITY_WORK,
@@ -440,6 +440,17 @@ export declare function listActiveSiteRuntimeConversations(db: Database, scope:
440
440
  everyoneTools: boolean;
441
441
  };
442
442
  }): Promise<string[]>;
443
+ /** Keyset-paged lifecycle inventory; no viewer transcript or age/count cutoff. */
444
+ export declare function listSiteRuntimeCancellationCandidates(db: Database, scope: SitePublishingScope, input: {
445
+ siteId: string;
446
+ after?: string;
447
+ limit: number;
448
+ startedBefore: Date;
449
+ toolsNarrowedTo?: {
450
+ tools: readonly string[];
451
+ everyoneTools: boolean;
452
+ };
453
+ }): Promise<string[]>;
443
454
  export type SiteRuntimeUsageSnapshot = {
444
455
  visitorDay: {
445
456
  conversations: number;
@@ -19,8 +19,8 @@ import {
19
19
  subscriptionCoreReselectionPoints,
20
20
  subscriptionCoreTurnActor,
21
21
  touchSubscriptionCoreCodexBinding
22
- } from "./chunk-JYPBOJ2Y.js";
23
- import "./chunk-VBG4TW5C.js";
22
+ } from "./chunk-AAOKWNHE.js";
23
+ import "./chunk-ZZOEKHGU.js";
24
24
  import {
25
25
  SUBSCRIPTION_CORE_CODEX_ENTITLEMENT_COOLDOWN_MS,
26
26
  SUBSCRIPTION_CORE_CODEX_EXHAUSTED_FALLBACK_MS,
@@ -28,10 +28,10 @@ import {
28
28
  SUBSCRIPTION_CORE_CODEX_RATE_LIMIT_FALLBACK_MS,
29
29
  SubscriptionCoreCodexAccessLostError,
30
30
  SubscriptionCoreCodexLeaseLostError
31
- } from "./chunk-IF63BST7.js";
32
- import "./chunk-6UU7OVUN.js";
31
+ } from "./chunk-Y4ERASEM.js";
32
+ import "./chunk-JTBJGSUZ.js";
33
33
  import "./chunk-SMCA7PNJ.js";
34
- import "./chunk-OXO32LTK.js";
34
+ import "./chunk-CEJ3NQ3B.js";
35
35
  import "./chunk-PZ5AY32C.js";
36
36
  export {
37
37
  SUBSCRIPTION_CORE_CODEX_ENTITLEMENT_COOLDOWN_MS,
@@ -61,4 +61,4 @@ export {
61
61
  subscriptionCoreTurnActor,
62
62
  touchSubscriptionCoreCodexBinding
63
63
  };
64
- //# sourceMappingURL=subscription-core-codex-4G4CCOJU.js.map
64
+ //# sourceMappingURL=subscription-core-codex-HIAP7CY2.js.map
@@ -12,17 +12,17 @@ import {
12
12
  setSubscriptionCoreCodexPrimary,
13
13
  setSubscriptionCoreCodexRotation,
14
14
  setSubscriptionCoreWorkspaceCodexSource
15
- } from "./chunk-YWHG45TK.js";
16
- import "./chunk-VBG4TW5C.js";
17
- import "./chunk-V22DUH4C.js";
15
+ } from "./chunk-PNEJVKKV.js";
16
+ import "./chunk-ZZOEKHGU.js";
17
+ import "./chunk-HWEHZKHA.js";
18
18
  import {
19
19
  SubscriptionCoreCodexSourceRefusedError
20
- } from "./chunk-IF63BST7.js";
21
- import "./chunk-L3NT43Z5.js";
20
+ } from "./chunk-Y4ERASEM.js";
21
+ import "./chunk-7K5AVQUZ.js";
22
22
  import "./chunk-4UJCXBR2.js";
23
- import "./chunk-6UU7OVUN.js";
23
+ import "./chunk-JTBJGSUZ.js";
24
24
  import "./chunk-SMCA7PNJ.js";
25
- import "./chunk-OXO32LTK.js";
25
+ import "./chunk-CEJ3NQ3B.js";
26
26
  import "./chunk-PZ5AY32C.js";
27
27
  export {
28
28
  SubscriptionCoreCodexSourceRefusedError,
@@ -40,4 +40,4 @@ export {
40
40
  setSubscriptionCoreCodexRotation,
41
41
  setSubscriptionCoreWorkspaceCodexSource
42
42
  };
43
- //# sourceMappingURL=subscription-core-codex-compat-64ACIZVP.js.map
43
+ //# sourceMappingURL=subscription-core-codex-compat-IBERN7DU.js.map
@@ -28,11 +28,11 @@ import {
28
28
  settleVideoGenerationFailure,
29
29
  settleVideoGenerationReady,
30
30
  updateWorkspaceVideoGenerationPolicy
31
- } from "./chunk-L3NT43Z5.js";
31
+ } from "./chunk-7K5AVQUZ.js";
32
32
  import "./chunk-4UJCXBR2.js";
33
- import "./chunk-6UU7OVUN.js";
33
+ import "./chunk-JTBJGSUZ.js";
34
34
  import "./chunk-SMCA7PNJ.js";
35
- import "./chunk-OXO32LTK.js";
35
+ import "./chunk-CEJ3NQ3B.js";
36
36
  import "./chunk-PZ5AY32C.js";
37
37
  export {
38
38
  ACTIVE_VIDEO_GENERATION_STATUSES,
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  lockWorkspaceInferenceControl
3
- } from "./chunk-V22DUH4C.js";
4
- import "./chunk-IF63BST7.js";
5
- import "./chunk-L3NT43Z5.js";
3
+ } from "./chunk-HWEHZKHA.js";
4
+ import "./chunk-Y4ERASEM.js";
5
+ import "./chunk-7K5AVQUZ.js";
6
6
  import "./chunk-4UJCXBR2.js";
7
7
  import {
8
8
  withWorkspaceRls
9
- } from "./chunk-6UU7OVUN.js";
9
+ } from "./chunk-JTBJGSUZ.js";
10
10
  import "./chunk-SMCA7PNJ.js";
11
- import "./chunk-OXO32LTK.js";
11
+ import "./chunk-CEJ3NQ3B.js";
12
12
  import "./chunk-PZ5AY32C.js";
13
13
 
14
14
  // src/workspace-tool-defaults.ts
@@ -0,0 +1,166 @@
1
+ -- deployment-mode: maintenance
2
+ -- maintenance-justification: old Site writers cannot supply the new immutable viewer audience or proof; drain them before activating the fail-closed native boundary.
3
+ -- Freeze an independent viewer audience on native sessions. Published legacy
4
+ -- mappings prove their audience; historical console provenance does not, and
5
+ -- is deliberately not converted into an asserted private owner.
6
+ SET LOCAL lock_timeout = '5s';
7
+ SET LOCAL statement_timeout = '5min';
8
+
9
+ ALTER TABLE sessions ADD COLUMN site_id uuid,
10
+ ADD COLUMN site_access_root_id uuid,
11
+ ADD COLUMN site_viewer_principal text,
12
+ ADD COLUMN site_owner_subject_id text;
13
+ ALTER TABLE model_call_facts ADD COLUMN site_id uuid;
14
+
15
+ -- Run the bounded source backfill under the same tenant scope as each row.
16
+ -- Maintenance cutover: the migration owner must enumerate all tenants even
17
+ -- when NOSUPERUSER/NOBYPASSRLS. These transaction-local DDL changes are restored
18
+ -- before commit; runtime roles never receive a cross-tenant read lane.
19
+ ALTER TABLE site_runtime_conversations NO FORCE ROW LEVEL SECURITY;
20
+ ALTER TABLE sessions NO FORCE ROW LEVEL SECURITY;
21
+ ALTER TABLE model_call_facts NO FORCE ROW LEVEL SECURITY;
22
+ -- Table locks already exclude runtime writers; do not acquire a workspace
23
+ -- advisory fence after those locks. Restore the exact trigger before commit.
24
+ ALTER TABLE sessions DISABLE TRIGGER session_tenancy_workspace_fence;
25
+ DO $backfill$
26
+ DECLARE scope record;
27
+ BEGIN
28
+ FOR scope IN SELECT DISTINCT account_id, workspace_id FROM site_runtime_conversations LOOP
29
+ PERFORM set_config('opengeni.account_id', scope.account_id::text, true);
30
+ PERFORM set_config('opengeni.workspace_id', scope.workspace_id::text, true);
31
+ WITH RECURSIVE bound AS (
32
+ SELECT s.id, s.id AS access_root, c.site_id, c.viewer_principal,
33
+ CASE WHEN c.viewer_principal LIKE 'user:%' THEN substr(c.viewer_principal,6) END AS owner
34
+ FROM sessions s JOIN site_runtime_conversations c
35
+ ON c.session_id=s.id AND c.account_id=s.account_id AND c.workspace_id=s.workspace_id
36
+ WHERE s.account_id=scope.account_id AND s.workspace_id=scope.workspace_id
37
+ UNION ALL
38
+ SELECT child.id, b.access_root,b.site_id,b.viewer_principal,b.owner
39
+ FROM sessions child JOIN bound b ON child.parent_session_id=b.id
40
+ WHERE child.account_id=scope.account_id AND child.workspace_id=scope.workspace_id
41
+ ) UPDATE sessions s SET site_id=b.site_id,site_access_root_id=b.access_root,
42
+ site_viewer_principal=b.viewer_principal,site_owner_subject_id=b.owner
43
+ FROM bound b WHERE s.id=b.id AND s.workspace_id=scope.workspace_id;
44
+ UPDATE model_call_facts f SET site_id=s.site_id FROM sessions s
45
+ WHERE f.account_id=scope.account_id AND f.workspace_id=scope.workspace_id
46
+ AND s.id=f.session_id AND s.workspace_id=f.workspace_id AND s.site_id IS NOT NULL;
47
+ END LOOP;
48
+ END
49
+ $backfill$;
50
+ ALTER TABLE sessions ENABLE TRIGGER session_tenancy_workspace_fence;
51
+ ALTER TABLE site_runtime_conversations FORCE ROW LEVEL SECURITY;
52
+ ALTER TABLE sessions FORCE ROW LEVEL SECURITY;
53
+ ALTER TABLE model_call_facts FORCE ROW LEVEL SECURITY;
54
+
55
+ CREATE FUNCTION bind_session_site_audience() RETURNS trigger
56
+ LANGUAGE plpgsql SECURITY DEFINER SET search_path FROM CURRENT AS $body$
57
+ DECLARE parent sessions%ROWTYPE; conversation site_runtime_conversations%ROWTYPE;
58
+ origin jsonb; enabled boolean;
59
+ BEGIN
60
+ IF TG_OP='UPDATE' THEN
61
+ IF OLD.site_access_root_id IS NOT NULL AND NEW.visibility IS DISTINCT FROM OLD.visibility THEN
62
+ RAISE EXCEPTION 'Site conversations keep their viewer audience; publish an output to share it' USING ERRCODE='42501';
63
+ END IF;
64
+ IF ROW(NEW.site_id,NEW.site_access_root_id,NEW.site_viewer_principal,NEW.site_owner_subject_id)
65
+ IS DISTINCT FROM ROW(OLD.site_id,OLD.site_access_root_id,OLD.site_viewer_principal,OLD.site_owner_subject_id)
66
+ THEN RAISE EXCEPTION 'Site session audience is immutable' USING ERRCODE='42501'; END IF;
67
+ RETURN NEW;
68
+ END IF;
69
+ IF NEW.site_id IS NOT NULL OR NEW.site_access_root_id IS NOT NULL
70
+ OR NEW.site_viewer_principal IS NOT NULL OR NEW.site_owner_subject_id IS NOT NULL
71
+ THEN RAISE EXCEPTION 'Site audience is derived by the server' USING ERRCODE='42501'; END IF;
72
+ IF NEW.parent_session_id IS NOT NULL THEN
73
+ SELECT * INTO parent FROM sessions WHERE id=NEW.parent_session_id
74
+ AND account_id=NEW.account_id AND workspace_id=NEW.workspace_id;
75
+ IF parent.site_access_root_id IS NOT NULL THEN
76
+ NEW.site_id:=parent.site_id; NEW.site_access_root_id:=parent.site_access_root_id;
77
+ NEW.site_viewer_principal:=parent.site_viewer_principal;
78
+ NEW.site_owner_subject_id:=parent.site_owner_subject_id;
79
+ RETURN NEW;
80
+ END IF;
81
+ END IF;
82
+ SELECT * INTO conversation FROM site_runtime_conversations
83
+ WHERE session_id=NEW.id AND account_id=NEW.account_id AND workspace_id=NEW.workspace_id;
84
+ IF FOUND THEN
85
+ NEW.site_id:=conversation.site_id; NEW.site_access_root_id:=NEW.id;
86
+ NEW.site_viewer_principal:=conversation.viewer_principal;
87
+ IF conversation.viewer_principal LIKE 'user:%' THEN
88
+ NEW.site_owner_subject_id:=substr(conversation.viewer_principal,6);
89
+ END IF;
90
+ ELSE
91
+ origin:=NEW.metadata->'_opengeniSiteOrigin';
92
+ IF jsonb_typeof(origin) IS DISTINCT FROM 'object' THEN RETURN NEW; END IF;
93
+ -- Native ownership must already have been derived by the authority fence.
94
+ -- Old console writers without private creation fail closed after migration.
95
+ IF NEW.owner_subject_id IS NULL OR
96
+ (NEW.parent_session_id IS NULL AND NEW.visibility<>'user_private') THEN
97
+ RAISE EXCEPTION 'Private Site sessions require verified viewer ownership' USING ERRCODE='42501';
98
+ END IF;
99
+ NEW.site_id:=nullif(origin->>'siteId','')::uuid;
100
+ NEW.site_access_root_id:=NEW.id; NEW.site_owner_subject_id:=NEW.owner_subject_id;
101
+ END IF;
102
+ IF current_setting('opengeni.site_session_audience_v1',true) IS DISTINCT FROM '1' THEN
103
+ RAISE EXCEPTION 'Site session writer upgrade required' USING ERRCODE='55000';
104
+ END IF;
105
+ -- Guest/service execution is not an exception to the organization's private
106
+ -- conversation policy. Native member creation already takes this lock first.
107
+ PERFORM pg_advisory_xact_lock(hashtextextended('organization-membership:'||NEW.account_id,0));
108
+ enabled:=organization_private_sessions_enabled(NEW.account_id);
109
+ IF NOT enabled AND NOT EXISTS(SELECT 1 FROM organization_memberships m
110
+ WHERE m.account_id=NEW.account_id AND m.status='active'
111
+ AND m.personal_workspace_id=NEW.workspace_id AND m.subject_id=NEW.site_owner_subject_id)
112
+ THEN RAISE EXCEPTION 'Private Site conversations are disabled by organization policy' USING ERRCODE='P0793'; END IF;
113
+ RETURN NEW;
114
+ END
115
+ $body$;
116
+ -- Alphabetical trigger order: ownership is frozen before the Site audience.
117
+ CREATE TRIGGER zz_sessions_site_audience BEFORE INSERT OR UPDATE ON sessions
118
+ FOR EACH ROW EXECUTE FUNCTION bind_session_site_audience();
119
+
120
+ CREATE FUNCTION session_site_actor_visible(p_site uuid,p_access_root uuid,p_root uuid,p_viewer text,p_owner text)
121
+ RETURNS boolean LANGUAGE sql STABLE SET search_path=pg_catalog AS $body$
122
+ SELECT p_access_root IS NULL
123
+ OR nullif(current_setting('opengeni.subject_id',true),'') IS NULL
124
+ OR p_owner=nullif(current_setting('opengeni.subject_id',true),'')
125
+ OR p_owner=nullif(current_setting('opengeni.initiating_human_subject_id',true),'')
126
+ OR (p_root::text=nullif(current_setting('opengeni.agent_root_session_id',true),'')
127
+ AND (p_access_root=p_root OR p_owner=nullif(current_setting('opengeni.initiating_human_subject_id',true),'')))
128
+ OR (current_setting('opengeni.subject_id',true)='site:'||p_site::text
129
+ AND p_site::text=current_setting('opengeni.site_id',true)
130
+ AND p_viewer=current_setting('opengeni.site_viewer_principal',true))
131
+ $body$;
132
+ -- No inventory-owner exception: privileged Insights transcript projections
133
+ -- also see only the viewer's Site sessions. Their existing masked raw amount
134
+ -- projections retain workspace totals without exposing private session data.
135
+ CREATE POLICY site_viewer_isolation ON sessions AS RESTRICTIVE FOR ALL
136
+ USING (session_site_actor_visible(site_id,site_access_root_id,root_session_id,site_viewer_principal,site_owner_subject_id))
137
+ WITH CHECK (session_site_actor_visible(site_id,site_access_root_id,root_session_id,site_viewer_principal,site_owner_subject_id));
138
+ CREATE INDEX sessions_site_viewer_idx ON sessions(workspace_id,site_id,site_viewer_principal)
139
+ WHERE site_access_root_id IS NOT NULL;
140
+
141
+ CREATE FUNCTION stamp_model_call_site() RETURNS trigger
142
+ LANGUAGE plpgsql SET search_path FROM CURRENT AS $body$
143
+ BEGIN
144
+ IF TG_OP='INSERT' THEN
145
+ SELECT s.site_id INTO NEW.site_id FROM sessions s
146
+ WHERE s.account_id=NEW.account_id AND s.workspace_id=NEW.workspace_id AND s.id=NEW.session_id;
147
+ ELSIF NEW.site_id IS DISTINCT FROM OLD.site_id THEN
148
+ RAISE EXCEPTION 'Model call Site attribution is immutable' USING ERRCODE='42501';
149
+ END IF;
150
+ RETURN NEW;
151
+ END
152
+ $body$;
153
+ CREATE TRIGGER model_call_site_attribution BEFORE INSERT OR UPDATE ON model_call_facts
154
+ FOR EACH ROW EXECUTE FUNCTION stamp_model_call_site();
155
+ CREATE INDEX model_call_facts_site_occurred_idx ON model_call_facts(workspace_id,site_id,occurred_at)
156
+ WHERE site_id IS NOT NULL;
157
+
158
+ REVOKE ALL ON FUNCTION bind_session_site_audience() FROM PUBLIC;
159
+ REVOKE ALL ON FUNCTION stamp_model_call_site() FROM PUBLIC;
160
+ DO $pin$
161
+ DECLARE data_schema text:=current_schema();
162
+ BEGIN
163
+ EXECUTE format('ALTER FUNCTION %I.bind_session_site_audience() SET search_path=pg_catalog,%I,pg_temp',data_schema,data_schema);
164
+ EXECUTE format('ALTER FUNCTION %I.stamp_model_call_site() SET search_path=pg_catalog,%I,pg_temp',data_schema,data_schema);
165
+ END
166
+ $pin$;
@@ -0,0 +1,175 @@
1
+ -- deployment-mode: maintenance
2
+ -- maintenance-justification: backfill exact model debit attribution during the private Site audience cutover before old writers or analytical readers can resume.
3
+ -- Retain exact Site attribution on actual model debits, independently of fact
4
+ -- delivery and session retention. No repricing or changes to charged amounts.
5
+ SET LOCAL lock_timeout='5s';
6
+ SET LOCAL statement_timeout='5min';
7
+ ALTER TABLE credit_ledger_entries ADD COLUMN site_id uuid;
8
+
9
+ ALTER TABLE sessions NO FORCE ROW LEVEL SECURITY;
10
+ ALTER TABLE credit_ledger_entries NO FORCE ROW LEVEL SECURITY;
11
+ DO $backfill$
12
+ DECLARE scope record;
13
+ BEGIN
14
+ FOR scope IN SELECT DISTINCT account_id,workspace_id FROM sessions WHERE site_id IS NOT NULL LOOP
15
+ PERFORM set_config('opengeni.account_id',scope.account_id::text,true);
16
+ PERFORM set_config('opengeni.workspace_id',scope.workspace_id::text,true);
17
+ UPDATE credit_ledger_entries c SET site_id=s.site_id FROM sessions s
18
+ WHERE c.account_id=scope.account_id AND c.workspace_id=scope.workspace_id
19
+ AND c.type='model_usage_debit' AND c.source_type='model_response'
20
+ AND s.account_id=c.account_id AND s.workspace_id=c.workspace_id
21
+ AND s.id::text=c.metadata->>'sessionId' AND s.site_id IS NOT NULL;
22
+ END LOOP;
23
+ END
24
+ $backfill$;
25
+ ALTER TABLE sessions FORCE ROW LEVEL SECURITY;
26
+ ALTER TABLE credit_ledger_entries FORCE ROW LEVEL SECURITY;
27
+
28
+ CREATE FUNCTION stamp_model_debit_site() RETURNS trigger
29
+ LANGUAGE plpgsql SET search_path FROM CURRENT AS $body$
30
+ BEGIN
31
+ IF TG_OP='INSERT' THEN
32
+ NEW.site_id:=NULL;
33
+ IF NEW.type='model_usage_debit' AND NEW.source_type='model_response' THEN
34
+ SELECT s.site_id INTO NEW.site_id FROM sessions s
35
+ WHERE s.account_id=NEW.account_id AND s.workspace_id=NEW.workspace_id
36
+ AND s.id::text=NEW.metadata->>'sessionId';
37
+ END IF;
38
+ ELSIF NEW.site_id IS DISTINCT FROM OLD.site_id THEN
39
+ RAISE EXCEPTION 'Model debit Site attribution is immutable' USING ERRCODE='42501';
40
+ END IF;
41
+ RETURN NEW;
42
+ END
43
+ $body$;
44
+ CREATE TRIGGER model_debit_site_attribution BEFORE INSERT OR UPDATE ON credit_ledger_entries
45
+ FOR EACH ROW EXECUTE FUNCTION stamp_model_debit_site();
46
+ REVOKE ALL ON FUNCTION stamp_model_debit_site() FROM PUBLIC;
47
+ CREATE INDEX credit_ledger_site_occurred_idx ON credit_ledger_entries(workspace_id,site_id,occurred_at)
48
+ WHERE site_id IS NOT NULL AND type='model_usage_debit' AND amount_micros<0;
49
+
50
+ DO $reader$
51
+ BEGIN
52
+ EXECUTE format($ddl$
53
+ CREATE FUNCTION opengeni_private.insights_site_usage(a uuid,w uuid,lo timestamptz,hi timestamptz,viewer text)
54
+ RETURNS TABLE(payload jsonb) LANGUAGE plpgsql VOLATILE SECURITY DEFINER
55
+ SET search_path=pg_catalog,%1$I,opengeni_private,pg_temp SET jit=off AS $fn$
56
+ DECLARE actor text:=nullif(current_setting('opengeni.subject_id',true),'');
57
+ human text:=nullif(current_setting('opengeni.initiating_human_subject_id',true),'');
58
+ BEGIN
59
+ IF a IS NULL OR w IS NULL OR a IS DISTINCT FROM nullif(current_setting('opengeni.account_id',true),'')::uuid
60
+ OR w IS DISTINCT FROM nullif(current_setting('opengeni.workspace_id',true),'')::uuid THEN
61
+ RAISE EXCEPTION 'Site Insights requires exact tenant scope' USING ERRCODE='42501'; END IF;
62
+ IF lo IS NULL OR hi IS NULL OR hi<lo OR hi-lo>interval '370 days' THEN
63
+ RAISE EXCEPTION 'Invalid Site Insights window' USING ERRCODE='22023'; END IF;
64
+ IF viewer IS NOT NULL AND viewer IS DISTINCT FROM actor AND viewer IS DISTINCT FROM human THEN
65
+ RAISE EXCEPTION 'Site Insights viewer differs from the verified actor' USING ERRCODE='42501'; END IF;
66
+ -- The existing owner+witness fact capability admits costs, not transcripts.
67
+ INSERT INTO opengeni_private.insights_fact_read_runtime_capabilities
68
+ (backend_pid,transaction_id,capability_kind,account_id,workspace_id,subject_id,initiating_human_subject_id)
69
+ VALUES(pg_backend_pid(),pg_current_xact_id(),'model_call_facts',a,w,actor,human);
70
+ BEGIN
71
+ RETURN QUERY WITH amounts AS MATERIALIZED (
72
+ SELECT f.site_id,1::bigint calls,CASE WHEN f.estimated_provider_cost_micros IS NULL THEN 0 ELSE 1 END::bigint priced,
73
+ 0::bigint charged,coalesce(f.estimated_provider_cost_micros,0) equivalent,
74
+ CASE WHEN opengeni_private.insights_usage_payer(f.provider,f.billing_path)='subscription'
75
+ THEN coalesce(f.estimated_provider_cost_micros,0) ELSE 0 END subscription,
76
+ CASE WHEN opengeni_private.insights_usage_payer(f.provider,f.billing_path)='own_key'
77
+ THEN coalesce(f.estimated_provider_cost_micros,0) ELSE 0 END own_key
78
+ FROM %1$I.model_call_facts f WHERE f.account_id=a AND f.workspace_id=w
79
+ AND f.site_id IS NOT NULL AND f.occurred_at>=lo AND f.occurred_at<hi
80
+ UNION ALL
81
+ SELECT c.site_id,0,0,-c.amount_micros,0,0,0 FROM %1$I.credit_ledger_entries c
82
+ WHERE c.account_id=a AND c.workspace_id=w AND c.site_id IS NOT NULL
83
+ AND c.type='model_usage_debit' AND c.source_type='model_response' AND c.amount_micros<0
84
+ AND c.occurred_at>=lo AND c.occurred_at<hi
85
+ ), visible AS MATERIALIZED (
86
+ -- Match workspace-artifacts.visibleTo: its RLS is tenant-only.
87
+ -- Private/unavailable Sites share one bucket; never leak their ids.
88
+ SELECT site.id,site.title FROM %1$I.workspace_artifacts site
89
+ WHERE site.account_id=a AND site.workspace_id=w
90
+ AND (site.visibility='workspace_shared' OR site.owner_subject_id=viewer)
91
+ ), grouped AS (
92
+ SELECT v.id,coalesce(v.title,'Private or unavailable Sites') title,
93
+ CASE WHEN v.id IS NULL THEN 'restricted' ELSE 'site' END kind,
94
+ sum(calls)::bigint calls,sum(priced)::bigint priced,sum(charged)::bigint charged,
95
+ sum(equivalent)::bigint equivalent,sum(subscription)::bigint subscription,sum(own_key)::bigint own_key
96
+ FROM amounts x LEFT JOIN visible v ON v.id=x.site_id GROUP BY v.id,v.title
97
+ ), ranked AS (
98
+ SELECT *,row_number() OVER(ORDER BY charged DESC,equivalent DESC,id NULLS LAST) position FROM grouped
99
+ ), bounded AS (
100
+ SELECT id,title,kind,calls,priced,charged,equivalent,subscription,own_key FROM ranked WHERE position<=200
101
+ UNION ALL SELECT NULL,'Other Sites','other',sum(calls)::bigint,sum(priced)::bigint,sum(charged)::bigint,
102
+ sum(equivalent)::bigint,sum(subscription)::bigint,sum(own_key)::bigint FROM ranked WHERE position>200 HAVING count(*)>0
103
+ ) SELECT jsonb_build_object('siteId',id,'title',title,'kind',kind,'calls',calls,'pricedCalls',priced,
104
+ 'chargedMicros',charged,'apiEquivalentMicros',equivalent,'subscriptionApiEquivalentMicros',subscription,
105
+ 'ownKeyApiEquivalentMicros',own_key) FROM bounded ORDER BY charged DESC,equivalent DESC,id NULLS LAST;
106
+ DELETE FROM opengeni_private.insights_fact_read_runtime_capabilities WHERE backend_pid=pg_backend_pid()
107
+ AND transaction_id=pg_current_xact_id_if_assigned() AND capability_kind='model_call_facts';
108
+ EXCEPTION WHEN OTHERS THEN
109
+ DELETE FROM opengeni_private.insights_fact_read_runtime_capabilities WHERE backend_pid=pg_backend_pid()
110
+ AND transaction_id=pg_current_xact_id_if_assigned() AND capability_kind='model_call_facts';
111
+ RAISE;
112
+ END;
113
+ END
114
+ $fn$;
115
+ $ddl$,current_schema());
116
+ EXECUTE format('ALTER FUNCTION %I.stamp_model_debit_site() SET search_path=pg_catalog,%I,pg_temp',current_schema(),current_schema());
117
+ END
118
+ $reader$;
119
+
120
+ REVOKE ALL ON FUNCTION opengeni_private.insights_site_usage(uuid,uuid,timestamptz,timestamptz,text) FROM PUBLIC;
121
+ DO $acl$
122
+ DECLARE role_name text;
123
+ BEGIN
124
+ FOR role_name IN SELECT r.rolname FROM jsonb_array_elements_text(current_setting('opengeni.migration_application_roles')::jsonb) configured(value)
125
+ JOIN pg_roles r ON r.rolname=configured.value
126
+ WHERE has_table_privilege(r.rolname,format('%I.model_call_facts',current_schema()),'SELECT') LOOP
127
+ EXECUTE format('GRANT EXECUTE ON FUNCTION opengeni_private.insights_site_usage(uuid,uuid,timestamptz,timestamptz,text) TO %I',role_name);
128
+ END LOOP;
129
+ END
130
+ $acl$;
131
+
132
+ -- Analytical definers also run in installations whose migration owner bypasses
133
+ -- RLS. Keep the independent Site audience explicit, without changing native
134
+ -- private-session predicates or granting inventory access to Site transcripts.
135
+ -- This invoker view adds only a filter; it never confers underlying table access.
136
+ CREATE VIEW insights_audience_sessions WITH (security_invoker=true,security_barrier=true) AS
137
+ SELECT * FROM sessions WHERE session_site_actor_visible(
138
+ site_id,site_access_root_id,root_session_id,site_viewer_principal,site_owner_subject_id);
139
+ REVOKE ALL ON insights_audience_sessions FROM PUBLIC;
140
+
141
+ -- Existing migrations use source-anchored function replacement to preserve
142
+ -- signatures, ACLs and accounting protocols. Change only the sessions relation
143
+ -- in the complete, enumerated family of analytical readers; refuse source drift.
144
+ DO $analytical_audience$
145
+ DECLARE routine record; original text; updated text; changed int:=0;
146
+ BEGIN
147
+ FOR routine IN SELECT p.oid,p.proname FROM pg_proc p JOIN pg_namespace n ON n.oid=p.pronamespace
148
+ WHERE n.nspname='opengeni_private' AND p.proname IN (
149
+ 'visible_workspace_insights_model_call_facts','visible_workspace_insights_usage_events',
150
+ 'visible_workspace_insights_usage_projection','complete_workspace_insights_usage_projection',
151
+ 'visible_workspace_insights_model_fact_rows','workspace_insights_amount_fact_rows',
152
+ 'insights_scoped_usage_rows','insights_scoped_calls_rows') LOOP
153
+ original:=pg_get_functiondef(routine.oid);
154
+ updated:=regexp_replace(original,'\.sessions([[:space:]])','.insights_audience_sessions\1','g');
155
+ IF updated=original THEN
156
+ -- The short overloads delegate to their scoped sibling without a join.
157
+ IF routine.proname NOT IN('visible_workspace_insights_model_call_facts','visible_workspace_insights_usage_events') THEN
158
+ RAISE EXCEPTION 'Insights audience source contract changed: %',routine.proname; END IF;
159
+ CONTINUE;
160
+ END IF;
161
+ IF routine.proname='insights_scoped_usage_rows' THEN
162
+ -- A hidden Site is restricted, not a deleted conversation. This metadata
163
+ -- existence check cannot project identifiers and keeps totals complete.
164
+ updated:=replace(updated,'WHEN s.id IS NULL OR r.id IS NULL THEN ''deleted''',format(
165
+ 'WHEN (s.id IS NULL OR r.id IS NULL) AND EXISTS(SELECT 1 FROM %I.model_call_facts sf
166
+ WHERE sf.account_id=a AND sf.workspace_id=w.id AND sf.session_id=f.session_id AND sf.site_id IS NOT NULL)
167
+ THEN ''restricted'' WHEN s.id IS NULL OR r.id IS NULL THEN ''deleted''',current_schema()));
168
+ IF position('sf.site_id IS NOT NULL' IN updated)=0 THEN RAISE EXCEPTION 'Insights classification source contract changed'; END IF;
169
+ END IF;
170
+ EXECUTE updated;
171
+ changed:=changed+1;
172
+ END LOOP;
173
+ IF changed<>9 THEN RAISE EXCEPTION 'Expected nine scoped analytical readers, updated %',changed; END IF;
174
+ END
175
+ $analytical_audience$;