@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
@@ -0,0 +1,121 @@
1
+ -- deployment-mode: rolling
2
+ -- Cancel-only host lifecycle commands reuse the native session control writer.
3
+ -- A private transaction-local witness admits its exact Site root/subtree; it
4
+ -- is never a viewer identity, bearer permission, or tenancy-inventory exception.
5
+ SET LOCAL lock_timeout = '5s';
6
+ SET LOCAL statement_timeout = '5min';
7
+
8
+ CREATE INDEX site_runtime_conversations_cancel_page_idx
9
+ ON site_runtime_conversations(account_id, workspace_id, site_id, session_id);
10
+
11
+ CREATE TABLE opengeni_private.site_session_cancellation_capabilities (
12
+ capability_id uuid NOT NULL UNIQUE,
13
+ target_schema oid NOT NULL,
14
+ backend_pid integer NOT NULL,
15
+ transaction_id xid8 NOT NULL,
16
+ account_id uuid NOT NULL,
17
+ workspace_id uuid NOT NULL,
18
+ site_id uuid NOT NULL,
19
+ root_session_id uuid NOT NULL,
20
+ subject_id text,
21
+ initiating_human_subject_id text,
22
+ PRIMARY KEY (backend_pid, transaction_id)
23
+ );
24
+ REVOKE ALL ON TABLE opengeni_private.site_session_cancellation_capabilities FROM PUBLIC;
25
+
26
+ DO $functions$
27
+ DECLARE data_schema text := current_schema();
28
+ BEGIN
29
+ EXECUTE format($ddl$
30
+ CREATE FUNCTION %1$I.site_session_cancellation_capability_active(
31
+ a uuid,w uuid,site uuid,root uuid
32
+ ) RETURNS boolean LANGUAGE sql STABLE SECURITY DEFINER SET search_path=pg_catalog AS $fn$
33
+ SELECT EXISTS (
34
+ SELECT 1 FROM opengeni_private.site_session_cancellation_capabilities c
35
+ WHERE c.target_schema=%2$s::oid AND c.backend_pid=pg_backend_pid()
36
+ AND c.transaction_id=pg_current_xact_id_if_assigned()
37
+ AND c.account_id=a AND c.workspace_id=w AND c.site_id=site AND c.root_session_id=root
38
+ AND a=nullif(current_setting('opengeni.account_id',true),'')::uuid
39
+ AND w=nullif(current_setting('opengeni.workspace_id',true),'')::uuid
40
+ AND c.subject_id IS NOT DISTINCT FROM nullif(current_setting('opengeni.subject_id',true),'')
41
+ AND c.initiating_human_subject_id IS NOT DISTINCT FROM nullif(current_setting('opengeni.initiating_human_subject_id',true),'')
42
+ )
43
+ $fn$;
44
+
45
+ CREATE FUNCTION opengeni_private.begin_site_session_cancellation(a uuid,w uuid,site uuid,root uuid)
46
+ RETURNS uuid LANGUAGE plpgsql VOLATILE SECURITY DEFINER
47
+ SET search_path=pg_catalog,%1$I,opengeni_private,pg_temp AS $fn$
48
+ DECLARE token uuid:=gen_random_uuid(); actor text:=nullif(current_setting('opengeni.subject_id',true),'');
49
+ human text:=nullif(current_setting('opengeni.initiating_human_subject_id',true),'');
50
+ BEGIN
51
+ IF a IS NULL OR w IS NULL OR site IS NULL OR root IS NULL
52
+ OR a IS DISTINCT FROM nullif(current_setting('opengeni.account_id',true),'')::uuid
53
+ OR w IS DISTINCT FROM nullif(current_setting('opengeni.workspace_id',true),'')::uuid
54
+ OR actor LIKE 'site:%%' THEN
55
+ RAISE EXCEPTION 'Site lifecycle cancellation requires exact host scope' USING ERRCODE='42501';
56
+ END IF;
57
+ -- Trusted application lifecycle only. The mapping proves association
58
+ -- without granting a service principal transcript access.
59
+ IF NOT EXISTS(SELECT 1 FROM %1$I.site_runtime_conversations c
60
+ WHERE c.account_id=a AND c.workspace_id=w AND c.site_id=site AND c.session_id=root) THEN
61
+ RAISE EXCEPTION 'Site cancellation target is unavailable' USING ERRCODE='42501';
62
+ END IF;
63
+ INSERT INTO opengeni_private.site_session_cancellation_capabilities
64
+ VALUES(token,%2$s::oid,pg_backend_pid(),pg_current_xact_id(),a,w,site,root,actor,human);
65
+ -- Root identity avoids the generic hidden-ancestor reader entirely.
66
+ IF NOT EXISTS(SELECT 1 FROM %1$I.sessions s WHERE s.account_id=a AND s.workspace_id=w
67
+ AND s.id=root AND s.site_id=site AND s.site_access_root_id=root
68
+ AND s.root_session_id=root AND s.parent_session_id IS NULL) THEN
69
+ DELETE FROM opengeni_private.site_session_cancellation_capabilities WHERE capability_id=token;
70
+ RETURN NULL;
71
+ END IF;
72
+ RETURN token;
73
+ END
74
+ $fn$;
75
+
76
+ CREATE FUNCTION opengeni_private.end_site_session_cancellation(token uuid)
77
+ RETURNS void LANGUAGE sql VOLATILE SECURITY DEFINER SET search_path=pg_catalog AS $fn$
78
+ DELETE FROM opengeni_private.site_session_cancellation_capabilities c
79
+ WHERE c.capability_id=token AND c.target_schema=%2$s::oid
80
+ AND c.backend_pid=pg_backend_pid() AND c.transaction_id=pg_current_xact_id_if_assigned()
81
+ $fn$;
82
+ $ddl$,data_schema,current_schema()::regnamespace::oid);
83
+ END
84
+ $functions$;
85
+
86
+ -- Preserve installed native/private and Site predicates. Both restrictive
87
+ -- gates admit only this exact command witness, never general inventory.
88
+ DO $policies$
89
+ DECLARE p record; n integer:=0;
90
+ allowance text:=format('%I.site_session_cancellation_capability_active(account_id,workspace_id,site_id,site_access_root_id)',current_schema());
91
+ BEGIN
92
+ FOR p IN SELECT polname,pg_get_expr(polqual,polrelid) qual,
93
+ pg_get_expr(polwithcheck,polrelid) check_expr,polpermissive,polcmd
94
+ FROM pg_policy WHERE polrelid='sessions'::regclass
95
+ AND polname IN ('session_visibility_isolation','site_viewer_isolation') LOOP
96
+ IF p.polpermissive OR p.polcmd<>'*' OR p.qual IS NULL OR p.check_expr IS NULL THEN
97
+ RAISE EXCEPTION 'Unexpected session cancellation policy contract' USING ERRCODE='55000';
98
+ END IF;
99
+ EXECUTE format('ALTER POLICY %I ON %I.sessions USING ((%s) OR %s) WITH CHECK ((%s) OR %s)',
100
+ p.polname,current_schema(),p.qual,allowance,p.check_expr,allowance);
101
+ n:=n+1;
102
+ END LOOP;
103
+ IF n<>2 THEN RAISE EXCEPTION 'Missing session cancellation policy contract' USING ERRCODE='55000'; END IF;
104
+ END
105
+ $policies$;
106
+
107
+ REVOKE ALL ON FUNCTION opengeni_private.begin_site_session_cancellation(uuid,uuid,uuid,uuid) FROM PUBLIC;
108
+ REVOKE ALL ON FUNCTION opengeni_private.end_site_session_cancellation(uuid) FROM PUBLIC;
109
+ -- The policy predicate is value-free; callers cannot mint its witness.
110
+ GRANT EXECUTE ON FUNCTION site_session_cancellation_capability_active(uuid,uuid,uuid,uuid) TO PUBLIC;
111
+ DO $acl$
112
+ DECLARE role_name text;
113
+ BEGIN
114
+ FOR role_name IN SELECT DISTINCT r.rolname FROM pg_roles r
115
+ JOIN jsonb_array_elements_text(current_setting('opengeni.migration_application_roles')::jsonb) configured(value)
116
+ ON r.rolname=configured.value LOOP
117
+ EXECUTE format('REVOKE ALL ON TABLE opengeni_private.site_session_cancellation_capabilities FROM %I',role_name);
118
+ EXECUTE format('GRANT EXECUTE ON FUNCTION opengeni_private.begin_site_session_cancellation(uuid,uuid,uuid,uuid),opengeni_private.end_site_session_cancellation(uuid) TO %I',role_name);
119
+ END LOOP;
120
+ END
121
+ $acl$;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/db",
3
- "version": "3.2.2",
3
+ "version": "3.3.0",
4
4
  "description": "Opengeni persistence: Drizzle schema, RLS-scoped query layer, the SQL migration runner, and role provisioning.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -117,14 +117,14 @@
117
117
  "prepublishOnly": "bash ../../scripts/prepublish-guard"
118
118
  },
119
119
  "dependencies": {
120
- "@opengeni/codemode": "3.2.2",
121
- "@opengeni/codex": "3.2.2",
122
- "@opengeni/config": "3.2.2",
123
- "@opengeni/contracts": "3.2.2",
124
- "@opengeni/network": "3.2.2",
125
- "@opengeni/observability": "3.2.2",
126
- "@opengeni/subscriptions": "3.2.2",
127
- "@opengeni/xai-subscription": "3.2.2",
120
+ "@opengeni/codemode": "3.3.0",
121
+ "@opengeni/codex": "3.3.0",
122
+ "@opengeni/config": "3.3.0",
123
+ "@opengeni/contracts": "3.3.0",
124
+ "@opengeni/network": "3.3.0",
125
+ "@opengeni/observability": "3.3.0",
126
+ "@opengeni/subscriptions": "3.3.0",
127
+ "@opengeni/xai-subscription": "3.3.0",
128
128
  "drizzle-orm": "^0.45.2",
129
129
  "postgres": "^3.4.7"
130
130
  },
package/src/database.ts CHANGED
@@ -63,8 +63,20 @@ export type SessionRlsActorContext = {
63
63
  /** Host-verified personal file ownership, never a request-supplied subject. */
64
64
  privateFileOwnerSubjectId?: string | null;
65
65
  initiatingHumanSubjectId?: string | null;
66
+ /** Root reconstructed from a verified live agent attempt, never page input. */
67
+ agentRootSessionId?: string | null;
66
68
  };
67
69
 
70
+ /** Request-local proof from the signed published-Site gateway and live viewer cookie. */
71
+ export type SiteViewerRlsContext = Readonly<{ siteId: string; principal: string }>;
72
+ const siteViewerRlsContext = new AsyncLocalStorage<SiteViewerRlsContext>();
73
+ export function currentSiteViewerRlsContext(): SiteViewerRlsContext | undefined {
74
+ return siteViewerRlsContext.getStore();
75
+ }
76
+ export function withSiteViewerRlsContext<T>(context: SiteViewerRlsContext, run: () => T): T {
77
+ return siteViewerRlsContext.run(Object.freeze({ ...context }), run);
78
+ }
79
+
68
80
  const sessionRlsActorContext = new AsyncLocalStorage<SessionRlsActorContext>();
69
81
  export function currentSessionAttachmentReadAccess(): SessionAttachmentReadAccess | undefined {
70
82
  return sessionRlsActorContext.getStore()?.sessionAttachmentReadAccess;
@@ -94,6 +106,8 @@ export function currentSessionRlsActorIdentityKey(): string | null {
94
106
  actor.initiatingHumanSubjectId ?? null,
95
107
  actor.privateFileOwnerSubjectId ?? null,
96
108
  actor.sessionAttachmentReadAccess ?? null,
109
+ actor.agentRootSessionId ?? null,
110
+ siteViewerRlsContext.getStore() ?? null,
97
111
  ]);
98
112
  }
99
113
 
@@ -144,6 +158,9 @@ type RlsContextSettings = {
144
158
  initiatingHumanSubjectId: string;
145
159
  personalResourceHumanSubjectId: string;
146
160
  personalResourceActorSubjectId: string;
161
+ agentRootSessionId: string;
162
+ siteId: string;
163
+ siteViewerPrincipal: string;
147
164
  };
148
165
 
149
166
  /**
@@ -475,8 +492,14 @@ export async function setRlsContext(db: Database, context: RlsContext): Promise<
475
492
  set_config('opengeni.pending_tool_event_output_v1', '1', true),
476
493
  set_config('opengeni.session_variable_set_attachments_v1', '1', true),
477
494
  set_config('opengeni.shared_personal_choice_v1', '1', true),
495
+ set_config('opengeni.site_session_audience_v1', '1', true),
478
496
  set_config('jit', 'off', true)`);
479
497
  const sessionActor = sessionRlsActorContext.getStore();
498
+ const siteViewer = siteViewerRlsContext.getStore();
499
+ await db.execute(sql`select
500
+ set_config('opengeni.agent_root_session_id', ${sessionActor?.agentRootSessionId ?? ""}, true),
501
+ set_config('opengeni.site_id', ${siteViewer?.siteId ?? ""}, true),
502
+ set_config('opengeni.site_viewer_principal', ${siteViewer?.principal ?? ""}, true)`);
480
503
  if (sessionActor) {
481
504
  await setSubjectRlsContext(db, sessionActor.subjectId);
482
505
  await db.execute(
@@ -498,6 +521,9 @@ async function readRlsContextSettings(db: Database): Promise<RlsContextSettings>
498
521
  initiating_human_subject_id: string | null;
499
522
  personal_resource_human_subject_id: string | null;
500
523
  personal_resource_actor_subject_id: string | null;
524
+ agent_root_session_id: string | null;
525
+ site_id: string | null;
526
+ site_viewer_principal: string | null;
501
527
  }>(
502
528
  db,
503
529
  sql`select
@@ -507,7 +533,10 @@ async function readRlsContextSettings(db: Database): Promise<RlsContextSettings>
507
533
  current_setting('opengeni.private_file_owner', true) as private_file_owner,
508
534
  current_setting('opengeni.initiating_human_subject_id', true) as initiating_human_subject_id,
509
535
  current_setting('opengeni.personal_resource_human_subject_id', true) as personal_resource_human_subject_id,
510
- current_setting('opengeni.personal_resource_actor_subject_id', true) as personal_resource_actor_subject_id`,
536
+ current_setting('opengeni.personal_resource_actor_subject_id', true) as personal_resource_actor_subject_id,
537
+ current_setting('opengeni.agent_root_session_id', true) as agent_root_session_id,
538
+ current_setting('opengeni.site_id', true) as site_id,
539
+ current_setting('opengeni.site_viewer_principal', true) as site_viewer_principal`,
511
540
  );
512
541
  return {
513
542
  accountId: settings?.account_id ?? "",
@@ -517,6 +546,9 @@ async function readRlsContextSettings(db: Database): Promise<RlsContextSettings>
517
546
  initiatingHumanSubjectId: settings?.initiating_human_subject_id ?? "",
518
547
  personalResourceHumanSubjectId: settings?.personal_resource_human_subject_id ?? "",
519
548
  personalResourceActorSubjectId: settings?.personal_resource_actor_subject_id ?? "",
549
+ agentRootSessionId: settings?.agent_root_session_id ?? "",
550
+ siteId: settings?.site_id ?? "",
551
+ siteViewerPrincipal: settings?.site_viewer_principal ?? "",
520
552
  };
521
553
  }
522
554
 
@@ -547,6 +579,9 @@ async function restoreRlsContextSettings(
547
579
  initiating_human_subject_id: string;
548
580
  personal_resource_human_subject_id: string;
549
581
  personal_resource_actor_subject_id: string;
582
+ agent_root_session_id: string;
583
+ site_id: string;
584
+ site_viewer_principal: string;
550
585
  }>(
551
586
  db,
552
587
  sql`select
@@ -556,7 +591,10 @@ async function restoreRlsContextSettings(
556
591
  set_config('opengeni.private_file_owner', ${settings.privateFileOwnerSubjectId}, true) as private_file_owner,
557
592
  set_config('opengeni.initiating_human_subject_id', ${settings.initiatingHumanSubjectId}, true) as initiating_human_subject_id,
558
593
  set_config('opengeni.personal_resource_human_subject_id', ${settings.personalResourceHumanSubjectId}, true) as personal_resource_human_subject_id,
559
- set_config('opengeni.personal_resource_actor_subject_id', ${settings.personalResourceActorSubjectId}, true) as personal_resource_actor_subject_id`,
594
+ set_config('opengeni.personal_resource_actor_subject_id', ${settings.personalResourceActorSubjectId}, true) as personal_resource_actor_subject_id,
595
+ set_config('opengeni.agent_root_session_id', ${settings.agentRootSessionId}, true) as agent_root_session_id,
596
+ set_config('opengeni.site_id', ${settings.siteId}, true) as site_id,
597
+ set_config('opengeni.site_viewer_principal', ${settings.siteViewerPrincipal}, true) as site_viewer_principal`,
560
598
  );
561
599
  if (
562
600
  restored?.account_id !== settings.accountId ||
@@ -565,7 +603,10 @@ async function restoreRlsContextSettings(
565
603
  restored.private_file_owner !== settings.privateFileOwnerSubjectId ||
566
604
  restored.initiating_human_subject_id !== settings.initiatingHumanSubjectId ||
567
605
  restored.personal_resource_human_subject_id !== settings.personalResourceHumanSubjectId ||
568
- restored.personal_resource_actor_subject_id !== settings.personalResourceActorSubjectId
606
+ restored.personal_resource_actor_subject_id !== settings.personalResourceActorSubjectId ||
607
+ restored.agent_root_session_id !== settings.agentRootSessionId ||
608
+ restored.site_id !== settings.siteId ||
609
+ restored.site_viewer_principal !== settings.siteViewerPrincipal
569
610
  ) {
570
611
  throw new Error("RLS context could not be restored after a nested scope");
571
612
  }
package/src/index.ts CHANGED
@@ -518,6 +518,7 @@ export * from "./site-data";
518
518
  export * from "./site-publishing";
519
519
  export { withSiteRuntimeAdmission } from "./site-runtime-admission";
520
520
  export * from "./site-platform";
521
+ export * from "./insights-site-usage";
521
522
  export * from "./scheduled-slack-bot-messages";
522
523
  export * from "./organization-signup-use-cases";
523
524
  export * from "./site-sql";
@@ -1011,6 +1012,7 @@ import {
1011
1012
  workspaceControlLockBudget,
1012
1013
  workspaceControlRequestLockTimeoutMs,
1013
1014
  lockWorkspaceInferenceControl,
1015
+ mutateSessionControlInTransaction,
1014
1016
  registerInternalUpdateWakeInTransaction,
1015
1017
  registerSessionWorkflowWakeInTransaction,
1016
1018
  SESSION_DISCOVERY_CONTROL_TITLE_MAX_CHARS,
@@ -1245,6 +1247,7 @@ export * from "./model-catalog";
1245
1247
 
1246
1248
  import {
1247
1249
  dbBindingFor,
1250
+ isTransactionHandle,
1248
1251
  rawRows,
1249
1252
  retryRlsPersistence,
1250
1253
  retryWorkspacePersistence,
@@ -1301,6 +1304,9 @@ export {
1301
1304
  } from "./database";
1302
1305
  export {
1303
1306
  currentSessionRlsActorIdentityKey,
1307
+ currentSessionRlsActorContext,
1308
+ currentSiteViewerRlsContext,
1309
+ withSiteViewerRlsContext,
1304
1310
  currentSessionRlsActorInitiatingHumanSubjectId,
1305
1311
  withSessionRlsActorContext,
1306
1312
  } from "./database";
@@ -29679,7 +29685,7 @@ async function createSessionInTransaction(
29679
29685
  );
29680
29686
  const createIdempotencyKey = input.createIdempotencyKey ?? null;
29681
29687
  const createRequestedVisibility = input.visibility ?? "workspace_shared";
29682
- if (createRequestedVisibility === "user_private") {
29688
+ if (createRequestedVisibility === "user_private" || sessionMetadata._opengeniSiteOrigin) {
29683
29689
  // Private creation later proves the owner's live organization membership
29684
29690
  // and, for a fresh top-level organization session, reads the mutable
29685
29691
  // owner/admin enablement setting. Serialize both checks with organization
@@ -30883,6 +30889,10 @@ export type SessionAuthorityProjection = SessionAccessProjection & {
30883
30889
  authorityEpoch: number;
30884
30890
  visibility: "user_private" | "workspace_shared";
30885
30891
  ownerSubjectId: string | null;
30892
+ siteId: string | null;
30893
+ siteAccessRootId: string | null;
30894
+ siteViewerPrincipal: string | null;
30895
+ siteOwnerSubjectId: string | null;
30886
30896
  };
30887
30897
 
30888
30898
  type SessionAccessRow = Pick<
@@ -30924,6 +30934,10 @@ export async function getSessionAuthorityProjection(
30924
30934
  visibility: schema.sessions.visibility,
30925
30935
  authorityEpoch: schema.sessions.authorityEpoch,
30926
30936
  ownerSubjectId: schema.sessions.ownerSubjectId,
30937
+ siteId: schema.sessions.siteId,
30938
+ siteAccessRootId: schema.sessions.siteAccessRootId,
30939
+ siteViewerPrincipal: schema.sessions.siteViewerPrincipal,
30940
+ siteOwnerSubjectId: schema.sessions.siteOwnerSubjectId,
30927
30941
  agentAccess: schema.sessions.agentAccess,
30928
30942
  scopeSubjectId: schema.sessions.scopeSubjectId,
30929
30943
  memoryScope: schema.sessions.memoryScope,
@@ -30938,6 +30952,10 @@ export async function getSessionAuthorityProjection(
30938
30952
  rootSessionId: row.rootSessionId,
30939
30953
  visibility: row.visibility as SessionAuthorityProjection["visibility"],
30940
30954
  ownerSubjectId: row.ownerSubjectId ?? null,
30955
+ siteId: row.siteId ?? null,
30956
+ siteAccessRootId: row.siteAccessRootId ?? null,
30957
+ siteViewerPrincipal: row.siteViewerPrincipal ?? null,
30958
+ siteOwnerSubjectId: row.siteOwnerSubjectId ?? null,
30941
30959
  agentAccess: sessionAgentAccessFromRow(row),
30942
30960
  scopeSubjectId: sessionEndUserFromRow(row),
30943
30961
  memoryScope: sessionMemoryScopeFromRow(row),
@@ -32548,7 +32566,17 @@ function sessionFilters(
32548
32566
  }
32549
32567
  if (options.originSiteId) {
32550
32568
  filters.push(
32551
- sql`${schema.sessions.metadata}->'_opengeniSiteOrigin'->>'siteId' = ${options.originSiteId}`,
32569
+ eq(schema.sessions.siteId, options.originSiteId),
32570
+ sql`${schema.sessions.siteAccessRootId} is not null`,
32571
+ );
32572
+ } else {
32573
+ // App-internal execution is not ordinary workspace conversation discovery.
32574
+ // This is presentation scope only: native ownership and Site RLS still apply.
32575
+ const ownRoot = options.authorizationScope?.agentAccessViewer?.callerRootSessionId;
32576
+ filters.push(
32577
+ ownRoot
32578
+ ? sql`(${schema.sessions.siteAccessRootId} is null OR ${schema.sessions.rootSessionId} = ${ownRoot}::uuid)`
32579
+ : sql`${schema.sessions.siteAccessRootId} is null`,
32552
32580
  );
32553
32581
  }
32554
32582
  const archivedRoot = sql`exists (
@@ -33280,7 +33308,7 @@ async function readSessionListForSubject(
33280
33308
  throw new SessionListCursorError();
33281
33309
  }
33282
33310
  let nextOffset: number | null = null;
33283
- if (options.authorizationScope) {
33311
+ {
33284
33312
  // A host may revoke rows between pages. Scan the immutable snapshot
33285
33313
  // in bounded database-filtered batches until this page is full plus
33286
33314
  // one authorized lookahead; never hydrate a stale broad slice and
@@ -33301,7 +33329,10 @@ async function readSessionListForSubject(
33301
33329
  and(
33302
33330
  eq(schema.sessions.workspaceId, workspaceId),
33303
33331
  inArray(schema.sessions.id, candidates),
33304
- sessionAuthorizationScopeFilter(options.authorizationScope),
33332
+ sql`${schema.sessions.siteAccessRootId} is null`,
33333
+ ...(options.authorizationScope
33334
+ ? [sessionAuthorizationScopeFilter(options.authorizationScope)]
33335
+ : []),
33305
33336
  ),
33306
33337
  );
33307
33338
  const allowed = new Set(allowedRows.map((row) => row.id));
@@ -33318,12 +33349,6 @@ async function readSessionListForSubject(
33318
33349
  if (visible.length > limit) {
33319
33350
  nextOffset = visible[limit - 1]!.offsetAfter;
33320
33351
  }
33321
- } else {
33322
- pageIds = snapshot.ordinarySessionIds.slice(cursor.offset, cursor.offset + limit);
33323
- const candidateOffset = cursor.offset + pageIds.length;
33324
- if (candidateOffset < snapshot.ordinarySessionIds.length) {
33325
- nextOffset = candidateOffset;
33326
- }
33327
33352
  }
33328
33353
  if (nextOffset !== null) {
33329
33354
  nextCursor = encodeSessionListCursor({
@@ -34755,7 +34780,15 @@ async function selectSessionDiscoveryPageRows(
34755
34780
  }
34756
34781
  if (options.filters.originSiteId) {
34757
34782
  authorizationFilters.push(
34758
- sql`${schema.sessions.metadata}->'_opengeniSiteOrigin'->>'siteId' = ${options.filters.originSiteId}`,
34783
+ eq(schema.sessions.siteId, options.filters.originSiteId),
34784
+ sql`${schema.sessions.siteAccessRootId} is not null`,
34785
+ );
34786
+ } else {
34787
+ const ownRoot = options.authorizationScope?.agentAccessViewer?.callerRootSessionId;
34788
+ authorizationFilters.push(
34789
+ ownRoot
34790
+ ? sql`(${schema.sessions.siteAccessRootId} is null OR ${schema.sessions.rootSessionId} = ${ownRoot}::uuid)`
34791
+ : sql`${schema.sessions.siteAccessRootId} is null`,
34759
34792
  );
34760
34793
  }
34761
34794
  if (options.filters.activeOnly) {
@@ -36813,6 +36846,104 @@ export async function getSessionEvent(
36813
36846
  });
36814
36847
  }
36815
36848
 
36849
+ /**
36850
+ * Trusted Site lifecycle only: cancel an exact published root using native
36851
+ * controls. No request/agent endpoint exposes this adapter or its transaction.
36852
+ * The host has authorized the Site mutation; its actor stays unchanged.
36853
+ * Only the receipt and events written by this command leave the capability.
36854
+ */
36855
+ export async function cancelSiteRuntimeConversation(
36856
+ db: Database,
36857
+ input: {
36858
+ accountId: string;
36859
+ workspaceId: string;
36860
+ siteId: string;
36861
+ sessionId: string;
36862
+ operationKey: string;
36863
+ reason: string;
36864
+ },
36865
+ ) {
36866
+ // Own the complete transaction, including activity finalization. A nested
36867
+ // activity gate could otherwise outlive the exact-root visibility witness.
36868
+ if (isTransactionHandle(db)) throw new Error("Site cancellation requires its own transaction");
36869
+ return await runIdempotentPersistenceTransaction(
36870
+ {
36871
+ stage: "site_lifecycle.cancel",
36872
+ eventTypes: ["session.control.paused", "turn.cancelled"],
36873
+ maxAttempts: 3,
36874
+ },
36875
+ () =>
36876
+ withRlsContext(db, input, async (scopedDb) => {
36877
+ const [opened] = await rawRows<{ token: string | null }>(
36878
+ scopedDb,
36879
+ sql`
36880
+ select opengeni_private.begin_site_session_cancellation(
36881
+ ${input.accountId}::uuid,${input.workspaceId}::uuid,${input.siteId}::uuid,${input.sessionId}::uuid
36882
+ ) as token`,
36883
+ );
36884
+ if (!opened?.token) return null;
36885
+ const value = await withRestoredSessionActivityRlsContext(scopedDb, input, async (tx) => {
36886
+ const [context] = await rawRows<{ actor: string | null }>(
36887
+ tx,
36888
+ sql`
36889
+ select nullif(current_setting('opengeni.subject_id',true),'') as actor`,
36890
+ );
36891
+ // Serialize the active-state decision with native admission/control.
36892
+ // Replays still enter the native receipt path after terminal settlement.
36893
+ await lockWorkspaceInferenceControl(tx, input.workspaceId, "update");
36894
+ const [candidate] = await rawRows<{ needed: boolean }>(
36895
+ tx,
36896
+ sql`
36897
+ select exists(select 1 from sessions s
36898
+ where s.workspace_id=${input.workspaceId} and s.site_access_root_id=${input.sessionId}::uuid
36899
+ and s.status in ('queued','running','requires_action','recovering','waiting_capacity'))
36900
+ or exists(select 1 from session_command_receipts r
36901
+ where r.workspace_id=${input.workspaceId} and r.target_session_id=${input.sessionId}::uuid
36902
+ and r.operation_key=${input.operationKey} and r.actor_type='service'
36903
+ and r.actor_subject_id=${context?.actor ?? "site-lifecycle"}) as needed`,
36904
+ );
36905
+ if (!candidate?.needed) return null;
36906
+ const result = await mutateSessionControlInTransaction(tx, {
36907
+ accountId: input.accountId,
36908
+ workspaceId: input.workspaceId,
36909
+ sessionId: input.sessionId,
36910
+ actor: { type: "service", subjectId: context?.actor ?? "site-lifecycle" },
36911
+ operationKey: input.operationKey,
36912
+ action: "cancel",
36913
+ reason: input.reason,
36914
+ });
36915
+ const events: SessionEvent[] = [];
36916
+ for (const affected of result.affectedSessionEvents) {
36917
+ for (const eventId of affected.eventIds) {
36918
+ const event = await getSessionEvent(tx, input.workspaceId, eventId);
36919
+ if (!event || event.sessionId !== affected.sessionId)
36920
+ throw new Error("Site cancellation lost its committed event");
36921
+ events.push(event);
36922
+ }
36923
+ }
36924
+ const workspaceEvent = result.workspaceControlEventId
36925
+ ? await getWorkspaceControlEvent(tx, input.workspaceId, result.workspaceControlEventId)
36926
+ : null;
36927
+ return {
36928
+ receiptId: result.receipt.id,
36929
+ replay: result.replay,
36930
+ cancelledSessionCount: result.cancelledSessionCount,
36931
+ cancelledTurnCount: result.cancelledTurnCount,
36932
+ wakeCount: result.wakeCount,
36933
+ events,
36934
+ workspaceEvent,
36935
+ };
36936
+ });
36937
+ // Also covers activity-revision stamping. On error the outer transaction
36938
+ // rolls the witness back, without masking the original SQL diagnostic.
36939
+ await scopedDb.execute(
36940
+ sql`select opengeni_private.end_site_session_cancellation(${opened.token}::uuid)`,
36941
+ );
36942
+ return value;
36943
+ }),
36944
+ );
36945
+ }
36946
+
36816
36947
  /** Read one durable event under the exact human subject visibility projection. */
36817
36948
  export async function getSessionEventForSubject(
36818
36949
  db: Database,
@@ -0,0 +1,37 @@
1
+ import { sql } from "drizzle-orm";
2
+ import {
3
+ InsightsSiteUsageResponse,
4
+ type InsightsSiteUsageQuery,
5
+ } from "@opengeni/contracts/insights-usage";
6
+ import { withRlsContext, type Database } from "./database";
7
+ import { insightsUsageWindow } from "./insights-unified";
8
+
9
+ /** Uses the native accounting ledger and frozen model-call price snapshots. */
10
+ export async function readInsightsSiteUsage(
11
+ db: Database,
12
+ input: {
13
+ accountId: string;
14
+ workspaceId: string;
15
+ query: InsightsSiteUsageQuery;
16
+ viewerSubjectId: string | null;
17
+ now: Date;
18
+ },
19
+ ): Promise<InsightsSiteUsageResponse> {
20
+ const window = insightsUsageWindow(input.query.range, input.now);
21
+ return withRlsContext(db, input, async (tx) => {
22
+ const result = await tx.execute(sql`select payload from opengeni_private.insights_site_usage(
23
+ ${input.accountId}::uuid, ${input.workspaceId}::uuid,
24
+ ${window.since.toISOString()}::timestamptz, ${window.until.toISOString()}::timestamptz,
25
+ ${input.viewerSubjectId}::text)`);
26
+ const rows = (Array.isArray(result) ? result : (result as { rows: unknown[] }).rows) as {
27
+ payload: unknown;
28
+ }[];
29
+ return InsightsSiteUsageResponse.parse({
30
+ workspaceId: input.workspaceId,
31
+ range: input.query.range,
32
+ windowStart: window.since.toISOString(),
33
+ windowEnd: window.until.toISOString(),
34
+ rows: rows.map((row) => row.payload),
35
+ });
36
+ });
37
+ }
@@ -2517,6 +2517,21 @@ BEGIN
2517
2517
  REVOKE ALL ON FUNCTION opengeni_private.insights_raw_amount_inputs(uuid,uuid,timestamptz,timestamptz) FROM PUBLIC;
2518
2518
  REVOKE ALL ON FUNCTION opengeni_private.insights_usage_payer(text,text), opengeni_private.insights_usage_filter(jsonb,jsonb) FROM PUBLIC;
2519
2519
  END IF;
2520
+ IF to_regprocedure('opengeni_private.insights_site_usage(uuid,uuid,timestamptz,timestamptz,text)') IS NOT NULL THEN
2521
+ REVOKE ALL ON FUNCTION opengeni_private.insights_site_usage(uuid,uuid,timestamptz,timestamptz,text) FROM PUBLIC;
2522
+ END IF;
2523
+ IF to_regclass('opengeni_private.site_session_cancellation_capabilities') IS NOT NULL THEN
2524
+ EXECUTE format('REVOKE ALL ON TABLE opengeni_private.site_session_cancellation_capabilities FROM %I', ${literal(role)});
2525
+ EXECUTE format('REVOKE ALL (%s) ON TABLE opengeni_private.site_session_cancellation_capabilities FROM %I',
2526
+ (SELECT string_agg(quote_ident(attname),',' ORDER BY attnum) FROM pg_attribute
2527
+ WHERE attrelid='opengeni_private.site_session_cancellation_capabilities'::regclass AND attnum>0 AND NOT attisdropped), ${literal(role)});
2528
+ REVOKE ALL ON TABLE opengeni_private.site_session_cancellation_capabilities FROM PUBLIC;
2529
+ EXECUTE format('REVOKE ALL (%s) ON TABLE opengeni_private.site_session_cancellation_capabilities FROM PUBLIC',
2530
+ (SELECT string_agg(quote_ident(attname),',' ORDER BY attnum) FROM pg_attribute
2531
+ WHERE attrelid='opengeni_private.site_session_cancellation_capabilities'::regclass AND attnum>0 AND NOT attisdropped));
2532
+ REVOKE ALL ON FUNCTION opengeni_private.begin_site_session_cancellation(uuid,uuid,uuid,uuid),
2533
+ opengeni_private.end_site_session_cancellation(uuid) FROM PUBLIC;
2534
+ END IF;
2520
2535
  IF to_regclass('opengeni_private.usage_allowance_capabilities') IS NOT NULL THEN
2521
2536
  EXECUTE format('REVOKE ALL ON TABLE opengeni_private.usage_allowance_capabilities FROM %I', ${literal(role)});
2522
2537
  EXECUTE format('REVOKE ALL (backend_pid,transaction_id,data_schema,account_id,workspace_id) ON TABLE opengeni_private.usage_allowance_capabilities FROM %I', ${literal(role)});