@opengeni/db 1.0.1 → 3.2.0-canary.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.
- package/dist/attached-browser-devices.d.ts +7 -0
- package/dist/automations.d.ts +154 -0
- package/dist/canonical-human-identities.js +3 -3
- package/dist/capability-integrations.d.ts +13 -0
- package/dist/child-lifecycle-notices.d.ts +68 -0
- package/dist/{chunk-4SR4HKKA.js → chunk-2EH4WRHL.js} +19 -11
- package/dist/chunk-2EH4WRHL.js.map +1 -0
- package/dist/{chunk-CUPM3FGB.js → chunk-2PRS4J5Q.js} +3 -3
- package/dist/chunk-6HMM2R5T.js +390 -0
- package/dist/chunk-6HMM2R5T.js.map +1 -0
- package/dist/{chunk-ZWREYEWA.js → chunk-AZHW7R5V.js} +56 -11
- package/dist/chunk-AZHW7R5V.js.map +1 -0
- package/dist/{chunk-LTG3S6ZY.js → chunk-E6CLMKEE.js} +2 -2
- package/dist/{chunk-6ZYKCRFO.js → chunk-KRLPEMLH.js} +7591 -4417
- package/dist/chunk-KRLPEMLH.js.map +1 -0
- package/dist/chunk-NAB6FKFN.js +516 -0
- package/dist/chunk-NAB6FKFN.js.map +1 -0
- package/dist/{chunk-SK7YYHBI.js → chunk-SIBYBA2D.js} +46 -1
- package/dist/chunk-SIBYBA2D.js.map +1 -0
- package/dist/chunk-UZNXG44K.js +4508 -0
- package/dist/chunk-UZNXG44K.js.map +1 -0
- package/dist/codemode-operations.d.ts +1 -6
- package/dist/company-brain-context-selection-schema.d.ts +705 -0
- package/dist/company-brain-context-selection.d.ts +94 -0
- package/dist/company-brain-governed-writes.d.ts +24 -0
- package/dist/company-brain.d.ts +55 -0
- package/dist/company-profile-agent-admin.d.ts +15 -0
- package/dist/company-profile-schema.d.ts +619 -2
- package/dist/company-profile.d.ts +9 -4
- package/dist/connection-authority.d.ts +95 -0
- package/dist/connection-scopes.d.ts +8 -0
- package/dist/connection-tenancy-backfill.d.ts +85 -0
- package/dist/connection-token-resolver.d.ts +30 -1
- package/dist/database.d.ts +23 -4
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.d.ts +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/governed-learning-activation-schema.d.ts +984 -0
- package/dist/governed-learning-activation.d.ts +98 -0
- package/dist/governed-learning-evaluator-schema.d.ts +633 -0
- package/dist/governed-learning-evaluator.d.ts +28 -0
- package/dist/index.d.ts +1606 -81
- package/dist/index.js +21898 -8147
- package/dist/index.js.map +1 -1
- package/dist/insights.d.ts +18 -0
- package/dist/integration-bindings.d.ts +1 -0
- package/dist/integration-facets.d.ts +30 -0
- package/dist/interaction-placement-loss.d.ts +22 -0
- package/dist/managed-human-provisioning.d.ts +25 -0
- package/dist/memory-domain.d.ts +3 -3
- package/dist/migrate.d.ts +11 -1
- package/dist/migrate.js +1 -1
- package/dist/new-session-drafts.d.ts +43 -5
- package/dist/organization-membership-backfill.d.ts +174 -0
- package/dist/organization-membership-lifecycle.d.ts +162 -0
- package/dist/pr-review.d.ts +107 -0
- package/dist/preference-registry-schema.d.ts +2 -0
- package/dist/preference-registry.d.ts +2 -0
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +29 -8
- package/dist/schema.d.ts +24914 -13703
- package/dist/schema.js +99 -1
- package/dist/scoped-knowledge-schema.d.ts +52 -1
- package/dist/session-background-commands.d.ts +125 -0
- package/dist/session-background-commands.js +34 -0
- package/dist/session-background-commands.js.map +1 -0
- package/dist/session-control.d.ts +156 -3
- package/dist/session-goal-pacing.d.ts +68 -0
- package/dist/session-queue-commands.d.ts +43 -4
- package/dist/session-realtime-ledger.d.ts +2 -0
- package/dist/session-tenancy.d.ts +71 -1
- package/dist/session-tenancy.js +25 -3
- package/dist/session-tool-call-settlement.d.ts +9 -4
- package/dist/slack-routing-personal-workspace.d.ts +23 -0
- package/dist/slack-routing.d.ts +268 -0
- package/dist/slack-task-policy-schema.d.ts +34 -0
- package/dist/slack-task-policy.d.ts +8 -0
- package/dist/slack-user-link-access.d.ts +28 -0
- package/dist/task-notes-schema.d.ts +594 -0
- package/dist/task-notes.d.ts +39 -0
- package/dist/turn-initiator.d.ts +2 -0
- package/dist/user-resource-authority.d.ts +109 -0
- package/dist/video-generation.js +3 -3
- package/dist/workspace-authority.d.ts +81 -0
- package/dist/workspace-instruction-policies-schema.d.ts +279 -0
- package/dist/workspace-instruction-policies.d.ts +62 -0
- package/dist/workspace-learning-policy.d.ts +1 -0
- package/dist/workspace-membership-permissions.d.ts +2 -0
- package/drizzle/0240_enrollment_connection_authority.sql +39 -0
- package/drizzle/0241_atomic_personal_resource_delegation.sql +1000 -0
- package/drizzle/0241_enrollment_agent_runtime.sql +70 -0
- package/drizzle/0244_slack_app_home_refresh_queue.sql +120 -0
- package/drizzle/0245_model_context_contribution_facts.sql +88 -0
- package/drizzle/0246_integration_personal_instance_authority.sql +70 -0
- package/drizzle/0247_terraform_stacks_provenance_repair.sql +428 -0
- package/drizzle/0248_terraform_stacks_component_resolution_fence.sql +83 -0
- package/drizzle/0249_personal_resource_delegation_authority_correction.sql +662 -0
- package/drizzle/0250_direct_retained_process_owner_liveness.sql +199 -0
- package/drizzle/0251_connected_machine_operation_policy.sql +45 -0
- package/drizzle/0252_scheduled_personal_resource_delegation.sql +1867 -0
- package/drizzle/0253_common_user_resource_authority_lifecycle.sql +1555 -0
- package/drizzle/0254_scoped_variable_set_authority.sql +1255 -0
- package/drizzle/0255_company_brain_governed_write_proposals.sql +524 -0
- package/drizzle/0256_connection_authority_delegation.sql +703 -0
- package/drizzle/0257_goal_revision_decisions_and_root_constraints.sql +436 -0
- package/drizzle/0258_three_scope_document_knowledge_authority.sql +1273 -0
- package/drizzle/0259_company_brain_context_selection_receipts.sql +869 -0
- package/drizzle/0260_task_note_knowledge_promotion.sql +820 -0
- package/drizzle/0261_preference_knowledge_proposal_actor_binding.sql +471 -0
- package/drizzle/0262_scoped_connected_machines_and_rigs.sql +1700 -0
- package/drizzle/0263_organization_membership_lifecycle.sql +3284 -0
- package/drizzle/0264_connection_authority_runtime_activation.sql +1084 -0
- package/drizzle/0266_company_brain_context_receipt_inspection.sql +156 -0
- package/drizzle/0268_governed_learning_decision_receipts.sql +825 -0
- package/drizzle/0269_governed_learning_activation_controller.sql +1874 -0
- package/drizzle/0270_governed_learning_history_inspection.sql +170 -0
- package/drizzle/0271_company_brain_retrieval_only_default.sql +524 -0
- package/drizzle/0272_human_confirmed_learning_activation.sql +591 -0
- package/drizzle/0273_scheduled_variable_set_materialization.sql +268 -0
- package/drizzle/0274_human_confirmed_knowledge_review.sql +310 -0
- package/drizzle/0275_scheduled_connection_authority.sql +5527 -0
- package/drizzle/0276_onboarding_proposal_initiating_human_guc.sql +198 -0
- package/drizzle/0277_workspace_writer_authority_attribution.sql +393 -0
- package/drizzle/0278_workspace_membership_removal_fencing.sql +1174 -0
- package/drizzle/0279_workspace_connection_use_lane.sql +343 -0
- package/drizzle/0280_connection_and_variable_set_audit_attribution.sql +773 -0
- package/drizzle/0281_viewer_holder_authority_claims.sql +52 -0
- package/drizzle/0282_variable_set_session_attach_attribution.sql +178 -0
- package/drizzle/0283_editable_spreadsheet_authored_state.sql +82 -0
- package/drizzle/0284_truthful_human_confirmed_review_reason.sql +930 -0
- package/drizzle/0285_organization_tenancy_inventory.sql +329 -0
- package/drizzle/0286_widen_task_note_expiry_ceiling.sql +391 -0
- package/drizzle/0287_open_suffix_pending_tool_calls.sql +18 -0
- package/drizzle/0288_attached_browser_reenrollment.sql +64 -0
- package/drizzle/0289_session_composer_policy_authority.sql +597 -0
- package/drizzle/0290_organization_membership_backfill.sql +268 -0
- package/drizzle/0291_resource_authority_classification_assertion.sql +371 -0
- package/drizzle/0292_truthful_tenancy_inventory_counters.sql +291 -0
- package/drizzle/0293_confirm_time_rule_rebaseline.sql +615 -0
- package/drizzle/0294_preference_activation_authority.sql +237 -0
- package/drizzle/0295_retire_legacy_standing_memory_mode.sql +476 -0
- package/drizzle/0296_force_rls_backfill_noop_repair.sql +215 -0
- package/drizzle/0297_session_ownership_classification_and_backfill.sql +752 -0
- package/drizzle/0298_organization_tenancy_parity.sql +609 -0
- package/drizzle/0299_organization_membership_lock_order.sql +291 -0
- package/drizzle/0300_tenancy_backfill_ledger.sql +459 -0
- package/drizzle/0301_session_snapshot_and_pin_visibility.sql +181 -0
- package/drizzle/0302_personal_workspace_session_ownership.sql +303 -0
- package/drizzle/0303_session_tenancy_product_activation.sql +919 -0
- package/drizzle/0304_personal_workspace_private_session_reads.sql +97 -0
- package/drizzle/0305_personal_resource_grant_management.sql +584 -0
- package/drizzle/0306_atomic_personal_resource_attachments.sql +1374 -0
- package/drizzle/0307_session_attention_state.sql +51 -0
- package/drizzle/0308_session_archives.sql +35 -0
- package/drizzle/0309_channel_project_pins.sql +6 -0
- package/drizzle/0310_channel_project_order.sql +27 -0
- package/drizzle/0311_company_scope_and_private_session_create.sql +656 -0
- package/drizzle/0312_quiescent_session_tree_deletion.sql +39 -0
- package/drizzle/0313_private_child_session_authority.sql +365 -0
- package/drizzle/0314_unregistered_organization_invitations.sql +606 -0
- package/drizzle/0315_personal_github_repository_selection.sql +624 -0
- package/drizzle/0316_human_confirmed_activation_resumed_generation.sql +869 -0
- package/drizzle/0317_session_goal_continuation_hold.sql +64 -0
- package/drizzle/0318_session_turn_startup_milestones.sql +115 -0
- package/drizzle/0319_event_triggered_automations.sql +254 -0
- package/drizzle/0320_pr_review_pack.sql +113 -0
- package/drizzle/0321_slack_bot_environment_display_name.sql +15 -0
- package/drizzle/0322_session_turns_unclaimed_prompt_trigger_index.sql +5 -0
- package/drizzle/0323_organization_private_session_enablement.sql +489 -0
- package/drizzle/0324_human_confirmed_company_profile_agent_admin.sql +856 -0
- package/drizzle/0325_child_lifecycle_notices.sql +44 -0
- package/drizzle/0326_interaction_operation_error_codes.sql +62 -0
- package/drizzle/0327_slack_first_task_hint.sql +29 -0
- package/drizzle/0328_session_background_commands.sql +590 -0
- package/drizzle/0329_slack_orchestration_delivery_events.sql +91 -0
- package/drizzle/0330_api_key_descriptions.sql +20 -0
- package/drizzle/0331_managed_organization_creation.sql +163 -0
- package/drizzle/0332_organization_shared_workspace_control_plane.sql +539 -0
- package/drizzle/0333_session_turn_prompt_routing.sql +45 -0
- package/drizzle/0334_connected_machine_workspace_root.sql +99 -0
- package/drizzle/0335_slack_workspace_routing.sql +460 -0
- package/drizzle/0336_atomic_session_fork_visibility.sql +667 -0
- package/drizzle/0337_slack_routed_action_handles.sql +189 -0
- package/drizzle/0338_atomic_connected_machine_attachments.sql +410 -0
- package/drizzle/0339_document_authority_reclassification.sql +1153 -0
- package/drizzle/0340_tenancy_backfill_activation_evidence.sql +1347 -0
- package/drizzle/0341_slack_routing_probe_organization_fence.sql +109 -0
- package/drizzle/0342_slack_route_prompt_single_pending.sql +32 -0
- package/drizzle/0343_personal_document_force_rls_lock_repair.sql +146 -0
- package/drizzle/0344_private_session_visibility_transition_gate.sql +85 -0
- package/drizzle/0345_tenant_scoped_session_tenancy_fence.sql +2839 -0
- package/drizzle/0346_document_migration_audit_surface.sql +454 -0
- package/drizzle/0347_connection_authority_convergence_evidence.sql +396 -0
- package/package.json +10 -6
- package/src/attached-browser-devices.ts +233 -5
- package/src/automations.ts +911 -0
- package/src/browser-sessions.ts +10 -0
- package/src/capability-integrations.ts +175 -1
- package/src/child-lifecycle-notices.ts +241 -0
- package/src/codemode-operations.ts +1 -35
- package/src/company-brain-context-selection-schema.ts +100 -0
- package/src/company-brain-context-selection.ts +359 -0
- package/src/company-brain-governed-writes.ts +1170 -0
- package/src/company-brain.ts +309 -0
- package/src/company-profile-agent-admin.ts +225 -0
- package/src/company-profile-schema.ts +121 -1
- package/src/company-profile.ts +21 -0
- package/src/computer-sessions.ts +10 -1
- package/src/connection-authority.ts +290 -0
- package/src/connection-scopes.ts +20 -0
- package/src/connection-tenancy-backfill.ts +167 -0
- package/src/connection-token-resolver.ts +263 -31
- package/src/database.ts +87 -2
- package/src/editable-artifacts-schema.ts +10 -5
- package/src/editable-artifacts.ts +21 -11
- package/src/governed-learning-activation-schema.ts +121 -0
- package/src/governed-learning-activation.ts +588 -0
- package/src/governed-learning-evaluator-schema.ts +72 -0
- package/src/governed-learning-evaluator.ts +217 -0
- package/src/index.ts +11676 -1550
- package/src/insights.ts +107 -0
- package/src/integration-bindings.ts +88 -6
- package/src/integration-facets.ts +287 -84
- package/src/interaction-placement-loss.ts +234 -0
- package/src/managed-human-provisioning.ts +116 -0
- package/src/memory-domain.ts +27 -32
- package/src/migrate.ts +77 -1
- package/src/new-session-drafts.ts +229 -35
- package/src/organization-membership-backfill.ts +735 -0
- package/src/organization-membership-lifecycle.ts +995 -0
- package/src/pr-review.ts +890 -0
- package/src/preference-registry.ts +31 -1
- package/src/provision-roles.ts +905 -6
- package/src/runtime-posture-cli.ts +2 -0
- package/src/runtime-posture.ts +1317 -72
- package/src/schema.ts +3316 -428
- package/src/scoped-knowledge-schema.ts +16 -8
- package/src/scoped-knowledge.ts +1 -1
- package/src/session-background-commands.ts +759 -0
- package/src/session-control.ts +640 -74
- package/src/session-goal-pacing.ts +126 -0
- package/src/session-queue-commands.ts +441 -85
- package/src/session-realtime-context.ts +4 -6
- package/src/session-realtime-ledger.ts +28 -14
- package/src/session-realtime-mirror.ts +1 -1
- package/src/session-tenancy.ts +410 -49
- package/src/session-tool-call-settlement.ts +38 -29
- package/src/slack-routing-personal-workspace.ts +67 -0
- package/src/slack-routing.ts +867 -0
- package/src/slack-task-policy-schema.ts +5 -0
- package/src/slack-task-policy.ts +18 -0
- package/src/slack-user-link-access.ts +138 -11
- package/src/task-notes-schema.ts +74 -0
- package/src/task-notes.ts +124 -2
- package/src/turn-initiator.ts +8 -0
- package/src/user-resource-authority.ts +377 -0
- package/src/workspace-authority.ts +160 -0
- package/src/workspace-instruction-policies-schema.ts +53 -1
- package/src/workspace-instruction-policies.ts +528 -77
- package/src/workspace-learning-policy.ts +21 -5
- package/src/workspace-membership-permissions.ts +9 -0
- package/dist/chunk-4SR4HKKA.js.map +0 -1
- package/dist/chunk-6ZYKCRFO.js.map +0 -1
- package/dist/chunk-NXAFLCQX.js +0 -2468
- package/dist/chunk-NXAFLCQX.js.map +0 -1
- package/dist/chunk-SK7YYHBI.js.map +0 -1
- package/dist/chunk-WD5ROSLL.js +0 -117
- package/dist/chunk-WD5ROSLL.js.map +0 -1
- package/dist/chunk-ZWREYEWA.js.map +0 -1
- /package/dist/{chunk-CUPM3FGB.js.map → chunk-2PRS4J5Q.js.map} +0 -0
- /package/dist/{chunk-LTG3S6ZY.js.map → chunk-E6CLMKEE.js.map} +0 -0
|
@@ -0,0 +1,919 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Installs the forward-only, per-organization activation receipt and the
|
|
3
|
+
-- hardened database contract for the still-inert session-tenancy adapters.
|
|
4
|
+
-- Ordinary migration is intentionally safe with activation disabled: no
|
|
5
|
+
-- organization is activated by this file. The separately drained operator
|
|
6
|
+
-- command calls activate_session_tenancy_product only after inventory/parity
|
|
7
|
+
-- evidence and the canonical deployment opt-in have been verified.
|
|
8
|
+
|
|
9
|
+
SET LOCAL lock_timeout = '5s';
|
|
10
|
+
SET LOCAL statement_timeout = '10min';
|
|
11
|
+
|
|
12
|
+
CREATE TABLE session_tenancy_activations (
|
|
13
|
+
account_id uuid PRIMARY KEY REFERENCES managed_accounts(id) ON DELETE RESTRICT,
|
|
14
|
+
activation_version integer NOT NULL,
|
|
15
|
+
inventory_digest text NOT NULL,
|
|
16
|
+
parity_digest text NOT NULL,
|
|
17
|
+
activated_by text NOT NULL,
|
|
18
|
+
activated_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
|
19
|
+
CONSTRAINT session_tenancy_activations_version_check CHECK (activation_version = 1),
|
|
20
|
+
CONSTRAINT session_tenancy_activations_digests_check CHECK (
|
|
21
|
+
inventory_digest ~ '^[0-9a-f]{64}$' AND parity_digest ~ '^[0-9a-f]{64}$'
|
|
22
|
+
),
|
|
23
|
+
CONSTRAINT session_tenancy_activations_actor_check CHECK (
|
|
24
|
+
octet_length(activated_by) BETWEEN 1 AND 256
|
|
25
|
+
)
|
|
26
|
+
);
|
|
27
|
+
ALTER TABLE session_tenancy_activations ENABLE ROW LEVEL SECURITY;
|
|
28
|
+
ALTER TABLE session_tenancy_activations FORCE ROW LEVEL SECURITY;
|
|
29
|
+
CREATE POLICY session_tenancy_activations_organization_isolation
|
|
30
|
+
ON session_tenancy_activations
|
|
31
|
+
FOR SELECT USING (
|
|
32
|
+
account_id = nullif(current_setting('opengeni.account_id', true), '')::uuid
|
|
33
|
+
);
|
|
34
|
+
REVOKE ALL ON TABLE session_tenancy_activations FROM PUBLIC;
|
|
35
|
+
|
|
36
|
+
CREATE FUNCTION session_tenancy_product_activated(
|
|
37
|
+
p_account_id uuid,
|
|
38
|
+
p_activation_version integer
|
|
39
|
+
) RETURNS boolean
|
|
40
|
+
LANGUAGE sql STABLE SECURITY DEFINER
|
|
41
|
+
SET search_path FROM CURRENT
|
|
42
|
+
AS $$
|
|
43
|
+
SELECT p_account_id IS NOT DISTINCT FROM
|
|
44
|
+
nullif(current_setting('opengeni.account_id', true), '')::uuid
|
|
45
|
+
AND EXISTS (
|
|
46
|
+
SELECT 1 FROM session_tenancy_activations activation
|
|
47
|
+
WHERE activation.account_id = p_account_id
|
|
48
|
+
AND activation.activation_version = p_activation_version
|
|
49
|
+
)
|
|
50
|
+
$$;
|
|
51
|
+
REVOKE ALL ON FUNCTION session_tenancy_product_activated(uuid, integer) FROM PUBLIC;
|
|
52
|
+
|
|
53
|
+
-- Startup/readiness cannot select tenant rows without a tenant GUC. This
|
|
54
|
+
-- deliberately value-free predicate lets the exact new image enforce the
|
|
55
|
+
-- forward-only deployment switch after the first organization is activated.
|
|
56
|
+
CREATE FUNCTION session_tenancy_any_product_activation()
|
|
57
|
+
RETURNS boolean
|
|
58
|
+
LANGUAGE sql STABLE SECURITY DEFINER
|
|
59
|
+
SET search_path FROM CURRENT
|
|
60
|
+
AS $$
|
|
61
|
+
SELECT EXISTS (SELECT 1 FROM session_tenancy_activations)
|
|
62
|
+
$$;
|
|
63
|
+
REVOKE ALL ON FUNCTION session_tenancy_any_product_activation() FROM PUBLIC;
|
|
64
|
+
|
|
65
|
+
-- Owner-only activation seam. The caller supplies the exact application login
|
|
66
|
+
-- inventory; it is checked both before and after taking the write-blocking
|
|
67
|
+
-- locks so a late old process cannot slip through the cutover.
|
|
68
|
+
CREATE FUNCTION activate_session_tenancy_product(
|
|
69
|
+
p_account_id uuid,
|
|
70
|
+
p_inventory_digest text,
|
|
71
|
+
p_parity_digest text,
|
|
72
|
+
p_activated_by text,
|
|
73
|
+
p_application_roles text[]
|
|
74
|
+
) RETURNS TABLE (
|
|
75
|
+
account_id uuid,
|
|
76
|
+
activation_version integer,
|
|
77
|
+
activated_at timestamptz,
|
|
78
|
+
replay boolean
|
|
79
|
+
)
|
|
80
|
+
LANGUAGE plpgsql SECURITY DEFINER
|
|
81
|
+
SET search_path FROM CURRENT
|
|
82
|
+
AS $$
|
|
83
|
+
DECLARE
|
|
84
|
+
existing session_tenancy_activations%ROWTYPE;
|
|
85
|
+
inserted session_tenancy_activations%ROWTYPE;
|
|
86
|
+
BEGIN
|
|
87
|
+
IF p_account_id IS NULL
|
|
88
|
+
OR p_inventory_digest !~ '^[0-9a-f]{64}$'
|
|
89
|
+
OR p_parity_digest !~ '^[0-9a-f]{64}$'
|
|
90
|
+
OR octet_length(btrim(p_activated_by)) NOT BETWEEN 1 AND 256
|
|
91
|
+
OR p_application_roles IS NULL
|
|
92
|
+
OR cardinality(p_application_roles) NOT BETWEEN 1 AND 16
|
|
93
|
+
OR EXISTS (
|
|
94
|
+
SELECT 1 FROM unnest(p_application_roles) role_name
|
|
95
|
+
WHERE role_name IS NULL OR role_name <> btrim(role_name)
|
|
96
|
+
OR octet_length(role_name) NOT BETWEEN 1 AND 63
|
|
97
|
+
)
|
|
98
|
+
OR cardinality(ARRAY(SELECT DISTINCT role_name FROM unnest(p_application_roles) role_name))
|
|
99
|
+
<> cardinality(p_application_roles)
|
|
100
|
+
THEN
|
|
101
|
+
RAISE EXCEPTION 'session tenancy activation request is invalid'
|
|
102
|
+
USING ERRCODE = '22023';
|
|
103
|
+
END IF;
|
|
104
|
+
|
|
105
|
+
IF EXISTS (
|
|
106
|
+
SELECT 1 FROM pg_stat_activity activity
|
|
107
|
+
WHERE activity.datname = current_database()
|
|
108
|
+
AND activity.usename = ANY(p_application_roles)
|
|
109
|
+
AND activity.pid <> pg_backend_pid()
|
|
110
|
+
) THEN
|
|
111
|
+
RAISE EXCEPTION 'session tenancy activation requires every application role session to be stopped'
|
|
112
|
+
USING ERRCODE = '55000';
|
|
113
|
+
END IF;
|
|
114
|
+
|
|
115
|
+
LOCK TABLE sessions, session_turns, session_turn_attempts,
|
|
116
|
+
session_attempt_interruptions, session_system_updates,
|
|
117
|
+
session_human_input_requests, session_pending_tool_calls, agent_run_states,
|
|
118
|
+
session_goals, codex_capacity_waiters, xai_capacity_waiters,
|
|
119
|
+
session_realtime_modes, session_realtime_connections, scheduled_tasks,
|
|
120
|
+
sandbox_workspace_mutation_admissions, sandbox_retained_processes,
|
|
121
|
+
sandbox_lease_holders, organization_user_resource_grants,
|
|
122
|
+
session_tenancy_activations IN ACCESS EXCLUSIVE MODE;
|
|
123
|
+
|
|
124
|
+
IF EXISTS (
|
|
125
|
+
SELECT 1 FROM pg_stat_activity activity
|
|
126
|
+
WHERE activity.datname = current_database()
|
|
127
|
+
AND activity.usename = ANY(p_application_roles)
|
|
128
|
+
AND activity.pid <> pg_backend_pid()
|
|
129
|
+
) THEN
|
|
130
|
+
RAISE EXCEPTION 'session tenancy activation requires every application role session to be stopped'
|
|
131
|
+
USING ERRCODE = '55000';
|
|
132
|
+
END IF;
|
|
133
|
+
|
|
134
|
+
-- Re-read only after the global activation lock. Concurrent same-evidence
|
|
135
|
+
-- activations converge on one receipt; changed evidence remains a conflict.
|
|
136
|
+
SELECT * INTO existing FROM session_tenancy_activations activation
|
|
137
|
+
WHERE activation.account_id = p_account_id;
|
|
138
|
+
IF FOUND THEN
|
|
139
|
+
IF existing.inventory_digest <> p_inventory_digest
|
|
140
|
+
OR existing.parity_digest <> p_parity_digest
|
|
141
|
+
THEN
|
|
142
|
+
RAISE EXCEPTION 'session tenancy activation evidence conflicts with the durable receipt'
|
|
143
|
+
USING ERRCODE = '23505';
|
|
144
|
+
END IF;
|
|
145
|
+
account_id := existing.account_id;
|
|
146
|
+
activation_version := existing.activation_version;
|
|
147
|
+
activated_at := existing.activated_at;
|
|
148
|
+
replay := true;
|
|
149
|
+
RETURN NEXT;
|
|
150
|
+
RETURN;
|
|
151
|
+
END IF;
|
|
152
|
+
|
|
153
|
+
INSERT INTO session_tenancy_activations (
|
|
154
|
+
account_id, activation_version, inventory_digest, parity_digest, activated_by
|
|
155
|
+
) VALUES (
|
|
156
|
+
p_account_id, 1, p_inventory_digest, p_parity_digest, btrim(p_activated_by)
|
|
157
|
+
) RETURNING * INTO inserted;
|
|
158
|
+
|
|
159
|
+
account_id := inserted.account_id;
|
|
160
|
+
activation_version := inserted.activation_version;
|
|
161
|
+
activated_at := inserted.activated_at;
|
|
162
|
+
replay := false;
|
|
163
|
+
RETURN NEXT;
|
|
164
|
+
END
|
|
165
|
+
$$;
|
|
166
|
+
REVOKE ALL ON FUNCTION
|
|
167
|
+
activate_session_tenancy_product(uuid, text, text, text, text[]) FROM PUBLIC;
|
|
168
|
+
|
|
169
|
+
-- Nested implementation helper only. The outer lifecycle functions call this
|
|
170
|
+
-- after their complete tenant, membership, owner, activation, and operation
|
|
171
|
+
-- checks. It is deliberately ungranted to the application role: by itself it
|
|
172
|
+
-- has no authority boundary and would otherwise expose a cross-tenant activity
|
|
173
|
+
-- oracle while taking global table locks. All mutation-bearing tables are
|
|
174
|
+
-- write-locked for the short transition/fork transaction so the zero
|
|
175
|
+
-- observation cannot race a new arrival. The stable blocker code is carried in
|
|
176
|
+
-- DETAIL; the adapter maps SQLSTATE 55P03 to a typed 409-ready error.
|
|
177
|
+
CREATE FUNCTION assert_session_tenancy_quiescent(
|
|
178
|
+
p_account_id uuid,
|
|
179
|
+
p_workspace_id uuid,
|
|
180
|
+
p_session_id uuid,
|
|
181
|
+
p_require_singleton_group boolean
|
|
182
|
+
) RETURNS void
|
|
183
|
+
LANGUAGE plpgsql SECURITY DEFINER
|
|
184
|
+
SET search_path FROM CURRENT
|
|
185
|
+
AS $$
|
|
186
|
+
DECLARE
|
|
187
|
+
session_group_id uuid;
|
|
188
|
+
previous_subject text := current_setting('opengeni.subject_id', true);
|
|
189
|
+
blocker text;
|
|
190
|
+
BEGIN
|
|
191
|
+
LOCK TABLE session_turns, session_turn_attempts, session_attempt_interruptions,
|
|
192
|
+
session_system_updates, session_human_input_requests,
|
|
193
|
+
session_pending_tool_calls, agent_run_states, session_goals,
|
|
194
|
+
codex_capacity_waiters, xai_capacity_waiters, session_realtime_modes,
|
|
195
|
+
session_realtime_connections, scheduled_tasks,
|
|
196
|
+
sandbox_workspace_mutation_admissions, sandbox_retained_processes,
|
|
197
|
+
sandbox_lease_holders IN SHARE ROW EXCLUSIVE MODE;
|
|
198
|
+
|
|
199
|
+
SELECT sandbox_group_id INTO session_group_id FROM sessions
|
|
200
|
+
WHERE account_id = p_account_id AND workspace_id = p_workspace_id
|
|
201
|
+
AND id = p_session_id;
|
|
202
|
+
|
|
203
|
+
IF EXISTS (SELECT 1 FROM session_turns WHERE workspace_id = p_workspace_id
|
|
204
|
+
AND session_id = p_session_id
|
|
205
|
+
AND status IN ('queued','running','requires_action','recovering','waiting_capacity'))
|
|
206
|
+
THEN blocker := 'nonterminal_turn';
|
|
207
|
+
ELSIF EXISTS (SELECT 1 FROM session_turn_attempts WHERE workspace_id = p_workspace_id
|
|
208
|
+
AND session_id = p_session_id AND state <> 'closed')
|
|
209
|
+
THEN blocker := 'nonterminal_attempt';
|
|
210
|
+
ELSIF EXISTS (SELECT 1 FROM session_attempt_interruptions WHERE workspace_id = p_workspace_id
|
|
211
|
+
AND session_id = p_session_id AND state IN ('pending','delivered','acknowledged'))
|
|
212
|
+
THEN blocker := 'unsettled_interruption';
|
|
213
|
+
ELSIF EXISTS (SELECT 1 FROM session_system_updates WHERE workspace_id = p_workspace_id
|
|
214
|
+
AND session_id = p_session_id AND state = 'pending')
|
|
215
|
+
THEN blocker := 'pending_system_update';
|
|
216
|
+
ELSIF EXISTS (SELECT 1 FROM session_human_input_requests WHERE workspace_id = p_workspace_id
|
|
217
|
+
AND session_id = p_session_id AND status = 'pending')
|
|
218
|
+
THEN blocker := 'pending_human_input';
|
|
219
|
+
ELSIF EXISTS (SELECT 1 FROM session_pending_tool_calls WHERE workspace_id = p_workspace_id
|
|
220
|
+
AND session_id = p_session_id)
|
|
221
|
+
THEN blocker := 'pending_tool_receipt';
|
|
222
|
+
ELSIF EXISTS (SELECT 1 FROM agent_run_states WHERE workspace_id = p_workspace_id
|
|
223
|
+
AND session_id = p_session_id)
|
|
224
|
+
THEN blocker := 'run_state';
|
|
225
|
+
ELSIF EXISTS (SELECT 1 FROM session_goals WHERE workspace_id = p_workspace_id
|
|
226
|
+
AND session_id = p_session_id AND status = 'active')
|
|
227
|
+
THEN blocker := 'active_goal';
|
|
228
|
+
ELSIF EXISTS (SELECT 1 FROM codex_capacity_waiters WHERE workspace_id = p_workspace_id
|
|
229
|
+
AND session_id = p_session_id AND status = 'waiting')
|
|
230
|
+
OR EXISTS (SELECT 1 FROM xai_capacity_waiters WHERE workspace_id = p_workspace_id
|
|
231
|
+
AND session_id = p_session_id AND status = 'waiting')
|
|
232
|
+
THEN blocker := 'capacity_waiter';
|
|
233
|
+
ELSIF EXISTS (SELECT 1 FROM session_realtime_modes WHERE workspace_id = p_workspace_id
|
|
234
|
+
AND session_id = p_session_id AND state = 'active')
|
|
235
|
+
OR EXISTS (SELECT 1 FROM session_realtime_connections WHERE workspace_id = p_workspace_id
|
|
236
|
+
AND session_id = p_session_id
|
|
237
|
+
AND state IN ('negotiating','ready','active'))
|
|
238
|
+
THEN blocker := 'active_realtime';
|
|
239
|
+
ELSIF EXISTS (SELECT 1 FROM scheduled_tasks WHERE workspace_id = p_workspace_id
|
|
240
|
+
AND reusable_session_id = p_session_id AND status = 'active')
|
|
241
|
+
THEN blocker := 'active_scheduled_task';
|
|
242
|
+
ELSIF EXISTS (SELECT 1 FROM sandbox_workspace_mutation_admissions
|
|
243
|
+
WHERE workspace_id = p_workspace_id AND session_id = p_session_id
|
|
244
|
+
AND settled_at IS NULL)
|
|
245
|
+
THEN blocker := 'workspace_mutation_admission';
|
|
246
|
+
ELSIF EXISTS (SELECT 1 FROM sandbox_retained_processes
|
|
247
|
+
WHERE workspace_id = p_workspace_id AND session_id = p_session_id
|
|
248
|
+
AND state = 'active')
|
|
249
|
+
THEN blocker := 'retained_process';
|
|
250
|
+
ELSIF EXISTS (
|
|
251
|
+
SELECT 1 FROM sandbox_lease_holders holder
|
|
252
|
+
JOIN sandbox_leases lease ON lease.id = holder.lease_id
|
|
253
|
+
WHERE lease.workspace_id = p_workspace_id
|
|
254
|
+
AND lease.sandbox_group_id = session_group_id
|
|
255
|
+
AND holder.kind IN ('viewer', 'interaction')
|
|
256
|
+
)
|
|
257
|
+
THEN blocker := 'active_sandbox_access';
|
|
258
|
+
END IF;
|
|
259
|
+
|
|
260
|
+
IF blocker IS NULL AND p_require_singleton_group THEN
|
|
261
|
+
-- The caller's own actor-scoped RLS must not hide a sibling. The function
|
|
262
|
+
-- has already locked and identified the authorized source session.
|
|
263
|
+
PERFORM set_config('opengeni.subject_id', '', true);
|
|
264
|
+
IF EXISTS (SELECT 1 FROM sessions sibling
|
|
265
|
+
WHERE sibling.account_id = p_account_id
|
|
266
|
+
AND sibling.workspace_id = p_workspace_id
|
|
267
|
+
AND sibling.sandbox_group_id = session_group_id
|
|
268
|
+
AND sibling.id <> p_session_id)
|
|
269
|
+
THEN blocker := 'shared_sandbox_group'; END IF;
|
|
270
|
+
PERFORM set_config(
|
|
271
|
+
'opengeni.subject_id', CASE WHEN previous_subject IS NULL THEN '' ELSE previous_subject END, true
|
|
272
|
+
);
|
|
273
|
+
END IF;
|
|
274
|
+
|
|
275
|
+
IF blocker IS NOT NULL THEN
|
|
276
|
+
RAISE EXCEPTION 'session tenancy mutation requires a quiescent session'
|
|
277
|
+
USING ERRCODE = '55P03', DETAIL = blocker;
|
|
278
|
+
END IF;
|
|
279
|
+
EXCEPTION WHEN OTHERS THEN
|
|
280
|
+
PERFORM set_config(
|
|
281
|
+
'opengeni.subject_id', CASE WHEN previous_subject IS NULL THEN '' ELSE previous_subject END, true
|
|
282
|
+
);
|
|
283
|
+
RAISE;
|
|
284
|
+
END
|
|
285
|
+
$$;
|
|
286
|
+
REVOKE ALL ON FUNCTION assert_session_tenancy_quiescent(uuid, uuid, uuid, boolean)
|
|
287
|
+
FROM PUBLIC;
|
|
288
|
+
|
|
289
|
+
DROP FUNCTION transition_session_visibility(uuid, uuid, uuid, text, text, integer, text, text);
|
|
290
|
+
CREATE FUNCTION transition_session_visibility(
|
|
291
|
+
p_account_id uuid,
|
|
292
|
+
p_workspace_id uuid,
|
|
293
|
+
p_session_id uuid,
|
|
294
|
+
p_actor_subject_id text,
|
|
295
|
+
p_target_visibility text,
|
|
296
|
+
p_expected_authority_epoch integer,
|
|
297
|
+
p_operation_key text,
|
|
298
|
+
p_canonical_request_hash text,
|
|
299
|
+
p_activation_version integer
|
|
300
|
+
) RETURNS TABLE (
|
|
301
|
+
operation_id uuid,
|
|
302
|
+
event_id uuid,
|
|
303
|
+
event_sequence integer,
|
|
304
|
+
visibility text,
|
|
305
|
+
authority_epoch integer,
|
|
306
|
+
owner_organization_membership_id uuid,
|
|
307
|
+
changed boolean,
|
|
308
|
+
replay boolean,
|
|
309
|
+
interrupted_attempt_count integer,
|
|
310
|
+
cancelled_turn_count integer,
|
|
311
|
+
cancelled_update_count integer,
|
|
312
|
+
paused_goal_count integer,
|
|
313
|
+
revoked_grant_count integer
|
|
314
|
+
)
|
|
315
|
+
LANGUAGE plpgsql SECURITY DEFINER
|
|
316
|
+
SET search_path FROM CURRENT
|
|
317
|
+
AS $$
|
|
318
|
+
DECLARE
|
|
319
|
+
actor_membership organization_memberships%ROWTYPE;
|
|
320
|
+
session_row sessions%ROWTYPE;
|
|
321
|
+
receipt_row session_command_receipts%ROWTYPE;
|
|
322
|
+
new_epoch integer;
|
|
323
|
+
grant_count integer := 0;
|
|
324
|
+
event_row_id uuid;
|
|
325
|
+
event_row_sequence integer;
|
|
326
|
+
visibility_write_capability_id uuid := gen_random_uuid();
|
|
327
|
+
previous_visibility_capability text := current_setting(
|
|
328
|
+
'opengeni.session_visibility_write_capability', true
|
|
329
|
+
);
|
|
330
|
+
previous_lifecycle text := current_setting('opengeni.organization_tenancy_lifecycle', true);
|
|
331
|
+
BEGIN
|
|
332
|
+
IF p_account_id IS NULL OR p_workspace_id IS NULL OR p_session_id IS NULL
|
|
333
|
+
OR p_actor_subject_id IS NULL OR p_target_visibility IS NULL
|
|
334
|
+
OR p_expected_authority_epoch IS NULL OR p_operation_key IS NULL
|
|
335
|
+
OR p_canonical_request_hash IS NULL OR p_activation_version IS NULL
|
|
336
|
+
THEN RAISE EXCEPTION 'session visibility transition requires complete authority'
|
|
337
|
+
USING ERRCODE = '42501'; END IF;
|
|
338
|
+
IF p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
|
|
339
|
+
OR p_workspace_id IS DISTINCT FROM opengeni_private.current_workspace_id()
|
|
340
|
+
OR p_actor_subject_id IS DISTINCT FROM opengeni_private.current_subject_id()
|
|
341
|
+
THEN RAISE EXCEPTION 'session visibility transition authority is invalid'
|
|
342
|
+
USING ERRCODE = '42501'; END IF;
|
|
343
|
+
IF p_target_visibility NOT IN ('user_private', 'workspace_shared')
|
|
344
|
+
OR p_expected_authority_epoch < 1
|
|
345
|
+
OR p_actor_subject_id <> btrim(p_actor_subject_id)
|
|
346
|
+
OR length(p_actor_subject_id) NOT BETWEEN 1 AND 1024
|
|
347
|
+
OR p_operation_key <> btrim(p_operation_key)
|
|
348
|
+
OR length(p_operation_key) NOT BETWEEN 1 AND 1024
|
|
349
|
+
OR p_canonical_request_hash !~ '^[0-9a-f]{64}$'
|
|
350
|
+
OR p_activation_version <> 1
|
|
351
|
+
THEN RAISE EXCEPTION 'session visibility transition request is invalid'
|
|
352
|
+
USING ERRCODE = '22023'; END IF;
|
|
353
|
+
IF NOT session_tenancy_product_activated(p_account_id, p_activation_version) THEN
|
|
354
|
+
RAISE EXCEPTION 'session tenancy product surface is not activated for this organization'
|
|
355
|
+
USING ERRCODE = '55000';
|
|
356
|
+
END IF;
|
|
357
|
+
|
|
358
|
+
-- Match the canonical organization-membership lifecycle prefix before any
|
|
359
|
+
-- table/row lock. This keeps visibility changes from reintroducing the
|
|
360
|
+
-- workspace/account lock cycle repaired by migration 0299.
|
|
361
|
+
PERFORM pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
|
|
362
|
+
'organization-membership:' || p_account_id::text, 0
|
|
363
|
+
));
|
|
364
|
+
|
|
365
|
+
LOCK TABLE sessions, session_turns, session_turn_attempts,
|
|
366
|
+
session_attempt_interruptions, session_system_updates,
|
|
367
|
+
session_human_input_requests, session_pending_tool_calls, agent_run_states,
|
|
368
|
+
session_goals, codex_capacity_waiters, xai_capacity_waiters,
|
|
369
|
+
session_realtime_modes, session_realtime_connections, scheduled_tasks,
|
|
370
|
+
sandbox_workspace_mutation_admissions, sandbox_retained_processes,
|
|
371
|
+
sandbox_lease_holders IN SHARE ROW EXCLUSIVE MODE;
|
|
372
|
+
|
|
373
|
+
PERFORM set_config('opengeni.organization_tenancy_lifecycle',
|
|
374
|
+
'session_visibility_activation', true);
|
|
375
|
+
PERFORM 1 FROM workspaces workspace_row
|
|
376
|
+
WHERE workspace_row.id = p_workspace_id AND workspace_row.account_id = p_account_id
|
|
377
|
+
FOR UPDATE;
|
|
378
|
+
IF NOT FOUND THEN RAISE EXCEPTION 'session visibility transition workspace is unavailable'
|
|
379
|
+
USING ERRCODE = '42501'; END IF;
|
|
380
|
+
|
|
381
|
+
SELECT membership.* INTO actor_membership
|
|
382
|
+
FROM organization_memberships membership
|
|
383
|
+
WHERE membership.account_id = p_account_id
|
|
384
|
+
AND membership.subject_id = p_actor_subject_id
|
|
385
|
+
AND membership.status = 'active'
|
|
386
|
+
FOR UPDATE;
|
|
387
|
+
IF NOT FOUND OR NOT (
|
|
388
|
+
actor_membership.personal_workspace_id = p_workspace_id
|
|
389
|
+
OR EXISTS (
|
|
390
|
+
SELECT 1 FROM workspace_memberships workspace_membership
|
|
391
|
+
WHERE workspace_membership.account_id = p_account_id
|
|
392
|
+
AND workspace_membership.workspace_id = p_workspace_id
|
|
393
|
+
AND workspace_membership.subject_id = p_actor_subject_id
|
|
394
|
+
)
|
|
395
|
+
) THEN RAISE EXCEPTION 'session visibility transition requires active membership'
|
|
396
|
+
USING ERRCODE = '42501'; END IF;
|
|
397
|
+
|
|
398
|
+
SELECT session.* INTO session_row FROM sessions session
|
|
399
|
+
WHERE session.account_id = p_account_id AND session.workspace_id = p_workspace_id
|
|
400
|
+
AND session.id = p_session_id
|
|
401
|
+
FOR UPDATE;
|
|
402
|
+
IF NOT FOUND THEN RAISE EXCEPTION 'session visibility transition session is unavailable'
|
|
403
|
+
USING ERRCODE = 'P0002'; END IF;
|
|
404
|
+
IF session_row.owner_organization_membership_id IS DISTINCT FROM actor_membership.id
|
|
405
|
+
OR session_row.owner_subject_id IS DISTINCT FROM actor_membership.subject_id
|
|
406
|
+
THEN RAISE EXCEPTION 'session visibility transition is owner-only'
|
|
407
|
+
USING ERRCODE = '42501'; END IF;
|
|
408
|
+
|
|
409
|
+
INSERT INTO session_command_receipts (
|
|
410
|
+
account_id, workspace_id, actor_type, actor_subject_id, action,
|
|
411
|
+
target_session_id, operation_key, canonical_request_hash
|
|
412
|
+
) VALUES (
|
|
413
|
+
p_account_id, p_workspace_id, 'human', p_actor_subject_id,
|
|
414
|
+
'session.visibility.change', p_session_id, p_operation_key, p_canonical_request_hash
|
|
415
|
+
) ON CONFLICT DO NOTHING;
|
|
416
|
+
SELECT receipt.* INTO receipt_row FROM session_command_receipts receipt
|
|
417
|
+
WHERE receipt.workspace_id = p_workspace_id AND receipt.actor_type = 'human'
|
|
418
|
+
AND receipt.actor_subject_id = p_actor_subject_id
|
|
419
|
+
AND receipt.actor_attempt_id IS NULL
|
|
420
|
+
AND receipt.action = 'session.visibility.change'
|
|
421
|
+
AND receipt.target_session_id = p_session_id AND receipt.target_turn_id IS NULL
|
|
422
|
+
AND receipt.operation_key = p_operation_key
|
|
423
|
+
FOR UPDATE;
|
|
424
|
+
IF receipt_row.canonical_request_hash <> p_canonical_request_hash THEN
|
|
425
|
+
RAISE EXCEPTION 'session visibility transition idempotency conflict'
|
|
426
|
+
USING ERRCODE = '23505';
|
|
427
|
+
END IF;
|
|
428
|
+
IF receipt_row.result ->> 'status' = 'applied' THEN
|
|
429
|
+
operation_id := receipt_row.id;
|
|
430
|
+
event_id := nullif(receipt_row.result ->> 'eventId', '')::uuid;
|
|
431
|
+
event_sequence := nullif(receipt_row.result ->> 'eventSequence', '')::integer;
|
|
432
|
+
visibility := receipt_row.result ->> 'visibility';
|
|
433
|
+
authority_epoch := (receipt_row.result ->> 'authorityEpoch')::integer;
|
|
434
|
+
owner_organization_membership_id := actor_membership.id;
|
|
435
|
+
changed := (receipt_row.result ->> 'changed')::boolean;
|
|
436
|
+
replay := true;
|
|
437
|
+
interrupted_attempt_count := 0; cancelled_turn_count := 0;
|
|
438
|
+
cancelled_update_count := 0; paused_goal_count := 0;
|
|
439
|
+
revoked_grant_count := (receipt_row.result ->> 'revokedGrantCount')::integer;
|
|
440
|
+
RETURN NEXT; RETURN;
|
|
441
|
+
END IF;
|
|
442
|
+
IF session_row.authority_epoch <> p_expected_authority_epoch THEN
|
|
443
|
+
RAISE EXCEPTION 'session visibility transition authority epoch conflict'
|
|
444
|
+
USING ERRCODE = '40001';
|
|
445
|
+
END IF;
|
|
446
|
+
|
|
447
|
+
new_epoch := session_row.authority_epoch;
|
|
448
|
+
IF session_row.visibility <> p_target_visibility THEN
|
|
449
|
+
PERFORM assert_session_tenancy_quiescent(
|
|
450
|
+
p_account_id, p_workspace_id, p_session_id, p_target_visibility = 'user_private'
|
|
451
|
+
);
|
|
452
|
+
new_epoch := session_row.authority_epoch + 1;
|
|
453
|
+
IF new_epoch < 2 THEN RAISE EXCEPTION 'session authority epoch exhausted'
|
|
454
|
+
USING ERRCODE = '22003'; END IF;
|
|
455
|
+
|
|
456
|
+
UPDATE organization_user_resource_grants grant_row
|
|
457
|
+
SET status = 'revoked', revoked_at = clock_timestamp(),
|
|
458
|
+
generation = grant_row.generation + 1, updated_at = clock_timestamp()
|
|
459
|
+
WHERE grant_row.account_id = p_account_id
|
|
460
|
+
AND grant_row.workspace_id = p_workspace_id
|
|
461
|
+
AND grant_row.session_id = p_session_id
|
|
462
|
+
AND grant_row.authority_epoch = session_row.authority_epoch
|
|
463
|
+
AND grant_row.status = 'active';
|
|
464
|
+
GET DIAGNOSTICS grant_count = ROW_COUNT;
|
|
465
|
+
|
|
466
|
+
INSERT INTO session_visibility_write_capabilities (
|
|
467
|
+
backend_pid, transaction_id, capability_id
|
|
468
|
+
) VALUES (pg_backend_pid(), pg_current_xact_id(), visibility_write_capability_id);
|
|
469
|
+
PERFORM set_config('opengeni.session_visibility_write_capability',
|
|
470
|
+
visibility_write_capability_id::text, true);
|
|
471
|
+
UPDATE sessions transition_target SET
|
|
472
|
+
visibility = p_target_visibility,
|
|
473
|
+
authority_epoch = new_epoch,
|
|
474
|
+
initial_personal_connection_delegations = '[]'::jsonb,
|
|
475
|
+
last_sequence = session_row.last_sequence + 1,
|
|
476
|
+
updated_at = clock_timestamp()
|
|
477
|
+
WHERE transition_target.id = p_session_id
|
|
478
|
+
AND transition_target.authority_epoch = session_row.authority_epoch;
|
|
479
|
+
IF NOT FOUND THEN RAISE EXCEPTION 'session visibility transition lost authority epoch CAS'
|
|
480
|
+
USING ERRCODE = '40001'; END IF;
|
|
481
|
+
|
|
482
|
+
INSERT INTO session_events (
|
|
483
|
+
account_id, workspace_id, session_id, sequence, type, payload, occurred_at
|
|
484
|
+
) VALUES (
|
|
485
|
+
p_account_id, p_workspace_id, p_session_id, session_row.last_sequence + 1,
|
|
486
|
+
'session.visibility.changed',
|
|
487
|
+
jsonb_build_object(
|
|
488
|
+
'operationId', receipt_row.id,
|
|
489
|
+
'fromVisibility', CASE session_row.visibility WHEN 'user_private' THEN 'private' ELSE 'workspace' END,
|
|
490
|
+
'toVisibility', CASE p_target_visibility WHEN 'user_private' THEN 'private' ELSE 'workspace' END,
|
|
491
|
+
'previousAuthorityEpoch', session_row.authority_epoch,
|
|
492
|
+
'authorityEpoch', new_epoch,
|
|
493
|
+
'interruptedAttemptCount', 0, 'cancelledTurnCount', 0,
|
|
494
|
+
'cancelledUpdateCount', 0, 'pausedGoalCount', 0,
|
|
495
|
+
'revokedGrantCount', grant_count
|
|
496
|
+
), clock_timestamp()
|
|
497
|
+
) RETURNING id, sequence INTO event_row_id, event_row_sequence;
|
|
498
|
+
DELETE FROM session_visibility_write_capabilities capability
|
|
499
|
+
WHERE capability.backend_pid = pg_backend_pid()
|
|
500
|
+
AND capability.transaction_id = pg_current_xact_id()
|
|
501
|
+
AND capability.capability_id = visibility_write_capability_id;
|
|
502
|
+
PERFORM set_config('opengeni.session_visibility_write_capability',
|
|
503
|
+
CASE WHEN previous_visibility_capability IS NULL THEN '' ELSE previous_visibility_capability END,
|
|
504
|
+
true);
|
|
505
|
+
END IF;
|
|
506
|
+
|
|
507
|
+
UPDATE session_command_receipts SET result = jsonb_build_object(
|
|
508
|
+
'status', 'applied', 'eventId', event_row_id,
|
|
509
|
+
'eventSequence', event_row_sequence, 'visibility', p_target_visibility,
|
|
510
|
+
'authorityEpoch', new_epoch, 'changed', session_row.visibility <> p_target_visibility,
|
|
511
|
+
'revokedGrantCount', grant_count
|
|
512
|
+
), updated_at = clock_timestamp() WHERE id = receipt_row.id;
|
|
513
|
+
|
|
514
|
+
operation_id := receipt_row.id; event_id := event_row_id;
|
|
515
|
+
event_sequence := event_row_sequence; visibility := p_target_visibility;
|
|
516
|
+
authority_epoch := new_epoch;
|
|
517
|
+
owner_organization_membership_id := actor_membership.id;
|
|
518
|
+
changed := session_row.visibility <> p_target_visibility; replay := false;
|
|
519
|
+
interrupted_attempt_count := 0; cancelled_turn_count := 0;
|
|
520
|
+
cancelled_update_count := 0; paused_goal_count := 0;
|
|
521
|
+
revoked_grant_count := grant_count;
|
|
522
|
+
PERFORM set_config('opengeni.organization_tenancy_lifecycle',
|
|
523
|
+
CASE WHEN previous_lifecycle IS NULL THEN '' ELSE previous_lifecycle END, true);
|
|
524
|
+
RETURN NEXT;
|
|
525
|
+
EXCEPTION WHEN OTHERS THEN
|
|
526
|
+
PERFORM set_config('opengeni.organization_tenancy_lifecycle',
|
|
527
|
+
CASE WHEN previous_lifecycle IS NULL THEN '' ELSE previous_lifecycle END, true);
|
|
528
|
+
PERFORM set_config('opengeni.session_visibility_write_capability',
|
|
529
|
+
CASE WHEN previous_visibility_capability IS NULL THEN '' ELSE previous_visibility_capability END,
|
|
530
|
+
true);
|
|
531
|
+
RAISE;
|
|
532
|
+
END
|
|
533
|
+
$$;
|
|
534
|
+
REVOKE ALL ON FUNCTION transition_session_visibility(
|
|
535
|
+
uuid, uuid, uuid, text, text, integer, text, text, integer
|
|
536
|
+
) FROM PUBLIC;
|
|
537
|
+
|
|
538
|
+
DROP FUNCTION fork_session_content(uuid, uuid, uuid, text, uuid, text, text, text);
|
|
539
|
+
CREATE FUNCTION fork_session_content(
|
|
540
|
+
p_account_id uuid,
|
|
541
|
+
p_source_workspace_id uuid,
|
|
542
|
+
p_source_session_id uuid,
|
|
543
|
+
p_actor_subject_id text,
|
|
544
|
+
p_destination_workspace_id uuid,
|
|
545
|
+
p_destination_visibility text,
|
|
546
|
+
p_operation_key text,
|
|
547
|
+
p_canonical_request_hash text,
|
|
548
|
+
p_activation_version integer
|
|
549
|
+
) RETURNS TABLE (
|
|
550
|
+
operation_id uuid,
|
|
551
|
+
event_id uuid,
|
|
552
|
+
event_sequence integer,
|
|
553
|
+
session_id uuid,
|
|
554
|
+
workspace_id uuid,
|
|
555
|
+
visibility text,
|
|
556
|
+
authority_epoch integer,
|
|
557
|
+
copied_history_item_count integer,
|
|
558
|
+
replay boolean
|
|
559
|
+
)
|
|
560
|
+
LANGUAGE plpgsql SECURITY DEFINER
|
|
561
|
+
SET search_path FROM CURRENT
|
|
562
|
+
AS $$
|
|
563
|
+
DECLARE
|
|
564
|
+
actor_membership organization_memberships%ROWTYPE;
|
|
565
|
+
source_session sessions%ROWTYPE;
|
|
566
|
+
destination_workspace workspaces%ROWTYPE;
|
|
567
|
+
receipt_row session_command_receipts%ROWTYPE;
|
|
568
|
+
destination_session_id uuid;
|
|
569
|
+
history_count integer := 0;
|
|
570
|
+
destination_activity_revision bigint;
|
|
571
|
+
destination_depth integer;
|
|
572
|
+
destination_depth_source text;
|
|
573
|
+
destination_resources jsonb := '[]'::jsonb;
|
|
574
|
+
event_row_id uuid;
|
|
575
|
+
visibility_write_capability_id uuid := gen_random_uuid();
|
|
576
|
+
previous_lifecycle text := current_setting('opengeni.organization_tenancy_lifecycle', true);
|
|
577
|
+
previous_gate_state text := current_setting('opengeni.session_activity_gate_state', true);
|
|
578
|
+
previous_gate_workspace text := current_setting(
|
|
579
|
+
'opengeni.session_activity_gate_workspace_id', true
|
|
580
|
+
);
|
|
581
|
+
previous_visibility_capability text := current_setting(
|
|
582
|
+
'opengeni.session_visibility_write_capability', true
|
|
583
|
+
);
|
|
584
|
+
BEGIN
|
|
585
|
+
IF p_account_id IS NULL OR p_source_workspace_id IS NULL
|
|
586
|
+
OR p_source_session_id IS NULL OR p_actor_subject_id IS NULL
|
|
587
|
+
OR p_destination_workspace_id IS NULL OR p_destination_visibility IS NULL
|
|
588
|
+
OR p_operation_key IS NULL OR p_canonical_request_hash IS NULL
|
|
589
|
+
OR p_activation_version IS NULL
|
|
590
|
+
THEN RAISE EXCEPTION 'session fork requires complete authority'
|
|
591
|
+
USING ERRCODE = '42501'; END IF;
|
|
592
|
+
IF p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
|
|
593
|
+
OR p_source_workspace_id IS DISTINCT FROM opengeni_private.current_workspace_id()
|
|
594
|
+
OR p_actor_subject_id IS DISTINCT FROM opengeni_private.current_subject_id()
|
|
595
|
+
THEN RAISE EXCEPTION 'session fork authority is invalid'
|
|
596
|
+
USING ERRCODE = '42501'; END IF;
|
|
597
|
+
-- First product contract: independent fork is deliberately same-workspace
|
|
598
|
+
-- and private-only. Cross-workspace/public expansion needs a later authority
|
|
599
|
+
-- design, not a permissive interpretation of this seam.
|
|
600
|
+
IF p_destination_workspace_id IS DISTINCT FROM p_source_workspace_id
|
|
601
|
+
OR p_destination_visibility <> 'user_private'
|
|
602
|
+
OR p_actor_subject_id <> btrim(p_actor_subject_id)
|
|
603
|
+
OR length(p_actor_subject_id) NOT BETWEEN 1 AND 1024
|
|
604
|
+
OR p_operation_key <> btrim(p_operation_key)
|
|
605
|
+
OR length(p_operation_key) NOT BETWEEN 1 AND 1024
|
|
606
|
+
OR p_canonical_request_hash !~ '^[0-9a-f]{64}$'
|
|
607
|
+
OR p_activation_version <> 1
|
|
608
|
+
THEN RAISE EXCEPTION 'session fork request is invalid'
|
|
609
|
+
USING ERRCODE = '22023'; END IF;
|
|
610
|
+
IF nullif(previous_gate_state, '') IS NOT NULL
|
|
611
|
+
OR nullif(previous_gate_workspace, '') IS NOT NULL
|
|
612
|
+
THEN RAISE EXCEPTION 'session fork requires ownership of the activity gate'
|
|
613
|
+
USING ERRCODE = '55000'; END IF;
|
|
614
|
+
IF NOT session_tenancy_product_activated(p_account_id, p_activation_version) THEN
|
|
615
|
+
RAISE EXCEPTION 'session tenancy product surface is not activated for this organization'
|
|
616
|
+
USING ERRCODE = '55000';
|
|
617
|
+
END IF;
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
PERFORM pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
|
|
621
|
+
'organization-membership:' || p_account_id::text, 0
|
|
622
|
+
));
|
|
623
|
+
|
|
624
|
+
LOCK TABLE sessions, session_turns, session_turn_attempts,
|
|
625
|
+
session_attempt_interruptions, session_system_updates,
|
|
626
|
+
session_human_input_requests, session_pending_tool_calls, agent_run_states,
|
|
627
|
+
session_goals, codex_capacity_waiters, xai_capacity_waiters,
|
|
628
|
+
session_realtime_modes, session_realtime_connections, scheduled_tasks,
|
|
629
|
+
sandbox_workspace_mutation_admissions, sandbox_retained_processes,
|
|
630
|
+
sandbox_lease_holders IN SHARE ROW EXCLUSIVE MODE;
|
|
631
|
+
|
|
632
|
+
PERFORM set_config('opengeni.organization_tenancy_lifecycle',
|
|
633
|
+
'session_visibility_activation', true);
|
|
634
|
+
SELECT * INTO destination_workspace FROM workspaces workspace_row
|
|
635
|
+
WHERE workspace_row.account_id = p_account_id
|
|
636
|
+
AND workspace_row.id = p_source_workspace_id
|
|
637
|
+
FOR UPDATE;
|
|
638
|
+
IF NOT FOUND THEN RAISE EXCEPTION 'session fork workspace is unavailable'
|
|
639
|
+
USING ERRCODE = '42501'; END IF;
|
|
640
|
+
|
|
641
|
+
SELECT membership.* INTO actor_membership
|
|
642
|
+
FROM organization_memberships membership
|
|
643
|
+
WHERE membership.account_id = p_account_id
|
|
644
|
+
AND membership.subject_id = p_actor_subject_id
|
|
645
|
+
AND membership.status = 'active'
|
|
646
|
+
FOR UPDATE;
|
|
647
|
+
IF NOT FOUND OR NOT (
|
|
648
|
+
actor_membership.personal_workspace_id = p_source_workspace_id
|
|
649
|
+
OR EXISTS (
|
|
650
|
+
SELECT 1 FROM workspace_memberships workspace_membership
|
|
651
|
+
WHERE workspace_membership.account_id = p_account_id
|
|
652
|
+
AND workspace_membership.workspace_id = p_source_workspace_id
|
|
653
|
+
AND workspace_membership.subject_id = p_actor_subject_id
|
|
654
|
+
)
|
|
655
|
+
) THEN RAISE EXCEPTION 'session fork requires active workspace authority'
|
|
656
|
+
USING ERRCODE = '42501'; END IF;
|
|
657
|
+
|
|
658
|
+
SELECT session.* INTO source_session FROM sessions session
|
|
659
|
+
WHERE session.account_id = p_account_id
|
|
660
|
+
AND session.workspace_id = p_source_workspace_id
|
|
661
|
+
AND session.id = p_source_session_id
|
|
662
|
+
FOR UPDATE;
|
|
663
|
+
IF NOT FOUND THEN RAISE EXCEPTION 'session fork source session is unavailable'
|
|
664
|
+
USING ERRCODE = 'P0002'; END IF;
|
|
665
|
+
IF source_session.owner_organization_membership_id IS DISTINCT FROM actor_membership.id
|
|
666
|
+
OR source_session.owner_subject_id IS DISTINCT FROM actor_membership.subject_id
|
|
667
|
+
THEN RAISE EXCEPTION 'session fork is owner-only'
|
|
668
|
+
USING ERRCODE = '42501'; END IF;
|
|
669
|
+
|
|
670
|
+
-- Resolve the durable operation receipt after current caller authorization
|
|
671
|
+
-- but before inspecting mutable source state. An identical retry returns the
|
|
672
|
+
-- already-committed destination even if new work began on the source after
|
|
673
|
+
-- the first fork. A reused key with different input remains a conflict.
|
|
674
|
+
INSERT INTO session_command_receipts (
|
|
675
|
+
account_id, workspace_id, actor_type, actor_subject_id, action,
|
|
676
|
+
target_session_id, operation_key, canonical_request_hash
|
|
677
|
+
) VALUES (
|
|
678
|
+
p_account_id, p_source_workspace_id, 'human', p_actor_subject_id,
|
|
679
|
+
'session.fork', p_source_session_id, p_operation_key, p_canonical_request_hash
|
|
680
|
+
) ON CONFLICT DO NOTHING;
|
|
681
|
+
SELECT receipt.* INTO receipt_row FROM session_command_receipts receipt
|
|
682
|
+
WHERE receipt.workspace_id = p_source_workspace_id
|
|
683
|
+
AND receipt.actor_type = 'human' AND receipt.actor_subject_id = p_actor_subject_id
|
|
684
|
+
AND receipt.actor_attempt_id IS NULL AND receipt.action = 'session.fork'
|
|
685
|
+
AND receipt.target_session_id = p_source_session_id
|
|
686
|
+
AND receipt.target_turn_id IS NULL AND receipt.operation_key = p_operation_key
|
|
687
|
+
FOR UPDATE;
|
|
688
|
+
IF receipt_row.canonical_request_hash <> p_canonical_request_hash THEN
|
|
689
|
+
RAISE EXCEPTION 'session fork idempotency conflict' USING ERRCODE = '23505';
|
|
690
|
+
END IF;
|
|
691
|
+
IF receipt_row.result ->> 'status' = 'applied' THEN
|
|
692
|
+
operation_id := receipt_row.id;
|
|
693
|
+
event_id := nullif(receipt_row.result ->> 'eventId', '')::uuid;
|
|
694
|
+
event_sequence := (receipt_row.result ->> 'eventSequence')::integer;
|
|
695
|
+
session_id := (receipt_row.result ->> 'sessionId')::uuid;
|
|
696
|
+
workspace_id := (receipt_row.result ->> 'workspaceId')::uuid;
|
|
697
|
+
visibility := receipt_row.result ->> 'visibility'; authority_epoch := 1;
|
|
698
|
+
copied_history_item_count :=
|
|
699
|
+
(receipt_row.result ->> 'copiedHistoryItemCount')::integer;
|
|
700
|
+
replay := true; RETURN NEXT; RETURN;
|
|
701
|
+
END IF;
|
|
702
|
+
|
|
703
|
+
PERFORM assert_session_tenancy_quiescent(
|
|
704
|
+
p_account_id, p_source_workspace_id, p_source_session_id, true
|
|
705
|
+
);
|
|
706
|
+
|
|
707
|
+
SELECT coalesce(jsonb_agg(
|
|
708
|
+
resource.value
|
|
709
|
+
- 'credentialBindingId' - 'connectionId' - 'installationId'
|
|
710
|
+
- 'projectId' - 'githubInstallationId'
|
|
711
|
+
ORDER BY resource.ordinality
|
|
712
|
+
), '[]'::jsonb) INTO destination_resources
|
|
713
|
+
FROM jsonb_array_elements(source_session.resources)
|
|
714
|
+
WITH ORDINALITY AS resource(value, ordinality);
|
|
715
|
+
|
|
716
|
+
DROP TABLE IF EXISTS pg_temp.opengeni_session_fork_history_spool;
|
|
717
|
+
CREATE TEMP TABLE opengeni_session_fork_history_spool (
|
|
718
|
+
position numeric NOT NULL,
|
|
719
|
+
item jsonb NOT NULL,
|
|
720
|
+
item_codec_version integer,
|
|
721
|
+
active boolean NOT NULL,
|
|
722
|
+
created_at timestamptz NOT NULL
|
|
723
|
+
) ON COMMIT DROP;
|
|
724
|
+
INSERT INTO opengeni_session_fork_history_spool (
|
|
725
|
+
position, item, item_codec_version, active, created_at
|
|
726
|
+
) SELECT source_item.position, source_item.item,
|
|
727
|
+
source_item.item_codec_version, source_item.active, source_item.created_at
|
|
728
|
+
FROM session_history_items source_item
|
|
729
|
+
WHERE source_item.account_id = p_account_id
|
|
730
|
+
AND source_item.workspace_id = p_source_workspace_id
|
|
731
|
+
AND source_item.session_id = p_source_session_id
|
|
732
|
+
ORDER BY source_item.position;
|
|
733
|
+
GET DIAGNOSTICS history_count = ROW_COUNT;
|
|
734
|
+
|
|
735
|
+
SELECT coalesce(
|
|
736
|
+
CASE WHEN (destination_workspace.settings ->> 'maxNestedAgentDepth') ~ '^\d+$'
|
|
737
|
+
THEN (destination_workspace.settings ->> 'maxNestedAgentDepth')::integer END,
|
|
738
|
+
configuration.max_nested_agent_depth
|
|
739
|
+
),
|
|
740
|
+
CASE WHEN (destination_workspace.settings ->> 'maxNestedAgentDepth') ~ '^\d+$'
|
|
741
|
+
THEN 'workspace' ELSE configuration.policy_source END
|
|
742
|
+
INTO destination_depth, destination_depth_source
|
|
743
|
+
FROM nested_agent_depth_configuration configuration
|
|
744
|
+
WHERE configuration.singleton = true;
|
|
745
|
+
IF destination_depth IS NULL OR destination_depth_source IS NULL THEN
|
|
746
|
+
RAISE EXCEPTION 'session fork destination depth policy is unavailable'
|
|
747
|
+
USING ERRCODE = '55000';
|
|
748
|
+
END IF;
|
|
749
|
+
|
|
750
|
+
destination_session_id := gen_random_uuid();
|
|
751
|
+
INSERT INTO session_visibility_write_capabilities (
|
|
752
|
+
backend_pid, transaction_id, capability_id
|
|
753
|
+
) VALUES (pg_backend_pid(), pg_current_xact_id(), visibility_write_capability_id);
|
|
754
|
+
PERFORM set_config('opengeni.session_visibility_write_capability',
|
|
755
|
+
visibility_write_capability_id::text, true);
|
|
756
|
+
PERFORM set_config('opengeni.session_activity_gate_state', 'open', true);
|
|
757
|
+
PERFORM set_config('opengeni.session_activity_gate_workspace_id',
|
|
758
|
+
p_source_workspace_id::text, true);
|
|
759
|
+
|
|
760
|
+
INSERT INTO sessions (
|
|
761
|
+
id, account_id, workspace_id, status,
|
|
762
|
+
initial_message, initial_message_codec_version,
|
|
763
|
+
title, title_source, instructions, policy_role,
|
|
764
|
+
resources, skills, tools, metadata,
|
|
765
|
+
created_by_kind, created_by_subject_id, created_by_context,
|
|
766
|
+
owner_organization_membership_id, owner_subject_id,
|
|
767
|
+
visibility, authority_epoch,
|
|
768
|
+
forked_from_session_id, forked_from_authority_epoch,
|
|
769
|
+
forked_from_visibility, forked_at, forked_by_organization_membership_id,
|
|
770
|
+
model, reasoning_effort, latency_mode, sandbox_backend, sandbox_os, sandbox_group_id,
|
|
771
|
+
first_party_mcp_permissions, first_party_mcp_tools,
|
|
772
|
+
initial_personal_connection_delegations, tool_policy,
|
|
773
|
+
root_session_id, nested_agent_depth,
|
|
774
|
+
max_nested_agent_depth_override, effective_max_nested_agent_depth,
|
|
775
|
+
nested_agent_depth_policy_source, nested_agent_depth_policy_session_id,
|
|
776
|
+
temporal_workflow_id, active_turn_id, variable_set_id,
|
|
777
|
+
rig_id, rig_version_id, active_sandbox_id, active_epoch,
|
|
778
|
+
working_dir, codex_pinned_credential_id, codex_last_credential_id,
|
|
779
|
+
codex_pin_source, codex_compaction_mode,
|
|
780
|
+
queue_version, queue_head_position, queue_tail_position, last_sequence
|
|
781
|
+
) VALUES (
|
|
782
|
+
destination_session_id, p_account_id, p_source_workspace_id, 'idle',
|
|
783
|
+
source_session.initial_message, source_session.initial_message_codec_version,
|
|
784
|
+
source_session.title, source_session.title_source,
|
|
785
|
+
source_session.instructions, source_session.policy_role,
|
|
786
|
+
destination_resources, '[]'::jsonb, '[]'::jsonb, '{}'::jsonb,
|
|
787
|
+
'subject', p_actor_subject_id, '{"fork":true}'::jsonb,
|
|
788
|
+
actor_membership.id, actor_membership.subject_id,
|
|
789
|
+
'user_private', 1,
|
|
790
|
+
p_source_session_id, source_session.authority_epoch,
|
|
791
|
+
source_session.visibility, clock_timestamp(), actor_membership.id,
|
|
792
|
+
source_session.model, source_session.reasoning_effort, source_session.latency_mode,
|
|
793
|
+
source_session.sandbox_backend, source_session.sandbox_os, destination_session_id,
|
|
794
|
+
NULL, '[]'::jsonb, '[]'::jsonb,
|
|
795
|
+
'{"mode":"explicit","inheritedFromSessionId":null}'::jsonb,
|
|
796
|
+
destination_session_id, 0, NULL, destination_depth,
|
|
797
|
+
destination_depth_source, NULL,
|
|
798
|
+
NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL,
|
|
799
|
+
source_session.codex_compaction_mode,
|
|
800
|
+
0, 0, 0, 1
|
|
801
|
+
);
|
|
802
|
+
|
|
803
|
+
INSERT INTO session_history_items (
|
|
804
|
+
account_id, workspace_id, session_id, turn_id,
|
|
805
|
+
position, item, item_codec_version, active,
|
|
806
|
+
provider_artifact_invalidated_at, provider_artifact_invalidation_reason,
|
|
807
|
+
provider_artifact_invalidated_by_attempt_id, created_at
|
|
808
|
+
) SELECT p_account_id, p_source_workspace_id, destination_session_id, NULL,
|
|
809
|
+
source_item.position, source_item.item, source_item.item_codec_version,
|
|
810
|
+
source_item.active, NULL, NULL, NULL, source_item.created_at
|
|
811
|
+
FROM opengeni_session_fork_history_spool source_item
|
|
812
|
+
ORDER BY source_item.position;
|
|
813
|
+
|
|
814
|
+
INSERT INTO session_events (
|
|
815
|
+
account_id, workspace_id, session_id, sequence, type, payload, occurred_at
|
|
816
|
+
) VALUES (
|
|
817
|
+
p_account_id, p_source_workspace_id, destination_session_id, 1,
|
|
818
|
+
'session.created', jsonb_build_object(
|
|
819
|
+
'forked', true, 'sourceSessionId', p_source_session_id,
|
|
820
|
+
'sourceAuthorityEpoch', source_session.authority_epoch,
|
|
821
|
+
'sourceVisibility', CASE source_session.visibility
|
|
822
|
+
WHEN 'user_private' THEN 'private' ELSE 'workspace' END,
|
|
823
|
+
'visibility', 'private', 'copiedHistoryItemCount', history_count
|
|
824
|
+
), clock_timestamp()
|
|
825
|
+
) RETURNING id INTO event_row_id;
|
|
826
|
+
|
|
827
|
+
PERFORM set_config('opengeni.session_activity_gate_state', 'preparing', true);
|
|
828
|
+
SET CONSTRAINTS ALL IMMEDIATE;
|
|
829
|
+
SET CONSTRAINTS sessions_activity_insert_commit_guard,
|
|
830
|
+
sessions_activity_update_commit_guard DEFERRED;
|
|
831
|
+
PERFORM set_config('opengeni.session_activity_gate_state', 'finalizing', true);
|
|
832
|
+
UPDATE workspace_session_activity_revisions counter
|
|
833
|
+
SET revision = counter.revision + 1
|
|
834
|
+
WHERE counter.workspace_id = p_source_workspace_id
|
|
835
|
+
RETURNING counter.revision INTO destination_activity_revision;
|
|
836
|
+
IF destination_activity_revision IS NULL THEN
|
|
837
|
+
RAISE EXCEPTION 'session fork destination activity counter is unavailable'
|
|
838
|
+
USING ERRCODE = '55000';
|
|
839
|
+
END IF;
|
|
840
|
+
UPDATE sessions destination_session SET
|
|
841
|
+
activity_revision = destination_activity_revision,
|
|
842
|
+
activity_revision_pending_xid = NULL
|
|
843
|
+
WHERE destination_session.id = destination_session_id
|
|
844
|
+
AND destination_session.activity_revision_pending_xid
|
|
845
|
+
= pg_current_xact_id()::text::bigint;
|
|
846
|
+
IF NOT FOUND THEN RAISE EXCEPTION 'session fork activity was not finalized'
|
|
847
|
+
USING ERRCODE = '55000'; END IF;
|
|
848
|
+
SET CONSTRAINTS sessions_activity_insert_commit_guard,
|
|
849
|
+
sessions_activity_update_commit_guard IMMEDIATE;
|
|
850
|
+
|
|
851
|
+
PERFORM set_config('opengeni.session_activity_gate_state', '', true);
|
|
852
|
+
PERFORM set_config('opengeni.session_activity_gate_workspace_id', '', true);
|
|
853
|
+
DELETE FROM session_visibility_write_capabilities capability
|
|
854
|
+
WHERE capability.backend_pid = pg_backend_pid()
|
|
855
|
+
AND capability.transaction_id = pg_current_xact_id()
|
|
856
|
+
AND capability.capability_id = visibility_write_capability_id;
|
|
857
|
+
PERFORM set_config('opengeni.session_visibility_write_capability',
|
|
858
|
+
CASE WHEN previous_visibility_capability IS NULL THEN '' ELSE previous_visibility_capability END,
|
|
859
|
+
true);
|
|
860
|
+
|
|
861
|
+
UPDATE session_command_receipts SET result = jsonb_build_object(
|
|
862
|
+
'status', 'applied', 'eventId', event_row_id, 'eventSequence', 1,
|
|
863
|
+
'sessionId', destination_session_id, 'workspaceId', p_source_workspace_id,
|
|
864
|
+
'visibility', 'user_private', 'authorityEpoch', 1,
|
|
865
|
+
'copiedHistoryItemCount', history_count
|
|
866
|
+
), updated_at = clock_timestamp() WHERE id = receipt_row.id;
|
|
867
|
+
|
|
868
|
+
operation_id := receipt_row.id; event_id := event_row_id; event_sequence := 1;
|
|
869
|
+
session_id := destination_session_id; workspace_id := p_source_workspace_id;
|
|
870
|
+
visibility := 'user_private'; authority_epoch := 1;
|
|
871
|
+
copied_history_item_count := history_count; replay := false;
|
|
872
|
+
PERFORM set_config('opengeni.organization_tenancy_lifecycle',
|
|
873
|
+
CASE WHEN previous_lifecycle IS NULL THEN '' ELSE previous_lifecycle END, true);
|
|
874
|
+
RETURN NEXT;
|
|
875
|
+
EXCEPTION WHEN OTHERS THEN
|
|
876
|
+
PERFORM set_config('opengeni.organization_tenancy_lifecycle',
|
|
877
|
+
CASE WHEN previous_lifecycle IS NULL THEN '' ELSE previous_lifecycle END, true);
|
|
878
|
+
PERFORM set_config('opengeni.session_activity_gate_state',
|
|
879
|
+
CASE WHEN previous_gate_state IS NULL THEN '' ELSE previous_gate_state END, true);
|
|
880
|
+
PERFORM set_config('opengeni.session_activity_gate_workspace_id',
|
|
881
|
+
CASE WHEN previous_gate_workspace IS NULL THEN '' ELSE previous_gate_workspace END, true);
|
|
882
|
+
PERFORM set_config('opengeni.session_visibility_write_capability',
|
|
883
|
+
CASE WHEN previous_visibility_capability IS NULL THEN '' ELSE previous_visibility_capability END,
|
|
884
|
+
true);
|
|
885
|
+
RAISE;
|
|
886
|
+
END
|
|
887
|
+
$$;
|
|
888
|
+
REVOKE ALL ON FUNCTION fork_session_content(
|
|
889
|
+
uuid, uuid, uuid, text, uuid, text, text, text, integer
|
|
890
|
+
) FROM PUBLIC;
|
|
891
|
+
|
|
892
|
+
DO $session_tenancy_runtime_grants$
|
|
893
|
+
BEGIN
|
|
894
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
895
|
+
GRANT SELECT ON session_tenancy_activations TO opengeni_app;
|
|
896
|
+
GRANT EXECUTE ON FUNCTION session_tenancy_product_activated(uuid, integer)
|
|
897
|
+
TO opengeni_app;
|
|
898
|
+
GRANT EXECUTE ON FUNCTION session_tenancy_any_product_activation()
|
|
899
|
+
TO opengeni_app;
|
|
900
|
+
REVOKE ALL ON FUNCTION assert_session_tenancy_quiescent(uuid, uuid, uuid, boolean)
|
|
901
|
+
FROM opengeni_app;
|
|
902
|
+
GRANT EXECUTE ON FUNCTION transition_session_visibility(
|
|
903
|
+
uuid, uuid, uuid, text, text, integer, text, text, integer
|
|
904
|
+
) TO opengeni_app;
|
|
905
|
+
GRANT EXECUTE ON FUNCTION fork_session_content(
|
|
906
|
+
uuid, uuid, uuid, text, uuid, text, text, text, integer
|
|
907
|
+
) TO opengeni_app;
|
|
908
|
+
END IF;
|
|
909
|
+
END
|
|
910
|
+
$session_tenancy_runtime_grants$;
|
|
911
|
+
|
|
912
|
+
COMMENT ON TABLE session_tenancy_activations IS
|
|
913
|
+
'Forward-only per-organization activation receipt for session visibility mutation and private fork.';
|
|
914
|
+
COMMENT ON FUNCTION transition_session_visibility(
|
|
915
|
+
uuid, uuid, uuid, text, text, integer, text, text, integer
|
|
916
|
+
) IS 'Activated owner-only, proven-quiescent visibility transition. It never cancels accepted work or wakes a workflow.';
|
|
917
|
+
COMMENT ON FUNCTION fork_session_content(
|
|
918
|
+
uuid, uuid, uuid, text, uuid, text, text, text, integer
|
|
919
|
+
) IS 'Activated same-workspace private fork of canonical durable content into a new root and singleton sandbox group.';
|