@opengeni/db 1.0.1 → 3.1.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-LTG3S6ZY.js → chunk-EULAN2NP.js} +2 -2
- package/dist/{chunk-CUPM3FGB.js → chunk-NJVZPWXY.js} +3 -3
- package/dist/{chunk-4SR4HKKA.js → chunk-NMUBV7Z5.js} +19 -11
- package/dist/chunk-NMUBV7Z5.js.map +1 -0
- package/dist/chunk-OF5OBB76.js +4457 -0
- package/dist/chunk-OF5OBB76.js.map +1 -0
- package/dist/{chunk-6ZYKCRFO.js → chunk-PR2WMLOQ.js} +7580 -4417
- package/dist/chunk-PR2WMLOQ.js.map +1 -0
- package/dist/{chunk-ZWREYEWA.js → chunk-SCPUJS53.js} +41 -4
- package/dist/chunk-SCPUJS53.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-TIKF3KTI.js +383 -0
- package/dist/chunk-TIKF3KTI.js.map +1 -0
- package/dist/chunk-ZZ5LJEFX.js +516 -0
- package/dist/chunk-ZZ5LJEFX.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 +41 -0
- package/dist/connection-token-resolver.d.ts +30 -1
- package/dist/database.d.ts +19 -0
- 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 +1595 -81
- package/dist/index.js +21571 -8126
- 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 +22 -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 +41 -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 +153 -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 +1 -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/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 +81 -0
- package/src/connection-token-resolver.ts +263 -31
- package/src/database.ts +75 -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 +11651 -1548
- 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 +985 -0
- package/src/pr-review.ts +890 -0
- package/src/preference-registry.ts +31 -1
- package/src/provision-roles.ts +877 -6
- package/src/runtime-posture-cli.ts +2 -0
- package/src/runtime-posture.ts +1291 -76
- package/src/schema.ts +3303 -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 +437 -84
- package/src/session-realtime-context.ts +3 -3
- package/src/session-realtime-ledger.ts +22 -11
- package/src/session-realtime-mirror.ts +1 -1
- package/src/session-tenancy.ts +403 -49
- package/src/session-tool-call-settlement.ts +38 -29
- package/src/slack-routing-personal-workspace.ts +67 -0
- package/src/slack-routing.ts +469 -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 +17 -1
- 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-LTG3S6ZY.js.map → chunk-EULAN2NP.js.map} +0 -0
- /package/dist/{chunk-CUPM3FGB.js.map → chunk-NJVZPWXY.js.map} +0 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Self-service organization creation for an authenticated managed human. One
|
|
3
|
+
-- lifecycle seam creates the organization, its first shared workspace, the
|
|
4
|
+
-- caller's personal workspace, and both authority anchors atomically.
|
|
5
|
+
|
|
6
|
+
SET LOCAL lock_timeout = '5s';
|
|
7
|
+
SET LOCAL statement_timeout = '10min';
|
|
8
|
+
|
|
9
|
+
CREATE OR REPLACE FUNCTION create_managed_organization(
|
|
10
|
+
p_subject_id text,
|
|
11
|
+
p_subject_label text,
|
|
12
|
+
p_name text,
|
|
13
|
+
p_operation_id uuid
|
|
14
|
+
) RETURNS jsonb
|
|
15
|
+
LANGUAGE plpgsql
|
|
16
|
+
SECURITY DEFINER
|
|
17
|
+
SET search_path FROM CURRENT
|
|
18
|
+
AS $body$
|
|
19
|
+
DECLARE
|
|
20
|
+
requested_name text := btrim(p_name);
|
|
21
|
+
account_row managed_accounts%ROWTYPE;
|
|
22
|
+
shared_workspace workspaces%ROWTYPE;
|
|
23
|
+
personal_workspace workspaces%ROWTYPE;
|
|
24
|
+
membership_row organization_memberships%ROWTYPE;
|
|
25
|
+
workspace_permissions jsonb := pg_catalog.jsonb_build_array(
|
|
26
|
+
'workspace:read', 'workspace:admin', 'members:manage',
|
|
27
|
+
'sessions:create', 'sessions:read', 'sessions:control',
|
|
28
|
+
'files:upload', 'files:read', 'documents:manage', 'documents:search',
|
|
29
|
+
'scheduled_tasks:manage', 'scheduled_tasks:run',
|
|
30
|
+
'github:manage', 'github:use', 'api_keys:manage',
|
|
31
|
+
'connections:read', 'connections:write',
|
|
32
|
+
'variable-sets:list', 'variable-sets:read', 'variable-sets:write',
|
|
33
|
+
'variable-sets:manage', 'variable-sets:attach', 'variable-sets:use',
|
|
34
|
+
'secrets:list', 'secrets:read', 'secrets:write',
|
|
35
|
+
'mcp_servers:attach', 'goals:manage',
|
|
36
|
+
'enrollments:read', 'enrollments:manage',
|
|
37
|
+
'artifacts:read', 'artifacts:publish'
|
|
38
|
+
);
|
|
39
|
+
BEGIN
|
|
40
|
+
IF p_operation_id IS NULL
|
|
41
|
+
OR p_subject_id IS NULL
|
|
42
|
+
OR p_subject_id NOT LIKE 'user:%'
|
|
43
|
+
OR p_subject_id IS DISTINCT FROM opengeni_private.current_subject_id()
|
|
44
|
+
OR requested_name IS NULL
|
|
45
|
+
OR length(requested_name) NOT BETWEEN 1 AND 120
|
|
46
|
+
OR NOT EXISTS (
|
|
47
|
+
SELECT 1 FROM auth_users auth_user
|
|
48
|
+
WHERE auth_user.id = substr(p_subject_id, length('user:') + 1)
|
|
49
|
+
)
|
|
50
|
+
THEN
|
|
51
|
+
RAISE EXCEPTION 'managed organization creation input is invalid'
|
|
52
|
+
USING ERRCODE = '22023';
|
|
53
|
+
END IF;
|
|
54
|
+
|
|
55
|
+
-- One operation id is one durable creation attempt. Serialize before the
|
|
56
|
+
-- replay lookup so concurrent retries cannot both miss the account row and
|
|
57
|
+
-- race its unique external identity.
|
|
58
|
+
PERFORM pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
|
|
59
|
+
'managed-organization-creation:' || p_operation_id::text, 0
|
|
60
|
+
));
|
|
61
|
+
|
|
62
|
+
SELECT * INTO account_row
|
|
63
|
+
FROM managed_accounts account
|
|
64
|
+
WHERE account.external_source = 'opengeni:managed-organization'
|
|
65
|
+
AND account.external_id = p_operation_id::text
|
|
66
|
+
FOR UPDATE;
|
|
67
|
+
|
|
68
|
+
IF FOUND THEN
|
|
69
|
+
IF account_row.name IS DISTINCT FROM requested_name THEN
|
|
70
|
+
RAISE EXCEPTION 'organization operation id was reused with different input'
|
|
71
|
+
USING ERRCODE = '23505';
|
|
72
|
+
END IF;
|
|
73
|
+
IF NOT EXISTS (
|
|
74
|
+
SELECT 1
|
|
75
|
+
FROM organization_memberships membership
|
|
76
|
+
WHERE membership.account_id = account_row.id
|
|
77
|
+
AND membership.subject_id = p_subject_id
|
|
78
|
+
AND membership.role = 'owner'
|
|
79
|
+
AND membership.status = 'active'
|
|
80
|
+
) THEN
|
|
81
|
+
RAISE EXCEPTION 'organization operation id belongs to another subject'
|
|
82
|
+
USING ERRCODE = '23505';
|
|
83
|
+
END IF;
|
|
84
|
+
SELECT * INTO shared_workspace
|
|
85
|
+
FROM workspaces workspace
|
|
86
|
+
WHERE workspace.account_id = account_row.id
|
|
87
|
+
AND workspace.external_source = 'opengeni:managed-organization-default';
|
|
88
|
+
IF NOT FOUND THEN
|
|
89
|
+
RAISE EXCEPTION 'managed organization creation is incomplete' USING ERRCODE = '55000';
|
|
90
|
+
END IF;
|
|
91
|
+
ELSE
|
|
92
|
+
INSERT INTO managed_accounts (name, external_source, external_id)
|
|
93
|
+
VALUES (requested_name, 'opengeni:managed-organization', p_operation_id::text)
|
|
94
|
+
RETURNING * INTO account_row;
|
|
95
|
+
|
|
96
|
+
PERFORM pg_catalog.set_config('opengeni.account_id', account_row.id::text, true);
|
|
97
|
+
INSERT INTO workspaces (account_id, name, slug, external_source, external_id)
|
|
98
|
+
VALUES (
|
|
99
|
+
account_row.id, 'Default workspace', NULL,
|
|
100
|
+
'opengeni:managed-organization-default', account_row.id::text
|
|
101
|
+
) RETURNING * INTO shared_workspace;
|
|
102
|
+
INSERT INTO workspace_inference_controls (workspace_id, account_id)
|
|
103
|
+
VALUES (shared_workspace.id, account_row.id);
|
|
104
|
+
|
|
105
|
+
INSERT INTO workspaces (account_id, name, slug, external_source, external_id)
|
|
106
|
+
VALUES (
|
|
107
|
+
account_row.id, 'Personal workspace', NULL,
|
|
108
|
+
'opengeni:organization-membership', account_row.id::text || ':' || p_subject_id
|
|
109
|
+
) RETURNING * INTO personal_workspace;
|
|
110
|
+
INSERT INTO workspace_inference_controls (workspace_id, account_id)
|
|
111
|
+
VALUES (personal_workspace.id, account_row.id);
|
|
112
|
+
|
|
113
|
+
PERFORM pg_catalog.set_config(
|
|
114
|
+
'opengeni.organization_tenancy_lifecycle',
|
|
115
|
+
'organization_membership_lifecycle',
|
|
116
|
+
true
|
|
117
|
+
);
|
|
118
|
+
INSERT INTO organization_memberships (
|
|
119
|
+
account_id, subject_id, role, status, personal_workspace_id
|
|
120
|
+
) VALUES (
|
|
121
|
+
account_row.id, p_subject_id, 'owner', 'active', personal_workspace.id
|
|
122
|
+
) RETURNING * INTO membership_row;
|
|
123
|
+
|
|
124
|
+
INSERT INTO workspace_memberships (
|
|
125
|
+
account_id, workspace_id, subject_id, subject_label, role, permissions
|
|
126
|
+
) VALUES (
|
|
127
|
+
account_row.id, shared_workspace.id, p_subject_id,
|
|
128
|
+
nullif(btrim(p_subject_label), ''), 'owner', workspace_permissions
|
|
129
|
+
);
|
|
130
|
+
END IF;
|
|
131
|
+
|
|
132
|
+
RETURN pg_catalog.jsonb_build_object(
|
|
133
|
+
'organization', pg_catalog.jsonb_build_object(
|
|
134
|
+
'id', account_row.id,
|
|
135
|
+
'name', account_row.name,
|
|
136
|
+
'createdAt', account_row.created_at,
|
|
137
|
+
'updatedAt', account_row.updated_at
|
|
138
|
+
),
|
|
139
|
+
'workspaceId', shared_workspace.id
|
|
140
|
+
);
|
|
141
|
+
END
|
|
142
|
+
$body$;
|
|
143
|
+
|
|
144
|
+
REVOKE ALL ON FUNCTION create_managed_organization(text, text, text, uuid) FROM PUBLIC;
|
|
145
|
+
DO $body$
|
|
146
|
+
DECLARE
|
|
147
|
+
target_schema text := pg_catalog.current_schema();
|
|
148
|
+
BEGIN
|
|
149
|
+
EXECUTE pg_catalog.format(
|
|
150
|
+
'ALTER FUNCTION %I.create_managed_organization(text,text,text,uuid) SET search_path = pg_catalog, %I, pg_temp',
|
|
151
|
+
target_schema, target_schema
|
|
152
|
+
);
|
|
153
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
154
|
+
EXECUTE pg_catalog.format(
|
|
155
|
+
'GRANT EXECUTE ON FUNCTION %I.create_managed_organization(text,text,text,uuid) TO opengeni_app',
|
|
156
|
+
target_schema
|
|
157
|
+
);
|
|
158
|
+
END IF;
|
|
159
|
+
END
|
|
160
|
+
$body$;
|
|
161
|
+
|
|
162
|
+
COMMENT ON FUNCTION create_managed_organization(text, text, text, uuid) IS
|
|
163
|
+
'Creates one managed organization and its initial authority graph atomically.';
|
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Active direct managed-human organization owners and administrators may
|
|
3
|
+
-- administer a shared workspace without acquiring operational workspace
|
|
4
|
+
-- access. Personal workspaces are excluded at the database capability.
|
|
5
|
+
|
|
6
|
+
SET LOCAL lock_timeout = '5s';
|
|
7
|
+
SET LOCAL statement_timeout = '5min';
|
|
8
|
+
|
|
9
|
+
ALTER TABLE organization_membership_operation_receipts
|
|
10
|
+
DROP CONSTRAINT organization_membership_operation_receipts_action_check;
|
|
11
|
+
ALTER TABLE organization_membership_operation_receipts
|
|
12
|
+
ADD CONSTRAINT organization_membership_operation_receipts_action_check CHECK (
|
|
13
|
+
action IN (
|
|
14
|
+
'invite', 'accept', 'revoke_invitation', 'change_role', 'suspend',
|
|
15
|
+
'reactivate', 'offboard', 'retention', 'create_workspace'
|
|
16
|
+
)
|
|
17
|
+
) NOT VALID;
|
|
18
|
+
ALTER TABLE organization_membership_operation_receipts
|
|
19
|
+
VALIDATE CONSTRAINT organization_membership_operation_receipts_action_check;
|
|
20
|
+
|
|
21
|
+
-- The organization-only exception to the last durable workspace-admin guard
|
|
22
|
+
-- must carry route provenance, not merely an organization role. This
|
|
23
|
+
-- transaction-scoped capability is opened only by the direct managed-cookie
|
|
24
|
+
-- organization route and is invisible to ordinary/delegated workspace paths.
|
|
25
|
+
CREATE TABLE organization_shared_workspace_administration_capabilities (
|
|
26
|
+
backend_pid integer NOT NULL,
|
|
27
|
+
transaction_id xid8 NOT NULL,
|
|
28
|
+
capability_id uuid NOT NULL,
|
|
29
|
+
account_id uuid NOT NULL,
|
|
30
|
+
workspace_id uuid NOT NULL,
|
|
31
|
+
actor_subject_id text NOT NULL,
|
|
32
|
+
PRIMARY KEY (backend_pid, transaction_id, capability_id)
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
ALTER TABLE organization_shared_workspace_administration_capabilities ENABLE ROW LEVEL SECURITY;
|
|
36
|
+
ALTER TABLE organization_shared_workspace_administration_capabilities FORCE ROW LEVEL SECURITY;
|
|
37
|
+
|
|
38
|
+
DO $capability_policy$
|
|
39
|
+
DECLARE
|
|
40
|
+
target_schema text := pg_catalog.current_schema();
|
|
41
|
+
migration_owner text := current_user;
|
|
42
|
+
BEGIN
|
|
43
|
+
EXECUTE pg_catalog.format(
|
|
44
|
+
'CREATE POLICY organization_shared_workspace_administration_capability_owner '
|
|
45
|
+
|| 'ON %I.organization_shared_workspace_administration_capabilities '
|
|
46
|
+
|| 'FOR ALL USING (current_user = %L) WITH CHECK (current_user = %L)',
|
|
47
|
+
target_schema, migration_owner, migration_owner
|
|
48
|
+
);
|
|
49
|
+
END
|
|
50
|
+
$capability_policy$;
|
|
51
|
+
|
|
52
|
+
CREATE FUNCTION opengeni_private.assert_organization_shared_workspace_administrator(
|
|
53
|
+
p_account_id uuid,
|
|
54
|
+
p_workspace_id uuid,
|
|
55
|
+
p_actor_subject_id text
|
|
56
|
+
) RETURNS void
|
|
57
|
+
LANGUAGE plpgsql
|
|
58
|
+
SET search_path FROM CURRENT
|
|
59
|
+
AS $body$
|
|
60
|
+
BEGIN
|
|
61
|
+
IF p_account_id IS NULL
|
|
62
|
+
OR p_workspace_id IS NULL
|
|
63
|
+
OR p_actor_subject_id IS NULL
|
|
64
|
+
OR p_actor_subject_id NOT LIKE 'user:%'
|
|
65
|
+
OR p_actor_subject_id IS DISTINCT FROM opengeni_private.current_subject_id()
|
|
66
|
+
OR p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
|
|
67
|
+
THEN
|
|
68
|
+
RAISE EXCEPTION 'organization shared-workspace administration authority is invalid'
|
|
69
|
+
USING ERRCODE = '42501';
|
|
70
|
+
END IF;
|
|
71
|
+
|
|
72
|
+
-- Serialize role/personal-workspace changes with this transaction. The lock
|
|
73
|
+
-- has no row-level dependency and therefore keeps the canonical lifecycle
|
|
74
|
+
-- ordering intact.
|
|
75
|
+
PERFORM pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
|
|
76
|
+
'organization-membership:' || p_account_id::text, 0
|
|
77
|
+
));
|
|
78
|
+
|
|
79
|
+
IF NOT EXISTS (
|
|
80
|
+
SELECT 1
|
|
81
|
+
FROM organization_memberships actor
|
|
82
|
+
WHERE actor.account_id = p_account_id
|
|
83
|
+
AND actor.subject_id = p_actor_subject_id
|
|
84
|
+
AND actor.status = 'active'
|
|
85
|
+
AND actor.role IN ('owner', 'admin')
|
|
86
|
+
) THEN
|
|
87
|
+
RAISE EXCEPTION 'organization administration required' USING ERRCODE = '42501';
|
|
88
|
+
END IF;
|
|
89
|
+
|
|
90
|
+
IF NOT EXISTS (
|
|
91
|
+
SELECT 1
|
|
92
|
+
FROM workspaces workspace
|
|
93
|
+
WHERE workspace.account_id = p_account_id
|
|
94
|
+
AND workspace.id = p_workspace_id
|
|
95
|
+
AND NOT EXISTS (
|
|
96
|
+
SELECT 1
|
|
97
|
+
FROM organization_memberships membership
|
|
98
|
+
WHERE membership.account_id = p_account_id
|
|
99
|
+
AND membership.personal_workspace_id = workspace.id
|
|
100
|
+
)
|
|
101
|
+
) THEN
|
|
102
|
+
-- Cross-organization, missing, and Personal workspaces intentionally share
|
|
103
|
+
-- one non-enumerating result.
|
|
104
|
+
RAISE EXCEPTION 'shared workspace not found' USING ERRCODE = 'P0002';
|
|
105
|
+
END IF;
|
|
106
|
+
END
|
|
107
|
+
$body$;
|
|
108
|
+
|
|
109
|
+
CREATE FUNCTION assert_organization_shared_workspace_administrator(
|
|
110
|
+
p_account_id uuid,
|
|
111
|
+
p_workspace_id uuid,
|
|
112
|
+
p_actor_subject_id text
|
|
113
|
+
) RETURNS void
|
|
114
|
+
LANGUAGE plpgsql
|
|
115
|
+
SECURITY DEFINER
|
|
116
|
+
SET search_path FROM CURRENT
|
|
117
|
+
AS $body$
|
|
118
|
+
BEGIN
|
|
119
|
+
PERFORM opengeni_private.assert_organization_shared_workspace_administrator(
|
|
120
|
+
p_account_id, p_workspace_id, p_actor_subject_id
|
|
121
|
+
);
|
|
122
|
+
END
|
|
123
|
+
$body$;
|
|
124
|
+
|
|
125
|
+
CREATE FUNCTION open_organization_shared_workspace_administration_capability(
|
|
126
|
+
p_account_id uuid,
|
|
127
|
+
p_workspace_id uuid,
|
|
128
|
+
p_actor_subject_id text
|
|
129
|
+
) RETURNS uuid
|
|
130
|
+
LANGUAGE plpgsql
|
|
131
|
+
SECURITY DEFINER
|
|
132
|
+
SET search_path FROM CURRENT
|
|
133
|
+
AS $body$
|
|
134
|
+
DECLARE
|
|
135
|
+
capability_id_value uuid := pg_catalog.gen_random_uuid();
|
|
136
|
+
BEGIN
|
|
137
|
+
PERFORM opengeni_private.assert_organization_shared_workspace_administrator(
|
|
138
|
+
p_account_id, p_workspace_id, p_actor_subject_id
|
|
139
|
+
);
|
|
140
|
+
INSERT INTO organization_shared_workspace_administration_capabilities (
|
|
141
|
+
backend_pid, transaction_id, capability_id,
|
|
142
|
+
account_id, workspace_id, actor_subject_id
|
|
143
|
+
) VALUES (
|
|
144
|
+
pg_catalog.pg_backend_pid(), pg_catalog.pg_current_xact_id(), capability_id_value,
|
|
145
|
+
p_account_id, p_workspace_id, p_actor_subject_id
|
|
146
|
+
);
|
|
147
|
+
PERFORM pg_catalog.set_config(
|
|
148
|
+
'opengeni.organization_shared_workspace_administration_capability',
|
|
149
|
+
capability_id_value::text,
|
|
150
|
+
true
|
|
151
|
+
);
|
|
152
|
+
RETURN capability_id_value;
|
|
153
|
+
END
|
|
154
|
+
$body$;
|
|
155
|
+
|
|
156
|
+
CREATE FUNCTION close_organization_shared_workspace_administration_capability(
|
|
157
|
+
p_capability_id uuid
|
|
158
|
+
) RETURNS void
|
|
159
|
+
LANGUAGE plpgsql
|
|
160
|
+
SECURITY DEFINER
|
|
161
|
+
SET search_path FROM CURRENT
|
|
162
|
+
AS $body$
|
|
163
|
+
BEGIN
|
|
164
|
+
DELETE FROM organization_shared_workspace_administration_capabilities capability
|
|
165
|
+
WHERE capability.backend_pid = pg_catalog.pg_backend_pid()
|
|
166
|
+
AND capability.transaction_id = pg_catalog.pg_current_xact_id()
|
|
167
|
+
AND capability.capability_id = p_capability_id;
|
|
168
|
+
IF pg_catalog.current_setting(
|
|
169
|
+
'opengeni.organization_shared_workspace_administration_capability', true
|
|
170
|
+
) = p_capability_id::text THEN
|
|
171
|
+
PERFORM pg_catalog.set_config(
|
|
172
|
+
'opengeni.organization_shared_workspace_administration_capability', '', true
|
|
173
|
+
);
|
|
174
|
+
END IF;
|
|
175
|
+
END
|
|
176
|
+
$body$;
|
|
177
|
+
|
|
178
|
+
CREATE FUNCTION create_organization_shared_workspace(
|
|
179
|
+
p_account_id uuid,
|
|
180
|
+
p_actor_subject_id text,
|
|
181
|
+
p_name text,
|
|
182
|
+
p_slug text,
|
|
183
|
+
p_agent_instructions text,
|
|
184
|
+
p_operation_id uuid
|
|
185
|
+
) RETURNS jsonb
|
|
186
|
+
LANGUAGE plpgsql
|
|
187
|
+
SECURITY DEFINER
|
|
188
|
+
SET search_path FROM CURRENT
|
|
189
|
+
AS $body$
|
|
190
|
+
DECLARE
|
|
191
|
+
requested_name text := pg_catalog.btrim(p_name);
|
|
192
|
+
requested_slug text := nullif(pg_catalog.btrim(p_slug), '');
|
|
193
|
+
requested_agent_instructions text := nullif(
|
|
194
|
+
pg_catalog.btrim(p_agent_instructions), ''
|
|
195
|
+
);
|
|
196
|
+
input_hash_value text;
|
|
197
|
+
receipt_row organization_membership_operation_receipts%ROWTYPE;
|
|
198
|
+
workspace_row workspaces%ROWTYPE;
|
|
199
|
+
result_value jsonb;
|
|
200
|
+
BEGIN
|
|
201
|
+
IF p_account_id IS NULL
|
|
202
|
+
OR p_operation_id IS NULL
|
|
203
|
+
OR p_actor_subject_id IS NULL
|
|
204
|
+
OR p_actor_subject_id NOT LIKE 'user:%'
|
|
205
|
+
OR p_actor_subject_id IS DISTINCT FROM opengeni_private.current_subject_id()
|
|
206
|
+
OR p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
|
|
207
|
+
OR requested_name IS NULL
|
|
208
|
+
OR pg_catalog.length(requested_name) NOT BETWEEN 1 AND 120
|
|
209
|
+
OR (requested_slug IS NOT NULL AND pg_catalog.length(requested_slug) > 120)
|
|
210
|
+
THEN
|
|
211
|
+
RAISE EXCEPTION 'organization shared-workspace creation input is invalid'
|
|
212
|
+
USING ERRCODE = '22023';
|
|
213
|
+
END IF;
|
|
214
|
+
|
|
215
|
+
-- Match the organization lifecycle lock order: advisory organization lock,
|
|
216
|
+
-- account key-share fence, then organization membership and workspace rows.
|
|
217
|
+
PERFORM pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
|
|
218
|
+
'organization-membership:' || p_account_id::text, 0
|
|
219
|
+
));
|
|
220
|
+
PERFORM 1 FROM managed_accounts account
|
|
221
|
+
WHERE account.id = p_account_id FOR KEY SHARE;
|
|
222
|
+
IF NOT FOUND THEN
|
|
223
|
+
RAISE EXCEPTION 'organization not found' USING ERRCODE = 'P0002';
|
|
224
|
+
END IF;
|
|
225
|
+
IF NOT EXISTS (
|
|
226
|
+
SELECT 1 FROM organization_memberships actor
|
|
227
|
+
WHERE actor.account_id = p_account_id
|
|
228
|
+
AND actor.subject_id = p_actor_subject_id
|
|
229
|
+
AND actor.status = 'active'
|
|
230
|
+
AND actor.role IN ('owner', 'admin')
|
|
231
|
+
) THEN
|
|
232
|
+
RAISE EXCEPTION 'organization administration required' USING ERRCODE = '42501';
|
|
233
|
+
END IF;
|
|
234
|
+
|
|
235
|
+
input_hash_value := pg_catalog.encode(pg_catalog.sha256(pg_catalog.convert_to(
|
|
236
|
+
pg_catalog.jsonb_build_object(
|
|
237
|
+
'actorSubjectId', p_actor_subject_id,
|
|
238
|
+
'name', requested_name,
|
|
239
|
+
'slug', requested_slug,
|
|
240
|
+
'agentInstructions', requested_agent_instructions
|
|
241
|
+
)::text,
|
|
242
|
+
'UTF8'
|
|
243
|
+
)), 'hex');
|
|
244
|
+
PERFORM pg_catalog.set_config(
|
|
245
|
+
'opengeni.organization_tenancy_lifecycle',
|
|
246
|
+
'organization_membership_lifecycle',
|
|
247
|
+
true
|
|
248
|
+
);
|
|
249
|
+
SELECT * INTO receipt_row
|
|
250
|
+
FROM organization_membership_operation_receipts receipt
|
|
251
|
+
WHERE receipt.account_id = p_account_id
|
|
252
|
+
AND receipt.operation_id = p_operation_id
|
|
253
|
+
FOR UPDATE;
|
|
254
|
+
IF FOUND THEN
|
|
255
|
+
IF receipt_row.action IS DISTINCT FROM 'create_workspace'
|
|
256
|
+
OR receipt_row.input_hash IS DISTINCT FROM input_hash_value
|
|
257
|
+
THEN
|
|
258
|
+
RAISE EXCEPTION 'organization operation id was reused with different input'
|
|
259
|
+
USING ERRCODE = '23505';
|
|
260
|
+
END IF;
|
|
261
|
+
RETURN receipt_row.result;
|
|
262
|
+
END IF;
|
|
263
|
+
|
|
264
|
+
INSERT INTO workspaces (
|
|
265
|
+
account_id, name, slug, external_source, external_id, agent_instructions
|
|
266
|
+
) VALUES (
|
|
267
|
+
p_account_id, requested_name, requested_slug,
|
|
268
|
+
'opengeni:organization-shared-workspace',
|
|
269
|
+
p_account_id::text || ':' || p_operation_id::text,
|
|
270
|
+
requested_agent_instructions
|
|
271
|
+
) RETURNING * INTO workspace_row;
|
|
272
|
+
INSERT INTO workspace_inference_controls (workspace_id, account_id)
|
|
273
|
+
VALUES (workspace_row.id, p_account_id);
|
|
274
|
+
|
|
275
|
+
result_value := pg_catalog.jsonb_build_object('workspaceId', workspace_row.id);
|
|
276
|
+
INSERT INTO organization_membership_operation_receipts (
|
|
277
|
+
account_id, operation_id, action, input_hash, result
|
|
278
|
+
) VALUES (
|
|
279
|
+
p_account_id, p_operation_id, 'create_workspace', input_hash_value, result_value
|
|
280
|
+
);
|
|
281
|
+
RETURN result_value;
|
|
282
|
+
END
|
|
283
|
+
$body$;
|
|
284
|
+
|
|
285
|
+
CREATE FUNCTION upsert_organization_shared_workspace_member(
|
|
286
|
+
p_account_id uuid,
|
|
287
|
+
p_workspace_id uuid,
|
|
288
|
+
p_actor_subject_id text,
|
|
289
|
+
p_target_membership_id uuid,
|
|
290
|
+
p_subject_label text,
|
|
291
|
+
p_role text,
|
|
292
|
+
p_permissions jsonb,
|
|
293
|
+
p_require_existing boolean
|
|
294
|
+
) RETURNS jsonb
|
|
295
|
+
LANGUAGE plpgsql
|
|
296
|
+
SECURITY DEFINER
|
|
297
|
+
SET search_path FROM CURRENT
|
|
298
|
+
AS $body$
|
|
299
|
+
DECLARE
|
|
300
|
+
target organization_memberships%ROWTYPE;
|
|
301
|
+
access workspace_memberships%ROWTYPE;
|
|
302
|
+
normalized_role text;
|
|
303
|
+
existing_role text;
|
|
304
|
+
BEGIN
|
|
305
|
+
IF p_target_membership_id IS NULL
|
|
306
|
+
OR p_require_existing IS NULL
|
|
307
|
+
OR p_permissions IS NULL
|
|
308
|
+
OR pg_catalog.jsonb_typeof(p_permissions) <> 'array'
|
|
309
|
+
OR pg_catalog.jsonb_array_length(p_permissions) > 128
|
|
310
|
+
OR EXISTS (
|
|
311
|
+
SELECT 1 FROM pg_catalog.jsonb_array_elements(p_permissions) item(value)
|
|
312
|
+
WHERE pg_catalog.jsonb_typeof(item.value) <> 'string'
|
|
313
|
+
OR char_length(item.value #>> '{}') NOT BETWEEN 1 AND 128
|
|
314
|
+
)
|
|
315
|
+
THEN
|
|
316
|
+
RAISE EXCEPTION 'organization shared-workspace member request is invalid'
|
|
317
|
+
USING ERRCODE = '22023';
|
|
318
|
+
END IF;
|
|
319
|
+
|
|
320
|
+
PERFORM opengeni_private.assert_organization_shared_workspace_administrator(
|
|
321
|
+
p_account_id, p_workspace_id, p_actor_subject_id
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
SELECT * INTO target
|
|
325
|
+
FROM organization_memberships membership
|
|
326
|
+
WHERE membership.account_id = p_account_id
|
|
327
|
+
AND membership.id = p_target_membership_id
|
|
328
|
+
AND membership.status = 'active'
|
|
329
|
+
AND membership.subject_id LIKE 'user:%'
|
|
330
|
+
FOR SHARE;
|
|
331
|
+
IF NOT FOUND THEN
|
|
332
|
+
RAISE EXCEPTION 'active organization member not found' USING ERRCODE = 'P0002';
|
|
333
|
+
END IF;
|
|
334
|
+
|
|
335
|
+
SELECT membership.role INTO existing_role
|
|
336
|
+
FROM workspace_memberships membership
|
|
337
|
+
WHERE membership.account_id = p_account_id
|
|
338
|
+
AND membership.workspace_id = p_workspace_id
|
|
339
|
+
AND membership.subject_id = target.subject_id;
|
|
340
|
+
IF p_require_existing AND existing_role IS NULL THEN
|
|
341
|
+
RAISE EXCEPTION 'workspace member not found' USING ERRCODE = 'P0002';
|
|
342
|
+
END IF;
|
|
343
|
+
normalized_role := coalesce(nullif(btrim(p_role), ''), existing_role, 'member');
|
|
344
|
+
IF char_length(normalized_role) NOT BETWEEN 1 AND 64 THEN
|
|
345
|
+
RAISE EXCEPTION 'organization shared-workspace member role is invalid'
|
|
346
|
+
USING ERRCODE = '22023';
|
|
347
|
+
END IF;
|
|
348
|
+
|
|
349
|
+
INSERT INTO workspace_memberships (
|
|
350
|
+
account_id, workspace_id, subject_id, subject_label, role, permissions
|
|
351
|
+
) VALUES (
|
|
352
|
+
p_account_id, p_workspace_id, target.subject_id,
|
|
353
|
+
nullif(btrim(p_subject_label), ''), normalized_role, p_permissions
|
|
354
|
+
)
|
|
355
|
+
ON CONFLICT (subject_id, workspace_id) DO UPDATE SET
|
|
356
|
+
subject_label = coalesce(EXCLUDED.subject_label, workspace_memberships.subject_label),
|
|
357
|
+
role = EXCLUDED.role,
|
|
358
|
+
permissions = EXCLUDED.permissions,
|
|
359
|
+
updated_at = pg_catalog.clock_timestamp()
|
|
360
|
+
RETURNING * INTO access;
|
|
361
|
+
|
|
362
|
+
RETURN pg_catalog.jsonb_build_object(
|
|
363
|
+
'subjectId', access.subject_id,
|
|
364
|
+
'subjectLabel', access.subject_label,
|
|
365
|
+
'role', access.role,
|
|
366
|
+
'permissions', access.permissions,
|
|
367
|
+
'createdAt', access.created_at
|
|
368
|
+
);
|
|
369
|
+
END
|
|
370
|
+
$body$;
|
|
371
|
+
|
|
372
|
+
-- Migration 0278 allowed an organization administrator to remove a workspace
|
|
373
|
+
-- member, but still required another durable workspace administrator to remain.
|
|
374
|
+
-- Once organization administration is a complete shared-workspace control
|
|
375
|
+
-- plane, that durable operational grant is no longer required. Preserve the
|
|
376
|
+
-- guard for workspace-only administrators and exclude Personal workspaces for
|
|
377
|
+
-- both actor paths.
|
|
378
|
+
CREATE OR REPLACE FUNCTION opengeni_private.assert_workspace_membership_removal_actor(
|
|
379
|
+
p_account_id uuid,
|
|
380
|
+
p_workspace_id uuid,
|
|
381
|
+
p_actor_subject text,
|
|
382
|
+
p_target_subject text
|
|
383
|
+
) RETURNS void
|
|
384
|
+
LANGUAGE plpgsql
|
|
385
|
+
AS $body$
|
|
386
|
+
DECLARE
|
|
387
|
+
actor_is_organization_administrator boolean;
|
|
388
|
+
actor_can_administer boolean;
|
|
389
|
+
BEGIN
|
|
390
|
+
IF p_actor_subject = p_target_subject THEN
|
|
391
|
+
RAISE EXCEPTION 'a member cannot remove their own workspace membership'
|
|
392
|
+
USING ERRCODE = '55000';
|
|
393
|
+
END IF;
|
|
394
|
+
IF NOT EXISTS (
|
|
395
|
+
SELECT 1 FROM workspaces workspace
|
|
396
|
+
WHERE workspace.account_id = p_account_id
|
|
397
|
+
AND workspace.id = p_workspace_id
|
|
398
|
+
AND NOT EXISTS (
|
|
399
|
+
SELECT 1 FROM organization_memberships membership
|
|
400
|
+
WHERE membership.account_id = p_account_id
|
|
401
|
+
AND membership.personal_workspace_id = workspace.id
|
|
402
|
+
)
|
|
403
|
+
) THEN
|
|
404
|
+
RAISE EXCEPTION 'shared workspace not found' USING ERRCODE = 'P0002';
|
|
405
|
+
END IF;
|
|
406
|
+
SELECT EXISTS (
|
|
407
|
+
SELECT 1
|
|
408
|
+
FROM organization_memberships org_actor
|
|
409
|
+
JOIN organization_shared_workspace_administration_capabilities capability
|
|
410
|
+
ON capability.account_id = org_actor.account_id
|
|
411
|
+
AND capability.workspace_id = p_workspace_id
|
|
412
|
+
AND capability.actor_subject_id = org_actor.subject_id
|
|
413
|
+
AND capability.backend_pid = pg_catalog.pg_backend_pid()
|
|
414
|
+
AND capability.transaction_id = pg_catalog.pg_current_xact_id()
|
|
415
|
+
AND capability.capability_id::text = pg_catalog.current_setting(
|
|
416
|
+
'opengeni.organization_shared_workspace_administration_capability', true
|
|
417
|
+
)
|
|
418
|
+
WHERE org_actor.account_id = p_account_id
|
|
419
|
+
AND org_actor.subject_id = p_actor_subject
|
|
420
|
+
AND p_actor_subject LIKE 'user:%'
|
|
421
|
+
AND org_actor.status = 'active'
|
|
422
|
+
AND org_actor.role IN ('owner', 'admin')
|
|
423
|
+
) INTO actor_is_organization_administrator;
|
|
424
|
+
SELECT actor_is_organization_administrator OR EXISTS (
|
|
425
|
+
SELECT 1 FROM workspace_memberships actor_row
|
|
426
|
+
WHERE actor_row.account_id = p_account_id
|
|
427
|
+
AND actor_row.workspace_id = p_workspace_id
|
|
428
|
+
AND actor_row.subject_id = p_actor_subject
|
|
429
|
+
AND actor_row.permissions ?| ARRAY['workspace:admin', 'members:manage']
|
|
430
|
+
) INTO actor_can_administer;
|
|
431
|
+
IF NOT actor_can_administer THEN
|
|
432
|
+
RAISE EXCEPTION 'workspace member administration required' USING ERRCODE = '42501';
|
|
433
|
+
END IF;
|
|
434
|
+
IF NOT actor_is_organization_administrator
|
|
435
|
+
AND EXISTS (
|
|
436
|
+
SELECT 1 FROM workspace_memberships target_row
|
|
437
|
+
WHERE target_row.account_id = p_account_id
|
|
438
|
+
AND target_row.workspace_id = p_workspace_id
|
|
439
|
+
AND target_row.subject_id = p_target_subject
|
|
440
|
+
AND target_row.permissions ?| ARRAY['workspace:admin', 'members:manage']
|
|
441
|
+
) AND NOT EXISTS (
|
|
442
|
+
SELECT 1 FROM workspace_memberships other
|
|
443
|
+
WHERE other.account_id = p_account_id
|
|
444
|
+
AND other.workspace_id = p_workspace_id
|
|
445
|
+
AND other.subject_id <> p_target_subject
|
|
446
|
+
AND other.permissions ?| ARRAY['workspace:admin', 'members:manage']
|
|
447
|
+
)
|
|
448
|
+
THEN
|
|
449
|
+
RAISE EXCEPTION 'cannot remove the last administering workspace member'
|
|
450
|
+
USING ERRCODE = '55000';
|
|
451
|
+
END IF;
|
|
452
|
+
END
|
|
453
|
+
$body$;
|
|
454
|
+
|
|
455
|
+
REVOKE ALL ON FUNCTION opengeni_private.assert_organization_shared_workspace_administrator(
|
|
456
|
+
uuid, uuid, text
|
|
457
|
+
) FROM PUBLIC;
|
|
458
|
+
REVOKE ALL ON FUNCTION assert_organization_shared_workspace_administrator(uuid, uuid, text)
|
|
459
|
+
FROM PUBLIC;
|
|
460
|
+
REVOKE ALL ON FUNCTION open_organization_shared_workspace_administration_capability(
|
|
461
|
+
uuid, uuid, text
|
|
462
|
+
) FROM PUBLIC;
|
|
463
|
+
REVOKE ALL ON FUNCTION close_organization_shared_workspace_administration_capability(uuid)
|
|
464
|
+
FROM PUBLIC;
|
|
465
|
+
REVOKE ALL ON FUNCTION create_organization_shared_workspace(
|
|
466
|
+
uuid, text, text, text, text, uuid
|
|
467
|
+
) FROM PUBLIC;
|
|
468
|
+
REVOKE ALL ON FUNCTION upsert_organization_shared_workspace_member(
|
|
469
|
+
uuid, uuid, text, uuid, text, text, jsonb, boolean
|
|
470
|
+
) FROM PUBLIC;
|
|
471
|
+
REVOKE ALL ON TABLE organization_shared_workspace_administration_capabilities FROM PUBLIC;
|
|
472
|
+
|
|
473
|
+
DO $body$
|
|
474
|
+
DECLARE
|
|
475
|
+
target_schema text := pg_catalog.current_schema();
|
|
476
|
+
BEGIN
|
|
477
|
+
EXECUTE pg_catalog.format(
|
|
478
|
+
'ALTER FUNCTION opengeni_private.assert_organization_shared_workspace_administrator(uuid,uuid,text) SET search_path = pg_catalog, %I, pg_temp',
|
|
479
|
+
target_schema
|
|
480
|
+
);
|
|
481
|
+
EXECUTE pg_catalog.format(
|
|
482
|
+
'ALTER FUNCTION opengeni_private.assert_workspace_membership_removal_actor(uuid,uuid,text,text) SET search_path = pg_catalog, %I, pg_temp',
|
|
483
|
+
target_schema
|
|
484
|
+
);
|
|
485
|
+
EXECUTE pg_catalog.format(
|
|
486
|
+
'ALTER FUNCTION %I.assert_organization_shared_workspace_administrator(uuid,uuid,text) SET search_path = pg_catalog, %I, pg_temp',
|
|
487
|
+
target_schema, target_schema
|
|
488
|
+
);
|
|
489
|
+
EXECUTE pg_catalog.format(
|
|
490
|
+
'ALTER FUNCTION %I.open_organization_shared_workspace_administration_capability(uuid,uuid,text) SET search_path = pg_catalog, %I, pg_temp',
|
|
491
|
+
target_schema, target_schema
|
|
492
|
+
);
|
|
493
|
+
EXECUTE pg_catalog.format(
|
|
494
|
+
'ALTER FUNCTION %I.close_organization_shared_workspace_administration_capability(uuid) SET search_path = pg_catalog, %I, pg_temp',
|
|
495
|
+
target_schema, target_schema
|
|
496
|
+
);
|
|
497
|
+
EXECUTE pg_catalog.format(
|
|
498
|
+
'ALTER FUNCTION %I.create_organization_shared_workspace(uuid,text,text,text,text,uuid) SET search_path = pg_catalog, %I, pg_temp',
|
|
499
|
+
target_schema, target_schema
|
|
500
|
+
);
|
|
501
|
+
EXECUTE pg_catalog.format(
|
|
502
|
+
'ALTER FUNCTION %I.upsert_organization_shared_workspace_member(uuid,uuid,text,uuid,text,text,jsonb,boolean) SET search_path = pg_catalog, %I, pg_temp',
|
|
503
|
+
target_schema, target_schema
|
|
504
|
+
);
|
|
505
|
+
IF EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
506
|
+
EXECUTE pg_catalog.format(
|
|
507
|
+
'REVOKE ALL ON TABLE %I.organization_shared_workspace_administration_capabilities FROM opengeni_app',
|
|
508
|
+
target_schema
|
|
509
|
+
);
|
|
510
|
+
EXECUTE pg_catalog.format(
|
|
511
|
+
'GRANT EXECUTE ON FUNCTION %I.assert_organization_shared_workspace_administrator(uuid,uuid,text) TO opengeni_app',
|
|
512
|
+
target_schema
|
|
513
|
+
);
|
|
514
|
+
EXECUTE pg_catalog.format(
|
|
515
|
+
'GRANT EXECUTE ON FUNCTION %I.upsert_organization_shared_workspace_member(uuid,uuid,text,uuid,text,text,jsonb,boolean) TO opengeni_app',
|
|
516
|
+
target_schema
|
|
517
|
+
);
|
|
518
|
+
EXECUTE pg_catalog.format(
|
|
519
|
+
'GRANT EXECUTE ON FUNCTION %I.open_organization_shared_workspace_administration_capability(uuid,uuid,text) TO opengeni_app',
|
|
520
|
+
target_schema
|
|
521
|
+
);
|
|
522
|
+
EXECUTE pg_catalog.format(
|
|
523
|
+
'GRANT EXECUTE ON FUNCTION %I.close_organization_shared_workspace_administration_capability(uuid) TO opengeni_app',
|
|
524
|
+
target_schema
|
|
525
|
+
);
|
|
526
|
+
EXECUTE pg_catalog.format(
|
|
527
|
+
'GRANT EXECUTE ON FUNCTION %I.create_organization_shared_workspace(uuid,text,text,text,text,uuid) TO opengeni_app',
|
|
528
|
+
target_schema
|
|
529
|
+
);
|
|
530
|
+
END IF;
|
|
531
|
+
END
|
|
532
|
+
$body$;
|
|
533
|
+
|
|
534
|
+
COMMENT ON FUNCTION assert_organization_shared_workspace_administrator(uuid,uuid,text) IS
|
|
535
|
+
'Authorizes the direct managed-human organization control plane for one shared workspace; Personal workspaces are excluded.';
|
|
536
|
+
COMMENT ON FUNCTION upsert_organization_shared_workspace_member(uuid,uuid,text,uuid,text,text,jsonb,boolean) IS
|
|
537
|
+
'Assigns or updates one active organization member in a shared workspace without granting the actor operational access.';
|
|
538
|
+
COMMENT ON FUNCTION create_organization_shared_workspace(uuid,text,text,text,text,uuid) IS
|
|
539
|
+
'Idempotently creates a shared workspace through the direct organization control plane without granting operational workspace access to the actor.';
|