@opengeni/db 0.28.1 → 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-DBS5HPEW.js → chunk-YHZXQ7HP.js} +760 -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/environment-crypto.d.ts +8 -4
- package/dist/generated-images.d.ts +139 -0
- package/dist/index.d.ts +958 -126
- package/dist/index.js +31204 -12172
- 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/lossless-columns.d.ts +58 -0
- package/dist/lossless-json.d.ts +39 -0
- package/dist/memory-domain.d.ts +3 -6
- 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 +7 -14
- 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 +19122 -8731
- package/dist/schema.js +173 -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 +8 -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-schema.d.ts +44 -6
- package/dist/transcription-recordings.d.ts +3 -3
- 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/0065_enrollment_credential_generation.sql +10 -0
- package/drizzle/0140_retained_screenshot_artifacts.sql +192 -0
- package/drizzle/0176_lossless_canonical_json.sql +975 -0
- package/drizzle/0177_session_events_workspace_turn_type_index.sql +5 -0
- package/drizzle/0178_permissioned_secret_reads.sql +14 -0
- package/drizzle/0179_slack_private_shortcut_delivery_gate.sql +85 -0
- package/drizzle/0180_retained_screenshot_lifecycle_fences.sql +273 -0
- package/drizzle/0181_connected_machine_removal.sql +52 -0
- package/drizzle/0182_connected_machine_remove_session_default.sql +77 -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 +434 -24
- 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/environment-crypto.ts +22 -9
- package/src/generated-images.ts +740 -0
- package/src/index.ts +10117 -2730
- 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/lossless-columns.ts +35 -0
- package/src/lossless-json.ts +322 -0
- package/src/memory-domain.ts +5 -44
- 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 +26 -34
- 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 +514 -5
- package/src/schema.ts +5177 -2957
- package/src/scoped-knowledge-schema.ts +1 -1
- package/src/scoped-knowledge.ts +655 -3
- package/src/session-control.ts +133 -81
- package/src/session-queue-commands.ts +511 -247
- package/src/session-realtime-context.ts +20 -7
- package/src/session-realtime-ledger.ts +32 -10
- package/src/session-realtime-mirror.ts +4 -2
- package/src/session-realtime-terminal.ts +89 -21
- package/src/session-realtime.ts +10 -9
- package/src/session-tool-call-settlement.ts +38 -15
- package/src/slack-user-link-access.ts +702 -0
- package/src/transcription-recordings-schema.ts +5 -2
- package/src/transcription-recordings.ts +70 -40
- 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-DBS5HPEW.js.map +0 -1
- package/dist/chunk-HT5T62CC.js +0 -6803
- package/dist/chunk-HT5T62CC.js.map +0 -1
- package/dist/event-payload-sanitizer.d.ts +0 -32
- package/src/event-payload-sanitizer.ts +0 -377
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- A workspace cascade cannot atomically delete its external Temporal schedules.
|
|
3
|
+
-- Persist that ownership before the cascade, then let any API replica claim and
|
|
4
|
+
-- settle the idempotent delete. The row intentionally has no workspace/account
|
|
5
|
+
-- FK: surviving deletion of those parents is its entire purpose.
|
|
6
|
+
|
|
7
|
+
CREATE TABLE "temporal_schedule_cleanup_outbox" (
|
|
8
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
9
|
+
"account_id" uuid NOT NULL,
|
|
10
|
+
"workspace_id" uuid NOT NULL,
|
|
11
|
+
"temporal_schedule_id" text NOT NULL,
|
|
12
|
+
"claim_id" uuid,
|
|
13
|
+
"claim_until" timestamptz,
|
|
14
|
+
"attempt_count" integer NOT NULL DEFAULT 0,
|
|
15
|
+
"next_attempt_at" timestamptz NOT NULL DEFAULT now(),
|
|
16
|
+
"last_error" text,
|
|
17
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
18
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
19
|
+
CONSTRAINT "temporal_schedule_cleanup_outbox_valid_chk" CHECK (
|
|
20
|
+
length("temporal_schedule_id") BETWEEN 1 AND 512
|
|
21
|
+
AND "attempt_count" >= 0
|
|
22
|
+
AND (("claim_id" IS NULL AND "claim_until" IS NULL)
|
|
23
|
+
OR ("claim_id" IS NOT NULL AND "claim_until" IS NOT NULL))
|
|
24
|
+
AND ("last_error" IS NULL OR length("last_error") <= 2000)
|
|
25
|
+
)
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
CREATE UNIQUE INDEX "temporal_schedule_cleanup_outbox_schedule_uq"
|
|
29
|
+
ON "temporal_schedule_cleanup_outbox" ("temporal_schedule_id");
|
|
30
|
+
CREATE INDEX "temporal_schedule_cleanup_outbox_due_idx"
|
|
31
|
+
ON "temporal_schedule_cleanup_outbox" (
|
|
32
|
+
"next_attempt_at", "claim_until", "id"
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
ALTER TABLE "temporal_schedule_cleanup_outbox" ENABLE ROW LEVEL SECURITY;
|
|
36
|
+
ALTER TABLE "temporal_schedule_cleanup_outbox" FORCE ROW LEVEL SECURITY;
|
|
37
|
+
CREATE POLICY workspace_isolation ON "temporal_schedule_cleanup_outbox"
|
|
38
|
+
USING (opengeni_private.workspace_rls_visible(account_id, workspace_id))
|
|
39
|
+
WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id));
|
|
40
|
+
|
|
41
|
+
DO $migration$
|
|
42
|
+
DECLARE target_schema text := current_schema();
|
|
43
|
+
BEGIN
|
|
44
|
+
EXECUTE format($create$
|
|
45
|
+
CREATE FUNCTION opengeni_private.claim_temporal_schedule_cleanups(
|
|
46
|
+
p_claim_id uuid,
|
|
47
|
+
p_limit integer,
|
|
48
|
+
p_claim_seconds integer
|
|
49
|
+
)
|
|
50
|
+
RETURNS TABLE (
|
|
51
|
+
id uuid,
|
|
52
|
+
account_id uuid,
|
|
53
|
+
workspace_id uuid,
|
|
54
|
+
temporal_schedule_id text,
|
|
55
|
+
attempt_count integer
|
|
56
|
+
)
|
|
57
|
+
LANGUAGE plpgsql
|
|
58
|
+
SECURITY DEFINER
|
|
59
|
+
SET search_path = pg_catalog
|
|
60
|
+
AS $function$
|
|
61
|
+
BEGIN
|
|
62
|
+
IF p_claim_id IS NULL THEN
|
|
63
|
+
RAISE EXCEPTION 'temporal schedule cleanup claim id is required'
|
|
64
|
+
USING ERRCODE = '22023';
|
|
65
|
+
END IF;
|
|
66
|
+
|
|
67
|
+
RETURN QUERY
|
|
68
|
+
WITH due AS (
|
|
69
|
+
SELECT cleanup.id
|
|
70
|
+
FROM %1$I.temporal_schedule_cleanup_outbox cleanup
|
|
71
|
+
WHERE cleanup.next_attempt_at <= pg_catalog.now()
|
|
72
|
+
AND (
|
|
73
|
+
cleanup.claim_until IS NULL
|
|
74
|
+
OR cleanup.claim_until <= pg_catalog.now()
|
|
75
|
+
)
|
|
76
|
+
ORDER BY cleanup.next_attempt_at, cleanup.created_at, cleanup.id
|
|
77
|
+
FOR UPDATE SKIP LOCKED
|
|
78
|
+
LIMIT greatest(1, least(coalesce(p_limit, 32), 100))
|
|
79
|
+
)
|
|
80
|
+
UPDATE %1$I.temporal_schedule_cleanup_outbox cleanup
|
|
81
|
+
SET claim_id = p_claim_id,
|
|
82
|
+
claim_until = pg_catalog.now() + pg_catalog.make_interval(
|
|
83
|
+
secs => greatest(
|
|
84
|
+
5,
|
|
85
|
+
least(coalesce(p_claim_seconds, 15), 300)
|
|
86
|
+
)
|
|
87
|
+
),
|
|
88
|
+
attempt_count = cleanup.attempt_count + 1,
|
|
89
|
+
updated_at = pg_catalog.now()
|
|
90
|
+
FROM due
|
|
91
|
+
WHERE cleanup.id = due.id
|
|
92
|
+
RETURNING cleanup.id, cleanup.account_id, cleanup.workspace_id,
|
|
93
|
+
cleanup.temporal_schedule_id, cleanup.attempt_count;
|
|
94
|
+
END $function$;
|
|
95
|
+
$create$, target_schema);
|
|
96
|
+
END $migration$;
|
|
97
|
+
|
|
98
|
+
REVOKE ALL ON FUNCTION opengeni_private.claim_temporal_schedule_cleanups(uuid, integer, integer)
|
|
99
|
+
FROM PUBLIC;
|
|
100
|
+
|
|
101
|
+
DO $migration$
|
|
102
|
+
DECLARE target_schema text := current_schema();
|
|
103
|
+
BEGIN
|
|
104
|
+
EXECUTE format($create$
|
|
105
|
+
CREATE FUNCTION opengeni_private.settle_temporal_schedule_cleanup(
|
|
106
|
+
p_id uuid,
|
|
107
|
+
p_claim_id uuid,
|
|
108
|
+
p_error text
|
|
109
|
+
)
|
|
110
|
+
RETURNS boolean
|
|
111
|
+
LANGUAGE plpgsql
|
|
112
|
+
SECURITY DEFINER
|
|
113
|
+
SET search_path = pg_catalog
|
|
114
|
+
AS $function$
|
|
115
|
+
DECLARE settled_rows bigint := 0;
|
|
116
|
+
BEGIN
|
|
117
|
+
IF p_id IS NULL OR p_claim_id IS NULL THEN
|
|
118
|
+
RAISE EXCEPTION 'temporal schedule cleanup id and claim id are required'
|
|
119
|
+
USING ERRCODE = '22023';
|
|
120
|
+
END IF;
|
|
121
|
+
|
|
122
|
+
IF p_error IS NULL THEN
|
|
123
|
+
DELETE FROM %1$I.temporal_schedule_cleanup_outbox cleanup
|
|
124
|
+
WHERE cleanup.id = p_id AND cleanup.claim_id = p_claim_id;
|
|
125
|
+
GET DIAGNOSTICS settled_rows = ROW_COUNT;
|
|
126
|
+
RETURN settled_rows = 1;
|
|
127
|
+
END IF;
|
|
128
|
+
|
|
129
|
+
UPDATE %1$I.temporal_schedule_cleanup_outbox cleanup
|
|
130
|
+
SET claim_id = NULL,
|
|
131
|
+
claim_until = NULL,
|
|
132
|
+
next_attempt_at = pg_catalog.now() + pg_catalog.make_interval(
|
|
133
|
+
secs => least(
|
|
134
|
+
300,
|
|
135
|
+
greatest(
|
|
136
|
+
1,
|
|
137
|
+
pg_catalog.power(2, least(cleanup.attempt_count - 1, 8))::integer
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
),
|
|
141
|
+
last_error = pg_catalog.left(p_error, 2000),
|
|
142
|
+
updated_at = pg_catalog.now()
|
|
143
|
+
WHERE cleanup.id = p_id AND cleanup.claim_id = p_claim_id;
|
|
144
|
+
GET DIAGNOSTICS settled_rows = ROW_COUNT;
|
|
145
|
+
RETURN settled_rows = 1;
|
|
146
|
+
END $function$;
|
|
147
|
+
$create$, target_schema);
|
|
148
|
+
END $migration$;
|
|
149
|
+
|
|
150
|
+
REVOKE ALL ON FUNCTION opengeni_private.settle_temporal_schedule_cleanup(uuid, uuid, text)
|
|
151
|
+
FROM PUBLIC;
|
|
152
|
+
|
|
153
|
+
DO $$
|
|
154
|
+
DECLARE target_schema text := current_schema();
|
|
155
|
+
BEGIN
|
|
156
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
157
|
+
EXECUTE format(
|
|
158
|
+
'GRANT SELECT, INSERT ON TABLE %I.temporal_schedule_cleanup_outbox TO opengeni_app',
|
|
159
|
+
target_schema
|
|
160
|
+
);
|
|
161
|
+
GRANT EXECUTE ON FUNCTION
|
|
162
|
+
opengeni_private.claim_temporal_schedule_cleanups(uuid, integer, integer)
|
|
163
|
+
TO opengeni_app;
|
|
164
|
+
GRANT EXECUTE ON FUNCTION
|
|
165
|
+
opengeni_private.settle_temporal_schedule_cleanup(uuid, uuid, text)
|
|
166
|
+
TO opengeni_app;
|
|
167
|
+
END IF;
|
|
168
|
+
END $$;
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Make provider capture takeover semantics explicit and provider-neutral. The
|
|
3
|
+
-- durable bit records whether a successor may safely overlap an ambiguous
|
|
4
|
+
-- predecessor. It covers both same-request provider idempotency and independent
|
|
5
|
+
-- read-only captures; replay_safe remains the narrower wire-idempotency proof.
|
|
6
|
+
|
|
7
|
+
SET LOCAL lock_timeout = '5s';
|
|
8
|
+
SET LOCAL statement_timeout = '5min';
|
|
9
|
+
|
|
10
|
+
ALTER TABLE "sandbox_leases"
|
|
11
|
+
ADD COLUMN IF NOT EXISTS "archive_capture_takeover_safe" boolean
|
|
12
|
+
NOT NULL DEFAULT false;
|
|
13
|
+
|
|
14
|
+
-- Claims created before this migration had only the narrower Modal replay
|
|
15
|
+
-- receipt. Preserve exactly that proven subset; all other legacy claims remain
|
|
16
|
+
-- exclusive until their deadline plus provider-specific proof.
|
|
17
|
+
UPDATE "sandbox_leases"
|
|
18
|
+
SET
|
|
19
|
+
"archive_capture_takeover_safe" = "archive_capture_provider_replay_safe",
|
|
20
|
+
"updated_at" = pg_catalog.clock_timestamp()
|
|
21
|
+
WHERE "archive_capture_id" IS NOT NULL
|
|
22
|
+
AND "archive_capture_takeover_safe" IS DISTINCT FROM
|
|
23
|
+
"archive_capture_provider_replay_safe";
|
|
24
|
+
|
|
25
|
+
CREATE OR REPLACE FUNCTION opengeni_private.stamp_sandbox_drain_teardown_fence()
|
|
26
|
+
RETURNS trigger
|
|
27
|
+
LANGUAGE plpgsql
|
|
28
|
+
SET search_path = pg_catalog
|
|
29
|
+
AS $function$
|
|
30
|
+
BEGIN
|
|
31
|
+
IF NEW.liveness = 'draining'
|
|
32
|
+
AND NEW.archive_capture_id IS NOT NULL
|
|
33
|
+
AND NEW.archive_capture_id IS DISTINCT FROM OLD.archive_capture_id
|
|
34
|
+
AND NEW.reaper_hold_id IS NOT NULL
|
|
35
|
+
AND NEW.reaper_hold_until > pg_catalog.clock_timestamp()
|
|
36
|
+
THEN
|
|
37
|
+
RAISE EXCEPTION 'sandbox reaper hold blocks drain capture for lease %', NEW.id
|
|
38
|
+
USING ERRCODE = '55000';
|
|
39
|
+
END IF;
|
|
40
|
+
|
|
41
|
+
IF OLD.liveness = 'draining'
|
|
42
|
+
AND NEW.liveness = 'cold'
|
|
43
|
+
AND NEW.reaper_hold_id IS NOT NULL
|
|
44
|
+
AND NEW.reaper_hold_until > pg_catalog.clock_timestamp()
|
|
45
|
+
THEN
|
|
46
|
+
RAISE EXCEPTION 'sandbox reaper hold blocks cold commit for lease %', NEW.id
|
|
47
|
+
USING ERRCODE = '55000';
|
|
48
|
+
END IF;
|
|
49
|
+
|
|
50
|
+
IF OLD.liveness = 'draining'
|
|
51
|
+
AND NEW.liveness = 'cold'
|
|
52
|
+
AND OLD.archive_capture_id IS NOT NULL
|
|
53
|
+
AND NULLIF(
|
|
54
|
+
pg_catalog.current_setting('opengeni.sandbox_drain_capture_id', true),
|
|
55
|
+
''
|
|
56
|
+
) IS DISTINCT FROM OLD.archive_capture_id::text
|
|
57
|
+
THEN
|
|
58
|
+
RAISE EXCEPTION 'sandbox drain capture ownership blocks cold commit for lease %', NEW.id
|
|
59
|
+
USING ERRCODE = '55000';
|
|
60
|
+
END IF;
|
|
61
|
+
|
|
62
|
+
IF NEW.archive_capture_id IS NULL THEN
|
|
63
|
+
NEW.archive_capture_operation_id := NULL;
|
|
64
|
+
NEW.archive_capture_provider_request_id := NULL;
|
|
65
|
+
NEW.archive_capture_provider_replay_safe := false;
|
|
66
|
+
NEW.archive_capture_takeover_safe := false;
|
|
67
|
+
NEW.archive_capture_attempt := NULL;
|
|
68
|
+
NEW.archive_capture_published_at := NULL;
|
|
69
|
+
ELSIF NEW.archive_capture_id IS DISTINCT FROM OLD.archive_capture_id THEN
|
|
70
|
+
NEW.archive_capture_published_at := NULL;
|
|
71
|
+
IF NEW.archive_capture_operation_id IS NOT DISTINCT FROM OLD.archive_capture_operation_id
|
|
72
|
+
AND NEW.archive_capture_attempt IS NOT DISTINCT FROM OLD.archive_capture_attempt
|
|
73
|
+
THEN
|
|
74
|
+
-- A pre-0186 writer knows neither takeover contract. Never inherit the
|
|
75
|
+
-- predecessor's permission merely because the new column is invisible to it.
|
|
76
|
+
NEW.archive_capture_operation_id := NEW.archive_capture_id;
|
|
77
|
+
NEW.archive_capture_provider_request_id := NEW.archive_capture_id;
|
|
78
|
+
NEW.archive_capture_provider_replay_safe := false;
|
|
79
|
+
NEW.archive_capture_takeover_safe := false;
|
|
80
|
+
NEW.archive_capture_attempt := 1;
|
|
81
|
+
ELSE
|
|
82
|
+
NEW.archive_capture_operation_id := COALESCE(
|
|
83
|
+
NEW.archive_capture_operation_id,
|
|
84
|
+
NEW.archive_capture_id
|
|
85
|
+
);
|
|
86
|
+
NEW.archive_capture_provider_request_id := COALESCE(
|
|
87
|
+
NEW.archive_capture_provider_request_id,
|
|
88
|
+
NEW.archive_capture_id
|
|
89
|
+
);
|
|
90
|
+
NEW.archive_capture_attempt := COALESCE(NEW.archive_capture_attempt, 1);
|
|
91
|
+
END IF;
|
|
92
|
+
ELSE
|
|
93
|
+
NEW.archive_capture_operation_id := COALESCE(
|
|
94
|
+
NEW.archive_capture_operation_id,
|
|
95
|
+
NEW.archive_capture_id
|
|
96
|
+
);
|
|
97
|
+
NEW.archive_capture_provider_request_id := COALESCE(
|
|
98
|
+
NEW.archive_capture_provider_request_id,
|
|
99
|
+
NEW.archive_capture_id
|
|
100
|
+
);
|
|
101
|
+
NEW.archive_capture_attempt := COALESCE(NEW.archive_capture_attempt, 1);
|
|
102
|
+
END IF;
|
|
103
|
+
|
|
104
|
+
IF NEW.archive_capture_provider_replay_safe
|
|
105
|
+
AND NOT NEW.archive_capture_takeover_safe
|
|
106
|
+
THEN
|
|
107
|
+
RAISE EXCEPTION 'provider-replay-safe capture must be takeover-safe for lease %', NEW.id
|
|
108
|
+
USING ERRCODE = '23514';
|
|
109
|
+
END IF;
|
|
110
|
+
|
|
111
|
+
IF OLD.liveness = 'draining'
|
|
112
|
+
AND NEW.liveness = 'draining'
|
|
113
|
+
AND OLD.archive_capture_id IS NULL
|
|
114
|
+
AND NEW.archive_capture_id IS NOT NULL
|
|
115
|
+
AND NEW.rotation_requested_at IS NULL
|
|
116
|
+
THEN
|
|
117
|
+
NEW.rotation_requested_at := COALESCE(
|
|
118
|
+
NEW.archive_capture_started_at,
|
|
119
|
+
pg_catalog.clock_timestamp()
|
|
120
|
+
);
|
|
121
|
+
NEW.rotation_reason := 'teardown_claim';
|
|
122
|
+
END IF;
|
|
123
|
+
RETURN NEW;
|
|
124
|
+
END
|
|
125
|
+
$function$;
|
|
126
|
+
|
|
127
|
+
DROP TRIGGER IF EXISTS sandbox_leases_drain_teardown_fence ON "sandbox_leases";
|
|
128
|
+
CREATE TRIGGER sandbox_leases_drain_teardown_fence
|
|
129
|
+
BEFORE UPDATE OF "archive_capture_id", "liveness" ON "sandbox_leases"
|
|
130
|
+
FOR EACH ROW
|
|
131
|
+
EXECUTE FUNCTION opengeni_private.stamp_sandbox_drain_teardown_fence();
|
|
132
|
+
|
|
133
|
+
ALTER TABLE "sandbox_leases"
|
|
134
|
+
DROP CONSTRAINT IF EXISTS "sandbox_leases_archive_capture_check",
|
|
135
|
+
ADD CONSTRAINT "sandbox_leases_archive_capture_check"
|
|
136
|
+
CHECK (
|
|
137
|
+
(
|
|
138
|
+
"archive_capture_id" IS NULL
|
|
139
|
+
AND "archive_capture_operation_id" IS NULL
|
|
140
|
+
AND "archive_capture_provider_request_id" IS NULL
|
|
141
|
+
AND "archive_capture_provider_replay_safe" = false
|
|
142
|
+
AND "archive_capture_takeover_safe" = false
|
|
143
|
+
AND "archive_capture_attempt" IS NULL
|
|
144
|
+
AND "archive_capture_generation" IS NULL
|
|
145
|
+
AND "archive_capture_started_at" IS NULL
|
|
146
|
+
AND "archive_capture_deadline_at" IS NULL
|
|
147
|
+
AND "archive_capture_published_at" IS NULL
|
|
148
|
+
) OR (
|
|
149
|
+
"archive_capture_id" IS NOT NULL
|
|
150
|
+
AND "archive_capture_operation_id" IS NOT NULL
|
|
151
|
+
AND "archive_capture_provider_request_id" IS NOT NULL
|
|
152
|
+
AND (
|
|
153
|
+
"archive_capture_provider_replay_safe" = false
|
|
154
|
+
OR "archive_capture_takeover_safe" = true
|
|
155
|
+
)
|
|
156
|
+
AND "archive_capture_attempt" IS NOT NULL
|
|
157
|
+
AND "archive_capture_attempt" > 0
|
|
158
|
+
AND "archive_capture_generation" IS NOT NULL
|
|
159
|
+
AND "archive_capture_generation" = "workspace_generation"
|
|
160
|
+
AND "archive_capture_started_at" IS NOT NULL
|
|
161
|
+
AND "archive_capture_deadline_at" IS NOT NULL
|
|
162
|
+
AND "archive_capture_deadline_at" > "archive_capture_started_at"
|
|
163
|
+
AND (
|
|
164
|
+
"archive_capture_published_at" IS NULL
|
|
165
|
+
OR "liveness" = 'draining'
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
) NOT VALID;
|
|
169
|
+
|
|
170
|
+
ALTER TABLE "sandbox_leases"
|
|
171
|
+
VALIDATE CONSTRAINT "sandbox_leases_archive_capture_check";
|
|
172
|
+
|
|
173
|
+
-- Keep the global DB-owned sweep aligned with the per-workspace implementation.
|
|
174
|
+
-- A live turn heartbeats every 10s. A canonical holder whose authoritative turn
|
|
175
|
+
-- is already closed is dead immediately; every other turn holder gets the normal
|
|
176
|
+
-- lease-TTL crash horizon. Once a capture claim exists, holder-zero dispatch is
|
|
177
|
+
-- immediate because that claim already is the durable admission/teardown fence.
|
|
178
|
+
DO $capture_aware_reaper$
|
|
179
|
+
DECLARE data_schema text := current_schema();
|
|
180
|
+
BEGIN
|
|
181
|
+
EXECUTE format($create$
|
|
182
|
+
CREATE OR REPLACE FUNCTION opengeni_private.reap_sandbox_leases(
|
|
183
|
+
p_viewer_holder_ttl_ms bigint,
|
|
184
|
+
p_turn_holder_ttl_ms bigint,
|
|
185
|
+
p_idle_grace_ms bigint
|
|
186
|
+
)
|
|
187
|
+
RETURNS TABLE (
|
|
188
|
+
workspace_id uuid,
|
|
189
|
+
sandbox_group_id uuid,
|
|
190
|
+
instance_id text,
|
|
191
|
+
lease_epoch integer
|
|
192
|
+
)
|
|
193
|
+
LANGUAGE plpgsql
|
|
194
|
+
SECURITY DEFINER
|
|
195
|
+
SET search_path = pg_catalog
|
|
196
|
+
AS $function$
|
|
197
|
+
DECLARE locked_ids uuid[];
|
|
198
|
+
BEGIN
|
|
199
|
+
PERFORM pg_catalog.set_config('opengeni.sandbox_recovery_protocol_v2', '1', true);
|
|
200
|
+
|
|
201
|
+
SELECT coalesce(
|
|
202
|
+
pg_catalog.array_agg(candidate.id),
|
|
203
|
+
ARRAY[]::uuid[]
|
|
204
|
+
)
|
|
205
|
+
INTO locked_ids
|
|
206
|
+
FROM (
|
|
207
|
+
SELECT lease.id
|
|
208
|
+
FROM %1$I.sandbox_leases lease
|
|
209
|
+
WHERE lease.liveness <> 'cold'
|
|
210
|
+
OR EXISTS (
|
|
211
|
+
SELECT 1
|
|
212
|
+
FROM %1$I.sandbox_lease_holders holder
|
|
213
|
+
WHERE holder.lease_id = lease.id
|
|
214
|
+
)
|
|
215
|
+
ORDER BY lease.updated_at, lease.id
|
|
216
|
+
LIMIT 500
|
|
217
|
+
FOR UPDATE OF lease SKIP LOCKED
|
|
218
|
+
) candidate;
|
|
219
|
+
|
|
220
|
+
IF pg_catalog.cardinality(locked_ids) = 0 THEN
|
|
221
|
+
RETURN;
|
|
222
|
+
END IF;
|
|
223
|
+
|
|
224
|
+
DELETE FROM %1$I.sandbox_lease_holders holder
|
|
225
|
+
USING (
|
|
226
|
+
SELECT stale.id
|
|
227
|
+
FROM %1$I.sandbox_lease_holders stale
|
|
228
|
+
WHERE stale.lease_id = ANY(locked_ids)
|
|
229
|
+
AND stale.kind IN ('viewer', 'direct')
|
|
230
|
+
AND stale.last_heartbeat_at
|
|
231
|
+
< pg_catalog.now()
|
|
232
|
+
- pg_catalog.make_interval(secs => p_viewer_holder_ttl_ms / 1000.0)
|
|
233
|
+
FOR UPDATE OF stale SKIP LOCKED
|
|
234
|
+
) victim
|
|
235
|
+
WHERE holder.id = victim.id;
|
|
236
|
+
|
|
237
|
+
IF p_turn_holder_ttl_ms > 0 THEN
|
|
238
|
+
DELETE FROM %1$I.sandbox_lease_holders holder
|
|
239
|
+
USING (
|
|
240
|
+
SELECT stale.id
|
|
241
|
+
FROM %1$I.sandbox_lease_holders stale
|
|
242
|
+
WHERE stale.lease_id = ANY(locked_ids)
|
|
243
|
+
AND stale.kind = 'turn'
|
|
244
|
+
AND (
|
|
245
|
+
stale.last_heartbeat_at
|
|
246
|
+
< pg_catalog.now()
|
|
247
|
+
- pg_catalog.make_interval(secs => p_turn_holder_ttl_ms / 1000.0)
|
|
248
|
+
OR (
|
|
249
|
+
stale.holder_id ~* '^turn-attempt:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
|
|
250
|
+
AND NOT EXISTS (
|
|
251
|
+
SELECT 1
|
|
252
|
+
FROM %1$I.session_turn_attempts attempt
|
|
253
|
+
JOIN %1$I.session_turns turn
|
|
254
|
+
ON turn.account_id = attempt.account_id
|
|
255
|
+
AND turn.workspace_id = attempt.workspace_id
|
|
256
|
+
AND turn.session_id = attempt.session_id
|
|
257
|
+
AND turn.id = attempt.turn_id
|
|
258
|
+
AND turn.execution_generation = attempt.execution_generation
|
|
259
|
+
AND turn.active_attempt_id = attempt.id
|
|
260
|
+
WHERE stale.holder_id = ('turn-attempt:' || attempt.id::text)
|
|
261
|
+
AND attempt.account_id = stale.account_id
|
|
262
|
+
AND attempt.workspace_id = stale.workspace_id
|
|
263
|
+
AND attempt.session_id = stale.subject_id
|
|
264
|
+
AND attempt.state IN ('claimed', 'running')
|
|
265
|
+
)
|
|
266
|
+
)
|
|
267
|
+
)
|
|
268
|
+
FOR UPDATE OF stale SKIP LOCKED
|
|
269
|
+
) victim
|
|
270
|
+
WHERE holder.id = victim.id;
|
|
271
|
+
END IF;
|
|
272
|
+
|
|
273
|
+
UPDATE %1$I.sandbox_leases lease SET
|
|
274
|
+
refcount = counts.total,
|
|
275
|
+
turn_holders = counts.turns,
|
|
276
|
+
viewer_holders = counts.viewers,
|
|
277
|
+
liveness = CASE
|
|
278
|
+
WHEN lease.liveness = 'warm'
|
|
279
|
+
AND counts.total = 0
|
|
280
|
+
AND counts.turns = 0
|
|
281
|
+
AND (
|
|
282
|
+
lease.reaper_hold_id IS NULL
|
|
283
|
+
OR lease.reaper_hold_until <= pg_catalog.now()
|
|
284
|
+
)
|
|
285
|
+
THEN 'draining' ELSE lease.liveness END,
|
|
286
|
+
expires_at = CASE
|
|
287
|
+
WHEN lease.liveness = 'warm'
|
|
288
|
+
AND counts.total = 0
|
|
289
|
+
AND counts.turns = 0
|
|
290
|
+
AND (
|
|
291
|
+
lease.reaper_hold_id IS NULL
|
|
292
|
+
OR lease.reaper_hold_until <= pg_catalog.now()
|
|
293
|
+
)
|
|
294
|
+
THEN CASE
|
|
295
|
+
WHEN lease.rotation_requested_at IS NOT NULL
|
|
296
|
+
OR lease.archive_capture_id IS NOT NULL
|
|
297
|
+
THEN pg_catalog.now() - interval '1 millisecond'
|
|
298
|
+
ELSE pg_catalog.now()
|
|
299
|
+
+ pg_catalog.make_interval(secs => p_idle_grace_ms / 1000.0)
|
|
300
|
+
END
|
|
301
|
+
ELSE lease.expires_at END,
|
|
302
|
+
updated_at = pg_catalog.now()
|
|
303
|
+
FROM (
|
|
304
|
+
SELECT candidate.id,
|
|
305
|
+
pg_catalog.count(holder.id)::int AS total,
|
|
306
|
+
pg_catalog.count(holder.id)
|
|
307
|
+
FILTER (WHERE holder.kind = 'turn')::int AS turns,
|
|
308
|
+
pg_catalog.count(holder.id)
|
|
309
|
+
FILTER (WHERE holder.kind = 'viewer')::int AS viewers
|
|
310
|
+
FROM pg_catalog.unnest(locked_ids) candidate(id)
|
|
311
|
+
LEFT JOIN %1$I.sandbox_lease_holders holder
|
|
312
|
+
ON holder.lease_id = candidate.id
|
|
313
|
+
GROUP BY candidate.id
|
|
314
|
+
) counts
|
|
315
|
+
WHERE lease.id = counts.id;
|
|
316
|
+
|
|
317
|
+
UPDATE %1$I.sandbox_leases lease SET
|
|
318
|
+
liveness = 'cold',
|
|
319
|
+
instance_id = null,
|
|
320
|
+
lease_epoch = lease.lease_epoch + 1,
|
|
321
|
+
reaper_hold_id = null,
|
|
322
|
+
reaper_hold_until = null,
|
|
323
|
+
reaper_hold_reason = null,
|
|
324
|
+
resume_state = opengeni_private.warming_reset_resume_state_v2(
|
|
325
|
+
lease.backend,
|
|
326
|
+
lease.resume_backend_id,
|
|
327
|
+
lease.resume_state,
|
|
328
|
+
lease.workspace_generation,
|
|
329
|
+
lease.archive_generation,
|
|
330
|
+
pg_catalog.clock_timestamp()
|
|
331
|
+
),
|
|
332
|
+
resume_backend_id = CASE
|
|
333
|
+
WHEN coalesce(
|
|
334
|
+
lease.resume_state #>> '{sessionState,workspaceArchive}', ''
|
|
335
|
+
) <> ''
|
|
336
|
+
OR coalesce(
|
|
337
|
+
lease.resume_state #>> '{sessionState,workspaceArchivePrev}', ''
|
|
338
|
+
) <> ''
|
|
339
|
+
THEN coalesce(lease.resume_backend_id, lease.backend)
|
|
340
|
+
ELSE null END,
|
|
341
|
+
data_plane_url = null,
|
|
342
|
+
terminal_data_plane_url = null,
|
|
343
|
+
provider_created_at = null,
|
|
344
|
+
provider_deadline_at = null,
|
|
345
|
+
rotation_requested_at = null,
|
|
346
|
+
rotation_reason = null,
|
|
347
|
+
updated_at = pg_catalog.now()
|
|
348
|
+
WHERE lease.id = ANY(locked_ids)
|
|
349
|
+
AND lease.liveness = 'warming'
|
|
350
|
+
AND lease.expires_at < pg_catalog.now()
|
|
351
|
+
AND lease.instance_id IS NULL
|
|
352
|
+
AND (
|
|
353
|
+
lease.reaper_hold_id IS NULL
|
|
354
|
+
OR lease.reaper_hold_until <= pg_catalog.now()
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
UPDATE %1$I.sandbox_leases lease SET
|
|
358
|
+
liveness = 'draining',
|
|
359
|
+
refcount = 0,
|
|
360
|
+
turn_holders = 0,
|
|
361
|
+
viewer_holders = 0,
|
|
362
|
+
data_plane_url = null,
|
|
363
|
+
terminal_data_plane_url = null,
|
|
364
|
+
lease_epoch = lease.lease_epoch + 1,
|
|
365
|
+
reaper_hold_id = null,
|
|
366
|
+
reaper_hold_until = null,
|
|
367
|
+
reaper_hold_reason = null,
|
|
368
|
+
expires_at = pg_catalog.now() - interval '1 millisecond',
|
|
369
|
+
updated_at = pg_catalog.now()
|
|
370
|
+
WHERE lease.id = ANY(locked_ids)
|
|
371
|
+
AND lease.liveness = 'warming'
|
|
372
|
+
AND lease.expires_at < pg_catalog.now()
|
|
373
|
+
AND lease.instance_id IS NOT NULL
|
|
374
|
+
AND (
|
|
375
|
+
lease.reaper_hold_id IS NULL
|
|
376
|
+
OR lease.reaper_hold_until <= pg_catalog.now()
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
RETURN QUERY
|
|
380
|
+
SELECT lease.workspace_id, lease.sandbox_group_id,
|
|
381
|
+
lease.instance_id, lease.lease_epoch
|
|
382
|
+
FROM %1$I.sandbox_leases lease
|
|
383
|
+
WHERE lease.id = ANY(locked_ids)
|
|
384
|
+
AND lease.liveness = 'draining'
|
|
385
|
+
AND lease.expires_at < pg_catalog.now()
|
|
386
|
+
AND lease.refcount = 0
|
|
387
|
+
AND (
|
|
388
|
+
lease.reaper_hold_id IS NULL
|
|
389
|
+
OR lease.reaper_hold_until <= pg_catalog.now()
|
|
390
|
+
);
|
|
391
|
+
END;
|
|
392
|
+
$function$;
|
|
393
|
+
$create$, data_schema);
|
|
394
|
+
END
|
|
395
|
+
$capture_aware_reaper$;
|
|
396
|
+
|
|
397
|
+
REVOKE ALL ON FUNCTION opengeni_private.stamp_sandbox_drain_teardown_fence() FROM PUBLIC;
|
|
398
|
+
|
|
399
|
+
RESET statement_timeout;
|
|
400
|
+
RESET lock_timeout;
|