@opengeni/db 0.28.9 → 0.31.1
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 +42 -0
- package/dist/attempt-tool-catalogs.d.ts +16 -0
- package/dist/browser-auth.d.ts +91 -0
- package/dist/browser-identities.d.ts +128 -0
- package/dist/browser-sessions.d.ts +272 -0
- package/dist/browser-state-artifacts.d.ts +17 -0
- package/dist/capability-components.d.ts +37 -0
- package/dist/capability-integrations.d.ts +192 -0
- package/dist/chunk-F5FT7BEZ.js +3535 -0
- package/dist/chunk-F5FT7BEZ.js.map +1 -0
- package/dist/chunk-ISI2TIUG.js +639 -0
- package/dist/chunk-ISI2TIUG.js.map +1 -0
- package/dist/chunk-MWDVXQOL.js +11639 -0
- package/dist/chunk-MWDVXQOL.js.map +1 -0
- package/dist/chunk-NBE6CHRI.js +955 -0
- package/dist/chunk-NBE6CHRI.js.map +1 -0
- package/dist/{chunk-VHBFHMPC.js → chunk-YHZXQ7HP.js} +754 -11
- package/dist/chunk-YHZXQ7HP.js.map +1 -0
- package/dist/codemode-operations.d.ts +117 -0
- package/dist/codex-token-resolver.d.ts +2 -2
- package/dist/company-profile-schema.d.ts +798 -0
- package/dist/company-profile.d.ts +268 -0
- package/dist/computer-sessions.d.ts +109 -0
- package/dist/connection-token-resolver.d.ts +9 -4
- package/dist/database.d.ts +52 -3
- package/dist/editable-artifact-durable-export.d.ts +153 -0
- package/dist/editable-artifact-durable-export.js +788 -0
- package/dist/editable-artifact-durable-export.js.map +1 -0
- package/dist/editable-artifact-materialization.d.ts +123 -0
- package/dist/editable-artifacts-schema.d.ts +4152 -0
- package/dist/editable-artifacts.d.ts +610 -0
- package/dist/editable-artifacts.js +26 -0
- package/dist/editable-artifacts.js.map +1 -0
- package/dist/generated-images.d.ts +139 -0
- package/dist/index.d.ts +698 -111
- package/dist/index.js +27573 -10322
- package/dist/index.js.map +1 -1
- package/dist/insights.d.ts +51 -4
- package/dist/integration-bindings.d.ts +70 -0
- package/dist/integration-features.d.ts +99 -0
- package/dist/interaction-revisions.d.ts +3 -0
- package/dist/interaction-schema.d.ts +5372 -0
- package/dist/knowledge-source-sync-schema.d.ts +1679 -0
- package/dist/knowledge-source-sync.d.ts +274 -0
- package/dist/memory-governance.d.ts +2 -2
- package/dist/memory-slack-delivery.d.ts +101 -0
- package/dist/new-session-drafts.d.ts +2 -2
- package/dist/pack-components.d.ts +76 -0
- package/dist/pack-installations.d.ts +86 -0
- package/dist/persistence-errors.d.ts +3 -3
- package/dist/plugin-packages.d.ts +126 -0
- package/dist/preference-registry.d.ts +1 -1
- package/dist/provision-roles.d.ts +9 -1
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +18 -6
- package/dist/schema.d.ts +18342 -9099
- package/dist/schema.js +165 -1
- package/dist/scoped-knowledge.d.ts +97 -1
- package/dist/session-control.d.ts +4 -4
- package/dist/session-queue-commands.d.ts +19 -11
- package/dist/session-realtime-context.d.ts +2 -2
- package/dist/session-realtime-ledger.d.ts +4 -4
- package/dist/session-realtime-mirror.d.ts +2 -2
- package/dist/session-realtime-state.d.ts +1 -1
- package/dist/session-realtime-terminal.d.ts +1 -1
- package/dist/session-realtime.d.ts +7 -7
- package/dist/session-tool-call-settlement.d.ts +1 -1
- package/dist/slack-user-link-access.d.ts +54 -0
- package/dist/transcription-recordings.d.ts +2 -2
- package/dist/turn-initiator.d.ts +2 -2
- package/dist/video-generation.d.ts +216 -0
- package/dist/video-generation.js +66 -0
- package/dist/video-generation.js.map +1 -0
- package/dist/workspace-artifacts.d.ts +1 -1
- package/dist/workspace-instruction-policies.d.ts +1 -1
- package/dist/workspace-learning-policy-schema.d.ts +932 -0
- package/dist/workspace-learning-policy.d.ts +161 -0
- package/drizzle/0183_model_call_provider_cost_estimates.sql +39 -0
- package/drizzle/0184_sandbox_drain_teardown_fence.sql +682 -0
- package/drizzle/0185_temporal_schedule_cleanup_outbox.sql +168 -0
- package/drizzle/0186_sandbox_capture_provider_contract.sql +400 -0
- package/drizzle/0187_generated_image_artifacts.sql +173 -0
- package/drizzle/0188_image_generation_retention_failure.sql +25 -0
- package/drizzle/0189_retained_session_image_formats.sql +13 -0
- package/drizzle/0190_timeline_annotations.sql +31 -0
- package/drizzle/0191_editable_artifact_engine.sql +3853 -0
- package/drizzle/0192_editable_artifact_live_tickets.sql +338 -0
- package/drizzle/0193_editable_artifact_authorization.sql +190 -0
- package/drizzle/0194_editable_artifact_durable_exports.sql +160 -0
- package/drizzle/0195_editable_artifact_import_authorization.sql +203 -0
- package/drizzle/0196_rig_provider_images.sql +152 -0
- package/drizzle/0197_knowledge_source_sync_schedules.sql +523 -0
- package/drizzle/0198_memory_slack_publication_delivery.sql +393 -0
- package/drizzle/0199_workspace_learning_policy.sql +980 -0
- package/drizzle/0201_company_profile_authority.sql +849 -0
- package/drizzle/0202_document_index_checkpoints.sql +85 -0
- package/drizzle/0203_durable_video_generation.sql +353 -0
- package/drizzle/0204_video_generation_funding.sql +44 -0
- package/drizzle/0205_attempt_tool_catalogs.sql +230 -0
- package/drizzle/0206_browser_sessions.sql +646 -0
- package/drizzle/0207_browser_identities.sql +379 -0
- package/drizzle/0208_attached_browser_devices.sql +165 -0
- package/drizzle/0209_computer_sessions.sql +575 -0
- package/drizzle/0210_browser_auth_network_interventions.sql +441 -0
- package/drizzle/0211_editable_artifact_session_links.sql +58 -0
- package/drizzle/0212_slack_installation_bindings.sql +348 -0
- package/drizzle/0213_slack_user_link_access_requests.sql +177 -0
- package/drizzle/0214_session_activity_commit_gate.sql +262 -0
- package/drizzle/0215_capabilities_platform.sql +980 -0
- package/drizzle/0216_pack_component_ownership.sql +235 -0
- package/drizzle/0217_capability_definition_delete_authority.sql +32 -0
- package/package.json +18 -5
- package/src/attached-browser-devices.ts +371 -0
- package/src/attempt-tool-catalogs.ts +131 -0
- package/src/browser-auth.ts +1896 -0
- package/src/browser-identities.ts +1168 -0
- package/src/browser-sessions.ts +2005 -0
- package/src/browser-state-artifacts.ts +59 -0
- package/src/capability-components.ts +166 -0
- package/src/capability-integrations.ts +1663 -0
- package/src/codemode-operations.ts +674 -0
- package/src/company-profile-schema.ts +143 -0
- package/src/company-profile.ts +968 -0
- package/src/computer-sessions.ts +1130 -0
- package/src/connection-token-resolver.ts +270 -25
- package/src/database.ts +427 -23
- package/src/editable-artifact-durable-export.ts +1201 -0
- package/src/editable-artifact-materialization.ts +893 -0
- package/src/editable-artifacts-schema.ts +879 -0
- package/src/editable-artifacts.ts +5404 -0
- package/src/generated-images.ts +740 -0
- package/src/index.ts +5917 -1182
- package/src/insights.ts +160 -13
- package/src/integration-bindings.ts +455 -0
- package/src/integration-features.ts +859 -0
- package/src/interaction-revisions.ts +40 -0
- package/src/interaction-schema.ts +1303 -0
- package/src/knowledge-source-sync-schema.ts +192 -0
- package/src/knowledge-source-sync.ts +1922 -0
- package/src/memory-slack-delivery.ts +801 -0
- package/src/pack-components.ts +954 -0
- package/src/pack-installations.ts +929 -0
- package/src/persistence-errors.ts +4 -7
- package/src/plugin-packages.ts +1186 -0
- package/src/provision-roles.ts +302 -1
- package/src/runtime-posture-cli.ts +9 -0
- package/src/runtime-posture.ts +508 -5
- package/src/schema.ts +5117 -3119
- package/src/scoped-knowledge-schema.ts +1 -1
- package/src/scoped-knowledge.ts +655 -3
- package/src/session-control.ts +8 -5
- package/src/session-queue-commands.ts +187 -14
- package/src/session-realtime-context.ts +2 -2
- package/src/session-realtime-ledger.ts +3 -3
- package/src/session-realtime.ts +7 -7
- package/src/session-tool-call-settlement.ts +9 -0
- package/src/slack-user-link-access.ts +702 -0
- package/src/video-generation.ts +1452 -0
- package/src/workspace-learning-policy-schema.ts +139 -0
- package/src/workspace-learning-policy.ts +595 -0
- package/dist/chunk-P6CUHXQZ.js +0 -7248
- package/dist/chunk-P6CUHXQZ.js.map +0 -1
- package/dist/chunk-VHBFHMPC.js.map +0 -1
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Additive Pack installation evidence and component ownership. Old API/worker
|
|
3
|
+
-- binaries continue to use the original columns; new binaries freeze the
|
|
4
|
+
-- reviewed manifest and populate the normalized component ledger.
|
|
5
|
+
|
|
6
|
+
ALTER TABLE "pack_installations"
|
|
7
|
+
ADD COLUMN "version" integer NOT NULL DEFAULT 1,
|
|
8
|
+
ADD COLUMN "manifest_snapshot" jsonb,
|
|
9
|
+
ADD COLUMN "manifest_digest" text,
|
|
10
|
+
ADD COLUMN "selected_rig_id" uuid REFERENCES "rigs"("id") ON DELETE SET NULL,
|
|
11
|
+
ADD COLUMN "installed_by_subject_id" text;
|
|
12
|
+
|
|
13
|
+
ALTER TABLE "pack_installations"
|
|
14
|
+
ADD CONSTRAINT "pack_installations_version_chk" CHECK ("version" > 0) NOT VALID,
|
|
15
|
+
ADD CONSTRAINT "pack_installations_manifest_chk" CHECK (
|
|
16
|
+
("manifest_snapshot" IS NULL AND "manifest_digest" IS NULL)
|
|
17
|
+
OR (
|
|
18
|
+
jsonb_typeof("manifest_snapshot") = 'object'
|
|
19
|
+
AND octet_length(convert_to("manifest_snapshot"::text, 'UTF8')) <= 1048576
|
|
20
|
+
AND "manifest_digest" ~ '^[0-9a-f]{64}$'
|
|
21
|
+
)
|
|
22
|
+
) NOT VALID,
|
|
23
|
+
ADD CONSTRAINT "pack_installations_actor_chk" CHECK (
|
|
24
|
+
"installed_by_subject_id" IS NULL
|
|
25
|
+
OR length(btrim("installed_by_subject_id")) BETWEEN 1 AND 1024
|
|
26
|
+
) NOT VALID,
|
|
27
|
+
ADD CONSTRAINT "pack_installations_status_chk" CHECK (
|
|
28
|
+
"status" IN ('installing', 'active', 'needs_attention', 'disabled')
|
|
29
|
+
) NOT VALID;
|
|
30
|
+
|
|
31
|
+
ALTER TABLE "pack_installations"
|
|
32
|
+
VALIDATE CONSTRAINT "pack_installations_version_chk";
|
|
33
|
+
ALTER TABLE "pack_installations"
|
|
34
|
+
VALIDATE CONSTRAINT "pack_installations_manifest_chk";
|
|
35
|
+
ALTER TABLE "pack_installations"
|
|
36
|
+
VALIDATE CONSTRAINT "pack_installations_actor_chk";
|
|
37
|
+
ALTER TABLE "pack_installations"
|
|
38
|
+
VALIDATE CONSTRAINT "pack_installations_status_chk";
|
|
39
|
+
|
|
40
|
+
CREATE INDEX "pack_installations_workspace_rig_idx"
|
|
41
|
+
ON "pack_installations" ("workspace_id", "selected_rig_id")
|
|
42
|
+
WHERE "selected_rig_id" IS NOT NULL;
|
|
43
|
+
|
|
44
|
+
CREATE TABLE "pack_installation_components" (
|
|
45
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
46
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
|
|
47
|
+
"workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
|
|
48
|
+
"pack_installation_id" uuid NOT NULL REFERENCES "pack_installations"("id") ON DELETE CASCADE,
|
|
49
|
+
"component_key" text NOT NULL,
|
|
50
|
+
"kind" text NOT NULL,
|
|
51
|
+
"capability_id" text NOT NULL,
|
|
52
|
+
"resolved_id" text NOT NULL,
|
|
53
|
+
"digest" text NOT NULL,
|
|
54
|
+
"metadata" jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
55
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
56
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
57
|
+
CONSTRAINT "pack_installation_components_key_chk" CHECK (
|
|
58
|
+
length("component_key") BETWEEN 1 AND 128
|
|
59
|
+
AND "component_key" ~ '^[a-z0-9]([a-z0-9._/-]*[a-z0-9])?$'
|
|
60
|
+
),
|
|
61
|
+
CONSTRAINT "pack_installation_components_kind_chk" CHECK (
|
|
62
|
+
"kind" IN ('plugin', 'skill', 'integration', 'feature', 'inline_skill')
|
|
63
|
+
),
|
|
64
|
+
CONSTRAINT "pack_installation_components_identity_chk" CHECK (
|
|
65
|
+
length("capability_id") BETWEEN 1 AND 512
|
|
66
|
+
AND length("resolved_id") BETWEEN 1 AND 512
|
|
67
|
+
AND "capability_id" !~ '[[:cntrl:]]'
|
|
68
|
+
AND "resolved_id" !~ '[[:cntrl:]]'
|
|
69
|
+
AND "digest" ~ '^[0-9a-f]{64}$'
|
|
70
|
+
),
|
|
71
|
+
CONSTRAINT "pack_installation_components_metadata_chk" CHECK (
|
|
72
|
+
jsonb_typeof("metadata") = 'object'
|
|
73
|
+
AND octet_length(convert_to("metadata"::text, 'UTF8')) <= 131072
|
|
74
|
+
),
|
|
75
|
+
CONSTRAINT "pack_installation_components_pack_key_uq"
|
|
76
|
+
UNIQUE ("pack_installation_id", "component_key")
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
CREATE INDEX "pack_installation_components_workspace_capability_idx"
|
|
80
|
+
ON "pack_installation_components" ("workspace_id", "kind", "capability_id");
|
|
81
|
+
|
|
82
|
+
CREATE OR REPLACE FUNCTION pack_v2_validate_installation()
|
|
83
|
+
RETURNS trigger
|
|
84
|
+
LANGUAGE plpgsql
|
|
85
|
+
AS $$
|
|
86
|
+
BEGIN
|
|
87
|
+
IF NEW."manifest_snapshot" IS NOT NULL
|
|
88
|
+
AND NEW."manifest_snapshot" ->> 'id' IS DISTINCT FROM NEW."pack_id"
|
|
89
|
+
THEN
|
|
90
|
+
RAISE EXCEPTION 'pack installation snapshot id does not match pack_id'
|
|
91
|
+
USING ERRCODE = '23514';
|
|
92
|
+
END IF;
|
|
93
|
+
IF NEW."selected_rig_id" IS NOT NULL AND NOT EXISTS (
|
|
94
|
+
SELECT 1 FROM "rigs" r
|
|
95
|
+
WHERE r."id" = NEW."selected_rig_id"
|
|
96
|
+
AND r."account_id" = NEW."account_id"
|
|
97
|
+
AND r."workspace_id" = NEW."workspace_id"
|
|
98
|
+
) THEN
|
|
99
|
+
RAISE EXCEPTION 'pack installation Rig belongs to another tenant'
|
|
100
|
+
USING ERRCODE = '23514';
|
|
101
|
+
END IF;
|
|
102
|
+
RETURN NEW;
|
|
103
|
+
END;
|
|
104
|
+
$$;
|
|
105
|
+
|
|
106
|
+
CREATE TRIGGER pack_installations_v2_validate
|
|
107
|
+
BEFORE INSERT OR UPDATE ON "pack_installations"
|
|
108
|
+
FOR EACH ROW EXECUTE FUNCTION pack_v2_validate_installation();
|
|
109
|
+
|
|
110
|
+
CREATE OR REPLACE FUNCTION pack_v2_validate_component()
|
|
111
|
+
RETURNS trigger
|
|
112
|
+
LANGUAGE plpgsql
|
|
113
|
+
AS $$
|
|
114
|
+
BEGIN
|
|
115
|
+
IF NOT EXISTS (
|
|
116
|
+
SELECT 1 FROM "pack_installations" i
|
|
117
|
+
WHERE i."id" = NEW."pack_installation_id"
|
|
118
|
+
AND i."account_id" = NEW."account_id"
|
|
119
|
+
AND i."workspace_id" = NEW."workspace_id"
|
|
120
|
+
) THEN
|
|
121
|
+
RAISE EXCEPTION 'Pack component does not match its installation tenant'
|
|
122
|
+
USING ERRCODE = '23514';
|
|
123
|
+
END IF;
|
|
124
|
+
RETURN NEW;
|
|
125
|
+
END;
|
|
126
|
+
$$;
|
|
127
|
+
|
|
128
|
+
CREATE TRIGGER pack_installation_components_validate
|
|
129
|
+
BEFORE INSERT OR UPDATE ON "pack_installation_components"
|
|
130
|
+
FOR EACH ROW EXECUTE FUNCTION pack_v2_validate_component();
|
|
131
|
+
|
|
132
|
+
-- Harden the polymorphic owner identity added by 0202 now that first-class
|
|
133
|
+
-- Pack installation ids exist. Direct/migration owners remain textual, and
|
|
134
|
+
-- rolling legacy Pack owners remain valid when they are not UUID-shaped.
|
|
135
|
+
CREATE OR REPLACE FUNCTION capability_v2_validate_component_owner()
|
|
136
|
+
RETURNS trigger
|
|
137
|
+
LANGUAGE plpgsql
|
|
138
|
+
AS $$
|
|
139
|
+
BEGIN
|
|
140
|
+
IF NOT EXISTS (
|
|
141
|
+
SELECT 1 FROM "capability_facet_installations" i
|
|
142
|
+
WHERE i."id" = NEW."facet_installation_id"
|
|
143
|
+
AND i."account_id" = NEW."account_id"
|
|
144
|
+
AND i."workspace_id" = NEW."workspace_id"
|
|
145
|
+
) THEN
|
|
146
|
+
RAISE EXCEPTION 'component owner does not match its facet installation tenant'
|
|
147
|
+
USING ERRCODE = '23514';
|
|
148
|
+
END IF;
|
|
149
|
+
IF NEW."owner_kind" = 'plugin' THEN
|
|
150
|
+
IF NEW."owner_id" !~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' THEN
|
|
151
|
+
RAISE EXCEPTION 'plugin component owner belongs to another tenant or does not exist'
|
|
152
|
+
USING ERRCODE = '23514';
|
|
153
|
+
ELSIF NOT EXISTS (
|
|
154
|
+
SELECT 1 FROM "capability_plugin_installations" p
|
|
155
|
+
WHERE p."id" = NEW."owner_id"::uuid
|
|
156
|
+
AND p."account_id" = NEW."account_id"
|
|
157
|
+
AND p."workspace_id" = NEW."workspace_id"
|
|
158
|
+
) THEN
|
|
159
|
+
RAISE EXCEPTION 'plugin component owner belongs to another tenant or does not exist'
|
|
160
|
+
USING ERRCODE = '23514';
|
|
161
|
+
END IF;
|
|
162
|
+
ELSIF NEW."owner_kind" = 'pack'
|
|
163
|
+
AND NEW."owner_id" ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
|
|
164
|
+
THEN
|
|
165
|
+
IF NOT EXISTS (
|
|
166
|
+
SELECT 1 FROM "pack_installations" p
|
|
167
|
+
WHERE p."id" = NEW."owner_id"::uuid
|
|
168
|
+
AND p."account_id" = NEW."account_id"
|
|
169
|
+
AND p."workspace_id" = NEW."workspace_id"
|
|
170
|
+
) THEN
|
|
171
|
+
RAISE EXCEPTION 'Pack component owner belongs to another tenant or does not exist'
|
|
172
|
+
USING ERRCODE = '23514';
|
|
173
|
+
END IF;
|
|
174
|
+
END IF;
|
|
175
|
+
RETURN NEW;
|
|
176
|
+
END;
|
|
177
|
+
$$;
|
|
178
|
+
|
|
179
|
+
CREATE OR REPLACE FUNCTION capability_v2_validate_feature_binding_owner()
|
|
180
|
+
RETURNS trigger
|
|
181
|
+
LANGUAGE plpgsql
|
|
182
|
+
AS $$
|
|
183
|
+
BEGIN
|
|
184
|
+
IF NOT EXISTS (
|
|
185
|
+
SELECT 1 FROM "integration_feature_bindings" b
|
|
186
|
+
WHERE b."id" = NEW."binding_id"
|
|
187
|
+
AND b."account_id" = NEW."account_id"
|
|
188
|
+
AND b."workspace_id" = NEW."workspace_id"
|
|
189
|
+
) THEN
|
|
190
|
+
RAISE EXCEPTION 'feature binding owner does not match its binding tenant'
|
|
191
|
+
USING ERRCODE = '23514';
|
|
192
|
+
END IF;
|
|
193
|
+
IF NEW."owner_kind" = 'plugin' THEN
|
|
194
|
+
IF NEW."owner_id" !~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' THEN
|
|
195
|
+
RAISE EXCEPTION 'plugin feature binding owner belongs to another tenant or does not exist'
|
|
196
|
+
USING ERRCODE = '23514';
|
|
197
|
+
ELSIF NOT EXISTS (
|
|
198
|
+
SELECT 1 FROM "capability_plugin_installations" p
|
|
199
|
+
WHERE p."id" = NEW."owner_id"::uuid
|
|
200
|
+
AND p."account_id" = NEW."account_id"
|
|
201
|
+
AND p."workspace_id" = NEW."workspace_id"
|
|
202
|
+
) THEN
|
|
203
|
+
RAISE EXCEPTION 'plugin feature binding owner belongs to another tenant or does not exist'
|
|
204
|
+
USING ERRCODE = '23514';
|
|
205
|
+
END IF;
|
|
206
|
+
ELSIF NEW."owner_kind" = 'pack'
|
|
207
|
+
AND NEW."owner_id" ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
|
|
208
|
+
THEN
|
|
209
|
+
IF NOT EXISTS (
|
|
210
|
+
SELECT 1 FROM "pack_installations" p
|
|
211
|
+
WHERE p."id" = NEW."owner_id"::uuid
|
|
212
|
+
AND p."account_id" = NEW."account_id"
|
|
213
|
+
AND p."workspace_id" = NEW."workspace_id"
|
|
214
|
+
) THEN
|
|
215
|
+
RAISE EXCEPTION 'Pack feature binding owner belongs to another tenant or does not exist'
|
|
216
|
+
USING ERRCODE = '23514';
|
|
217
|
+
END IF;
|
|
218
|
+
END IF;
|
|
219
|
+
RETURN NEW;
|
|
220
|
+
END;
|
|
221
|
+
$$;
|
|
222
|
+
|
|
223
|
+
ALTER TABLE "pack_installation_components" ENABLE ROW LEVEL SECURITY;
|
|
224
|
+
ALTER TABLE "pack_installation_components" FORCE ROW LEVEL SECURITY;
|
|
225
|
+
|
|
226
|
+
CREATE POLICY workspace_isolation ON "pack_installation_components"
|
|
227
|
+
USING (opengeni_private.workspace_rls_visible(account_id, workspace_id))
|
|
228
|
+
WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id));
|
|
229
|
+
|
|
230
|
+
DO $$
|
|
231
|
+
BEGIN
|
|
232
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
233
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON "pack_installation_components" TO opengeni_app;
|
|
234
|
+
END IF;
|
|
235
|
+
END $$;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Shared capability and Integration definitions are globally readable catalog
|
|
3
|
+
-- data, not tenant lifecycle rows. Revoke ordinary runtime deletion authority
|
|
4
|
+
-- without narrowing the installation, binding, owner, or operation lifecycle.
|
|
5
|
+
|
|
6
|
+
DO $$
|
|
7
|
+
BEGIN
|
|
8
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
9
|
+
REVOKE DELETE ON
|
|
10
|
+
"capability_plugins",
|
|
11
|
+
"capability_plugin_versions",
|
|
12
|
+
"capability_facets",
|
|
13
|
+
"capability_integration_facets",
|
|
14
|
+
"capability_mcp_facets",
|
|
15
|
+
"capability_api_facets",
|
|
16
|
+
"capability_skill_facets",
|
|
17
|
+
"capability_skill_files",
|
|
18
|
+
"integration_spec_revisions",
|
|
19
|
+
"integration_tools",
|
|
20
|
+
"integration_feature_facets"
|
|
21
|
+
FROM opengeni_app;
|
|
22
|
+
|
|
23
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON
|
|
24
|
+
"capability_plugin_installations",
|
|
25
|
+
"capability_facet_installations",
|
|
26
|
+
"capability_component_owners",
|
|
27
|
+
"integration_feature_bindings",
|
|
28
|
+
"integration_feature_binding_owners",
|
|
29
|
+
"capability_operations"
|
|
30
|
+
TO opengeni_app;
|
|
31
|
+
END IF;
|
|
32
|
+
END $$;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/db",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.1",
|
|
4
4
|
"description": "OpenGeni persistence: Drizzle schema, RLS-scoped query layer, the SQL migration runner, and role provisioning.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -34,6 +34,18 @@
|
|
|
34
34
|
"./provision-roles": {
|
|
35
35
|
"types": "./dist/provision-roles.d.ts",
|
|
36
36
|
"import": "./dist/provision-roles.js"
|
|
37
|
+
},
|
|
38
|
+
"./editable-artifacts": {
|
|
39
|
+
"types": "./dist/editable-artifacts.d.ts",
|
|
40
|
+
"import": "./dist/editable-artifacts.js"
|
|
41
|
+
},
|
|
42
|
+
"./editable-artifact-durable-export": {
|
|
43
|
+
"types": "./dist/editable-artifact-durable-export.d.ts",
|
|
44
|
+
"import": "./dist/editable-artifact-durable-export.js"
|
|
45
|
+
},
|
|
46
|
+
"./video-generation": {
|
|
47
|
+
"types": "./dist/video-generation.d.ts",
|
|
48
|
+
"import": "./dist/video-generation.js"
|
|
37
49
|
}
|
|
38
50
|
},
|
|
39
51
|
"publishConfig": {
|
|
@@ -50,10 +62,11 @@
|
|
|
50
62
|
"prepublishOnly": "bash ../../scripts/prepublish-guard"
|
|
51
63
|
},
|
|
52
64
|
"dependencies": {
|
|
53
|
-
"@opengeni/
|
|
54
|
-
"@opengeni/
|
|
55
|
-
"@opengeni/
|
|
56
|
-
"@opengeni/
|
|
65
|
+
"@opengeni/codemode": "^0.2.2",
|
|
66
|
+
"@opengeni/codex": "^0.2.15",
|
|
67
|
+
"@opengeni/config": "^0.13.2",
|
|
68
|
+
"@opengeni/contracts": "^0.44.1",
|
|
69
|
+
"@opengeni/network": "^0.2.2",
|
|
57
70
|
"drizzle-orm": "^0.45.2",
|
|
58
71
|
"postgres": "^3.4.7"
|
|
59
72
|
},
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AttachedBrowserDevice,
|
|
3
|
+
AttachedBrowserDeviceCapabilities,
|
|
4
|
+
AttachedBrowserDeviceListResponse,
|
|
5
|
+
AttachedBrowserInventorySnapshot,
|
|
6
|
+
type AttachedBrowserDevice as AttachedBrowserDeviceValue,
|
|
7
|
+
type AttachedBrowserDeviceAnnouncement,
|
|
8
|
+
type AttachedBrowserDeviceListResponse as AttachedBrowserDeviceListResponseValue,
|
|
9
|
+
type AttachedBrowserInventorySnapshot as AttachedBrowserInventorySnapshotValue,
|
|
10
|
+
} from "@opengeni/contracts";
|
|
11
|
+
import { and, asc, desc, eq, inArray, isNull } from "drizzle-orm";
|
|
12
|
+
import { type Database, withRlsContext } from "./database";
|
|
13
|
+
import {
|
|
14
|
+
advanceWorkspaceInteractionRevision,
|
|
15
|
+
readWorkspaceInteractionRevision,
|
|
16
|
+
} from "./interaction-revisions";
|
|
17
|
+
import * as schema from "./schema";
|
|
18
|
+
|
|
19
|
+
type DeviceRow = typeof schema.attachedBrowserDevices.$inferSelect;
|
|
20
|
+
|
|
21
|
+
const CONSISTENT_READ = {
|
|
22
|
+
isolationLevel: "repeatable read",
|
|
23
|
+
accessMode: "read only",
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
export class AttachedBrowserDeviceNotFoundError extends Error {
|
|
27
|
+
readonly name = "AttachedBrowserDeviceNotFoundError";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class AttachedBrowserInventoryConflictError extends Error {
|
|
31
|
+
readonly name = "AttachedBrowserInventoryConflictError";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type ReconcileAttachedBrowserInventoryResult = {
|
|
35
|
+
accepted: boolean;
|
|
36
|
+
changed: boolean;
|
|
37
|
+
revision: number;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function safeInteger(value: number, label: string): number {
|
|
41
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new Error(`${label} is outside safe range`);
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function deviceFromRow(row: DeviceRow): AttachedBrowserDeviceValue {
|
|
46
|
+
return AttachedBrowserDevice.parse({
|
|
47
|
+
id: row.id,
|
|
48
|
+
accountId: row.accountId,
|
|
49
|
+
workspaceId: row.workspaceId,
|
|
50
|
+
enrollmentId: row.enrollmentId,
|
|
51
|
+
name: row.name,
|
|
52
|
+
profileLabel: row.profileLabel,
|
|
53
|
+
browserName: row.browserName,
|
|
54
|
+
browserVersion: row.browserVersion,
|
|
55
|
+
extensionVersion: row.extensionVersion,
|
|
56
|
+
platform: row.platform,
|
|
57
|
+
architecture: row.architecture,
|
|
58
|
+
state: row.disconnectedAt ? "disconnected" : "connected",
|
|
59
|
+
connectionGeneration: row.connectionGeneration,
|
|
60
|
+
inventoryRevision: safeInteger(row.inventoryRevision, "attached browser inventory revision"),
|
|
61
|
+
tabCount: safeInteger(row.tabCount, "attached browser tab count"),
|
|
62
|
+
capabilities: row.capabilities,
|
|
63
|
+
lastSeenAt: row.lastSeenAt.toISOString(),
|
|
64
|
+
disconnectedAt: row.disconnectedAt?.toISOString() ?? null,
|
|
65
|
+
createdAt: row.createdAt.toISOString(),
|
|
66
|
+
updatedAt: row.updatedAt.toISOString(),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function sameAnnouncement(row: DeviceRow, value: AttachedBrowserDeviceAnnouncement): boolean {
|
|
71
|
+
return (
|
|
72
|
+
row.name === value.name &&
|
|
73
|
+
row.profileLabel === value.profileLabel &&
|
|
74
|
+
row.browserName === value.browserName &&
|
|
75
|
+
row.browserVersion === value.browserVersion &&
|
|
76
|
+
row.extensionVersion === value.extensionVersion &&
|
|
77
|
+
row.platform === value.platform &&
|
|
78
|
+
row.architecture === value.architecture &&
|
|
79
|
+
row.connectionGeneration === value.connectionGeneration &&
|
|
80
|
+
row.inventoryRevision === value.inventoryRevision &&
|
|
81
|
+
row.tabCount === value.tabCount &&
|
|
82
|
+
JSON.stringify(AttachedBrowserDeviceCapabilities.parse(row.capabilities)) ===
|
|
83
|
+
JSON.stringify(value.capabilities) &&
|
|
84
|
+
row.disconnectedAt === null
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Reconcile one full browser-bridge snapshot. The enrollment row serializes
|
|
89
|
+
* concurrent heartbeats; a lower revision from the same bridge generation is
|
|
90
|
+
* ignored, and a replay only refreshes liveness. */
|
|
91
|
+
export async function reconcileAttachedBrowserInventory(
|
|
92
|
+
db: Database,
|
|
93
|
+
input: {
|
|
94
|
+
accountId: string;
|
|
95
|
+
workspaceId: string;
|
|
96
|
+
enrollmentId: string;
|
|
97
|
+
snapshot: AttachedBrowserInventorySnapshotValue;
|
|
98
|
+
},
|
|
99
|
+
): Promise<ReconcileAttachedBrowserInventoryResult> {
|
|
100
|
+
const snapshot = AttachedBrowserInventorySnapshot.parse(input.snapshot);
|
|
101
|
+
return await withRlsContext(
|
|
102
|
+
db,
|
|
103
|
+
input,
|
|
104
|
+
async (scopedDb) =>
|
|
105
|
+
await scopedDb.transaction(async (rawTx) => {
|
|
106
|
+
const tx = rawTx as unknown as Database;
|
|
107
|
+
const [enrollment] = await tx
|
|
108
|
+
.select({
|
|
109
|
+
id: schema.enrollments.id,
|
|
110
|
+
accountId: schema.enrollments.accountId,
|
|
111
|
+
workspaceId: schema.enrollments.workspaceId,
|
|
112
|
+
status: schema.enrollments.status,
|
|
113
|
+
})
|
|
114
|
+
.from(schema.enrollments)
|
|
115
|
+
.where(
|
|
116
|
+
and(
|
|
117
|
+
eq(schema.enrollments.id, input.enrollmentId),
|
|
118
|
+
eq(schema.enrollments.accountId, input.accountId),
|
|
119
|
+
eq(schema.enrollments.workspaceId, input.workspaceId),
|
|
120
|
+
),
|
|
121
|
+
)
|
|
122
|
+
.for("update")
|
|
123
|
+
.limit(1);
|
|
124
|
+
if (!enrollment || enrollment.status !== "active") {
|
|
125
|
+
throw new AttachedBrowserInventoryConflictError(
|
|
126
|
+
"attached browser inventory belongs to an unavailable enrollment",
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const [cursor] = await tx
|
|
131
|
+
.select()
|
|
132
|
+
.from(schema.attachedBrowserInventories)
|
|
133
|
+
.where(
|
|
134
|
+
and(
|
|
135
|
+
eq(schema.attachedBrowserInventories.workspaceId, input.workspaceId),
|
|
136
|
+
eq(schema.attachedBrowserInventories.enrollmentId, input.enrollmentId),
|
|
137
|
+
),
|
|
138
|
+
)
|
|
139
|
+
.for("update")
|
|
140
|
+
.limit(1);
|
|
141
|
+
if (
|
|
142
|
+
cursor?.bridgeGeneration === snapshot.bridgeGeneration &&
|
|
143
|
+
snapshot.revision < cursor.revision
|
|
144
|
+
) {
|
|
145
|
+
return {
|
|
146
|
+
accepted: false,
|
|
147
|
+
changed: false,
|
|
148
|
+
revision: await readWorkspaceInteractionRevision(tx, input.workspaceId),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const now = new Date();
|
|
153
|
+
const rows = await tx
|
|
154
|
+
.select()
|
|
155
|
+
.from(schema.attachedBrowserDevices)
|
|
156
|
+
.where(eq(schema.attachedBrowserDevices.workspaceId, input.workspaceId))
|
|
157
|
+
.for("update");
|
|
158
|
+
const byId = new Map(rows.map((row) => [row.id, row]));
|
|
159
|
+
let changed = false;
|
|
160
|
+
for (const device of snapshot.devices) {
|
|
161
|
+
const existing = byId.get(device.id);
|
|
162
|
+
if (existing && existing.enrollmentId !== input.enrollmentId) {
|
|
163
|
+
throw new AttachedBrowserInventoryConflictError(
|
|
164
|
+
"attached browser device id is already owned by another enrollment",
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
if (!existing) {
|
|
168
|
+
await tx.insert(schema.attachedBrowserDevices).values({
|
|
169
|
+
id: device.id,
|
|
170
|
+
accountId: input.accountId,
|
|
171
|
+
workspaceId: input.workspaceId,
|
|
172
|
+
enrollmentId: input.enrollmentId,
|
|
173
|
+
name: device.name,
|
|
174
|
+
profileLabel: device.profileLabel,
|
|
175
|
+
browserName: device.browserName,
|
|
176
|
+
browserVersion: device.browserVersion,
|
|
177
|
+
extensionVersion: device.extensionVersion,
|
|
178
|
+
platform: device.platform,
|
|
179
|
+
architecture: device.architecture,
|
|
180
|
+
connectionGeneration: device.connectionGeneration,
|
|
181
|
+
inventoryRevision: device.inventoryRevision,
|
|
182
|
+
tabCount: device.tabCount,
|
|
183
|
+
capabilities: device.capabilities,
|
|
184
|
+
lastSeenAt: now,
|
|
185
|
+
disconnectedAt: null,
|
|
186
|
+
createdAt: now,
|
|
187
|
+
updatedAt: now,
|
|
188
|
+
});
|
|
189
|
+
changed = true;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const materialChanged = !sameAnnouncement(existing, device);
|
|
193
|
+
await tx
|
|
194
|
+
.update(schema.attachedBrowserDevices)
|
|
195
|
+
.set({
|
|
196
|
+
name: device.name,
|
|
197
|
+
profileLabel: device.profileLabel,
|
|
198
|
+
browserName: device.browserName,
|
|
199
|
+
browserVersion: device.browserVersion,
|
|
200
|
+
extensionVersion: device.extensionVersion,
|
|
201
|
+
platform: device.platform,
|
|
202
|
+
architecture: device.architecture,
|
|
203
|
+
connectionGeneration: device.connectionGeneration,
|
|
204
|
+
inventoryRevision: device.inventoryRevision,
|
|
205
|
+
tabCount: device.tabCount,
|
|
206
|
+
capabilities: device.capabilities,
|
|
207
|
+
lastSeenAt: now,
|
|
208
|
+
disconnectedAt: null,
|
|
209
|
+
updatedAt: materialChanged ? now : existing.updatedAt,
|
|
210
|
+
})
|
|
211
|
+
.where(
|
|
212
|
+
and(
|
|
213
|
+
eq(schema.attachedBrowserDevices.workspaceId, input.workspaceId),
|
|
214
|
+
eq(schema.attachedBrowserDevices.id, device.id),
|
|
215
|
+
),
|
|
216
|
+
);
|
|
217
|
+
changed ||= materialChanged;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const announced = new Set(snapshot.devices.map((device) => device.id));
|
|
221
|
+
const disconnectedIds = rows
|
|
222
|
+
.filter(
|
|
223
|
+
(row) =>
|
|
224
|
+
row.enrollmentId === input.enrollmentId &&
|
|
225
|
+
row.disconnectedAt === null &&
|
|
226
|
+
!announced.has(row.id),
|
|
227
|
+
)
|
|
228
|
+
.map((row) => row.id);
|
|
229
|
+
if (disconnectedIds.length > 0) {
|
|
230
|
+
await tx
|
|
231
|
+
.update(schema.attachedBrowserDevices)
|
|
232
|
+
.set({ disconnectedAt: now, updatedAt: now })
|
|
233
|
+
.where(
|
|
234
|
+
and(
|
|
235
|
+
eq(schema.attachedBrowserDevices.workspaceId, input.workspaceId),
|
|
236
|
+
eq(schema.attachedBrowserDevices.enrollmentId, input.enrollmentId),
|
|
237
|
+
inArray(schema.attachedBrowserDevices.id, disconnectedIds),
|
|
238
|
+
isNull(schema.attachedBrowserDevices.disconnectedAt),
|
|
239
|
+
),
|
|
240
|
+
);
|
|
241
|
+
changed = true;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (cursor) {
|
|
245
|
+
await tx
|
|
246
|
+
.update(schema.attachedBrowserInventories)
|
|
247
|
+
.set({
|
|
248
|
+
bridgeGeneration: snapshot.bridgeGeneration,
|
|
249
|
+
revision: snapshot.revision,
|
|
250
|
+
observedAt: now,
|
|
251
|
+
updatedAt: now,
|
|
252
|
+
})
|
|
253
|
+
.where(
|
|
254
|
+
and(
|
|
255
|
+
eq(schema.attachedBrowserInventories.workspaceId, input.workspaceId),
|
|
256
|
+
eq(schema.attachedBrowserInventories.enrollmentId, input.enrollmentId),
|
|
257
|
+
),
|
|
258
|
+
);
|
|
259
|
+
} else {
|
|
260
|
+
await tx.insert(schema.attachedBrowserInventories).values({
|
|
261
|
+
accountId: input.accountId,
|
|
262
|
+
workspaceId: input.workspaceId,
|
|
263
|
+
enrollmentId: input.enrollmentId,
|
|
264
|
+
bridgeGeneration: snapshot.bridgeGeneration,
|
|
265
|
+
revision: snapshot.revision,
|
|
266
|
+
observedAt: now,
|
|
267
|
+
updatedAt: now,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
const revision = changed
|
|
271
|
+
? await advanceWorkspaceInteractionRevision(tx, input.accountId, input.workspaceId)
|
|
272
|
+
: await readWorkspaceInteractionRevision(tx, input.workspaceId);
|
|
273
|
+
return { accepted: true, changed, revision };
|
|
274
|
+
}),
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** Immediately mark every live Chrome profile on a cleanly disconnected agent
|
|
279
|
+
* offline. Historical endpoint rows remain discoverable and reconnect in place. */
|
|
280
|
+
export async function disconnectAttachedBrowserDevices(
|
|
281
|
+
db: Database,
|
|
282
|
+
input: { accountId: string; workspaceId: string; enrollmentId: string },
|
|
283
|
+
): Promise<{ changed: boolean; revision: number }> {
|
|
284
|
+
return await withRlsContext(
|
|
285
|
+
db,
|
|
286
|
+
input,
|
|
287
|
+
async (scopedDb) =>
|
|
288
|
+
await scopedDb.transaction(async (rawTx) => {
|
|
289
|
+
const tx = rawTx as unknown as Database;
|
|
290
|
+
const now = new Date();
|
|
291
|
+
const rows = await tx
|
|
292
|
+
.update(schema.attachedBrowserDevices)
|
|
293
|
+
.set({ disconnectedAt: now, updatedAt: now })
|
|
294
|
+
.where(
|
|
295
|
+
and(
|
|
296
|
+
eq(schema.attachedBrowserDevices.workspaceId, input.workspaceId),
|
|
297
|
+
eq(schema.attachedBrowserDevices.enrollmentId, input.enrollmentId),
|
|
298
|
+
isNull(schema.attachedBrowserDevices.disconnectedAt),
|
|
299
|
+
),
|
|
300
|
+
)
|
|
301
|
+
.returning({ id: schema.attachedBrowserDevices.id });
|
|
302
|
+
const revision = rows.length
|
|
303
|
+
? await advanceWorkspaceInteractionRevision(tx, input.accountId, input.workspaceId)
|
|
304
|
+
: await readWorkspaceInteractionRevision(tx, input.workspaceId);
|
|
305
|
+
return { changed: rows.length > 0, revision };
|
|
306
|
+
}),
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export async function listAttachedBrowserDevices(
|
|
311
|
+
db: Database,
|
|
312
|
+
input: {
|
|
313
|
+
accountId: string;
|
|
314
|
+
workspaceId: string;
|
|
315
|
+
includeDisconnected?: boolean;
|
|
316
|
+
},
|
|
317
|
+
): Promise<AttachedBrowserDeviceListResponseValue> {
|
|
318
|
+
return await withRlsContext(
|
|
319
|
+
db,
|
|
320
|
+
input,
|
|
321
|
+
async (scopedDb) => {
|
|
322
|
+
const rows = await scopedDb
|
|
323
|
+
.select()
|
|
324
|
+
.from(schema.attachedBrowserDevices)
|
|
325
|
+
.where(
|
|
326
|
+
input.includeDisconnected
|
|
327
|
+
? eq(schema.attachedBrowserDevices.workspaceId, input.workspaceId)
|
|
328
|
+
: and(
|
|
329
|
+
eq(schema.attachedBrowserDevices.workspaceId, input.workspaceId),
|
|
330
|
+
isNull(schema.attachedBrowserDevices.disconnectedAt),
|
|
331
|
+
),
|
|
332
|
+
)
|
|
333
|
+
.orderBy(
|
|
334
|
+
desc(schema.attachedBrowserDevices.disconnectedAt),
|
|
335
|
+
desc(schema.attachedBrowserDevices.updatedAt),
|
|
336
|
+
asc(schema.attachedBrowserDevices.name),
|
|
337
|
+
asc(schema.attachedBrowserDevices.id),
|
|
338
|
+
);
|
|
339
|
+
return AttachedBrowserDeviceListResponse.parse({
|
|
340
|
+
revision: await readWorkspaceInteractionRevision(scopedDb, input.workspaceId),
|
|
341
|
+
devices: rows.map(deviceFromRow),
|
|
342
|
+
});
|
|
343
|
+
},
|
|
344
|
+
CONSISTENT_READ,
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export async function getAttachedBrowserDevice(
|
|
349
|
+
db: Database,
|
|
350
|
+
input: { accountId: string; workspaceId: string; deviceId: string },
|
|
351
|
+
): Promise<AttachedBrowserDeviceValue> {
|
|
352
|
+
return await withRlsContext(
|
|
353
|
+
db,
|
|
354
|
+
input,
|
|
355
|
+
async (scopedDb) => {
|
|
356
|
+
const [row] = await scopedDb
|
|
357
|
+
.select()
|
|
358
|
+
.from(schema.attachedBrowserDevices)
|
|
359
|
+
.where(
|
|
360
|
+
and(
|
|
361
|
+
eq(schema.attachedBrowserDevices.workspaceId, input.workspaceId),
|
|
362
|
+
eq(schema.attachedBrowserDevices.id, input.deviceId),
|
|
363
|
+
),
|
|
364
|
+
)
|
|
365
|
+
.limit(1);
|
|
366
|
+
if (!row) throw new AttachedBrowserDeviceNotFoundError("Attached browser not found");
|
|
367
|
+
return deviceFromRow(row);
|
|
368
|
+
},
|
|
369
|
+
CONSISTENT_READ,
|
|
370
|
+
);
|
|
371
|
+
}
|