@opengeni/db 1.0.1 → 3.2.0-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/attached-browser-devices.d.ts +7 -0
- package/dist/automations.d.ts +154 -0
- package/dist/canonical-human-identities.js +3 -3
- package/dist/capability-integrations.d.ts +13 -0
- package/dist/child-lifecycle-notices.d.ts +68 -0
- package/dist/{chunk-4SR4HKKA.js → chunk-2EH4WRHL.js} +19 -11
- package/dist/chunk-2EH4WRHL.js.map +1 -0
- package/dist/{chunk-CUPM3FGB.js → chunk-2PRS4J5Q.js} +3 -3
- package/dist/chunk-6HMM2R5T.js +390 -0
- package/dist/chunk-6HMM2R5T.js.map +1 -0
- package/dist/{chunk-ZWREYEWA.js → chunk-AZHW7R5V.js} +56 -11
- package/dist/chunk-AZHW7R5V.js.map +1 -0
- package/dist/{chunk-LTG3S6ZY.js → chunk-E6CLMKEE.js} +2 -2
- package/dist/{chunk-6ZYKCRFO.js → chunk-KRLPEMLH.js} +7591 -4417
- package/dist/chunk-KRLPEMLH.js.map +1 -0
- package/dist/chunk-NAB6FKFN.js +516 -0
- package/dist/chunk-NAB6FKFN.js.map +1 -0
- package/dist/{chunk-SK7YYHBI.js → chunk-SIBYBA2D.js} +46 -1
- package/dist/chunk-SIBYBA2D.js.map +1 -0
- package/dist/chunk-UZNXG44K.js +4508 -0
- package/dist/chunk-UZNXG44K.js.map +1 -0
- package/dist/codemode-operations.d.ts +1 -6
- package/dist/company-brain-context-selection-schema.d.ts +705 -0
- package/dist/company-brain-context-selection.d.ts +94 -0
- package/dist/company-brain-governed-writes.d.ts +24 -0
- package/dist/company-brain.d.ts +55 -0
- package/dist/company-profile-agent-admin.d.ts +15 -0
- package/dist/company-profile-schema.d.ts +619 -2
- package/dist/company-profile.d.ts +9 -4
- package/dist/connection-authority.d.ts +95 -0
- package/dist/connection-scopes.d.ts +8 -0
- package/dist/connection-tenancy-backfill.d.ts +85 -0
- package/dist/connection-token-resolver.d.ts +30 -1
- package/dist/database.d.ts +23 -4
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.d.ts +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/governed-learning-activation-schema.d.ts +984 -0
- package/dist/governed-learning-activation.d.ts +98 -0
- package/dist/governed-learning-evaluator-schema.d.ts +633 -0
- package/dist/governed-learning-evaluator.d.ts +28 -0
- package/dist/index.d.ts +1606 -81
- package/dist/index.js +21898 -8147
- package/dist/index.js.map +1 -1
- package/dist/insights.d.ts +18 -0
- package/dist/integration-bindings.d.ts +1 -0
- package/dist/integration-facets.d.ts +30 -0
- package/dist/interaction-placement-loss.d.ts +22 -0
- package/dist/managed-human-provisioning.d.ts +25 -0
- package/dist/memory-domain.d.ts +3 -3
- package/dist/migrate.d.ts +11 -1
- package/dist/migrate.js +1 -1
- package/dist/new-session-drafts.d.ts +43 -5
- package/dist/organization-membership-backfill.d.ts +174 -0
- package/dist/organization-membership-lifecycle.d.ts +162 -0
- package/dist/pr-review.d.ts +107 -0
- package/dist/preference-registry-schema.d.ts +2 -0
- package/dist/preference-registry.d.ts +2 -0
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +29 -8
- package/dist/schema.d.ts +24914 -13703
- package/dist/schema.js +99 -1
- package/dist/scoped-knowledge-schema.d.ts +52 -1
- package/dist/session-background-commands.d.ts +125 -0
- package/dist/session-background-commands.js +34 -0
- package/dist/session-background-commands.js.map +1 -0
- package/dist/session-control.d.ts +156 -3
- package/dist/session-goal-pacing.d.ts +68 -0
- package/dist/session-queue-commands.d.ts +43 -4
- package/dist/session-realtime-ledger.d.ts +2 -0
- package/dist/session-tenancy.d.ts +71 -1
- package/dist/session-tenancy.js +25 -3
- package/dist/session-tool-call-settlement.d.ts +9 -4
- package/dist/slack-routing-personal-workspace.d.ts +23 -0
- package/dist/slack-routing.d.ts +268 -0
- package/dist/slack-task-policy-schema.d.ts +34 -0
- package/dist/slack-task-policy.d.ts +8 -0
- package/dist/slack-user-link-access.d.ts +28 -0
- package/dist/task-notes-schema.d.ts +594 -0
- package/dist/task-notes.d.ts +39 -0
- package/dist/turn-initiator.d.ts +2 -0
- package/dist/user-resource-authority.d.ts +109 -0
- package/dist/video-generation.js +3 -3
- package/dist/workspace-authority.d.ts +81 -0
- package/dist/workspace-instruction-policies-schema.d.ts +279 -0
- package/dist/workspace-instruction-policies.d.ts +62 -0
- package/dist/workspace-learning-policy.d.ts +1 -0
- package/dist/workspace-membership-permissions.d.ts +2 -0
- package/drizzle/0240_enrollment_connection_authority.sql +39 -0
- package/drizzle/0241_atomic_personal_resource_delegation.sql +1000 -0
- package/drizzle/0241_enrollment_agent_runtime.sql +70 -0
- package/drizzle/0244_slack_app_home_refresh_queue.sql +120 -0
- package/drizzle/0245_model_context_contribution_facts.sql +88 -0
- package/drizzle/0246_integration_personal_instance_authority.sql +70 -0
- package/drizzle/0247_terraform_stacks_provenance_repair.sql +428 -0
- package/drizzle/0248_terraform_stacks_component_resolution_fence.sql +83 -0
- package/drizzle/0249_personal_resource_delegation_authority_correction.sql +662 -0
- package/drizzle/0250_direct_retained_process_owner_liveness.sql +199 -0
- package/drizzle/0251_connected_machine_operation_policy.sql +45 -0
- package/drizzle/0252_scheduled_personal_resource_delegation.sql +1867 -0
- package/drizzle/0253_common_user_resource_authority_lifecycle.sql +1555 -0
- package/drizzle/0254_scoped_variable_set_authority.sql +1255 -0
- package/drizzle/0255_company_brain_governed_write_proposals.sql +524 -0
- package/drizzle/0256_connection_authority_delegation.sql +703 -0
- package/drizzle/0257_goal_revision_decisions_and_root_constraints.sql +436 -0
- package/drizzle/0258_three_scope_document_knowledge_authority.sql +1273 -0
- package/drizzle/0259_company_brain_context_selection_receipts.sql +869 -0
- package/drizzle/0260_task_note_knowledge_promotion.sql +820 -0
- package/drizzle/0261_preference_knowledge_proposal_actor_binding.sql +471 -0
- package/drizzle/0262_scoped_connected_machines_and_rigs.sql +1700 -0
- package/drizzle/0263_organization_membership_lifecycle.sql +3284 -0
- package/drizzle/0264_connection_authority_runtime_activation.sql +1084 -0
- package/drizzle/0266_company_brain_context_receipt_inspection.sql +156 -0
- package/drizzle/0268_governed_learning_decision_receipts.sql +825 -0
- package/drizzle/0269_governed_learning_activation_controller.sql +1874 -0
- package/drizzle/0270_governed_learning_history_inspection.sql +170 -0
- package/drizzle/0271_company_brain_retrieval_only_default.sql +524 -0
- package/drizzle/0272_human_confirmed_learning_activation.sql +591 -0
- package/drizzle/0273_scheduled_variable_set_materialization.sql +268 -0
- package/drizzle/0274_human_confirmed_knowledge_review.sql +310 -0
- package/drizzle/0275_scheduled_connection_authority.sql +5527 -0
- package/drizzle/0276_onboarding_proposal_initiating_human_guc.sql +198 -0
- package/drizzle/0277_workspace_writer_authority_attribution.sql +393 -0
- package/drizzle/0278_workspace_membership_removal_fencing.sql +1174 -0
- package/drizzle/0279_workspace_connection_use_lane.sql +343 -0
- package/drizzle/0280_connection_and_variable_set_audit_attribution.sql +773 -0
- package/drizzle/0281_viewer_holder_authority_claims.sql +52 -0
- package/drizzle/0282_variable_set_session_attach_attribution.sql +178 -0
- package/drizzle/0283_editable_spreadsheet_authored_state.sql +82 -0
- package/drizzle/0284_truthful_human_confirmed_review_reason.sql +930 -0
- package/drizzle/0285_organization_tenancy_inventory.sql +329 -0
- package/drizzle/0286_widen_task_note_expiry_ceiling.sql +391 -0
- package/drizzle/0287_open_suffix_pending_tool_calls.sql +18 -0
- package/drizzle/0288_attached_browser_reenrollment.sql +64 -0
- package/drizzle/0289_session_composer_policy_authority.sql +597 -0
- package/drizzle/0290_organization_membership_backfill.sql +268 -0
- package/drizzle/0291_resource_authority_classification_assertion.sql +371 -0
- package/drizzle/0292_truthful_tenancy_inventory_counters.sql +291 -0
- package/drizzle/0293_confirm_time_rule_rebaseline.sql +615 -0
- package/drizzle/0294_preference_activation_authority.sql +237 -0
- package/drizzle/0295_retire_legacy_standing_memory_mode.sql +476 -0
- package/drizzle/0296_force_rls_backfill_noop_repair.sql +215 -0
- package/drizzle/0297_session_ownership_classification_and_backfill.sql +752 -0
- package/drizzle/0298_organization_tenancy_parity.sql +609 -0
- package/drizzle/0299_organization_membership_lock_order.sql +291 -0
- package/drizzle/0300_tenancy_backfill_ledger.sql +459 -0
- package/drizzle/0301_session_snapshot_and_pin_visibility.sql +181 -0
- package/drizzle/0302_personal_workspace_session_ownership.sql +303 -0
- package/drizzle/0303_session_tenancy_product_activation.sql +919 -0
- package/drizzle/0304_personal_workspace_private_session_reads.sql +97 -0
- package/drizzle/0305_personal_resource_grant_management.sql +584 -0
- package/drizzle/0306_atomic_personal_resource_attachments.sql +1374 -0
- package/drizzle/0307_session_attention_state.sql +51 -0
- package/drizzle/0308_session_archives.sql +35 -0
- package/drizzle/0309_channel_project_pins.sql +6 -0
- package/drizzle/0310_channel_project_order.sql +27 -0
- package/drizzle/0311_company_scope_and_private_session_create.sql +656 -0
- package/drizzle/0312_quiescent_session_tree_deletion.sql +39 -0
- package/drizzle/0313_private_child_session_authority.sql +365 -0
- package/drizzle/0314_unregistered_organization_invitations.sql +606 -0
- package/drizzle/0315_personal_github_repository_selection.sql +624 -0
- package/drizzle/0316_human_confirmed_activation_resumed_generation.sql +869 -0
- package/drizzle/0317_session_goal_continuation_hold.sql +64 -0
- package/drizzle/0318_session_turn_startup_milestones.sql +115 -0
- package/drizzle/0319_event_triggered_automations.sql +254 -0
- package/drizzle/0320_pr_review_pack.sql +113 -0
- package/drizzle/0321_slack_bot_environment_display_name.sql +15 -0
- package/drizzle/0322_session_turns_unclaimed_prompt_trigger_index.sql +5 -0
- package/drizzle/0323_organization_private_session_enablement.sql +489 -0
- package/drizzle/0324_human_confirmed_company_profile_agent_admin.sql +856 -0
- package/drizzle/0325_child_lifecycle_notices.sql +44 -0
- package/drizzle/0326_interaction_operation_error_codes.sql +62 -0
- package/drizzle/0327_slack_first_task_hint.sql +29 -0
- package/drizzle/0328_session_background_commands.sql +590 -0
- package/drizzle/0329_slack_orchestration_delivery_events.sql +91 -0
- package/drizzle/0330_api_key_descriptions.sql +20 -0
- package/drizzle/0331_managed_organization_creation.sql +163 -0
- package/drizzle/0332_organization_shared_workspace_control_plane.sql +539 -0
- package/drizzle/0333_session_turn_prompt_routing.sql +45 -0
- package/drizzle/0334_connected_machine_workspace_root.sql +99 -0
- package/drizzle/0335_slack_workspace_routing.sql +460 -0
- package/drizzle/0336_atomic_session_fork_visibility.sql +667 -0
- package/drizzle/0337_slack_routed_action_handles.sql +189 -0
- package/drizzle/0338_atomic_connected_machine_attachments.sql +410 -0
- package/drizzle/0339_document_authority_reclassification.sql +1153 -0
- package/drizzle/0340_tenancy_backfill_activation_evidence.sql +1347 -0
- package/drizzle/0341_slack_routing_probe_organization_fence.sql +109 -0
- package/drizzle/0342_slack_route_prompt_single_pending.sql +32 -0
- package/drizzle/0343_personal_document_force_rls_lock_repair.sql +146 -0
- package/drizzle/0344_private_session_visibility_transition_gate.sql +85 -0
- package/drizzle/0345_tenant_scoped_session_tenancy_fence.sql +2839 -0
- package/drizzle/0346_document_migration_audit_surface.sql +454 -0
- package/drizzle/0347_connection_authority_convergence_evidence.sql +396 -0
- package/package.json +10 -6
- package/src/attached-browser-devices.ts +233 -5
- package/src/automations.ts +911 -0
- package/src/browser-sessions.ts +10 -0
- package/src/capability-integrations.ts +175 -1
- package/src/child-lifecycle-notices.ts +241 -0
- package/src/codemode-operations.ts +1 -35
- package/src/company-brain-context-selection-schema.ts +100 -0
- package/src/company-brain-context-selection.ts +359 -0
- package/src/company-brain-governed-writes.ts +1170 -0
- package/src/company-brain.ts +309 -0
- package/src/company-profile-agent-admin.ts +225 -0
- package/src/company-profile-schema.ts +121 -1
- package/src/company-profile.ts +21 -0
- package/src/computer-sessions.ts +10 -1
- package/src/connection-authority.ts +290 -0
- package/src/connection-scopes.ts +20 -0
- package/src/connection-tenancy-backfill.ts +167 -0
- package/src/connection-token-resolver.ts +263 -31
- package/src/database.ts +87 -2
- package/src/editable-artifacts-schema.ts +10 -5
- package/src/editable-artifacts.ts +21 -11
- package/src/governed-learning-activation-schema.ts +121 -0
- package/src/governed-learning-activation.ts +588 -0
- package/src/governed-learning-evaluator-schema.ts +72 -0
- package/src/governed-learning-evaluator.ts +217 -0
- package/src/index.ts +11676 -1550
- package/src/insights.ts +107 -0
- package/src/integration-bindings.ts +88 -6
- package/src/integration-facets.ts +287 -84
- package/src/interaction-placement-loss.ts +234 -0
- package/src/managed-human-provisioning.ts +116 -0
- package/src/memory-domain.ts +27 -32
- package/src/migrate.ts +77 -1
- package/src/new-session-drafts.ts +229 -35
- package/src/organization-membership-backfill.ts +735 -0
- package/src/organization-membership-lifecycle.ts +995 -0
- package/src/pr-review.ts +890 -0
- package/src/preference-registry.ts +31 -1
- package/src/provision-roles.ts +905 -6
- package/src/runtime-posture-cli.ts +2 -0
- package/src/runtime-posture.ts +1317 -72
- package/src/schema.ts +3316 -428
- package/src/scoped-knowledge-schema.ts +16 -8
- package/src/scoped-knowledge.ts +1 -1
- package/src/session-background-commands.ts +759 -0
- package/src/session-control.ts +640 -74
- package/src/session-goal-pacing.ts +126 -0
- package/src/session-queue-commands.ts +441 -85
- package/src/session-realtime-context.ts +4 -6
- package/src/session-realtime-ledger.ts +28 -14
- package/src/session-realtime-mirror.ts +1 -1
- package/src/session-tenancy.ts +410 -49
- package/src/session-tool-call-settlement.ts +38 -29
- package/src/slack-routing-personal-workspace.ts +67 -0
- package/src/slack-routing.ts +867 -0
- package/src/slack-task-policy-schema.ts +5 -0
- package/src/slack-task-policy.ts +18 -0
- package/src/slack-user-link-access.ts +138 -11
- package/src/task-notes-schema.ts +74 -0
- package/src/task-notes.ts +124 -2
- package/src/turn-initiator.ts +8 -0
- package/src/user-resource-authority.ts +377 -0
- package/src/workspace-authority.ts +160 -0
- package/src/workspace-instruction-policies-schema.ts +53 -1
- package/src/workspace-instruction-policies.ts +528 -77
- package/src/workspace-learning-policy.ts +21 -5
- package/src/workspace-membership-permissions.ts +9 -0
- package/dist/chunk-4SR4HKKA.js.map +0 -1
- package/dist/chunk-6ZYKCRFO.js.map +0 -1
- package/dist/chunk-NXAFLCQX.js +0 -2468
- package/dist/chunk-NXAFLCQX.js.map +0 -1
- package/dist/chunk-SK7YYHBI.js.map +0 -1
- package/dist/chunk-WD5ROSLL.js +0 -117
- package/dist/chunk-WD5ROSLL.js.map +0 -1
- package/dist/chunk-ZWREYEWA.js.map +0 -1
- /package/dist/{chunk-CUPM3FGB.js.map → chunk-2PRS4J5Q.js.map} +0 -0
- /package/dist/{chunk-LTG3S6ZY.js.map → chunk-E6CLMKEE.js.map} +0 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { and, eq, inArray, sql } from "drizzle-orm";
|
|
2
|
+
import { type Database, withRlsContext } from "./database";
|
|
3
|
+
import {
|
|
4
|
+
advanceWorkspaceInteractionRevision,
|
|
5
|
+
readWorkspaceInteractionRevision,
|
|
6
|
+
} from "./interaction-revisions";
|
|
7
|
+
import * as schema from "./schema";
|
|
8
|
+
|
|
9
|
+
export const SOURCE_PLACEMENT_CHANGED = "source_placement_changed";
|
|
10
|
+
|
|
11
|
+
const CONNECTED_PLACEMENT_LIVE_LIFECYCLES = [
|
|
12
|
+
"starting",
|
|
13
|
+
"active",
|
|
14
|
+
"suspending",
|
|
15
|
+
"restoring",
|
|
16
|
+
"ending",
|
|
17
|
+
] as const;
|
|
18
|
+
|
|
19
|
+
export type TerminalizeStaleConnectedInteractionPlacementResult = {
|
|
20
|
+
sourcePlacementChanged: boolean;
|
|
21
|
+
changed: boolean;
|
|
22
|
+
browserSessionIds: string[];
|
|
23
|
+
computerSessionIds: string[];
|
|
24
|
+
revision: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Reconcile interaction resources after their source task moves away from a
|
|
29
|
+
* Connected Machine. The source row is the race fence: a stale route lookup
|
|
30
|
+
* may never retire resources while that machine is still the active placement.
|
|
31
|
+
* Browser and Desktop resources are settled together because headed browsers
|
|
32
|
+
* can share one physical controller with a linked Desktop.
|
|
33
|
+
*/
|
|
34
|
+
export async function terminalizeStaleConnectedInteractionPlacement(
|
|
35
|
+
db: Database,
|
|
36
|
+
input: {
|
|
37
|
+
accountId: string;
|
|
38
|
+
workspaceId: string;
|
|
39
|
+
sourceSessionId: string;
|
|
40
|
+
connectedSandboxId: string;
|
|
41
|
+
},
|
|
42
|
+
): Promise<TerminalizeStaleConnectedInteractionPlacementResult> {
|
|
43
|
+
return await withRlsContext(
|
|
44
|
+
db,
|
|
45
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
46
|
+
async (scopedDb) =>
|
|
47
|
+
await scopedDb.transaction(async (tx) => {
|
|
48
|
+
const [source] = await tx
|
|
49
|
+
.select({ activeSandboxId: schema.sessions.activeSandboxId })
|
|
50
|
+
.from(schema.sessions)
|
|
51
|
+
.where(
|
|
52
|
+
and(
|
|
53
|
+
eq(schema.sessions.accountId, input.accountId),
|
|
54
|
+
eq(schema.sessions.workspaceId, input.workspaceId),
|
|
55
|
+
eq(schema.sessions.id, input.sourceSessionId),
|
|
56
|
+
),
|
|
57
|
+
)
|
|
58
|
+
.for("update")
|
|
59
|
+
.limit(1);
|
|
60
|
+
|
|
61
|
+
const sourcePlacementChanged =
|
|
62
|
+
source !== undefined && source.activeSandboxId !== input.connectedSandboxId;
|
|
63
|
+
if (!sourcePlacementChanged) {
|
|
64
|
+
return {
|
|
65
|
+
sourcePlacementChanged: false,
|
|
66
|
+
changed: false,
|
|
67
|
+
browserSessionIds: [],
|
|
68
|
+
computerSessionIds: [],
|
|
69
|
+
revision: await readWorkspaceInteractionRevision(tx, input.workspaceId),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const browserRows = await tx
|
|
74
|
+
.select({ id: schema.browserSessions.id })
|
|
75
|
+
.from(schema.browserSessions)
|
|
76
|
+
.innerJoin(
|
|
77
|
+
schema.browserSessionAssociations,
|
|
78
|
+
and(
|
|
79
|
+
eq(schema.browserSessionAssociations.browserSessionId, schema.browserSessions.id),
|
|
80
|
+
eq(schema.browserSessionAssociations.workspaceId, input.workspaceId),
|
|
81
|
+
eq(schema.browserSessionAssociations.sessionId, input.sourceSessionId),
|
|
82
|
+
eq(schema.browserSessionAssociations.relationship, "created"),
|
|
83
|
+
),
|
|
84
|
+
)
|
|
85
|
+
.where(
|
|
86
|
+
and(
|
|
87
|
+
eq(schema.browserSessions.accountId, input.accountId),
|
|
88
|
+
eq(schema.browserSessions.workspaceId, input.workspaceId),
|
|
89
|
+
eq(schema.browserSessions.placementKind, "connected_machine"),
|
|
90
|
+
eq(schema.browserSessions.connectedSandboxId, input.connectedSandboxId),
|
|
91
|
+
inArray(schema.browserSessions.lifecycle, [...CONNECTED_PLACEMENT_LIVE_LIFECYCLES]),
|
|
92
|
+
),
|
|
93
|
+
);
|
|
94
|
+
const computerRows = await tx
|
|
95
|
+
.select({ id: schema.computerSessions.id })
|
|
96
|
+
.from(schema.computerSessions)
|
|
97
|
+
.innerJoin(
|
|
98
|
+
schema.computerSessionAssociations,
|
|
99
|
+
and(
|
|
100
|
+
eq(schema.computerSessionAssociations.computerSessionId, schema.computerSessions.id),
|
|
101
|
+
eq(schema.computerSessionAssociations.workspaceId, input.workspaceId),
|
|
102
|
+
eq(schema.computerSessionAssociations.sessionId, input.sourceSessionId),
|
|
103
|
+
eq(schema.computerSessionAssociations.relationship, "created"),
|
|
104
|
+
),
|
|
105
|
+
)
|
|
106
|
+
.where(
|
|
107
|
+
and(
|
|
108
|
+
eq(schema.computerSessions.accountId, input.accountId),
|
|
109
|
+
eq(schema.computerSessions.workspaceId, input.workspaceId),
|
|
110
|
+
eq(schema.computerSessions.placementKind, "connected_machine"),
|
|
111
|
+
eq(schema.computerSessions.connectedSandboxId, input.connectedSandboxId),
|
|
112
|
+
inArray(schema.computerSessions.lifecycle, [...CONNECTED_PLACEMENT_LIVE_LIFECYCLES]),
|
|
113
|
+
),
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const browserSessionIds = [...new Set(browserRows.map((row) => row.id))].sort();
|
|
117
|
+
const computerSessionIds = [...new Set(computerRows.map((row) => row.id))].sort();
|
|
118
|
+
const resourceIds = [...browserSessionIds, ...computerSessionIds];
|
|
119
|
+
if (resourceIds.length === 0) {
|
|
120
|
+
return {
|
|
121
|
+
sourcePlacementChanged: true,
|
|
122
|
+
changed: false,
|
|
123
|
+
browserSessionIds,
|
|
124
|
+
computerSessionIds,
|
|
125
|
+
revision: await readWorkspaceInteractionRevision(tx, input.workspaceId),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const operationRows = await tx
|
|
130
|
+
.select({ operationId: schema.interactionOperations.operationId })
|
|
131
|
+
.from(schema.interactionOperations)
|
|
132
|
+
.where(
|
|
133
|
+
and(
|
|
134
|
+
eq(schema.interactionOperations.accountId, input.accountId),
|
|
135
|
+
eq(schema.interactionOperations.workspaceId, input.workspaceId),
|
|
136
|
+
inArray(schema.interactionOperations.resourceId, resourceIds),
|
|
137
|
+
inArray(schema.interactionOperations.state, ["prepared", "dispatched"]),
|
|
138
|
+
),
|
|
139
|
+
);
|
|
140
|
+
const operationIds = operationRows.map((row) => row.operationId).sort();
|
|
141
|
+
|
|
142
|
+
// Stable lock order matches the attached-device generation-loss path.
|
|
143
|
+
for (const operationId of operationIds) {
|
|
144
|
+
await tx.execute(sql`
|
|
145
|
+
select operation_id from interaction_operations
|
|
146
|
+
where workspace_id = ${input.workspaceId} and operation_id = ${operationId}
|
|
147
|
+
for update
|
|
148
|
+
`);
|
|
149
|
+
}
|
|
150
|
+
for (const browserSessionId of browserSessionIds) {
|
|
151
|
+
await tx.execute(sql`
|
|
152
|
+
select id from browser_sessions
|
|
153
|
+
where workspace_id = ${input.workspaceId} and id = ${browserSessionId}
|
|
154
|
+
for update
|
|
155
|
+
`);
|
|
156
|
+
}
|
|
157
|
+
for (const computerSessionId of computerSessionIds) {
|
|
158
|
+
await tx.execute(sql`
|
|
159
|
+
select id from computer_sessions
|
|
160
|
+
where workspace_id = ${input.workspaceId} and id = ${computerSessionId}
|
|
161
|
+
for update
|
|
162
|
+
`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const now = new Date();
|
|
166
|
+
if (operationIds.length > 0) {
|
|
167
|
+
await tx
|
|
168
|
+
.update(schema.interactionOperations)
|
|
169
|
+
.set({
|
|
170
|
+
state: "outcome_unknown",
|
|
171
|
+
errorCode: "outcome_unknown",
|
|
172
|
+
errorMessage: "The source task moved to another placement",
|
|
173
|
+
errorRetryable: false,
|
|
174
|
+
errorDetails: { reason: SOURCE_PLACEMENT_CHANGED },
|
|
175
|
+
settledAt: now,
|
|
176
|
+
updatedAt: now,
|
|
177
|
+
})
|
|
178
|
+
.where(
|
|
179
|
+
and(
|
|
180
|
+
eq(schema.interactionOperations.workspaceId, input.workspaceId),
|
|
181
|
+
inArray(schema.interactionOperations.operationId, operationIds),
|
|
182
|
+
inArray(schema.interactionOperations.state, ["prepared", "dispatched"]),
|
|
183
|
+
),
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
if (browserSessionIds.length > 0) {
|
|
187
|
+
await tx
|
|
188
|
+
.update(schema.browserSessions)
|
|
189
|
+
.set({
|
|
190
|
+
lifecycle: "lost",
|
|
191
|
+
failureCode: SOURCE_PLACEMENT_CHANGED,
|
|
192
|
+
updatedAt: now,
|
|
193
|
+
})
|
|
194
|
+
.where(
|
|
195
|
+
and(
|
|
196
|
+
eq(schema.browserSessions.workspaceId, input.workspaceId),
|
|
197
|
+
inArray(schema.browserSessions.id, browserSessionIds),
|
|
198
|
+
inArray(schema.browserSessions.lifecycle, [...CONNECTED_PLACEMENT_LIVE_LIFECYCLES]),
|
|
199
|
+
),
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
if (computerSessionIds.length > 0) {
|
|
203
|
+
await tx
|
|
204
|
+
.update(schema.computerSessions)
|
|
205
|
+
.set({
|
|
206
|
+
lifecycle: "lost",
|
|
207
|
+
failureCode: SOURCE_PLACEMENT_CHANGED,
|
|
208
|
+
updatedAt: now,
|
|
209
|
+
})
|
|
210
|
+
.where(
|
|
211
|
+
and(
|
|
212
|
+
eq(schema.computerSessions.workspaceId, input.workspaceId),
|
|
213
|
+
inArray(schema.computerSessions.id, computerSessionIds),
|
|
214
|
+
inArray(schema.computerSessions.lifecycle, [
|
|
215
|
+
...CONNECTED_PLACEMENT_LIVE_LIFECYCLES,
|
|
216
|
+
]),
|
|
217
|
+
),
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
const revision = await advanceWorkspaceInteractionRevision(
|
|
221
|
+
tx,
|
|
222
|
+
input.accountId,
|
|
223
|
+
input.workspaceId,
|
|
224
|
+
);
|
|
225
|
+
return {
|
|
226
|
+
sourcePlacementChanged: true,
|
|
227
|
+
changed: true,
|
|
228
|
+
browserSessionIds,
|
|
229
|
+
computerSessionIds,
|
|
230
|
+
revision,
|
|
231
|
+
};
|
|
232
|
+
}),
|
|
233
|
+
);
|
|
234
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { and, eq, sql } from "drizzle-orm";
|
|
2
|
+
import type { Database } from "./database";
|
|
3
|
+
import { rawRows, setRlsContext } from "./database";
|
|
4
|
+
import * as schema from "./schema";
|
|
5
|
+
|
|
6
|
+
export type ManagedHumanPersonalWorkspaceProvisioning = {
|
|
7
|
+
organizationMembershipId: string;
|
|
8
|
+
personalWorkspaceId: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Converge the deterministic managed-human personal workspace, its inference
|
|
13
|
+
* control row, and the organization-membership anchor through the exact 0219
|
|
14
|
+
* `ensure_managed_human_personal_workspace` lifecycle seam.
|
|
15
|
+
*
|
|
16
|
+
* This is the single implementation of that preparation. The Better Auth
|
|
17
|
+
* managed-access hook and the operator membership backfill driver both call it,
|
|
18
|
+
* so neither owns a second copy of the deterministic personal-workspace
|
|
19
|
+
* identity. All authority - account binding, owner-membership proof, workspace
|
|
20
|
+
* identity, control-row validation, membership status - stays inside the
|
|
21
|
+
* SECURITY DEFINER seam, which fails closed; nothing here writes an
|
|
22
|
+
* organization-tenancy table directly.
|
|
23
|
+
*
|
|
24
|
+
* Must be called inside a transaction whose RLS context is already the exact
|
|
25
|
+
* account with no workspace. It restores that context before returning.
|
|
26
|
+
*/
|
|
27
|
+
export async function ensureManagedHumanPersonalWorkspace(
|
|
28
|
+
tx: Database,
|
|
29
|
+
input: { accountId: string; subjectId: string },
|
|
30
|
+
): Promise<ManagedHumanPersonalWorkspaceProvisioning> {
|
|
31
|
+
const personalWorkspaceExternalSource = "opengeni:organization-membership";
|
|
32
|
+
const personalWorkspaceExternalId = `${input.accountId}:${input.subjectId}`;
|
|
33
|
+
let [personalWorkspace] = await tx
|
|
34
|
+
.select()
|
|
35
|
+
.from(schema.workspaces)
|
|
36
|
+
.where(
|
|
37
|
+
and(
|
|
38
|
+
eq(schema.workspaces.externalSource, personalWorkspaceExternalSource),
|
|
39
|
+
eq(schema.workspaces.externalId, personalWorkspaceExternalId),
|
|
40
|
+
),
|
|
41
|
+
)
|
|
42
|
+
.limit(1);
|
|
43
|
+
if (!personalWorkspace) {
|
|
44
|
+
[personalWorkspace] = await tx
|
|
45
|
+
.insert(schema.workspaces)
|
|
46
|
+
.values({
|
|
47
|
+
accountId: input.accountId,
|
|
48
|
+
name: "Personal workspace",
|
|
49
|
+
slug: null,
|
|
50
|
+
externalSource: personalWorkspaceExternalSource,
|
|
51
|
+
externalId: personalWorkspaceExternalId,
|
|
52
|
+
})
|
|
53
|
+
.onConflictDoUpdate({
|
|
54
|
+
target: [schema.workspaces.externalSource, schema.workspaces.externalId],
|
|
55
|
+
set: { updatedAt: new Date() },
|
|
56
|
+
})
|
|
57
|
+
.returning();
|
|
58
|
+
}
|
|
59
|
+
if (!personalWorkspace) {
|
|
60
|
+
throw new Error("Failed to ensure personal workspace");
|
|
61
|
+
}
|
|
62
|
+
if (
|
|
63
|
+
personalWorkspace.accountId !== input.accountId ||
|
|
64
|
+
personalWorkspace.externalSource !== personalWorkspaceExternalSource ||
|
|
65
|
+
personalWorkspace.externalId !== personalWorkspaceExternalId
|
|
66
|
+
) {
|
|
67
|
+
throw new Error("Managed personal workspace identity conflict");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await setRlsContext(tx, {
|
|
71
|
+
accountId: input.accountId,
|
|
72
|
+
workspaceId: personalWorkspace.id,
|
|
73
|
+
});
|
|
74
|
+
const [personalWorkspaceControl] = await tx
|
|
75
|
+
.select({ workspaceId: schema.workspaceInferenceControls.workspaceId })
|
|
76
|
+
.from(schema.workspaceInferenceControls)
|
|
77
|
+
.where(eq(schema.workspaceInferenceControls.workspaceId, personalWorkspace.id))
|
|
78
|
+
.limit(1);
|
|
79
|
+
if (!personalWorkspaceControl) {
|
|
80
|
+
await tx
|
|
81
|
+
.insert(schema.workspaceInferenceControls)
|
|
82
|
+
.values({
|
|
83
|
+
workspaceId: personalWorkspace.id,
|
|
84
|
+
accountId: input.accountId,
|
|
85
|
+
})
|
|
86
|
+
.onConflictDoNothing();
|
|
87
|
+
}
|
|
88
|
+
await setRlsContext(tx, {
|
|
89
|
+
accountId: input.accountId,
|
|
90
|
+
workspaceId: null,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const [provisionedMembership] = await rawRows<{
|
|
94
|
+
organization_membership_id: string;
|
|
95
|
+
personal_workspace_id: string;
|
|
96
|
+
}>(
|
|
97
|
+
tx,
|
|
98
|
+
sql`
|
|
99
|
+
select * from ensure_managed_human_personal_workspace(
|
|
100
|
+
${input.accountId},
|
|
101
|
+
${input.subjectId},
|
|
102
|
+
${personalWorkspace.id}
|
|
103
|
+
)
|
|
104
|
+
`,
|
|
105
|
+
);
|
|
106
|
+
if (
|
|
107
|
+
!provisionedMembership ||
|
|
108
|
+
provisionedMembership.personal_workspace_id !== personalWorkspace.id
|
|
109
|
+
) {
|
|
110
|
+
throw new Error("Managed organization membership provisioning did not converge");
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
organizationMembershipId: provisionedMembership.organization_membership_id,
|
|
114
|
+
personalWorkspaceId: provisionedMembership.personal_workspace_id,
|
|
115
|
+
};
|
|
116
|
+
}
|
package/src/memory-domain.ts
CHANGED
|
@@ -428,13 +428,7 @@ export const WORKSPACE_MEMORY_BLOCK_EMPTY = `## Workspace memory
|
|
|
428
428
|
This workspace has shared long-lived memory, currently empty. Your context is lost when the session ends; memory is not. When you learn something durably useful — a preference, an environment fact, a procedure that worked, a decision and its reason — save it with memory_save (one crisp, self-contained fact per record). Never store secrets.`;
|
|
429
429
|
|
|
430
430
|
export const MEMORY_SEARCH_TOOL_DESCRIPTION =
|
|
431
|
-
"Search this workspace's shared long-lived memory (semantic + keyword). Use it before starting a new non-trivial task when the injected notes or current conversation do not already answer how the workspace does something. Results persist in conversation context: do not repeat the same search as routine setup on every continuation, resume, or interrupted turn. Returns scored records with ids.";
|
|
432
|
-
|
|
433
|
-
export const MEMORY_SAVE_TOOL_DESCRIPTION =
|
|
434
|
-
"Save one durable, future-useful fact to this workspace's shared memory: a stable preference, an environment fact, a procedure that worked, or a decision and its reason. Write it compactly (1–3 sentences), self-contained (no 'this session/above' references, absolute dates, name concrete things), so a future session can act on it alone. Do NOT save: session-specific state, speculation, anything derivable from the repo/docs, or near-duplicates of existing memories (search first — to refine or replace an existing record pass replaces_id). Most turns have nothing worth saving.";
|
|
435
|
-
|
|
436
|
-
export const MEMORY_CORRECT_TOOL_DESCRIPTION =
|
|
437
|
-
"Flag a workspace memory as wrong or outdated the moment you discover it — this is the most valuable memory action, because a wrong memory misleads every future session. Pass the record's id (as shown in [brackets]); optionally give replacement_text with the corrected fact, otherwise the record is archived.";
|
|
431
|
+
"Search this workspace's shared long-lived memory (semantic + keyword). Use it before starting a new non-trivial task when the injected notes or current conversation do not already answer how the workspace does something. Results persist in conversation context: do not repeat the same search as routine setup on every continuation, resume, or interrupted turn. Returns scored records with ids. Indexed workspace documents are a separate store: search those with `knowledge_search`/`knowledge_get` (or `search_documents`) on the Document Search (docs) MCP server, not with this tool.";
|
|
438
432
|
|
|
439
433
|
// ---------------------------------------------------------------------------
|
|
440
434
|
// Text normalization + hashing (MUST match migration 0045 backfill exactly)
|
|
@@ -469,7 +463,7 @@ export function isMemoryTextTooLong(text: string): boolean {
|
|
|
469
463
|
// ---------------------------------------------------------------------------
|
|
470
464
|
|
|
471
465
|
export function estimateMemoryTokens(text: string): number {
|
|
472
|
-
return Math.ceil(text
|
|
466
|
+
return Math.ceil(Buffer.byteLength(text, "utf8") / 4);
|
|
473
467
|
}
|
|
474
468
|
|
|
475
469
|
// Short id shown in the block/tool output = first 8 chars of the uuid. Tools
|
|
@@ -491,46 +485,47 @@ export type MemoryBlockRecord = {
|
|
|
491
485
|
// into kind sections. Episodic is excluded. Returns null if nothing renders
|
|
492
486
|
// (no non-episodic records) — the caller substitutes the empty-state block.
|
|
493
487
|
export function renderWorkspaceMemoryBlock(records: readonly MemoryBlockRecord[]): string | null {
|
|
494
|
-
|
|
495
|
-
if (renderable.length === 0) {
|
|
488
|
+
if (!records.some((record) => record.kind !== "episodic")) {
|
|
496
489
|
return null;
|
|
497
490
|
}
|
|
491
|
+
const selected = selectWorkspaceMemoryBlockRecords(records);
|
|
492
|
+
|
|
493
|
+
const lines: string[] = [WORKSPACE_MEMORY_BLOCK_HEADER_POPULATED];
|
|
494
|
+
for (const kind of MEMORY_BLOCK_KIND_ORDER) {
|
|
495
|
+
const inSection = selected.filter((record) => record.kind === kind);
|
|
496
|
+
if (inSection.length === 0) {
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
lines.push("", `### ${MEMORY_KIND_SECTION_TITLES[kind]}`);
|
|
500
|
+
for (const record of inSection) {
|
|
501
|
+
lines.push(renderMemoryEntry(record));
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return lines.join("\n");
|
|
505
|
+
}
|
|
498
506
|
|
|
499
|
-
|
|
500
|
-
|
|
507
|
+
/** Canonical whole-entry prompt-budget selector, shared with receipt validation. */
|
|
508
|
+
export function selectWorkspaceMemoryBlockRecords(
|
|
509
|
+
records: readonly MemoryBlockRecord[],
|
|
510
|
+
): readonly MemoryBlockRecord[] {
|
|
511
|
+
const renderable = records.filter((record) => record.kind !== "episodic");
|
|
501
512
|
const headerTokens = estimateMemoryTokens(WORKSPACE_MEMORY_BLOCK_HEADER_POPULATED);
|
|
502
513
|
let usedTokens = headerTokens;
|
|
503
514
|
const seenSections = new Set<KnowledgeMemoryKind>();
|
|
504
515
|
const selected: MemoryBlockRecord[] = [];
|
|
505
516
|
for (const record of renderable) {
|
|
506
517
|
const entryLine = renderMemoryEntry(record);
|
|
507
|
-
let cost = estimateMemoryTokens(entryLine) + 1;
|
|
518
|
+
let cost = estimateMemoryTokens(entryLine) + 1;
|
|
508
519
|
if (!seenSections.has(record.kind)) {
|
|
509
520
|
const sectionTitle = `### ${MEMORY_KIND_SECTION_TITLES[record.kind]}`;
|
|
510
|
-
cost += estimateMemoryTokens(sectionTitle) + 2;
|
|
511
|
-
}
|
|
512
|
-
if (usedTokens + cost > WORKSPACE_MEMORY_BLOCK_TOKEN_BUDGET) {
|
|
513
|
-
// Skip entries that don't fit instead of stopping: one oversized entry
|
|
514
|
-
// must not starve smaller lower-priority records of the remaining budget.
|
|
515
|
-
continue;
|
|
521
|
+
cost += estimateMemoryTokens(sectionTitle) + 2;
|
|
516
522
|
}
|
|
523
|
+
if (usedTokens + cost > WORKSPACE_MEMORY_BLOCK_TOKEN_BUDGET) continue;
|
|
517
524
|
usedTokens += cost;
|
|
518
525
|
seenSections.add(record.kind);
|
|
519
526
|
selected.push(record);
|
|
520
527
|
}
|
|
521
|
-
|
|
522
|
-
const lines: string[] = [WORKSPACE_MEMORY_BLOCK_HEADER_POPULATED];
|
|
523
|
-
for (const kind of MEMORY_BLOCK_KIND_ORDER) {
|
|
524
|
-
const inSection = selected.filter((record) => record.kind === kind);
|
|
525
|
-
if (inSection.length === 0) {
|
|
526
|
-
continue;
|
|
527
|
-
}
|
|
528
|
-
lines.push("", `### ${MEMORY_KIND_SECTION_TITLES[kind]}`);
|
|
529
|
-
for (const record of inSection) {
|
|
530
|
-
lines.push(renderMemoryEntry(record));
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
return lines.join("\n");
|
|
528
|
+
return selected;
|
|
534
529
|
}
|
|
535
530
|
|
|
536
531
|
function renderMemoryEntry(record: MemoryBlockRecord): string {
|
package/src/migrate.ts
CHANGED
|
@@ -6,6 +6,12 @@ import postgres from "postgres";
|
|
|
6
6
|
const DEFAULT_DATABASE_URL = "postgres://opengeni:opengeni@127.0.0.1:5432/opengeni";
|
|
7
7
|
const DEFAULT_MAX_NESTED_AGENT_DEPTH = 3;
|
|
8
8
|
const MAX_NESTED_AGENT_DEPTH = 2_147_483_647;
|
|
9
|
+
const DEFAULT_APPLICATION_DATABASE_ROLE = "opengeni_app";
|
|
10
|
+
const GOAL_REVISION_CUTOVER_MIGRATION = "0257_goal_revision_decisions_and_root_constraints.sql";
|
|
11
|
+
const ATOMIC_PERSONAL_RESOURCE_CUTOVER_MIGRATION = "0306_atomic_personal_resource_attachments.sql";
|
|
12
|
+
const UNREGISTERED_INVITATION_CUTOVER_MIGRATION = "0314_unregistered_organization_invitations.sql";
|
|
13
|
+
const ATOMIC_CONNECTED_MACHINE_CUTOVER_MIGRATION = "0338_atomic_connected_machine_attachments.sql";
|
|
14
|
+
const MAX_MIGRATION_APPLICATION_ROLES = 16;
|
|
9
15
|
const batchedBackfillDirective =
|
|
10
16
|
/^-- opengeni:batched-backfill batch-size=(\d+) lock-timeout=(\d+(?:ms|s|min)) statement-timeout=(\d+(?:ms|s|min))$/;
|
|
11
17
|
const concurrentIndexDirective = /^-- opengeni:concurrent-index lock-timeout=(\d+(?:ms|s|min))$/;
|
|
@@ -30,6 +36,12 @@ export interface ConcurrentIndexMigration {
|
|
|
30
36
|
|
|
31
37
|
export type MigrationRuntimeOptions = {
|
|
32
38
|
maxNestedAgentDepth?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Exact database login roles that may run an OpenGeni API or worker against
|
|
41
|
+
* this target. Maintenance cutovers use this list to reject a live mixed-
|
|
42
|
+
* version fleet. Dedicated-schema and custom-role deployments must supply it.
|
|
43
|
+
*/
|
|
44
|
+
applicationDatabaseRoles?: string[];
|
|
33
45
|
};
|
|
34
46
|
|
|
35
47
|
type DeploymentDepthPolicy = {
|
|
@@ -230,6 +242,53 @@ function deploymentDepthPolicy(
|
|
|
230
242
|
return { maxNestedAgentDepth: value, source: "deployment" };
|
|
231
243
|
}
|
|
232
244
|
|
|
245
|
+
function migrationApplicationRoles(
|
|
246
|
+
schema: string | undefined,
|
|
247
|
+
options: MigrationRuntimeOptions | undefined,
|
|
248
|
+
): string[] {
|
|
249
|
+
const configured = options?.applicationDatabaseRoles;
|
|
250
|
+
const environment = process.env.OPENGENI_MIGRATION_APPLICATION_DATABASE_ROLES?.trim();
|
|
251
|
+
const explicit =
|
|
252
|
+
configured !== undefined
|
|
253
|
+
? configured
|
|
254
|
+
: environment
|
|
255
|
+
? environment.split(",").map((role) => role.trim())
|
|
256
|
+
: undefined;
|
|
257
|
+
|
|
258
|
+
if (!explicit) {
|
|
259
|
+
const configuredRuntimeRoles = [
|
|
260
|
+
process.env.OPENGENI_APP_DATABASE_USER?.trim(),
|
|
261
|
+
process.env.OPENGENI_RUNTIME_DATABASE_ROLE?.trim(),
|
|
262
|
+
].filter((role): role is string => Boolean(role) && role !== DEFAULT_APPLICATION_DATABASE_ROLE);
|
|
263
|
+
if (schema || configuredRuntimeRoles.length > 0) {
|
|
264
|
+
throw new Error(
|
|
265
|
+
"Migration 0257 requires the exact application database roles via " +
|
|
266
|
+
"MigrationRuntimeOptions.applicationDatabaseRoles or " +
|
|
267
|
+
"OPENGENI_MIGRATION_APPLICATION_DATABASE_ROLES for dedicated-schema or custom-role deployments",
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
return [DEFAULT_APPLICATION_DATABASE_ROLE];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (explicit.length < 1 || explicit.length > MAX_MIGRATION_APPLICATION_ROLES) {
|
|
274
|
+
throw new Error(
|
|
275
|
+
`Migration application database roles must contain 1-${MAX_MIGRATION_APPLICATION_ROLES} entries`,
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
const normalized = [...new Set(explicit)].sort((left, right) =>
|
|
279
|
+
Buffer.compare(Buffer.from(left, "utf8"), Buffer.from(right, "utf8")),
|
|
280
|
+
);
|
|
281
|
+
if (
|
|
282
|
+
normalized.length !== explicit.length ||
|
|
283
|
+
normalized.some((role) => role.length === 0 || Buffer.byteLength(role, "utf8") > 63)
|
|
284
|
+
) {
|
|
285
|
+
throw new Error(
|
|
286
|
+
"Migration application database roles must be unique, non-empty Postgres role names of at most 63 UTF-8 bytes",
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
return normalized;
|
|
290
|
+
}
|
|
291
|
+
|
|
233
292
|
async function persistDeploymentDepthPolicy(
|
|
234
293
|
sql: postgres.Sql,
|
|
235
294
|
policy: DeploymentDepthPolicy,
|
|
@@ -277,6 +336,9 @@ async function persistDeploymentDepthPolicy(
|
|
|
277
336
|
* 2. `public` stays LAST on the path so `gen_random_uuid()` (pgcrypto) and the
|
|
278
337
|
* `vector` type — both installed into `public` by 0000 — still resolve. The
|
|
279
338
|
* `opengeni_private.*` helpers are always called with an absolute prefix.
|
|
339
|
+
* Dedicated-schema callers must also pass the exact API/worker database login
|
|
340
|
+
* identities in `applicationDatabaseRoles`; maintenance cutovers cannot infer
|
|
341
|
+
* host-managed or scoped runtime roles from the admin connection.
|
|
280
342
|
*
|
|
281
343
|
* `OPENGENI_DB_SCHEMA` defaults UNSET → `public` → standalone, so the default
|
|
282
344
|
* binding never regresses.
|
|
@@ -312,6 +374,19 @@ export async function migrate(
|
|
|
312
374
|
);
|
|
313
375
|
const appliedRows = await sql`SELECT "name" FROM "schema_migrations"`;
|
|
314
376
|
const applied = new Set(appliedRows.map((row) => row.name as string));
|
|
377
|
+
if (
|
|
378
|
+
!applied.has(GOAL_REVISION_CUTOVER_MIGRATION) ||
|
|
379
|
+
!applied.has(ATOMIC_PERSONAL_RESOURCE_CUTOVER_MIGRATION) ||
|
|
380
|
+
!applied.has(UNREGISTERED_INVITATION_CUTOVER_MIGRATION) ||
|
|
381
|
+
!applied.has(ATOMIC_CONNECTED_MACHINE_CUTOVER_MIGRATION)
|
|
382
|
+
) {
|
|
383
|
+
const applicationRoles = migrationApplicationRoles(schema, runtimeOptions);
|
|
384
|
+
await sql`select set_config(
|
|
385
|
+
'opengeni.migration_application_roles',
|
|
386
|
+
${JSON.stringify(applicationRoles)},
|
|
387
|
+
false
|
|
388
|
+
)`;
|
|
389
|
+
}
|
|
315
390
|
for (const file of files) {
|
|
316
391
|
if (applied.has(file)) {
|
|
317
392
|
continue;
|
|
@@ -332,7 +407,8 @@ export async function migrate(
|
|
|
332
407
|
* SDK entry point (Step I): run the migration chain over a host-supplied admin
|
|
333
408
|
* connection string against an explicit target schema. This is the embedded
|
|
334
409
|
* topology's named entry — a host calls `runMigrations(adminConnection,
|
|
335
|
-
* targetSchema)` from its own provisioning code
|
|
410
|
+
* targetSchema, { applicationDatabaseRoles })` from its own provisioning code
|
|
411
|
+
* instead of relying on env vars.
|
|
336
412
|
* `targetSchema` undefined → `public` → standalone behavior. Thin wrapper over
|
|
337
413
|
* `migrate` so there is one migration engine.
|
|
338
414
|
*/
|