@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,606 @@
|
|
|
1
|
+
-- deployment-mode: maintenance
|
|
2
|
+
-- Permit an organization invitation to exist before its target has an
|
|
3
|
+
-- OpenGeni login. A verified Better Auth email binds the invitation to the
|
|
4
|
+
-- canonical user subject before the existing acceptance lifecycle runs.
|
|
5
|
+
-- This is a one-way application protocol cutover: stop every API, control
|
|
6
|
+
-- worker, and turn worker before applying it, and never restart a pre-0314
|
|
7
|
+
-- image. Old callers can accept without initial workspace grants and can
|
|
8
|
+
-- provision a fallback organization before verified invitation convergence.
|
|
9
|
+
|
|
10
|
+
SET LOCAL lock_timeout = '5s';
|
|
11
|
+
SET LOCAL statement_timeout = '5min';
|
|
12
|
+
|
|
13
|
+
DO $organization_invitation_writer_drain_before_lock$
|
|
14
|
+
DECLARE
|
|
15
|
+
configured_roles_text text := nullif(
|
|
16
|
+
current_setting('opengeni.migration_application_roles', true), ''
|
|
17
|
+
);
|
|
18
|
+
configured_roles jsonb;
|
|
19
|
+
BEGIN
|
|
20
|
+
IF configured_roles_text IS NULL THEN
|
|
21
|
+
RAISE EXCEPTION
|
|
22
|
+
'0314 organization invitation activation requires an explicit application database role list'
|
|
23
|
+
USING ERRCODE = '55000';
|
|
24
|
+
END IF;
|
|
25
|
+
BEGIN
|
|
26
|
+
configured_roles := configured_roles_text::jsonb;
|
|
27
|
+
EXCEPTION WHEN OTHERS THEN
|
|
28
|
+
RAISE EXCEPTION
|
|
29
|
+
'0314 organization invitation activation received a malformed application database role list'
|
|
30
|
+
USING ERRCODE = '55000';
|
|
31
|
+
END;
|
|
32
|
+
IF jsonb_typeof(configured_roles) <> 'array'
|
|
33
|
+
OR jsonb_array_length(configured_roles) NOT BETWEEN 1 AND 16
|
|
34
|
+
OR EXISTS (
|
|
35
|
+
SELECT 1 FROM jsonb_array_elements(configured_roles) AS roles(value)
|
|
36
|
+
WHERE jsonb_typeof(value) <> 'string'
|
|
37
|
+
OR btrim(value #>> '{}') = ''
|
|
38
|
+
OR octet_length(value #>> '{}') > 63
|
|
39
|
+
)
|
|
40
|
+
OR (
|
|
41
|
+
SELECT count(*) FROM jsonb_array_elements_text(configured_roles)
|
|
42
|
+
) <> (
|
|
43
|
+
SELECT count(DISTINCT value)
|
|
44
|
+
FROM jsonb_array_elements_text(configured_roles) AS roles(value)
|
|
45
|
+
)
|
|
46
|
+
THEN
|
|
47
|
+
RAISE EXCEPTION
|
|
48
|
+
'0314 organization invitation activation received an invalid application database role list'
|
|
49
|
+
USING ERRCODE = '55000';
|
|
50
|
+
END IF;
|
|
51
|
+
IF EXISTS (
|
|
52
|
+
SELECT 1 FROM pg_stat_activity activity
|
|
53
|
+
JOIN jsonb_array_elements_text(configured_roles) roles(role_name)
|
|
54
|
+
ON roles.role_name = activity.usename
|
|
55
|
+
WHERE activity.datname = current_database()
|
|
56
|
+
AND activity.pid <> pg_backend_pid()
|
|
57
|
+
)
|
|
58
|
+
THEN
|
|
59
|
+
RAISE EXCEPTION
|
|
60
|
+
'0314 organization invitation activation requires all configured OpenGeni application database sessions to be stopped'
|
|
61
|
+
USING ERRCODE = '55000';
|
|
62
|
+
END IF;
|
|
63
|
+
END
|
|
64
|
+
$organization_invitation_writer_drain_before_lock$;
|
|
65
|
+
|
|
66
|
+
LOCK TABLE organization_membership_invitations IN ACCESS EXCLUSIVE MODE;
|
|
67
|
+
LOCK TABLE organization_membership_operation_receipts IN ACCESS EXCLUSIVE MODE;
|
|
68
|
+
LOCK TABLE organization_memberships IN ACCESS EXCLUSIVE MODE;
|
|
69
|
+
LOCK TABLE workspaces IN ACCESS EXCLUSIVE MODE;
|
|
70
|
+
LOCK TABLE workspace_memberships IN ACCESS EXCLUSIVE MODE;
|
|
71
|
+
|
|
72
|
+
DO $organization_invitation_writer_drain_after_lock$
|
|
73
|
+
DECLARE
|
|
74
|
+
configured_roles jsonb := current_setting(
|
|
75
|
+
'opengeni.migration_application_roles', false
|
|
76
|
+
)::jsonb;
|
|
77
|
+
BEGIN
|
|
78
|
+
IF EXISTS (
|
|
79
|
+
SELECT 1 FROM pg_stat_activity activity
|
|
80
|
+
JOIN jsonb_array_elements_text(configured_roles) roles(role_name)
|
|
81
|
+
ON roles.role_name = activity.usename
|
|
82
|
+
WHERE activity.datname = current_database()
|
|
83
|
+
AND activity.pid <> pg_backend_pid()
|
|
84
|
+
)
|
|
85
|
+
THEN
|
|
86
|
+
RAISE EXCEPTION
|
|
87
|
+
'0314 organization invitation activation requires all configured OpenGeni application database sessions to be stopped'
|
|
88
|
+
USING ERRCODE = '55000';
|
|
89
|
+
END IF;
|
|
90
|
+
END
|
|
91
|
+
$organization_invitation_writer_drain_after_lock$;
|
|
92
|
+
|
|
93
|
+
ALTER TABLE organization_membership_invitations
|
|
94
|
+
ALTER COLUMN target_subject_id DROP NOT NULL,
|
|
95
|
+
ADD COLUMN target_name text,
|
|
96
|
+
ADD COLUMN initial_workspace_ids uuid[] NOT NULL DEFAULT '{}'::uuid[];
|
|
97
|
+
|
|
98
|
+
ALTER TABLE organization_membership_invitations
|
|
99
|
+
DROP CONSTRAINT organization_membership_invitations_subject_check,
|
|
100
|
+
ADD CONSTRAINT organization_membership_invitations_subject_check CHECK (
|
|
101
|
+
target_subject_id IS NULL
|
|
102
|
+
OR (
|
|
103
|
+
target_subject_id = btrim(target_subject_id)
|
|
104
|
+
AND target_subject_id LIKE 'user:%'
|
|
105
|
+
AND octet_length(convert_to(target_subject_id, 'UTF8')) BETWEEN 6 AND 1024
|
|
106
|
+
)
|
|
107
|
+
),
|
|
108
|
+
ADD CONSTRAINT organization_membership_invitations_name_check CHECK (
|
|
109
|
+
target_name IS NULL
|
|
110
|
+
OR (
|
|
111
|
+
target_name = btrim(target_name)
|
|
112
|
+
AND octet_length(convert_to(target_name, 'UTF8')) BETWEEN 1 AND 120
|
|
113
|
+
)
|
|
114
|
+
),
|
|
115
|
+
ADD CONSTRAINT organization_membership_invitations_workspace_count_check CHECK (
|
|
116
|
+
cardinality(initial_workspace_ids) BETWEEN 0 AND 100
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
DROP INDEX organization_membership_invitations_pending_target_uq;
|
|
120
|
+
CREATE UNIQUE INDEX organization_membership_invitations_pending_target_uq
|
|
121
|
+
ON organization_membership_invitations (account_id, target_subject_id)
|
|
122
|
+
WHERE status = 'pending' AND target_subject_id IS NOT NULL;
|
|
123
|
+
CREATE UNIQUE INDEX organization_membership_invitations_pending_email_uq
|
|
124
|
+
ON organization_membership_invitations (account_id, target_email)
|
|
125
|
+
WHERE status = 'pending';
|
|
126
|
+
|
|
127
|
+
CREATE TABLE organization_invitation_binding_events (
|
|
128
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
129
|
+
account_id uuid NOT NULL REFERENCES managed_accounts(id) ON DELETE CASCADE,
|
|
130
|
+
invitation_id uuid NOT NULL,
|
|
131
|
+
target_subject_id text NOT NULL,
|
|
132
|
+
resulting_revision bigint NOT NULL,
|
|
133
|
+
bound_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
|
134
|
+
CONSTRAINT organization_invitation_binding_events_invitation_fk
|
|
135
|
+
FOREIGN KEY (invitation_id, account_id)
|
|
136
|
+
REFERENCES organization_membership_invitations(id, account_id) ON DELETE RESTRICT,
|
|
137
|
+
CONSTRAINT organization_invitation_binding_events_subject_check CHECK (
|
|
138
|
+
target_subject_id = btrim(target_subject_id)
|
|
139
|
+
AND target_subject_id LIKE 'user:%'
|
|
140
|
+
AND octet_length(convert_to(target_subject_id, 'UTF8')) BETWEEN 6 AND 1024
|
|
141
|
+
),
|
|
142
|
+
CONSTRAINT organization_invitation_binding_events_revision_check CHECK (
|
|
143
|
+
resulting_revision > 1
|
|
144
|
+
)
|
|
145
|
+
);
|
|
146
|
+
CREATE UNIQUE INDEX organization_invitation_binding_events_invitation_uq
|
|
147
|
+
ON organization_invitation_binding_events (account_id, invitation_id);
|
|
148
|
+
|
|
149
|
+
ALTER TABLE organization_invitation_binding_events ENABLE ROW LEVEL SECURITY;
|
|
150
|
+
ALTER TABLE organization_invitation_binding_events FORCE ROW LEVEL SECURITY;
|
|
151
|
+
CREATE POLICY organization_tenancy_lifecycle ON organization_invitation_binding_events
|
|
152
|
+
USING (current_setting('opengeni.organization_tenancy_lifecycle', true)
|
|
153
|
+
= 'organization_membership_lifecycle')
|
|
154
|
+
WITH CHECK (current_setting('opengeni.organization_tenancy_lifecycle', true)
|
|
155
|
+
= 'organization_membership_lifecycle');
|
|
156
|
+
|
|
157
|
+
CREATE OR REPLACE FUNCTION opengeni_private.organization_invitation_row_json(
|
|
158
|
+
p_invitation organization_membership_invitations
|
|
159
|
+
) RETURNS jsonb
|
|
160
|
+
LANGUAGE sql STABLE
|
|
161
|
+
SET search_path = pg_catalog
|
|
162
|
+
AS $body$
|
|
163
|
+
SELECT pg_catalog.jsonb_build_object(
|
|
164
|
+
'id', p_invitation.id,
|
|
165
|
+
'organizationId', p_invitation.account_id,
|
|
166
|
+
'targetEmail', p_invitation.target_email,
|
|
167
|
+
'targetName', p_invitation.target_name,
|
|
168
|
+
'initialWorkspaceIds', p_invitation.initial_workspace_ids,
|
|
169
|
+
'role', p_invitation.role,
|
|
170
|
+
'status', CASE
|
|
171
|
+
WHEN p_invitation.status = 'pending'
|
|
172
|
+
AND p_invitation.expires_at <= pg_catalog.clock_timestamp()
|
|
173
|
+
THEN 'expired'
|
|
174
|
+
ELSE p_invitation.status
|
|
175
|
+
END,
|
|
176
|
+
'revision', p_invitation.revision,
|
|
177
|
+
'expiresAt', p_invitation.expires_at,
|
|
178
|
+
'acceptedMembershipId', p_invitation.accepted_membership_id,
|
|
179
|
+
'createdAt', p_invitation.created_at,
|
|
180
|
+
'updatedAt', p_invitation.updated_at
|
|
181
|
+
)
|
|
182
|
+
$body$;
|
|
183
|
+
|
|
184
|
+
CREATE FUNCTION create_organization_invitation_v2(p_command jsonb)
|
|
185
|
+
RETURNS jsonb
|
|
186
|
+
LANGUAGE plpgsql
|
|
187
|
+
SECURITY DEFINER
|
|
188
|
+
SET search_path FROM CURRENT
|
|
189
|
+
AS $body$
|
|
190
|
+
DECLARE
|
|
191
|
+
account_id_value uuid := nullif(p_command ->> 'organizationId', '')::uuid;
|
|
192
|
+
actor_subject text := p_command ->> 'actorSubjectId';
|
|
193
|
+
operation_id_value uuid := nullif(p_command ->> 'operationId', '')::uuid;
|
|
194
|
+
input_hash_value text;
|
|
195
|
+
receipt_row organization_membership_operation_receipts%ROWTYPE;
|
|
196
|
+
actor organization_memberships%ROWTYPE;
|
|
197
|
+
invitation organization_membership_invitations%ROWTYPE;
|
|
198
|
+
target_subject text := nullif(p_command ->> 'targetSubjectId', '');
|
|
199
|
+
target_email_value text := lower(btrim(p_command ->> 'targetEmail'));
|
|
200
|
+
target_name_value text := nullif(btrim(p_command ->> 'targetName'), '');
|
|
201
|
+
requested_role text := p_command ->> 'role';
|
|
202
|
+
expires_at_value timestamptz := nullif(p_command ->> 'expiresAt', '')::timestamptz;
|
|
203
|
+
requested_workspace_ids uuid[];
|
|
204
|
+
now_value timestamptz := pg_catalog.clock_timestamp();
|
|
205
|
+
result jsonb;
|
|
206
|
+
BEGIN
|
|
207
|
+
IF p_command IS NULL
|
|
208
|
+
OR account_id_value IS NULL
|
|
209
|
+
OR actor_subject IS NULL
|
|
210
|
+
OR actor_subject IS DISTINCT FROM opengeni_private.current_subject_id()
|
|
211
|
+
OR account_id_value IS DISTINCT FROM opengeni_private.current_account_id()
|
|
212
|
+
OR operation_id_value IS NULL
|
|
213
|
+
THEN
|
|
214
|
+
RAISE EXCEPTION 'organization invitation authority is invalid' USING ERRCODE = '42501';
|
|
215
|
+
END IF;
|
|
216
|
+
|
|
217
|
+
SELECT coalesce(pg_catalog.array_agg(candidate.workspace_id ORDER BY candidate.workspace_id), '{}')
|
|
218
|
+
INTO requested_workspace_ids
|
|
219
|
+
FROM (
|
|
220
|
+
SELECT DISTINCT value::uuid AS workspace_id
|
|
221
|
+
FROM pg_catalog.jsonb_array_elements_text(
|
|
222
|
+
coalesce(p_command -> 'initialWorkspaceIds', '[]'::jsonb)
|
|
223
|
+
) value
|
|
224
|
+
) candidate;
|
|
225
|
+
|
|
226
|
+
IF target_email_value IS NULL
|
|
227
|
+
OR target_email_value <> btrim(target_email_value)
|
|
228
|
+
OR octet_length(convert_to(target_email_value, 'UTF8')) NOT BETWEEN 3 AND 320
|
|
229
|
+
OR (target_subject IS NOT NULL AND target_subject NOT LIKE 'user:%')
|
|
230
|
+
OR (target_name_value IS NOT NULL
|
|
231
|
+
AND octet_length(convert_to(target_name_value, 'UTF8')) NOT BETWEEN 1 AND 120)
|
|
232
|
+
OR requested_role NOT IN ('owner', 'admin', 'member')
|
|
233
|
+
OR expires_at_value <= now_value
|
|
234
|
+
OR expires_at_value > now_value + interval '30 days'
|
|
235
|
+
OR cardinality(requested_workspace_ids) > 100
|
|
236
|
+
THEN
|
|
237
|
+
RAISE EXCEPTION 'organization invitation input is invalid' USING ERRCODE = '22023';
|
|
238
|
+
END IF;
|
|
239
|
+
|
|
240
|
+
-- Email-first is the canonical ordering shared with verified signup
|
|
241
|
+
-- convergence. It makes invitation creation and fallback organization
|
|
242
|
+
-- provisioning mutually exclusive for one normalized identity.
|
|
243
|
+
PERFORM pg_catalog.pg_advisory_xact_lock(
|
|
244
|
+
pg_catalog.hashtextextended(
|
|
245
|
+
'organization-invitation-email:' || target_email_value, 0
|
|
246
|
+
)
|
|
247
|
+
);
|
|
248
|
+
PERFORM pg_catalog.pg_advisory_xact_lock(
|
|
249
|
+
pg_catalog.hashtextextended('organization-membership:' || account_id_value::text, 0)
|
|
250
|
+
);
|
|
251
|
+
PERFORM 1 FROM managed_accounts account
|
|
252
|
+
WHERE account.id = account_id_value FOR KEY SHARE;
|
|
253
|
+
IF NOT FOUND THEN
|
|
254
|
+
RAISE EXCEPTION 'organization not found' USING ERRCODE = 'P0002';
|
|
255
|
+
END IF;
|
|
256
|
+
PERFORM pg_catalog.set_config(
|
|
257
|
+
'opengeni.organization_tenancy_lifecycle',
|
|
258
|
+
'organization_membership_lifecycle', true
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
input_hash_value := pg_catalog.encode(
|
|
262
|
+
pg_catalog.sha256(pg_catalog.convert_to(p_command::text, 'UTF8')), 'hex'
|
|
263
|
+
);
|
|
264
|
+
SELECT * INTO receipt_row
|
|
265
|
+
FROM organization_membership_operation_receipts receipt
|
|
266
|
+
WHERE receipt.account_id = account_id_value
|
|
267
|
+
AND receipt.operation_id = operation_id_value
|
|
268
|
+
FOR UPDATE;
|
|
269
|
+
IF FOUND THEN
|
|
270
|
+
IF receipt_row.action IS DISTINCT FROM 'invite'
|
|
271
|
+
OR receipt_row.input_hash IS DISTINCT FROM input_hash_value
|
|
272
|
+
THEN
|
|
273
|
+
RAISE EXCEPTION 'organization operation id was reused with different input'
|
|
274
|
+
USING ERRCODE = '23505';
|
|
275
|
+
END IF;
|
|
276
|
+
RETURN receipt_row.result;
|
|
277
|
+
END IF;
|
|
278
|
+
|
|
279
|
+
SELECT * INTO actor FROM organization_memberships membership
|
|
280
|
+
WHERE membership.account_id = account_id_value
|
|
281
|
+
AND membership.subject_id = actor_subject
|
|
282
|
+
FOR UPDATE;
|
|
283
|
+
IF NOT FOUND OR actor.status <> 'active' OR actor.role NOT IN ('owner', 'admin') THEN
|
|
284
|
+
RAISE EXCEPTION 'organization administration required' USING ERRCODE = '42501';
|
|
285
|
+
END IF;
|
|
286
|
+
IF actor.role = 'admin' AND requested_role <> 'member' THEN
|
|
287
|
+
RAISE EXCEPTION 'administrators may invite members only' USING ERRCODE = '22023';
|
|
288
|
+
END IF;
|
|
289
|
+
|
|
290
|
+
IF cardinality(requested_workspace_ids) > 0 THEN
|
|
291
|
+
IF (
|
|
292
|
+
SELECT pg_catalog.count(*)::integer
|
|
293
|
+
FROM workspaces workspace
|
|
294
|
+
WHERE workspace.account_id = account_id_value
|
|
295
|
+
AND workspace.id = ANY(requested_workspace_ids)
|
|
296
|
+
AND NOT EXISTS (
|
|
297
|
+
SELECT 1 FROM organization_memberships membership
|
|
298
|
+
WHERE membership.account_id = account_id_value
|
|
299
|
+
AND membership.personal_workspace_id = workspace.id
|
|
300
|
+
)
|
|
301
|
+
) IS DISTINCT FROM cardinality(requested_workspace_ids)
|
|
302
|
+
THEN
|
|
303
|
+
RAISE EXCEPTION 'initial workspace access contains an unavailable workspace'
|
|
304
|
+
USING ERRCODE = '22023';
|
|
305
|
+
END IF;
|
|
306
|
+
END IF;
|
|
307
|
+
|
|
308
|
+
IF target_subject IS NOT NULL AND EXISTS (
|
|
309
|
+
SELECT 1 FROM organization_memberships membership
|
|
310
|
+
WHERE membership.account_id = account_id_value
|
|
311
|
+
AND membership.subject_id = target_subject
|
|
312
|
+
AND membership.status = 'active'
|
|
313
|
+
) THEN
|
|
314
|
+
RAISE EXCEPTION 'subject is already an active organization member' USING ERRCODE = '55000';
|
|
315
|
+
END IF;
|
|
316
|
+
IF target_subject IS NOT NULL AND EXISTS (
|
|
317
|
+
SELECT 1 FROM organization_memberships membership
|
|
318
|
+
WHERE membership.account_id = account_id_value
|
|
319
|
+
AND membership.subject_id = target_subject
|
|
320
|
+
AND membership.status IN ('suspended', 'revoked')
|
|
321
|
+
) THEN
|
|
322
|
+
RAISE EXCEPTION 'inactive organization membership cannot be re-invited'
|
|
323
|
+
USING ERRCODE = '55000';
|
|
324
|
+
END IF;
|
|
325
|
+
|
|
326
|
+
UPDATE organization_membership_invitations SET
|
|
327
|
+
status = 'revoked', revision = revision + 1, updated_at = now_value
|
|
328
|
+
WHERE account_id = account_id_value
|
|
329
|
+
AND status = 'pending'
|
|
330
|
+
AND (
|
|
331
|
+
organization_membership_invitations.target_email = target_email_value
|
|
332
|
+
OR (target_subject IS NOT NULL AND target_subject_id = target_subject)
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
INSERT INTO organization_membership_invitations (
|
|
336
|
+
account_id, target_subject_id, target_email, target_name,
|
|
337
|
+
initial_workspace_ids, role, created_by_membership_id, expires_at
|
|
338
|
+
) VALUES (
|
|
339
|
+
account_id_value, target_subject, target_email_value, target_name_value,
|
|
340
|
+
requested_workspace_ids, requested_role, actor.id, expires_at_value
|
|
341
|
+
) RETURNING * INTO invitation;
|
|
342
|
+
|
|
343
|
+
result := opengeni_private.organization_invitation_row_json(invitation);
|
|
344
|
+
INSERT INTO organization_membership_operation_receipts (
|
|
345
|
+
account_id, operation_id, action, input_hash, result
|
|
346
|
+
) VALUES (
|
|
347
|
+
account_id_value, operation_id_value, 'invite', input_hash_value, result
|
|
348
|
+
);
|
|
349
|
+
INSERT INTO organization_membership_lifecycle_events (
|
|
350
|
+
account_id, operation_id, actor_membership_id, target_membership_id, kind,
|
|
351
|
+
prior_authorization_revision, resulting_authorization_revision, reason
|
|
352
|
+
) VALUES (
|
|
353
|
+
account_id_value, operation_id_value, actor.id, NULL, 'invite', NULL, NULL, NULL
|
|
354
|
+
);
|
|
355
|
+
RETURN result;
|
|
356
|
+
END
|
|
357
|
+
$body$;
|
|
358
|
+
|
|
359
|
+
CREATE FUNCTION bind_pending_organization_invitations_for_verified_email(
|
|
360
|
+
p_subject_id text,
|
|
361
|
+
p_email text
|
|
362
|
+
) RETURNS integer
|
|
363
|
+
LANGUAGE plpgsql
|
|
364
|
+
SECURITY DEFINER
|
|
365
|
+
SET search_path FROM CURRENT
|
|
366
|
+
AS $body$
|
|
367
|
+
DECLARE
|
|
368
|
+
normalized_email text := lower(btrim(p_email));
|
|
369
|
+
user_id_value text;
|
|
370
|
+
bound_count integer := 0;
|
|
371
|
+
account_id_value uuid;
|
|
372
|
+
BEGIN
|
|
373
|
+
IF p_subject_id IS NULL
|
|
374
|
+
OR p_subject_id IS DISTINCT FROM opengeni_private.current_subject_id()
|
|
375
|
+
OR p_subject_id NOT LIKE 'user:%'
|
|
376
|
+
OR normalized_email IS NULL
|
|
377
|
+
OR octet_length(convert_to(normalized_email, 'UTF8')) NOT BETWEEN 3 AND 320
|
|
378
|
+
THEN
|
|
379
|
+
RAISE EXCEPTION 'verified managed human authority required' USING ERRCODE = '42501';
|
|
380
|
+
END IF;
|
|
381
|
+
user_id_value := substring(p_subject_id FROM 6);
|
|
382
|
+
IF user_id_value IS NULL
|
|
383
|
+
OR octet_length(convert_to(user_id_value, 'UTF8')) NOT BETWEEN 1 AND 1019
|
|
384
|
+
THEN
|
|
385
|
+
RAISE EXCEPTION 'verified managed human authority required' USING ERRCODE = '42501';
|
|
386
|
+
END IF;
|
|
387
|
+
IF NOT EXISTS (
|
|
388
|
+
SELECT 1 FROM auth_users auth_user
|
|
389
|
+
WHERE auth_user.id = user_id_value
|
|
390
|
+
AND auth_user.email_verified IS TRUE
|
|
391
|
+
AND lower(btrim(auth_user.email)) = normalized_email
|
|
392
|
+
) THEN
|
|
393
|
+
RAISE EXCEPTION 'verified managed human authority required' USING ERRCODE = '42501';
|
|
394
|
+
END IF;
|
|
395
|
+
|
|
396
|
+
-- Hold this transaction-scoped fence through the caller's subsequent
|
|
397
|
+
-- pending-invitation check and fallback provisioning decision. Creation
|
|
398
|
+
-- takes the same fence before any organization lock, so neither path can
|
|
399
|
+
-- snapshot an absence while a matching invitation is committing.
|
|
400
|
+
PERFORM pg_catalog.pg_advisory_xact_lock(
|
|
401
|
+
pg_catalog.hashtextextended(
|
|
402
|
+
'organization-invitation-email:' || normalized_email, 0
|
|
403
|
+
)
|
|
404
|
+
);
|
|
405
|
+
PERFORM pg_catalog.set_config(
|
|
406
|
+
'opengeni.organization_tenancy_lifecycle',
|
|
407
|
+
'organization_membership_lifecycle', true
|
|
408
|
+
);
|
|
409
|
+
FOR account_id_value IN
|
|
410
|
+
SELECT DISTINCT invitation.account_id
|
|
411
|
+
FROM organization_membership_invitations invitation
|
|
412
|
+
WHERE invitation.target_subject_id IS NULL
|
|
413
|
+
AND invitation.target_email = normalized_email
|
|
414
|
+
AND invitation.status = 'pending'
|
|
415
|
+
AND invitation.expires_at > pg_catalog.clock_timestamp()
|
|
416
|
+
ORDER BY invitation.account_id
|
|
417
|
+
LOOP
|
|
418
|
+
PERFORM pg_catalog.pg_advisory_xact_lock(
|
|
419
|
+
pg_catalog.hashtextextended(
|
|
420
|
+
'organization-membership:' || account_id_value::text, 0
|
|
421
|
+
)
|
|
422
|
+
);
|
|
423
|
+
PERFORM 1 FROM managed_accounts account
|
|
424
|
+
WHERE account.id = account_id_value FOR KEY SHARE;
|
|
425
|
+
WITH bound AS (
|
|
426
|
+
UPDATE organization_membership_invitations invitation SET
|
|
427
|
+
target_subject_id = p_subject_id,
|
|
428
|
+
revision = invitation.revision + 1,
|
|
429
|
+
updated_at = pg_catalog.clock_timestamp()
|
|
430
|
+
WHERE invitation.account_id = account_id_value
|
|
431
|
+
AND invitation.target_subject_id IS NULL
|
|
432
|
+
AND invitation.target_email = normalized_email
|
|
433
|
+
AND invitation.status = 'pending'
|
|
434
|
+
AND invitation.expires_at > pg_catalog.clock_timestamp()
|
|
435
|
+
AND NOT EXISTS (
|
|
436
|
+
SELECT 1 FROM organization_membership_invitations existing
|
|
437
|
+
WHERE existing.account_id = invitation.account_id
|
|
438
|
+
AND existing.target_subject_id = p_subject_id
|
|
439
|
+
AND existing.status = 'pending'
|
|
440
|
+
)
|
|
441
|
+
RETURNING invitation.id, invitation.account_id, invitation.revision
|
|
442
|
+
), recorded AS (
|
|
443
|
+
INSERT INTO organization_invitation_binding_events (
|
|
444
|
+
account_id, invitation_id, target_subject_id, resulting_revision
|
|
445
|
+
)
|
|
446
|
+
SELECT account_id, id, p_subject_id, revision FROM bound
|
|
447
|
+
RETURNING 1
|
|
448
|
+
)
|
|
449
|
+
SELECT bound_count + pg_catalog.count(*)::integer
|
|
450
|
+
INTO bound_count FROM recorded;
|
|
451
|
+
END LOOP;
|
|
452
|
+
RETURN bound_count;
|
|
453
|
+
END
|
|
454
|
+
$body$;
|
|
455
|
+
|
|
456
|
+
CREATE FUNCTION has_pending_organization_invitation_for_subject(p_subject_id text)
|
|
457
|
+
RETURNS boolean
|
|
458
|
+
LANGUAGE plpgsql
|
|
459
|
+
SECURITY DEFINER
|
|
460
|
+
SET search_path FROM CURRENT
|
|
461
|
+
AS $body$
|
|
462
|
+
BEGIN
|
|
463
|
+
IF p_subject_id IS NULL
|
|
464
|
+
OR p_subject_id IS DISTINCT FROM opengeni_private.current_subject_id()
|
|
465
|
+
OR p_subject_id NOT LIKE 'user:%'
|
|
466
|
+
THEN
|
|
467
|
+
RAISE EXCEPTION 'managed human subject authority required' USING ERRCODE = '42501';
|
|
468
|
+
END IF;
|
|
469
|
+
PERFORM pg_catalog.set_config(
|
|
470
|
+
'opengeni.organization_tenancy_lifecycle',
|
|
471
|
+
'organization_membership_lifecycle', true
|
|
472
|
+
);
|
|
473
|
+
RETURN EXISTS (
|
|
474
|
+
SELECT 1 FROM organization_membership_invitations invitation
|
|
475
|
+
WHERE invitation.target_subject_id = p_subject_id
|
|
476
|
+
AND invitation.status = 'pending'
|
|
477
|
+
AND invitation.expires_at > pg_catalog.clock_timestamp()
|
|
478
|
+
);
|
|
479
|
+
END
|
|
480
|
+
$body$;
|
|
481
|
+
|
|
482
|
+
CREATE FUNCTION accept_organization_invitation_v2(p_command jsonb)
|
|
483
|
+
RETURNS jsonb
|
|
484
|
+
LANGUAGE plpgsql
|
|
485
|
+
SECURITY DEFINER
|
|
486
|
+
SET search_path FROM CURRENT
|
|
487
|
+
AS $body$
|
|
488
|
+
DECLARE
|
|
489
|
+
account_id_value uuid := nullif(p_command ->> 'organizationId', '')::uuid;
|
|
490
|
+
actor_subject text := p_command ->> 'actorSubjectId';
|
|
491
|
+
invitation_id_value uuid := nullif(p_command ->> 'invitationId', '')::uuid;
|
|
492
|
+
invitation organization_membership_invitations%ROWTYPE;
|
|
493
|
+
result jsonb;
|
|
494
|
+
default_member_permissions jsonb := '[
|
|
495
|
+
"workspace:read", "sessions:create", "sessions:read", "sessions:control",
|
|
496
|
+
"files:upload", "files:read", "documents:manage", "documents:search",
|
|
497
|
+
"scheduled_tasks:manage", "scheduled_tasks:run", "github:use",
|
|
498
|
+
"variable-sets:list", "variable-sets:read", "variable-sets:write",
|
|
499
|
+
"variable-sets:attach", "variable-sets:use", "secrets:list", "secrets:write",
|
|
500
|
+
"goals:manage"
|
|
501
|
+
]'::jsonb;
|
|
502
|
+
BEGIN
|
|
503
|
+
IF account_id_value IS NULL
|
|
504
|
+
OR actor_subject IS NULL
|
|
505
|
+
OR actor_subject IS DISTINCT FROM opengeni_private.current_subject_id()
|
|
506
|
+
OR account_id_value IS DISTINCT FROM opengeni_private.current_account_id()
|
|
507
|
+
OR invitation_id_value IS NULL
|
|
508
|
+
THEN
|
|
509
|
+
RAISE EXCEPTION 'organization invitation acceptance authority is invalid'
|
|
510
|
+
USING ERRCODE = '42501';
|
|
511
|
+
END IF;
|
|
512
|
+
PERFORM pg_catalog.set_config(
|
|
513
|
+
'opengeni.organization_tenancy_lifecycle',
|
|
514
|
+
'organization_membership_lifecycle', true
|
|
515
|
+
);
|
|
516
|
+
result := organization_membership_command(p_command);
|
|
517
|
+
|
|
518
|
+
SELECT * INTO invitation
|
|
519
|
+
FROM organization_membership_invitations candidate
|
|
520
|
+
WHERE candidate.account_id = account_id_value
|
|
521
|
+
AND candidate.id = invitation_id_value;
|
|
522
|
+
IF NOT FOUND OR invitation.target_subject_id IS DISTINCT FROM actor_subject THEN
|
|
523
|
+
RAISE EXCEPTION 'organization invitation not found' USING ERRCODE = 'P0002';
|
|
524
|
+
END IF;
|
|
525
|
+
|
|
526
|
+
IF cardinality(invitation.initial_workspace_ids) > 0 THEN
|
|
527
|
+
IF (
|
|
528
|
+
SELECT pg_catalog.count(*)::integer
|
|
529
|
+
FROM workspaces workspace
|
|
530
|
+
WHERE workspace.account_id = account_id_value
|
|
531
|
+
AND workspace.id = ANY(invitation.initial_workspace_ids)
|
|
532
|
+
AND NOT EXISTS (
|
|
533
|
+
SELECT 1 FROM organization_memberships membership
|
|
534
|
+
WHERE membership.account_id = account_id_value
|
|
535
|
+
AND membership.personal_workspace_id = workspace.id
|
|
536
|
+
)
|
|
537
|
+
) IS DISTINCT FROM cardinality(invitation.initial_workspace_ids)
|
|
538
|
+
THEN
|
|
539
|
+
RAISE EXCEPTION 'initial workspace access is no longer available'
|
|
540
|
+
USING ERRCODE = '55000';
|
|
541
|
+
END IF;
|
|
542
|
+
INSERT INTO workspace_memberships (
|
|
543
|
+
account_id, workspace_id, subject_id, subject_label, role, permissions
|
|
544
|
+
)
|
|
545
|
+
SELECT
|
|
546
|
+
account_id_value, workspace_id, actor_subject,
|
|
547
|
+
coalesce(invitation.target_name, invitation.target_email),
|
|
548
|
+
'member', default_member_permissions
|
|
549
|
+
FROM pg_catalog.unnest(invitation.initial_workspace_ids) workspace_id
|
|
550
|
+
ON CONFLICT (subject_id, workspace_id) DO NOTHING;
|
|
551
|
+
END IF;
|
|
552
|
+
RETURN result;
|
|
553
|
+
END
|
|
554
|
+
$body$;
|
|
555
|
+
|
|
556
|
+
DO $search_paths$
|
|
557
|
+
DECLARE data_schema text := pg_catalog.current_schema();
|
|
558
|
+
BEGIN
|
|
559
|
+
EXECUTE format(
|
|
560
|
+
'ALTER FUNCTION %I.create_organization_invitation_v2(jsonb) SET search_path = pg_catalog, %I, pg_temp',
|
|
561
|
+
data_schema, data_schema
|
|
562
|
+
);
|
|
563
|
+
EXECUTE format(
|
|
564
|
+
'ALTER FUNCTION %I.bind_pending_organization_invitations_for_verified_email(text,text) SET search_path = pg_catalog, %I, pg_temp',
|
|
565
|
+
data_schema, data_schema
|
|
566
|
+
);
|
|
567
|
+
EXECUTE format(
|
|
568
|
+
'ALTER FUNCTION %I.has_pending_organization_invitation_for_subject(text) SET search_path = pg_catalog, %I, pg_temp',
|
|
569
|
+
data_schema, data_schema
|
|
570
|
+
);
|
|
571
|
+
EXECUTE format(
|
|
572
|
+
'ALTER FUNCTION %I.accept_organization_invitation_v2(jsonb) SET search_path = pg_catalog, %I, pg_temp',
|
|
573
|
+
data_schema, data_schema
|
|
574
|
+
);
|
|
575
|
+
END
|
|
576
|
+
$search_paths$;
|
|
577
|
+
|
|
578
|
+
REVOKE ALL ON FUNCTION create_organization_invitation_v2(jsonb) FROM PUBLIC;
|
|
579
|
+
REVOKE ALL ON FUNCTION bind_pending_organization_invitations_for_verified_email(text,text)
|
|
580
|
+
FROM PUBLIC;
|
|
581
|
+
REVOKE ALL ON FUNCTION has_pending_organization_invitation_for_subject(text) FROM PUBLIC;
|
|
582
|
+
REVOKE ALL ON FUNCTION accept_organization_invitation_v2(jsonb) FROM PUBLIC;
|
|
583
|
+
REVOKE ALL ON TABLE organization_invitation_binding_events FROM PUBLIC;
|
|
584
|
+
|
|
585
|
+
CREATE TRIGGER organization_invitation_binding_events_immutable
|
|
586
|
+
BEFORE UPDATE OR DELETE ON organization_invitation_binding_events
|
|
587
|
+
FOR EACH ROW EXECUTE FUNCTION opengeni_private.organization_membership_history_immutable();
|
|
588
|
+
|
|
589
|
+
DO $runtime_grants$
|
|
590
|
+
BEGIN
|
|
591
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
592
|
+
REVOKE ALL ON TABLE organization_invitation_binding_events FROM opengeni_app;
|
|
593
|
+
GRANT EXECUTE ON FUNCTION create_organization_invitation_v2(jsonb) TO opengeni_app;
|
|
594
|
+
GRANT EXECUTE ON FUNCTION
|
|
595
|
+
bind_pending_organization_invitations_for_verified_email(text,text) TO opengeni_app;
|
|
596
|
+
GRANT EXECUTE ON FUNCTION
|
|
597
|
+
has_pending_organization_invitation_for_subject(text) TO opengeni_app;
|
|
598
|
+
GRANT EXECUTE ON FUNCTION accept_organization_invitation_v2(jsonb) TO opengeni_app;
|
|
599
|
+
END IF;
|
|
600
|
+
END
|
|
601
|
+
$runtime_grants$;
|
|
602
|
+
|
|
603
|
+
COMMENT ON FUNCTION bind_pending_organization_invitations_for_verified_email(text,text) IS
|
|
604
|
+
'Binds active pending email invitations only after the exact Better Auth user has verified that email.';
|
|
605
|
+
COMMENT ON TABLE organization_invitation_binding_events IS
|
|
606
|
+
'Immutable exact-subject and revision evidence for verified-email invitation binding.';
|