@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,45 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- A physically queued turn is not necessarily part of the operator-visible
|
|
3
|
+
-- prompt queue: an idle Send and a Steer are accepted immediately, then wait
|
|
4
|
+
-- only for the worker claim boundary. Persist that admission decision so UI
|
|
5
|
+
-- projections never infer product semantics from worker implementation state.
|
|
6
|
+
|
|
7
|
+
SET LOCAL lock_timeout = '5s';
|
|
8
|
+
SET LOCAL statement_timeout = '5min';
|
|
9
|
+
|
|
10
|
+
ALTER TABLE session_turns
|
|
11
|
+
ADD COLUMN prompt_routing text;
|
|
12
|
+
|
|
13
|
+
ALTER TABLE session_turns
|
|
14
|
+
ADD CONSTRAINT session_turns_prompt_routing_check CHECK (
|
|
15
|
+
prompt_routing IS NULL OR prompt_routing IN (
|
|
16
|
+
'accepted_for_execution',
|
|
17
|
+
'queued_for_execution',
|
|
18
|
+
'accepted_for_steering'
|
|
19
|
+
)
|
|
20
|
+
) NOT VALID;
|
|
21
|
+
|
|
22
|
+
ALTER TABLE session_turns
|
|
23
|
+
VALIDATE CONSTRAINT session_turns_prompt_routing_check;
|
|
24
|
+
|
|
25
|
+
CREATE FUNCTION opengeni_private.reject_session_turn_prompt_routing_mutation()
|
|
26
|
+
RETURNS trigger
|
|
27
|
+
LANGUAGE plpgsql
|
|
28
|
+
AS $$
|
|
29
|
+
BEGIN
|
|
30
|
+
IF NEW.prompt_routing IS DISTINCT FROM OLD.prompt_routing THEN
|
|
31
|
+
RAISE EXCEPTION 'turn prompt routing is immutable after admission'
|
|
32
|
+
USING ERRCODE = '55000';
|
|
33
|
+
END IF;
|
|
34
|
+
RETURN NEW;
|
|
35
|
+
END;
|
|
36
|
+
$$;
|
|
37
|
+
|
|
38
|
+
REVOKE ALL ON FUNCTION opengeni_private.reject_session_turn_prompt_routing_mutation() FROM PUBLIC;
|
|
39
|
+
|
|
40
|
+
CREATE TRIGGER session_turns_prompt_routing_immutable
|
|
41
|
+
BEFORE UPDATE OF prompt_routing ON session_turns
|
|
42
|
+
FOR EACH ROW EXECUTE FUNCTION opengeni_private.reject_session_turn_prompt_routing_mutation();
|
|
43
|
+
|
|
44
|
+
COMMENT ON COLUMN session_turns.prompt_routing IS
|
|
45
|
+
'Immutable admission intent for human/API prompts. Null is rolling/legacy compatibility; status remains physical execution state.';
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
|
|
3
|
+
ALTER TABLE "enrollments"
|
|
4
|
+
ADD COLUMN "workspace_root" text;
|
|
5
|
+
|
|
6
|
+
ALTER TABLE "enrollments"
|
|
7
|
+
ADD CONSTRAINT "enrollments_workspace_root_shape"
|
|
8
|
+
CHECK (
|
|
9
|
+
"workspace_root" IS NULL
|
|
10
|
+
OR (
|
|
11
|
+
length("workspace_root") BETWEEN 1 AND 4096
|
|
12
|
+
)
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
-- Scoped machine reads return an explicit JSON contract rather than row_to_json,
|
|
16
|
+
-- so the new root must be projected here as well. This preserves organization,
|
|
17
|
+
-- workspace, and user-private machine authority without a second unscoped read.
|
|
18
|
+
CREATE OR REPLACE FUNCTION list_scoped_enrollments(
|
|
19
|
+
p_account_id uuid, p_workspace_id uuid, p_enrollment_id uuid DEFAULT NULL,
|
|
20
|
+
p_status text DEFAULT 'active'
|
|
21
|
+
) RETURNS SETOF jsonb
|
|
22
|
+
LANGUAGE plpgsql SECURITY DEFINER SET search_path FROM CURRENT AS $$
|
|
23
|
+
DECLARE actor_membership_id uuid; enrollment_row record;
|
|
24
|
+
BEGIN
|
|
25
|
+
INSERT INTO opengeni_private.scoped_compute_capabilities(
|
|
26
|
+
backend_pid, transaction_id, capability_kind
|
|
27
|
+
) VALUES (pg_catalog.pg_backend_pid(), pg_catalog.pg_current_xact_id(), 'read')
|
|
28
|
+
ON CONFLICT DO NOTHING;
|
|
29
|
+
actor_membership_id := scoped_compute_actor_membership(p_account_id, p_workspace_id);
|
|
30
|
+
IF p_status NOT IN ('active', 'revoked') THEN
|
|
31
|
+
RAISE EXCEPTION 'invalid enrollment status' USING ERRCODE = '22023';
|
|
32
|
+
END IF;
|
|
33
|
+
FOR enrollment_row IN
|
|
34
|
+
SELECT enrollment.*, sandbox.id AS sandbox_id, sandbox.name AS sandbox_name
|
|
35
|
+
FROM enrollments enrollment
|
|
36
|
+
LEFT JOIN LATERAL (
|
|
37
|
+
SELECT value.id, value.name FROM sandboxes value
|
|
38
|
+
WHERE value.enrollment_id = enrollment.id AND value.account_id = enrollment.account_id
|
|
39
|
+
ORDER BY value.created_at LIMIT 1
|
|
40
|
+
) sandbox ON true
|
|
41
|
+
WHERE enrollment.account_id = p_account_id AND enrollment.status = p_status
|
|
42
|
+
AND (p_enrollment_id IS NULL OR enrollment.id = p_enrollment_id)
|
|
43
|
+
AND (enrollment.authority_scope = 'organization'
|
|
44
|
+
OR (enrollment.authority_scope = 'workspace'
|
|
45
|
+
AND enrollment.workspace_id = p_workspace_id)
|
|
46
|
+
OR (enrollment.authority_scope = 'user'
|
|
47
|
+
AND enrollment.owner_organization_membership_id = actor_membership_id))
|
|
48
|
+
ORDER BY enrollment.created_at DESC, enrollment.id
|
|
49
|
+
LOOP
|
|
50
|
+
RETURN NEXT pg_catalog.jsonb_build_object(
|
|
51
|
+
'id', enrollment_row.id, 'accountId', enrollment_row.account_id,
|
|
52
|
+
'workspaceId', enrollment_row.workspace_id, 'scope', enrollment_row.authority_scope,
|
|
53
|
+
'generation', enrollment_row.generation, 'pubkey', enrollment_row.pubkey,
|
|
54
|
+
'exposure', enrollment_row.exposure, 'hasDisplay', enrollment_row.has_display,
|
|
55
|
+
'opStream', enrollment_row.op_stream,
|
|
56
|
+
'desktopUnavailableReason', enrollment_row.desktop_unavailable_reason,
|
|
57
|
+
'allowScreenControl', enrollment_row.allow_screen_control,
|
|
58
|
+
'operationPolicy', pg_catalog.jsonb_build_object(
|
|
59
|
+
'memoryMaxBytes', enrollment_row.operation_memory_max_bytes,
|
|
60
|
+
'memoryHighBytes', enrollment_row.operation_memory_high_bytes,
|
|
61
|
+
'cpuMaxMillicores', enrollment_row.operation_cpu_max_millicores,
|
|
62
|
+
'revision', enrollment_row.operation_policy_revision,
|
|
63
|
+
'updatedAt', enrollment_row.operation_policy_updated_at
|
|
64
|
+
),
|
|
65
|
+
'status', enrollment_row.status,
|
|
66
|
+
'credentialGeneration', enrollment_row.credential_generation,
|
|
67
|
+
'connectionInstanceId', enrollment_row.connection_instance_id,
|
|
68
|
+
'connectionGeneration', enrollment_row.connection_generation,
|
|
69
|
+
'connectionLeaseExpiresAt', enrollment_row.connection_lease_expires_at,
|
|
70
|
+
'connectionDuplicateDeniedCount', enrollment_row.connection_duplicate_denied_count,
|
|
71
|
+
'connectionDuplicateDeniedAt', enrollment_row.connection_duplicate_denied_at,
|
|
72
|
+
'os', enrollment_row.os, 'arch', enrollment_row.arch,
|
|
73
|
+
'workspaceRoot', enrollment_row.workspace_root,
|
|
74
|
+
'lastSeenAt', enrollment_row.last_seen_at,
|
|
75
|
+
'wentOfflineAt', enrollment_row.went_offline_at,
|
|
76
|
+
'wentOfflineReason', enrollment_row.went_offline_reason,
|
|
77
|
+
'agentVersion', enrollment_row.agent_version,
|
|
78
|
+
'agentBinarySha256', enrollment_row.agent_binary_sha256,
|
|
79
|
+
'agentUpdateChannel', enrollment_row.agent_update_channel,
|
|
80
|
+
'agentCapabilities', enrollment_row.agent_capabilities,
|
|
81
|
+
'agentUpdate', NULL, 'sandboxId', enrollment_row.sandbox_id,
|
|
82
|
+
'sandboxName', enrollment_row.sandbox_name,
|
|
83
|
+
'createdAt', enrollment_row.created_at, 'revokedAt', enrollment_row.revoked_at,
|
|
84
|
+
'updatedAt', enrollment_row.updated_at
|
|
85
|
+
);
|
|
86
|
+
END LOOP;
|
|
87
|
+
DELETE FROM opengeni_private.scoped_compute_capabilities
|
|
88
|
+
WHERE backend_pid = pg_catalog.pg_backend_pid()
|
|
89
|
+
AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
|
|
90
|
+
AND capability_kind = 'read';
|
|
91
|
+
RETURN;
|
|
92
|
+
EXCEPTION WHEN OTHERS THEN
|
|
93
|
+
DELETE FROM opengeni_private.scoped_compute_capabilities
|
|
94
|
+
WHERE backend_pid = pg_catalog.pg_backend_pid()
|
|
95
|
+
AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned();
|
|
96
|
+
RAISE;
|
|
97
|
+
END
|
|
98
|
+
$$;
|
|
99
|
+
REVOKE ALL ON FUNCTION list_scoped_enrollments(uuid, uuid, uuid, text) FROM PUBLIC;
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Per-channel and per-DM Slack workspace routing. The installation binding is
|
|
3
|
+
-- untouched: one team still resolves to one credential and one home workspace.
|
|
4
|
+
--
|
|
5
|
+
-- Two tenancy scopes, not one relocated workspace. HOME is the installation
|
|
6
|
+
-- binding's (account_id, workspace_id): it owns the `connections` row and bot
|
|
7
|
+
-- credential, the bot post/update/delete ledgers, `slack_interaction_inbox`,
|
|
8
|
+
-- `slack_bot_user_links`, `slack_app_home_refreshes`, reaction-summon settings,
|
|
9
|
+
-- Slack task policy, and every table created here. TARGET is the routed
|
|
10
|
+
-- (account_id, workspace_id): it owns the `slack_interactions` row, its action
|
|
11
|
+
-- handles, its progress deliveries, the grant, the session, and the session
|
|
12
|
+
-- events. Posting from a routed workspace on the installation's connection
|
|
13
|
+
-- fails hard, so the bot client is always built from HOME.
|
|
14
|
+
--
|
|
15
|
+
-- Cross-organization targeting is out of scope: `target_account_id =
|
|
16
|
+
-- account_id` is a table CHECK. Dropping that CHECK later is a rolling
|
|
17
|
+
-- migration; adding it later is not.
|
|
18
|
+
--
|
|
19
|
+
-- Rolling: every element is additive and there is no backfill. `route_state IS
|
|
20
|
+
-- NULL` on the inbox means "legacy / never routed" and is exactly today's
|
|
21
|
+
-- behaviour, so an old API image ignores these tables and columns and keeps
|
|
22
|
+
-- single-workspace behaviour.
|
|
23
|
+
SET LOCAL lock_timeout = '5s';
|
|
24
|
+
SET LOCAL statement_timeout = '10min';
|
|
25
|
+
|
|
26
|
+
-- One channel routes to one workspace within the installation's organization.
|
|
27
|
+
-- The row is the durable ask-once memory: once a human picks, or an admin sets
|
|
28
|
+
-- it in the web sheet, later messages in that channel never ask again.
|
|
29
|
+
CREATE TABLE "slack_channel_routes" (
|
|
30
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
31
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
|
|
32
|
+
"workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
|
|
33
|
+
"connection_id" uuid NOT NULL REFERENCES "connections"("id") ON DELETE CASCADE,
|
|
34
|
+
"slack_team_id" text NOT NULL,
|
|
35
|
+
"slack_channel_id" text NOT NULL,
|
|
36
|
+
"target_account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
|
|
37
|
+
"target_workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
|
|
38
|
+
"decided_by_subject_id" text NOT NULL,
|
|
39
|
+
"decided_by_slack_user_id" text NOT NULL,
|
|
40
|
+
"source" text NOT NULL,
|
|
41
|
+
"version" integer NOT NULL DEFAULT 1,
|
|
42
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
43
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
44
|
+
CONSTRAINT "slack_channel_routes_channel_uq"
|
|
45
|
+
UNIQUE ("connection_id", "slack_channel_id"),
|
|
46
|
+
CONSTRAINT "slack_channel_routes_workspace_account_fk"
|
|
47
|
+
FOREIGN KEY ("workspace_id", "account_id")
|
|
48
|
+
REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE,
|
|
49
|
+
CONSTRAINT "slack_channel_routes_target_workspace_account_fk"
|
|
50
|
+
FOREIGN KEY ("target_workspace_id", "target_account_id")
|
|
51
|
+
REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE,
|
|
52
|
+
CONSTRAINT "slack_channel_routes_same_account_check"
|
|
53
|
+
CHECK ("target_account_id" = "account_id"),
|
|
54
|
+
CONSTRAINT "slack_channel_routes_bounds_check"
|
|
55
|
+
CHECK (
|
|
56
|
+
octet_length("slack_team_id") BETWEEN 1 AND 64
|
|
57
|
+
AND octet_length("slack_channel_id") BETWEEN 1 AND 64
|
|
58
|
+
AND octet_length("decided_by_subject_id") BETWEEN 1 AND 1024
|
|
59
|
+
AND octet_length("decided_by_slack_user_id") BETWEEN 1 AND 64
|
|
60
|
+
AND "source" IN ('picker', 'admin')
|
|
61
|
+
AND "version" > 0
|
|
62
|
+
)
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
CREATE INDEX "slack_channel_routes_target_idx"
|
|
66
|
+
ON "slack_channel_routes" ("target_workspace_id", "slack_team_id");
|
|
67
|
+
|
|
68
|
+
-- One Slack human's direct messages route to one workspace. Absent a row, a DM
|
|
69
|
+
-- derives that human's own personal workspace from their active organization
|
|
70
|
+
-- membership pointer; the id is never accepted from a Slack payload.
|
|
71
|
+
CREATE TABLE "slack_user_dm_routes" (
|
|
72
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
73
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
|
|
74
|
+
"workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
|
|
75
|
+
"connection_id" uuid NOT NULL REFERENCES "connections"("id") ON DELETE CASCADE,
|
|
76
|
+
"slack_team_id" text NOT NULL,
|
|
77
|
+
"slack_user_id" text NOT NULL,
|
|
78
|
+
"target_account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
|
|
79
|
+
"target_workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
|
|
80
|
+
"decided_by_subject_id" text NOT NULL,
|
|
81
|
+
"decided_by_slack_user_id" text NOT NULL,
|
|
82
|
+
"source" text NOT NULL,
|
|
83
|
+
"version" integer NOT NULL DEFAULT 1,
|
|
84
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
85
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
86
|
+
CONSTRAINT "slack_user_dm_routes_user_uq"
|
|
87
|
+
UNIQUE ("connection_id", "slack_user_id"),
|
|
88
|
+
CONSTRAINT "slack_user_dm_routes_workspace_account_fk"
|
|
89
|
+
FOREIGN KEY ("workspace_id", "account_id")
|
|
90
|
+
REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE,
|
|
91
|
+
CONSTRAINT "slack_user_dm_routes_target_workspace_account_fk"
|
|
92
|
+
FOREIGN KEY ("target_workspace_id", "target_account_id")
|
|
93
|
+
REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE,
|
|
94
|
+
CONSTRAINT "slack_user_dm_routes_same_account_check"
|
|
95
|
+
CHECK ("target_account_id" = "account_id"),
|
|
96
|
+
CONSTRAINT "slack_user_dm_routes_bounds_check"
|
|
97
|
+
CHECK (
|
|
98
|
+
octet_length("slack_team_id") BETWEEN 1 AND 64
|
|
99
|
+
AND octet_length("slack_user_id") BETWEEN 1 AND 64
|
|
100
|
+
AND octet_length("decided_by_subject_id") BETWEEN 1 AND 1024
|
|
101
|
+
AND octet_length("decided_by_slack_user_id") BETWEEN 1 AND 64
|
|
102
|
+
AND "source" IN ('picker', 'admin')
|
|
103
|
+
AND "version" > 0
|
|
104
|
+
)
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
CREATE INDEX "slack_user_dm_routes_target_idx"
|
|
108
|
+
ON "slack_user_dm_routes" ("target_workspace_id", "slack_team_id");
|
|
109
|
+
|
|
110
|
+
-- The pending first-use picker. `slack_interaction_action_handles` cannot carry
|
|
111
|
+
-- it: that table's `session_id` is NOT NULL and composite-FK'd to an existing
|
|
112
|
+
-- `slack_interactions` row, while a picker exists BEFORE any session.
|
|
113
|
+
--
|
|
114
|
+
-- `inbox_id` deliberately carries no foreign key. The `awaiting_choice` inbox
|
|
115
|
+
-- row is settled `processed` before the human answers, so the answer arrives as
|
|
116
|
+
-- its own `block_action` inbox row; this column is provenance, not a live edge.
|
|
117
|
+
--
|
|
118
|
+
-- `request_text` mirrors `slack_interaction_inbox_bounds_check`'s exact 12000
|
|
119
|
+
-- byte bound so a prompt can always carry the originating row's text
|
|
120
|
+
-- losslessly, and `provider_event_id` is the ORIGINAL event so the answer can
|
|
121
|
+
-- re-materialize it under the inbox's own dedupe unique.
|
|
122
|
+
CREATE TABLE "slack_route_prompts" (
|
|
123
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
124
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
|
|
125
|
+
"workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
|
|
126
|
+
"connection_id" uuid NOT NULL REFERENCES "connections"("id") ON DELETE CASCADE,
|
|
127
|
+
"inbox_id" uuid NOT NULL,
|
|
128
|
+
"slack_team_id" text NOT NULL,
|
|
129
|
+
"slack_user_id" text NOT NULL,
|
|
130
|
+
"slack_channel_id" text NOT NULL,
|
|
131
|
+
"slack_message_ts" text NOT NULL,
|
|
132
|
+
"provider_event_id" text NOT NULL,
|
|
133
|
+
"trigger_kind" text NOT NULL,
|
|
134
|
+
"request_text" text NOT NULL,
|
|
135
|
+
"has_files" boolean NOT NULL DEFAULT false,
|
|
136
|
+
"slack_thread_ts" text,
|
|
137
|
+
"message_operation_id" uuid NOT NULL,
|
|
138
|
+
"status" text NOT NULL DEFAULT 'pending',
|
|
139
|
+
"answered_target_account_id" uuid,
|
|
140
|
+
"answered_target_workspace_id" uuid,
|
|
141
|
+
"answered_by_subject_id" text,
|
|
142
|
+
"answered_at" timestamptz,
|
|
143
|
+
"expires_at" timestamptz NOT NULL,
|
|
144
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
145
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
146
|
+
CONSTRAINT "slack_route_prompts_inbox_uq" UNIQUE ("connection_id", "inbox_id"),
|
|
147
|
+
CONSTRAINT "slack_route_prompts_event_uq" UNIQUE ("connection_id", "provider_event_id"),
|
|
148
|
+
CONSTRAINT "slack_route_prompts_identity_uq" UNIQUE ("account_id", "workspace_id", "id"),
|
|
149
|
+
CONSTRAINT "slack_route_prompts_workspace_account_fk"
|
|
150
|
+
FOREIGN KEY ("workspace_id", "account_id")
|
|
151
|
+
REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE,
|
|
152
|
+
CONSTRAINT "slack_route_prompts_status_check"
|
|
153
|
+
CHECK ("status" IN ('pending', 'answered', 'expired', 'cancelled')),
|
|
154
|
+
CONSTRAINT "slack_route_prompts_trigger_check"
|
|
155
|
+
CHECK (
|
|
156
|
+
"trigger_kind" IN (
|
|
157
|
+
'app_mention', 'dm', 'reaction', 'slash_command',
|
|
158
|
+
'message_shortcut', 'thread_reply', 'block_action'
|
|
159
|
+
)
|
|
160
|
+
),
|
|
161
|
+
CONSTRAINT "slack_route_prompts_answer_check"
|
|
162
|
+
CHECK (
|
|
163
|
+
("status" = 'answered') = ("answered_target_workspace_id" IS NOT NULL)
|
|
164
|
+
AND ("answered_target_account_id" IS NULL) = ("answered_target_workspace_id" IS NULL)
|
|
165
|
+
AND ("answered_target_account_id" IS NULL OR "answered_target_account_id" = "account_id")
|
|
166
|
+
AND ("answered_target_workspace_id" IS NULL) = ("answered_at" IS NULL)
|
|
167
|
+
),
|
|
168
|
+
CONSTRAINT "slack_route_prompts_bounds_check"
|
|
169
|
+
CHECK (
|
|
170
|
+
octet_length("slack_team_id") BETWEEN 1 AND 64
|
|
171
|
+
AND octet_length("slack_user_id") BETWEEN 1 AND 64
|
|
172
|
+
AND octet_length("slack_channel_id") BETWEEN 1 AND 64
|
|
173
|
+
AND octet_length("slack_message_ts") BETWEEN 1 AND 64
|
|
174
|
+
AND octet_length("provider_event_id") BETWEEN 1 AND 256
|
|
175
|
+
AND octet_length("request_text") BETWEEN 1 AND 12000
|
|
176
|
+
AND ("slack_thread_ts" IS NULL OR octet_length("slack_thread_ts") BETWEEN 1 AND 64)
|
|
177
|
+
AND (
|
|
178
|
+
"answered_by_subject_id" IS NULL
|
|
179
|
+
OR octet_length("answered_by_subject_id") BETWEEN 1 AND 1024
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
CREATE INDEX "slack_route_prompts_pending_idx"
|
|
185
|
+
ON "slack_route_prompts" ("expires_at", "id")
|
|
186
|
+
WHERE "status" = 'pending';
|
|
187
|
+
|
|
188
|
+
-- One row per offered workspace. The row `id` is the Slack button `value`, so
|
|
189
|
+
-- it must be a UUID: the block-action normalizer already requires that shape,
|
|
190
|
+
-- which is why the picker uses buttons rather than a `static_select`.
|
|
191
|
+
--
|
|
192
|
+
-- These rows are a snapshot taken at prompt time and are NEVER authority. The
|
|
193
|
+
-- answer path re-authorizes the chosen workspace live.
|
|
194
|
+
CREATE TABLE "slack_route_prompt_options" (
|
|
195
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
196
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
|
|
197
|
+
"workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
|
|
198
|
+
"prompt_id" uuid NOT NULL,
|
|
199
|
+
"candidate_account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
|
|
200
|
+
"candidate_workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
|
|
201
|
+
"candidate_label" text NOT NULL,
|
|
202
|
+
"position" integer NOT NULL,
|
|
203
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
204
|
+
CONSTRAINT "slack_route_prompt_options_workspace_account_fk"
|
|
205
|
+
FOREIGN KEY ("workspace_id", "account_id")
|
|
206
|
+
REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE,
|
|
207
|
+
CONSTRAINT "slack_route_prompt_options_prompt_fk"
|
|
208
|
+
FOREIGN KEY ("account_id", "workspace_id", "prompt_id")
|
|
209
|
+
REFERENCES "slack_route_prompts"("account_id", "workspace_id", "id") ON DELETE CASCADE,
|
|
210
|
+
CONSTRAINT "slack_route_prompt_options_candidate_workspace_account_fk"
|
|
211
|
+
FOREIGN KEY ("candidate_workspace_id", "candidate_account_id")
|
|
212
|
+
REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE,
|
|
213
|
+
CONSTRAINT "slack_route_prompt_options_prompt_workspace_uq"
|
|
214
|
+
UNIQUE ("prompt_id", "candidate_workspace_id"),
|
|
215
|
+
CONSTRAINT "slack_route_prompt_options_prompt_position_uq"
|
|
216
|
+
UNIQUE ("prompt_id", "position"),
|
|
217
|
+
CONSTRAINT "slack_route_prompt_options_same_account_check"
|
|
218
|
+
CHECK ("candidate_account_id" = "account_id"),
|
|
219
|
+
CONSTRAINT "slack_route_prompt_options_bounds_check"
|
|
220
|
+
CHECK (
|
|
221
|
+
octet_length("candidate_label") BETWEEN 1 AND 128
|
|
222
|
+
AND "position" >= 0
|
|
223
|
+
)
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
ALTER TABLE "slack_channel_routes" ENABLE ROW LEVEL SECURITY;
|
|
227
|
+
ALTER TABLE "slack_channel_routes" FORCE ROW LEVEL SECURITY;
|
|
228
|
+
ALTER TABLE "slack_user_dm_routes" ENABLE ROW LEVEL SECURITY;
|
|
229
|
+
ALTER TABLE "slack_user_dm_routes" FORCE ROW LEVEL SECURITY;
|
|
230
|
+
ALTER TABLE "slack_route_prompts" ENABLE ROW LEVEL SECURITY;
|
|
231
|
+
ALTER TABLE "slack_route_prompts" FORCE ROW LEVEL SECURITY;
|
|
232
|
+
ALTER TABLE "slack_route_prompt_options" ENABLE ROW LEVEL SECURITY;
|
|
233
|
+
ALTER TABLE "slack_route_prompt_options" FORCE ROW LEVEL SECURITY;
|
|
234
|
+
|
|
235
|
+
CREATE POLICY workspace_isolation ON "slack_channel_routes"
|
|
236
|
+
USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
|
|
237
|
+
WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
|
|
238
|
+
CREATE POLICY workspace_isolation ON "slack_user_dm_routes"
|
|
239
|
+
USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
|
|
240
|
+
WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
|
|
241
|
+
CREATE POLICY workspace_isolation ON "slack_route_prompts"
|
|
242
|
+
USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
|
|
243
|
+
WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
|
|
244
|
+
CREATE POLICY workspace_isolation ON "slack_route_prompt_options"
|
|
245
|
+
USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
|
|
246
|
+
WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
|
|
247
|
+
|
|
248
|
+
-- The inbox keeps HOME tenancy. `account_id`/`workspace_id` stay NOT NULL on
|
|
249
|
+
-- purpose: `opengeni_private.workspace_rls_visible` is strict equality, so a
|
|
250
|
+
-- NULL `workspace_id` would make an un-routed row invisible to EVERY tenant
|
|
251
|
+
-- including its own, and `settleSlackInteractionInbox` would update zero rows
|
|
252
|
+
-- while reporting success.
|
|
253
|
+
--
|
|
254
|
+
-- `target_workspace_id` deliberately carries no foreign key. An inbox row is
|
|
255
|
+
-- short-lived and definer-claimed; a runtime re-check degrades to "ask again",
|
|
256
|
+
-- while an FK would hard-fail on a workspace deleted mid-flight.
|
|
257
|
+
ALTER TABLE "slack_interaction_inbox"
|
|
258
|
+
ADD COLUMN "route_state" text,
|
|
259
|
+
ADD COLUMN "target_account_id" uuid,
|
|
260
|
+
ADD COLUMN "target_workspace_id" uuid,
|
|
261
|
+
ADD COLUMN "route_prompt_id" uuid,
|
|
262
|
+
ADD COLUMN "route_prompt_expires_at" timestamptz;
|
|
263
|
+
|
|
264
|
+
ALTER TABLE "slack_interaction_inbox"
|
|
265
|
+
ADD CONSTRAINT "slack_interaction_inbox_route_check"
|
|
266
|
+
CHECK (
|
|
267
|
+
"route_state" IS NULL
|
|
268
|
+
OR (
|
|
269
|
+
"route_state" IN ('resolved', 'awaiting_choice', 'denied')
|
|
270
|
+
AND ("route_state" = 'resolved') = ("target_workspace_id" IS NOT NULL)
|
|
271
|
+
AND ("target_account_id" IS NULL) = ("target_workspace_id" IS NULL)
|
|
272
|
+
AND ("target_account_id" IS NULL OR "target_account_id" = "account_id")
|
|
273
|
+
AND ("route_state" = 'awaiting_choice') = ("route_prompt_id" IS NOT NULL)
|
|
274
|
+
)
|
|
275
|
+
) NOT VALID;
|
|
276
|
+
|
|
277
|
+
ALTER TABLE "slack_interaction_inbox"
|
|
278
|
+
VALIDATE CONSTRAINT "slack_interaction_inbox_route_check";
|
|
279
|
+
|
|
280
|
+
-- Frozen at bind time, never looked up at post time: a workspace rename between
|
|
281
|
+
-- the original post and a reconciliation would otherwise make the byte-compare
|
|
282
|
+
-- in `reconcilePostMessage` raise `post_reconciliation_mismatch`.
|
|
283
|
+
ALTER TABLE "slack_interactions"
|
|
284
|
+
ADD COLUMN "routed_workspace_label" text;
|
|
285
|
+
|
|
286
|
+
ALTER TABLE "slack_interactions"
|
|
287
|
+
ADD CONSTRAINT "slack_interactions_routed_label_check"
|
|
288
|
+
CHECK (
|
|
289
|
+
"routed_workspace_label" IS NULL
|
|
290
|
+
OR octet_length("routed_workspace_label") BETWEEN 1 AND 128
|
|
291
|
+
) NOT VALID;
|
|
292
|
+
|
|
293
|
+
ALTER TABLE "slack_interactions"
|
|
294
|
+
VALIDATE CONSTRAINT "slack_interactions_routed_label_check";
|
|
295
|
+
|
|
296
|
+
-- The ONE new SECURITY DEFINER routine: a narrow, content-free tenancy probe.
|
|
297
|
+
--
|
|
298
|
+
-- Thread continuation must cross workspaces because `slack_interactions_route_uq`
|
|
299
|
+
-- is `(connection_id, route_key)` and therefore connection-global, while every
|
|
300
|
+
-- ordinary interaction read is workspace-fenced. This returns ids only - no
|
|
301
|
+
-- text, no subject, no session content - and it cannot widen visibility,
|
|
302
|
+
-- because the caller must then re-read the full row under the returned
|
|
303
|
+
-- tenancy's own RLS.
|
|
304
|
+
--
|
|
305
|
+
-- SECURITY DEFINER alone is NOT enough here. `slack_interactions` carries
|
|
306
|
+
-- `FORCE ROW LEVEL SECURITY`, which binds the TABLE OWNER, and OpenGeni's
|
|
307
|
+
-- migration principal is a non-superuser without `BYPASSRLS`. The routine
|
|
308
|
+
-- therefore executes as a role the strict `workspace_isolation` policy still
|
|
309
|
+
-- applies to, and a bare definer probe returns ZERO rows for exactly the
|
|
310
|
+
-- cross-workspace lookup it exists for. This uses the same private
|
|
311
|
+
-- transaction-capability seam migration 0243 established for Google Drive
|
|
312
|
+
-- object ACLs: one transaction-local row in a private table opens one
|
|
313
|
+
-- owner-checked permissive SELECT policy for the duration of the probe.
|
|
314
|
+
-- `opengeni_app` holds no privilege on the capability table and cannot forge
|
|
315
|
+
-- the path through a custom GUC.
|
|
316
|
+
--
|
|
317
|
+
-- `resolve_slack_installation`, `slack_installation_bindings_active_team_uq`,
|
|
318
|
+
-- and `sync_slack_installation_binding()` are deliberately NOT touched: one team
|
|
319
|
+
-- still installs into exactly one home workspace and one credential. Routing is
|
|
320
|
+
-- a separate, additive fact.
|
|
321
|
+
CREATE TABLE opengeni_private.slack_routing_runtime_capabilities (
|
|
322
|
+
"backend_pid" integer NOT NULL,
|
|
323
|
+
"transaction_id" xid8 NOT NULL,
|
|
324
|
+
"capability_kind" text NOT NULL,
|
|
325
|
+
"created_at" timestamptz NOT NULL DEFAULT clock_timestamp(),
|
|
326
|
+
CONSTRAINT "slack_routing_runtime_capabilities_kind_chk" CHECK (
|
|
327
|
+
"capability_kind" IN ('interaction_tenancy')
|
|
328
|
+
),
|
|
329
|
+
CONSTRAINT "slack_routing_runtime_capabilities_pk" PRIMARY KEY (
|
|
330
|
+
"backend_pid", "transaction_id", "capability_kind"
|
|
331
|
+
)
|
|
332
|
+
);
|
|
333
|
+
REVOKE ALL ON TABLE opengeni_private.slack_routing_runtime_capabilities FROM PUBLIC;
|
|
334
|
+
|
|
335
|
+
CREATE FUNCTION opengeni_private.slack_routing_capability_active()
|
|
336
|
+
RETURNS boolean
|
|
337
|
+
LANGUAGE sql STABLE SECURITY DEFINER
|
|
338
|
+
SET search_path = opengeni_private, pg_catalog
|
|
339
|
+
AS $$
|
|
340
|
+
SELECT EXISTS (
|
|
341
|
+
SELECT 1
|
|
342
|
+
FROM slack_routing_runtime_capabilities capability
|
|
343
|
+
WHERE capability.backend_pid = pg_catalog.pg_backend_pid()
|
|
344
|
+
AND capability.transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
|
|
345
|
+
AND capability.capability_kind = 'interaction_tenancy'
|
|
346
|
+
);
|
|
347
|
+
$$;
|
|
348
|
+
REVOKE ALL ON FUNCTION opengeni_private.slack_routing_capability_active() FROM PUBLIC;
|
|
349
|
+
|
|
350
|
+
-- Permissive SELECT, additive to the existing `workspace_isolation` policy and
|
|
351
|
+
-- gated on BOTH the effective role being the table owner AND a live capability
|
|
352
|
+
-- row. The pre-existing RESTRICTIVE `session_visibility_isolation` policy on
|
|
353
|
+
-- this table still applies, so this never widens session visibility.
|
|
354
|
+
DO $slack_routing_capability_policy$
|
|
355
|
+
DECLARE data_schema text := current_schema();
|
|
356
|
+
BEGIN
|
|
357
|
+
EXECUTE format($policy$
|
|
358
|
+
CREATE POLICY slack_routing_capability_read ON %1$I.slack_interactions
|
|
359
|
+
FOR SELECT USING (
|
|
360
|
+
current_user = pg_catalog.pg_get_userbyid(
|
|
361
|
+
(SELECT relation.relowner
|
|
362
|
+
FROM pg_catalog.pg_class relation
|
|
363
|
+
WHERE relation.oid = %2$L::pg_catalog.regclass)
|
|
364
|
+
)
|
|
365
|
+
AND opengeni_private.slack_routing_capability_active()
|
|
366
|
+
)
|
|
367
|
+
$policy$, data_schema, data_schema || '.slack_interactions');
|
|
368
|
+
END
|
|
369
|
+
$slack_routing_capability_policy$;
|
|
370
|
+
|
|
371
|
+
DO $privileged_functions$
|
|
372
|
+
DECLARE data_schema text := current_schema();
|
|
373
|
+
BEGIN
|
|
374
|
+
EXECUTE format($ddl$
|
|
375
|
+
CREATE FUNCTION opengeni_private.resolve_slack_interaction_tenancy(
|
|
376
|
+
p_connection_id uuid,
|
|
377
|
+
p_route_key text
|
|
378
|
+
)
|
|
379
|
+
RETURNS TABLE (account_id uuid, workspace_id uuid, interaction_id uuid)
|
|
380
|
+
LANGUAGE plpgsql
|
|
381
|
+
SECURITY DEFINER
|
|
382
|
+
SET search_path = %1$I, pg_catalog
|
|
383
|
+
AS $body$
|
|
384
|
+
BEGIN
|
|
385
|
+
IF p_connection_id IS NULL OR p_route_key IS NULL THEN
|
|
386
|
+
RETURN;
|
|
387
|
+
END IF;
|
|
388
|
+
INSERT INTO opengeni_private.slack_routing_runtime_capabilities (
|
|
389
|
+
backend_pid, transaction_id, capability_kind
|
|
390
|
+
) VALUES (
|
|
391
|
+
pg_catalog.pg_backend_pid(), pg_catalog.pg_current_xact_id(), 'interaction_tenancy'
|
|
392
|
+
) ON CONFLICT DO NOTHING;
|
|
393
|
+
BEGIN
|
|
394
|
+
RETURN QUERY
|
|
395
|
+
SELECT I.account_id, I.workspace_id, I.id
|
|
396
|
+
FROM slack_interactions I
|
|
397
|
+
WHERE I.connection_id = p_connection_id
|
|
398
|
+
AND I.route_key = p_route_key
|
|
399
|
+
LIMIT 1;
|
|
400
|
+
DELETE FROM opengeni_private.slack_routing_runtime_capabilities
|
|
401
|
+
WHERE backend_pid = pg_catalog.pg_backend_pid()
|
|
402
|
+
AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
|
|
403
|
+
AND capability_kind = 'interaction_tenancy';
|
|
404
|
+
RETURN;
|
|
405
|
+
EXCEPTION WHEN OTHERS THEN
|
|
406
|
+
DELETE FROM opengeni_private.slack_routing_runtime_capabilities
|
|
407
|
+
WHERE backend_pid = pg_catalog.pg_backend_pid()
|
|
408
|
+
AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
|
|
409
|
+
AND capability_kind = 'interaction_tenancy';
|
|
410
|
+
RAISE;
|
|
411
|
+
END;
|
|
412
|
+
END;
|
|
413
|
+
$body$
|
|
414
|
+
$ddl$, data_schema);
|
|
415
|
+
END
|
|
416
|
+
$privileged_functions$;
|
|
417
|
+
|
|
418
|
+
REVOKE ALL ON FUNCTION opengeni_private.resolve_slack_interaction_tenancy(uuid, text)
|
|
419
|
+
FROM PUBLIC;
|
|
420
|
+
|
|
421
|
+
DO $grants$
|
|
422
|
+
DECLARE target_schema text := current_schema();
|
|
423
|
+
BEGIN
|
|
424
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
425
|
+
EXECUTE format(
|
|
426
|
+
'GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %I.slack_channel_routes TO opengeni_app',
|
|
427
|
+
target_schema
|
|
428
|
+
);
|
|
429
|
+
EXECUTE format(
|
|
430
|
+
'GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %I.slack_user_dm_routes TO opengeni_app',
|
|
431
|
+
target_schema
|
|
432
|
+
);
|
|
433
|
+
EXECUTE format(
|
|
434
|
+
'GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %I.slack_route_prompts TO opengeni_app',
|
|
435
|
+
target_schema
|
|
436
|
+
);
|
|
437
|
+
EXECUTE format(
|
|
438
|
+
'GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %I.slack_route_prompt_options TO opengeni_app',
|
|
439
|
+
target_schema
|
|
440
|
+
);
|
|
441
|
+
GRANT EXECUTE ON FUNCTION opengeni_private.resolve_slack_interaction_tenancy(uuid, text)
|
|
442
|
+
TO opengeni_app;
|
|
443
|
+
REVOKE ALL ON TABLE opengeni_private.slack_routing_runtime_capabilities FROM opengeni_app;
|
|
444
|
+
GRANT EXECUTE ON FUNCTION opengeni_private.slack_routing_capability_active()
|
|
445
|
+
TO opengeni_app;
|
|
446
|
+
END IF;
|
|
447
|
+
END
|
|
448
|
+
$grants$;
|
|
449
|
+
|
|
450
|
+
COMMENT ON TABLE "slack_channel_routes" IS
|
|
451
|
+
'Per-channel Slack workspace routing within one organization; home tenancy owns the row, target tenancy owns the session.';
|
|
452
|
+
COMMENT ON TABLE "slack_user_dm_routes" IS
|
|
453
|
+
'Per-Slack-human direct-message workspace routing; absent a row a DM derives that human own personal workspace.';
|
|
454
|
+
COMMENT ON TABLE "slack_route_prompts" IS
|
|
455
|
+
'Pending first-use Slack workspace picker plus its bounded original request; the answer arrives as its own block_action event.';
|
|
456
|
+
COMMENT ON TABLE "slack_route_prompt_options" IS
|
|
457
|
+
'Offered picker workspaces; the row id is the Slack button value and is a prompt-time snapshot, never authority.';
|
|
458
|
+
|
|
459
|
+
RESET statement_timeout;
|
|
460
|
+
RESET lock_timeout;
|