@opengeni/db 3.7.4 → 4.0.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/automations.d.ts +4 -0
- package/dist/canonical-human-identities.js +3 -3
- package/dist/capability-integrations.d.ts +2 -0
- package/dist/{chunk-6DPJA36C.js → chunk-2XEZK4LF.js} +7 -1
- package/dist/chunk-2XEZK4LF.js.map +1 -0
- package/dist/{chunk-4NAR7FBA.js → chunk-3RI33YQN.js} +67 -1
- package/dist/chunk-3RI33YQN.js.map +1 -0
- package/dist/{chunk-SZAWDXQC.js → chunk-CJROYHSC.js} +3 -3
- package/dist/{chunk-DZCYW2WT.js → chunk-MX6V5F65.js} +2 -2
- package/dist/{chunk-S3TDDCS2.js → chunk-N5VXJ4I2.js} +151 -78
- package/dist/chunk-N5VXJ4I2.js.map +1 -0
- package/dist/{chunk-U4WPPEBH.js → chunk-NLFHMPUB.js} +2 -2
- package/dist/{chunk-5NNCGFQY.js → chunk-P6NSEG2S.js} +2 -2
- package/dist/{chunk-O56DM5GZ.js → chunk-QM6YOWHZ.js} +624 -57
- package/dist/chunk-QM6YOWHZ.js.map +1 -0
- package/dist/{chunk-US4IS6IV.js → chunk-RZNOSEMC.js} +2 -2
- package/dist/{chunk-F6PBS6WE.js → chunk-UFAMOUWL.js} +2 -2
- package/dist/codemode-operations.d.ts +5 -0
- package/dist/codex-token-resolver.d.ts +6 -2
- package/dist/connection-token-resolver.d.ts +8 -0
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/generated-images.d.ts +15 -0
- package/dist/index.d.ts +393 -91
- package/dist/index.js +9992 -6069
- package/dist/index.js.map +1 -1
- package/dist/insights.d.ts +9 -0
- package/dist/managed-auth-session-sets.js +3 -3
- package/dist/managed-user-setup.d.ts +3 -0
- package/dist/mcp-oauth.d.ts +80 -0
- package/dist/migrate.d.ts +1 -0
- package/dist/migrate.js +3 -1
- package/dist/model-catalog.d.ts +153 -0
- package/dist/model-context-snapshots.d.ts +20 -0
- package/dist/new-session-drafts.d.ts +12 -0
- package/dist/organization-membership-lifecycle.d.ts +8 -1
- package/dist/organization-model-providers.d.ts +101 -0
- package/dist/pack-components.d.ts +1 -0
- package/dist/pr-review.d.ts +9 -0
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +8 -8
- package/dist/schema.d.ts +7708 -5050
- package/dist/schema.js +25 -1
- package/dist/session-background-commands.d.ts +18 -6
- package/dist/session-background-commands.js +15 -9
- package/dist/session-control.d.ts +37 -2
- package/dist/session-goal-pacing.d.ts +3 -6
- package/dist/session-queue-commands.d.ts +17 -0
- package/dist/session-tenancy.js +3 -3
- package/dist/tool-gateway-approvals.d.ts +31 -0
- package/dist/video-generation.js +3 -3
- package/dist/workspace-artifacts.d.ts +17 -3
- package/dist/xai-subscription.d.ts +8 -0
- package/drizzle/0387_bound_host_export_session_payloads.sql +165 -0
- package/drizzle/0388_sandbox_provider_deadline_interactions.sql +491 -0
- package/drizzle/0389_model_catalog_and_gateway_custom_models.sql +241 -0
- package/drizzle/0390_organization_model_provider_connections.sql +197 -0
- package/drizzle/0391_sandbox_provider_deadline_interaction_followup.sql +295 -0
- package/drizzle/0392_context_compaction_pending_observability.sql +198 -0
- package/drizzle/0393_workspace_memory_and_learning_defaults.sql +102 -0
- package/drizzle/0394_session_selected_skill_activation.sql +20 -0
- package/drizzle/0395_scheduled_task_unclaimed_occurrence_invalidation.sql +154 -0
- package/drizzle/0396_model_call_equivalent_credit_cost.sql +28 -0
- package/drizzle/0397_sandbox_deadline_rotation_preemption.sql +113 -0
- package/drizzle/0398_organization_workspace_management_entry.sql +206 -0
- package/drizzle/0399_additional_managed_organization_creation.sql +691 -0
- package/drizzle/0400_session_attempt_model_context_snapshots.sql +65 -0
- package/drizzle/0401_organization_user_setup_token_transport.sql +178 -0
- package/drizzle/0402_session_input_wait_and_background_command_results.sql +1023 -0
- package/drizzle/0403_codex_unconditional_credential_leasing.sql +181 -0
- package/drizzle/0404_mcp_oauth_authorization_server.sql +471 -0
- package/drizzle/0405_tool_gateway_approval_capabilities.sql +184 -0
- package/drizzle/0406_workspace_html_site_sources.sql +71 -0
- package/package.json +6 -6
- package/src/automations.ts +9 -1
- package/src/capability-integrations.ts +8 -0
- package/src/codemode-operations.ts +31 -21
- package/src/codex-token-resolver.ts +16 -6
- package/src/connection-token-resolver.ts +52 -3
- package/src/generated-images.ts +70 -6
- package/src/index.ts +3894 -994
- package/src/insights-model-bundle.ts +54 -5
- package/src/insights.ts +53 -7
- package/src/managed-user-setup.ts +14 -3
- package/src/mcp-oauth.ts +488 -0
- package/src/migrate.ts +9 -1
- package/src/model-catalog.ts +682 -0
- package/src/model-context-snapshots.ts +151 -0
- package/src/new-session-drafts.ts +66 -21
- package/src/organization-membership-lifecycle.ts +47 -5
- package/src/organization-model-providers.ts +614 -0
- package/src/pack-components.ts +15 -2
- package/src/pr-review.ts +19 -0
- package/src/provision-roles.ts +4 -0
- package/src/runtime-posture.ts +64 -0
- package/src/schema.ts +666 -61
- package/src/session-background-commands.ts +180 -78
- package/src/session-control.ts +181 -12
- package/src/session-goal-pacing.ts +4 -8
- package/src/session-queue-commands.ts +157 -82
- package/src/tool-gateway-approvals.ts +168 -0
- package/src/workspace-artifacts.ts +633 -178
- package/src/xai-subscription.ts +66 -14
- package/dist/chunk-4NAR7FBA.js.map +0 -1
- package/dist/chunk-6DPJA36C.js.map +0 -1
- package/dist/chunk-O56DM5GZ.js.map +0 -1
- package/dist/chunk-S3TDDCS2.js.map +0 -1
- /package/dist/{chunk-SZAWDXQC.js.map → chunk-CJROYHSC.js.map} +0 -0
- /package/dist/{chunk-DZCYW2WT.js.map → chunk-MX6V5F65.js.map} +0 -0
- /package/dist/{chunk-U4WPPEBH.js.map → chunk-NLFHMPUB.js.map} +0 -0
- /package/dist/{chunk-5NNCGFQY.js.map → chunk-P6NSEG2S.js.map} +0 -0
- /package/dist/{chunk-US4IS6IV.js.map → chunk-RZNOSEMC.js.map} +0 -0
- /package/dist/{chunk-F6PBS6WE.js.map → chunk-UFAMOUWL.js.map} +0 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
-- deployment-mode: maintenance
|
|
2
|
+
-- Current-human HTTP tool calls use hash-only, single-use approval capabilities
|
|
3
|
+
-- instead of trusting a caller-provided approval boolean.
|
|
4
|
+
-- This changes the exact runtime-posture table/grant/RLS contract. Stop every
|
|
5
|
+
-- API, control worker, and turn worker before applying it, and never restart a
|
|
6
|
+
-- pre-0404 image after commit.
|
|
7
|
+
|
|
8
|
+
SET LOCAL lock_timeout = '5s';
|
|
9
|
+
SET LOCAL statement_timeout = '5min';
|
|
10
|
+
|
|
11
|
+
DO $tool_gateway_approval_runtime_drain_before$
|
|
12
|
+
DECLARE
|
|
13
|
+
configured_roles_text text := nullif(
|
|
14
|
+
current_setting('opengeni.migration_application_roles', true), ''
|
|
15
|
+
);
|
|
16
|
+
configured_roles jsonb;
|
|
17
|
+
BEGIN
|
|
18
|
+
IF configured_roles_text IS NULL THEN
|
|
19
|
+
RAISE EXCEPTION
|
|
20
|
+
'0404 tool gateway approval activation requires an explicit application database role list'
|
|
21
|
+
USING ERRCODE = '55000';
|
|
22
|
+
END IF;
|
|
23
|
+
BEGIN
|
|
24
|
+
configured_roles := configured_roles_text::jsonb;
|
|
25
|
+
EXCEPTION WHEN OTHERS THEN
|
|
26
|
+
RAISE EXCEPTION
|
|
27
|
+
'0404 tool gateway approval activation received a malformed application database role list'
|
|
28
|
+
USING ERRCODE = '55000';
|
|
29
|
+
END;
|
|
30
|
+
IF jsonb_typeof(configured_roles) <> 'array'
|
|
31
|
+
OR jsonb_array_length(configured_roles) NOT BETWEEN 1 AND 16
|
|
32
|
+
OR EXISTS (
|
|
33
|
+
SELECT 1 FROM jsonb_array_elements(configured_roles) AS roles(value)
|
|
34
|
+
WHERE jsonb_typeof(value) <> 'string'
|
|
35
|
+
OR btrim(value #>> '{}') = ''
|
|
36
|
+
OR octet_length(value #>> '{}') > 63
|
|
37
|
+
)
|
|
38
|
+
OR (
|
|
39
|
+
SELECT count(*) FROM jsonb_array_elements_text(configured_roles)
|
|
40
|
+
) <> (
|
|
41
|
+
SELECT count(DISTINCT value)
|
|
42
|
+
FROM jsonb_array_elements_text(configured_roles) AS roles(value)
|
|
43
|
+
)
|
|
44
|
+
THEN
|
|
45
|
+
RAISE EXCEPTION
|
|
46
|
+
'0404 tool gateway approval activation received an invalid application database role list'
|
|
47
|
+
USING ERRCODE = '55000';
|
|
48
|
+
END IF;
|
|
49
|
+
IF EXISTS (
|
|
50
|
+
SELECT 1 FROM pg_stat_activity activity
|
|
51
|
+
JOIN jsonb_array_elements_text(configured_roles) roles(role_name)
|
|
52
|
+
ON roles.role_name = activity.usename
|
|
53
|
+
WHERE activity.datname = current_database()
|
|
54
|
+
AND activity.pid <> pg_backend_pid()
|
|
55
|
+
)
|
|
56
|
+
THEN
|
|
57
|
+
RAISE EXCEPTION
|
|
58
|
+
'0404 tool gateway approval activation requires all configured OpenGeni application database sessions to be stopped'
|
|
59
|
+
USING ERRCODE = '55000';
|
|
60
|
+
END IF;
|
|
61
|
+
END
|
|
62
|
+
$tool_gateway_approval_runtime_drain_before$;
|
|
63
|
+
|
|
64
|
+
CREATE TABLE "tool_gateway_approval_capabilities" (
|
|
65
|
+
"token_hash" text PRIMARY KEY,
|
|
66
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
|
|
67
|
+
"workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
|
|
68
|
+
"subject_id" text NOT NULL,
|
|
69
|
+
"operation_id" uuid NOT NULL,
|
|
70
|
+
"catalog_digest" text NOT NULL,
|
|
71
|
+
"server_id" text NOT NULL,
|
|
72
|
+
"tool_name" text NOT NULL,
|
|
73
|
+
"arguments_digest" text NOT NULL,
|
|
74
|
+
"authority_digest" text NOT NULL,
|
|
75
|
+
"expires_at" timestamptz NOT NULL,
|
|
76
|
+
"consumed_at" timestamptz,
|
|
77
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
78
|
+
CONSTRAINT "tool_gateway_approval_capabilities_workspace_account_fk"
|
|
79
|
+
FOREIGN KEY ("workspace_id", "account_id")
|
|
80
|
+
REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE,
|
|
81
|
+
CONSTRAINT "tool_gateway_approval_capabilities_token_hash_chk"
|
|
82
|
+
CHECK ("token_hash" ~ '^[0-9a-f]{64}$'),
|
|
83
|
+
CONSTRAINT "tool_gateway_approval_capabilities_catalog_digest_chk"
|
|
84
|
+
CHECK ("catalog_digest" ~ '^[0-9a-f]{64}$'),
|
|
85
|
+
CONSTRAINT "tool_gateway_approval_capabilities_arguments_digest_chk"
|
|
86
|
+
CHECK ("arguments_digest" ~ '^[0-9a-f]{64}$'),
|
|
87
|
+
CONSTRAINT "tool_gateway_approval_capabilities_authority_digest_chk"
|
|
88
|
+
CHECK ("authority_digest" ~ '^[0-9a-f]{64}$'),
|
|
89
|
+
CONSTRAINT "tool_gateway_approval_capabilities_subject_chk"
|
|
90
|
+
CHECK (length(btrim("subject_id")) BETWEEN 1 AND 1024),
|
|
91
|
+
CONSTRAINT "tool_gateway_approval_capabilities_identity_chk"
|
|
92
|
+
CHECK (
|
|
93
|
+
length("server_id") BETWEEN 1 AND 256
|
|
94
|
+
AND length("tool_name") BETWEEN 1 AND 512
|
|
95
|
+
),
|
|
96
|
+
CONSTRAINT "tool_gateway_approval_capabilities_expiry_chk"
|
|
97
|
+
CHECK ("expires_at" > "created_at" AND "expires_at" <= "created_at" + interval '10 minutes')
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
CREATE UNIQUE INDEX "tool_gateway_approval_capabilities_operation_uq"
|
|
101
|
+
ON "tool_gateway_approval_capabilities" ("workspace_id", "subject_id", "operation_id");
|
|
102
|
+
CREATE INDEX "tool_gateway_approval_capabilities_expires_idx"
|
|
103
|
+
ON "tool_gateway_approval_capabilities" ("expires_at", "token_hash");
|
|
104
|
+
CREATE INDEX "tool_gateway_approval_capabilities_live_subject_expiry_idx"
|
|
105
|
+
ON "tool_gateway_approval_capabilities" (
|
|
106
|
+
"workspace_id", "subject_id", "expires_at", "token_hash"
|
|
107
|
+
)
|
|
108
|
+
WHERE "consumed_at" IS NULL;
|
|
109
|
+
|
|
110
|
+
ALTER TABLE "tool_gateway_approval_capabilities" ENABLE ROW LEVEL SECURITY;
|
|
111
|
+
ALTER TABLE "tool_gateway_approval_capabilities" FORCE ROW LEVEL SECURITY;
|
|
112
|
+
CREATE POLICY "workspace_subject_isolation" ON "tool_gateway_approval_capabilities"
|
|
113
|
+
USING (
|
|
114
|
+
opengeni_private.workspace_rls_visible("account_id", "workspace_id")
|
|
115
|
+
AND "subject_id" = opengeni_private.current_subject_id()
|
|
116
|
+
)
|
|
117
|
+
WITH CHECK (
|
|
118
|
+
opengeni_private.workspace_rls_visible("account_id", "workspace_id")
|
|
119
|
+
AND "subject_id" = opengeni_private.current_subject_id()
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
REVOKE ALL ON "tool_gateway_approval_capabilities" FROM PUBLIC;
|
|
123
|
+
|
|
124
|
+
-- The application role list is drain detection only. Remove any explicit ACL
|
|
125
|
+
-- inherited from owner default privileges, including an old side of a runtime
|
|
126
|
+
-- role rotation. The post-migration role provisioner grants only the exact
|
|
127
|
+
-- current target application role.
|
|
128
|
+
DO $tool_gateway_approval_table_acl_reset$
|
|
129
|
+
DECLARE
|
|
130
|
+
data_schema text := pg_catalog.current_schema();
|
|
131
|
+
role_name text;
|
|
132
|
+
BEGIN
|
|
133
|
+
EXECUTE pg_catalog.format(
|
|
134
|
+
'REVOKE ALL ON TABLE %I.tool_gateway_approval_capabilities FROM PUBLIC',
|
|
135
|
+
data_schema
|
|
136
|
+
);
|
|
137
|
+
FOR role_name IN
|
|
138
|
+
SELECT grantee_role.rolname
|
|
139
|
+
FROM pg_catalog.pg_class relation
|
|
140
|
+
CROSS JOIN LATERAL pg_catalog.aclexplode(
|
|
141
|
+
coalesce(
|
|
142
|
+
relation.relacl,
|
|
143
|
+
pg_catalog.acldefault('r', relation.relowner)
|
|
144
|
+
)
|
|
145
|
+
) privilege
|
|
146
|
+
INNER JOIN pg_catalog.pg_roles grantee_role
|
|
147
|
+
ON grantee_role.oid = privilege.grantee
|
|
148
|
+
WHERE relation.oid = pg_catalog.to_regclass(
|
|
149
|
+
pg_catalog.format('%I.tool_gateway_approval_capabilities', data_schema)
|
|
150
|
+
)
|
|
151
|
+
AND privilege.grantee <> 0
|
|
152
|
+
AND privilege.grantee <> relation.relowner
|
|
153
|
+
GROUP BY grantee_role.rolname
|
|
154
|
+
ORDER BY grantee_role.rolname COLLATE "C"
|
|
155
|
+
LOOP
|
|
156
|
+
EXECUTE pg_catalog.format(
|
|
157
|
+
'REVOKE ALL ON TABLE %I.tool_gateway_approval_capabilities FROM %I',
|
|
158
|
+
data_schema,
|
|
159
|
+
role_name
|
|
160
|
+
);
|
|
161
|
+
END LOOP;
|
|
162
|
+
END
|
|
163
|
+
$tool_gateway_approval_table_acl_reset$;
|
|
164
|
+
|
|
165
|
+
DO $tool_gateway_approval_runtime_drain_after$
|
|
166
|
+
DECLARE
|
|
167
|
+
configured_roles jsonb := current_setting(
|
|
168
|
+
'opengeni.migration_application_roles', false
|
|
169
|
+
)::jsonb;
|
|
170
|
+
BEGIN
|
|
171
|
+
IF EXISTS (
|
|
172
|
+
SELECT 1 FROM pg_stat_activity activity
|
|
173
|
+
JOIN jsonb_array_elements_text(configured_roles) roles(role_name)
|
|
174
|
+
ON roles.role_name = activity.usename
|
|
175
|
+
WHERE activity.datname = current_database()
|
|
176
|
+
AND activity.pid <> pg_backend_pid()
|
|
177
|
+
)
|
|
178
|
+
THEN
|
|
179
|
+
RAISE EXCEPTION
|
|
180
|
+
'0404 tool gateway approval activation observed a configured OpenGeni application database session after schema installation'
|
|
181
|
+
USING ERRCODE = '55000';
|
|
182
|
+
END IF;
|
|
183
|
+
END
|
|
184
|
+
$tool_gateway_approval_runtime_drain_after$;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Retain editable source and exact requested-tool identities beside each
|
|
3
|
+
-- immutable Workspace HTML artifact version. Existing versions remain valid:
|
|
4
|
+
-- their source columns stay NULL and readers derive a one-file index.html
|
|
5
|
+
-- source bundle from the already-verified published HTML.
|
|
6
|
+
|
|
7
|
+
SET LOCAL lock_timeout = '5s';
|
|
8
|
+
SET LOCAL statement_timeout = '5min';
|
|
9
|
+
|
|
10
|
+
ALTER TABLE "workspace_artifact_versions"
|
|
11
|
+
ADD COLUMN "source_key" text,
|
|
12
|
+
ADD COLUMN "source_sha256" text,
|
|
13
|
+
ADD COLUMN "source_size_bytes" integer,
|
|
14
|
+
ADD COLUMN "requested_tools" jsonb NOT NULL DEFAULT '[]'::jsonb;
|
|
15
|
+
|
|
16
|
+
ALTER TABLE "workspace_artifact_versions"
|
|
17
|
+
DROP CONSTRAINT "workspace_artifact_versions_content_chk";
|
|
18
|
+
|
|
19
|
+
ALTER TABLE "workspace_artifact_versions"
|
|
20
|
+
ADD CONSTRAINT "workspace_artifact_versions_content_chk" CHECK (
|
|
21
|
+
"content_type" = 'text/html'
|
|
22
|
+
AND "content_sha256" ~ '^[0-9a-f]{64}$'
|
|
23
|
+
AND "size_bytes" BETWEEN 1 AND 4194304
|
|
24
|
+
AND length("content_key") BETWEEN 1 AND 1024
|
|
25
|
+
AND (
|
|
26
|
+
(
|
|
27
|
+
"source_key" IS NULL
|
|
28
|
+
AND "source_sha256" IS NULL
|
|
29
|
+
AND "source_size_bytes" IS NULL
|
|
30
|
+
) OR (
|
|
31
|
+
"source_key" IS NOT NULL
|
|
32
|
+
AND "source_sha256" IS NOT NULL
|
|
33
|
+
AND "source_size_bytes" IS NOT NULL
|
|
34
|
+
AND
|
|
35
|
+
length("source_key") BETWEEN 1 AND 1024
|
|
36
|
+
AND "source_sha256" ~ '^[0-9a-f]{64}$'
|
|
37
|
+
AND "source_size_bytes" BETWEEN 1 AND 4194304
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
AND jsonb_typeof("requested_tools") = 'array'
|
|
41
|
+
AND jsonb_array_length("requested_tools") <= 128
|
|
42
|
+
) NOT VALID;
|
|
43
|
+
|
|
44
|
+
ALTER TABLE "workspace_artifact_versions"
|
|
45
|
+
VALIDATE CONSTRAINT "workspace_artifact_versions_content_chk";
|
|
46
|
+
|
|
47
|
+
ALTER TABLE "workspace_artifact_events"
|
|
48
|
+
ADD COLUMN "request_digest" text;
|
|
49
|
+
|
|
50
|
+
ALTER TABLE "workspace_artifact_events"
|
|
51
|
+
ADD CONSTRAINT "workspace_artifact_events_request_digest_chk"
|
|
52
|
+
CHECK (
|
|
53
|
+
"request_digest" IS NULL
|
|
54
|
+
OR "request_digest" ~ '^[0-9a-f]{64}$'
|
|
55
|
+
) NOT VALID;
|
|
56
|
+
|
|
57
|
+
ALTER TABLE "workspace_artifact_events"
|
|
58
|
+
VALIDATE CONSTRAINT "workspace_artifact_events_request_digest_chk";
|
|
59
|
+
|
|
60
|
+
ALTER TABLE "workspace_artifact_events"
|
|
61
|
+
DROP CONSTRAINT "workspace_artifact_events_type_chk";
|
|
62
|
+
|
|
63
|
+
ALTER TABLE "workspace_artifact_events"
|
|
64
|
+
ADD CONSTRAINT "workspace_artifact_events_type_chk"
|
|
65
|
+
CHECK ("type" IN ('published', 'rolled_back', 'archived', 'restored')) NOT VALID;
|
|
66
|
+
|
|
67
|
+
ALTER TABLE "workspace_artifact_events"
|
|
68
|
+
VALIDATE CONSTRAINT "workspace_artifact_events_type_chk";
|
|
69
|
+
|
|
70
|
+
CREATE INDEX "workspace_artifacts_status_list_idx"
|
|
71
|
+
ON "workspace_artifacts" ("workspace_id", "status", "updated_at");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/db",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0-canary.0",
|
|
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": {
|
|
@@ -78,11 +78,11 @@
|
|
|
78
78
|
"prepublishOnly": "bash ../../scripts/prepublish-guard"
|
|
79
79
|
},
|
|
80
80
|
"dependencies": {
|
|
81
|
-
"@opengeni/codemode": "^0.4.
|
|
82
|
-
"@opengeni/codex": "^0.2.
|
|
83
|
-
"@opengeni/config": "^0.
|
|
84
|
-
"@opengeni/contracts": "^2.
|
|
85
|
-
"@opengeni/network": "^0.
|
|
81
|
+
"@opengeni/codemode": "^0.4.27-canary.0",
|
|
82
|
+
"@opengeni/codex": "^0.2.21-canary.0",
|
|
83
|
+
"@opengeni/config": "^1.0.0-canary.0",
|
|
84
|
+
"@opengeni/contracts": "^2.13.0-canary.0",
|
|
85
|
+
"@opengeni/network": "^0.3.0-canary.0",
|
|
86
86
|
"drizzle-orm": "^0.45.2",
|
|
87
87
|
"postgres": "^3.4.7"
|
|
88
88
|
},
|
package/src/automations.ts
CHANGED
|
@@ -223,6 +223,8 @@ export async function createAutomationTrigger(
|
|
|
223
223
|
createdBySubjectId: string;
|
|
224
224
|
request: CreateAutomationTriggerRequest;
|
|
225
225
|
adapterId: string;
|
|
226
|
+
/** Trusted database-only admission seam. Throwing rolls creation back. */
|
|
227
|
+
beforeCreateCommit?: (tx: Database) => Promise<void>;
|
|
226
228
|
},
|
|
227
229
|
): Promise<AutomationTrigger> {
|
|
228
230
|
return await withWorkspaceRls(
|
|
@@ -289,6 +291,7 @@ export async function createAutomationTrigger(
|
|
|
289
291
|
throw new Error("Automation Pack trigger must use its frozen manifest template");
|
|
290
292
|
}
|
|
291
293
|
}
|
|
294
|
+
await input.beforeCreateCommit?.(tx);
|
|
292
295
|
const [head] = await tx
|
|
293
296
|
.insert(schema.automationTriggers)
|
|
294
297
|
.values({
|
|
@@ -438,6 +441,8 @@ export async function updateAutomationTrigger(
|
|
|
438
441
|
triggerId: string;
|
|
439
442
|
subjectId: string;
|
|
440
443
|
request: UpdateAutomationTriggerRequest;
|
|
444
|
+
/** Trusted database-only admission seam. Throwing rolls the revision back. */
|
|
445
|
+
beforeUpdateCommit?: (tx: Database) => Promise<void>;
|
|
441
446
|
},
|
|
442
447
|
): Promise<AutomationTrigger | null> {
|
|
443
448
|
return await withWorkspaceRls(
|
|
@@ -478,6 +483,7 @@ export async function updateAutomationTrigger(
|
|
|
478
483
|
"Pack-owned automation triggers must be managed through their Pack setup API",
|
|
479
484
|
);
|
|
480
485
|
}
|
|
486
|
+
await input.beforeUpdateCommit?.(tx);
|
|
481
487
|
const revisionNumber = existing.head.currentRevision + 1;
|
|
482
488
|
const [head] = await tx
|
|
483
489
|
.update(schema.automationTriggers)
|
|
@@ -696,7 +702,9 @@ export async function claimAutomationRun(
|
|
|
696
702
|
.for("update")
|
|
697
703
|
.limit(1);
|
|
698
704
|
if (!row) return null;
|
|
699
|
-
if (row.status === "dispatched" || row.status === "skipped"
|
|
705
|
+
if (row.status === "dispatched" || row.status === "skipped" || row.status === "failed") {
|
|
706
|
+
return mapRunExecution(row);
|
|
707
|
+
}
|
|
700
708
|
const [updated] = await tx
|
|
701
709
|
.update(schema.automationRuns)
|
|
702
710
|
.set({
|
|
@@ -176,6 +176,8 @@ export type ApiIntegrationRuntime = {
|
|
|
176
176
|
providerDomain: string;
|
|
177
177
|
authScheme: Record<string, unknown>;
|
|
178
178
|
connectionRef: McpServerConnectionRef | null;
|
|
179
|
+
/** Live credential authority revision used to invalidate stale human approvals. */
|
|
180
|
+
connectionAuthorityGeneration: number | null;
|
|
179
181
|
allowedTools: string[];
|
|
180
182
|
requireApproval: true | string[];
|
|
181
183
|
revision: StoredApiIntegrationRevision;
|
|
@@ -843,6 +845,7 @@ export async function listInstalledApiIntegrationsInRlsContext(
|
|
|
843
845
|
runtimeKey: schema.integrationFacetBindings.runtimeKey,
|
|
844
846
|
bindingConfig: schema.integrationFacetBindings.config,
|
|
845
847
|
connectionId: schema.integrationFacetBindings.connectionId,
|
|
848
|
+
connectionAuthorityGeneration: schema.connections.authorityGeneration,
|
|
846
849
|
revision: schema.integrationSpecRevisions.spec,
|
|
847
850
|
})
|
|
848
851
|
.from(schema.integrationFacetBindings)
|
|
@@ -890,6 +893,10 @@ export async function listInstalledApiIntegrationsInRlsContext(
|
|
|
890
893
|
eq(schema.integrationSpecRevisions.status, "active"),
|
|
891
894
|
),
|
|
892
895
|
)
|
|
896
|
+
.leftJoin(
|
|
897
|
+
schema.connections,
|
|
898
|
+
eq(schema.connections.id, schema.integrationFacetBindings.connectionId),
|
|
899
|
+
)
|
|
893
900
|
.where(
|
|
894
901
|
and(
|
|
895
902
|
eq(schema.capabilityPluginInstallations.workspaceId, workspaceId),
|
|
@@ -998,6 +1005,7 @@ export async function listInstalledApiIntegrationsInRlsContext(
|
|
|
998
1005
|
providerDomain: row.providerDomain,
|
|
999
1006
|
authScheme: objectValue(row.authScheme),
|
|
1000
1007
|
connectionRef,
|
|
1008
|
+
connectionAuthorityGeneration: row.connectionAuthorityGeneration,
|
|
1001
1009
|
allowedTools,
|
|
1002
1010
|
requireApproval,
|
|
1003
1011
|
revision,
|
|
@@ -92,6 +92,14 @@ export async function submitCodemodeOperation(
|
|
|
92
92
|
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
93
93
|
async (scopedDb) =>
|
|
94
94
|
await scopedDb.transaction(async (tx) => {
|
|
95
|
+
// Serialize the caller-owned id before testing for an existing row.
|
|
96
|
+
// Without this fence, two first submissions can both observe absence
|
|
97
|
+
// and the later insert fails its unique constraint instead of replaying.
|
|
98
|
+
await tx.execute(sql`
|
|
99
|
+
select pg_advisory_xact_lock(
|
|
100
|
+
hashtextextended(${`codemode-operation:${input.workspaceId}:${call.operationId}`}, 0)
|
|
101
|
+
)
|
|
102
|
+
`);
|
|
95
103
|
const [existing] = await tx
|
|
96
104
|
.select()
|
|
97
105
|
.from(schema.sessionAttemptCodemodeCalls)
|
|
@@ -213,7 +221,13 @@ export async function getCodemodeOperation(
|
|
|
213
221
|
}
|
|
214
222
|
|
|
215
223
|
export type ClaimCodemodeOperationResult =
|
|
216
|
-
| {
|
|
224
|
+
| {
|
|
225
|
+
status: "claimed";
|
|
226
|
+
operation: CodemodeOperationValue;
|
|
227
|
+
claimId: string;
|
|
228
|
+
reclaimed: boolean;
|
|
229
|
+
}
|
|
230
|
+
| { status: "execution_owner_lost"; operation: CodemodeOperationValue; claimId: string }
|
|
217
231
|
| { status: "already_running" | "terminal"; operation: CodemodeOperationValue }
|
|
218
232
|
| { status: "rejected"; operation: CodemodeOperationValue | null };
|
|
219
233
|
|
|
@@ -250,30 +264,20 @@ export async function claimCodemodeOperation(
|
|
|
250
264
|
}
|
|
251
265
|
const now = input.now ?? new Date();
|
|
252
266
|
const claimExpiresAt = new Date(now.getTime() + boundedClaimLeaseMs(input.claimLeaseMs));
|
|
267
|
+
let reclaimed = false;
|
|
253
268
|
if (row.state === "running") {
|
|
254
269
|
if (!row.claimExpiresAt || row.claimExpiresAt.getTime() > now.getTime()) {
|
|
255
270
|
return { status: "already_running", operation: mapOperation(row) };
|
|
256
271
|
}
|
|
257
272
|
if (row.executionStartedAt) {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
updatedAt: now,
|
|
267
|
-
})
|
|
268
|
-
.where(
|
|
269
|
-
and(
|
|
270
|
-
eq(schema.sessionAttemptCodemodeCalls.operationId, input.operationId),
|
|
271
|
-
eq(schema.sessionAttemptCodemodeCalls.state, "running"),
|
|
272
|
-
eq(schema.sessionAttemptCodemodeCalls.claimId, row.claimId!),
|
|
273
|
-
),
|
|
274
|
-
)
|
|
275
|
-
.returning();
|
|
276
|
-
return { status: "terminal", operation: mapOperation(unknown ?? row) };
|
|
273
|
+
if (!row.claimId) {
|
|
274
|
+
throw new Error("Codemode running operation is missing its claim id");
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
status: "execution_owner_lost",
|
|
278
|
+
operation: mapOperation(row),
|
|
279
|
+
claimId: row.claimId,
|
|
280
|
+
};
|
|
277
281
|
}
|
|
278
282
|
const [requeued] = await tx
|
|
279
283
|
.update(schema.sessionAttemptCodemodeCalls)
|
|
@@ -294,6 +298,7 @@ export async function claimCodemodeOperation(
|
|
|
294
298
|
.returning();
|
|
295
299
|
if (!requeued) throw new Error("Codemode expired claim recovery lost its row lock");
|
|
296
300
|
row = requeued;
|
|
301
|
+
reclaimed = true;
|
|
297
302
|
}
|
|
298
303
|
if (isTerminalState(row.state)) return { status: "terminal", operation: mapOperation(row) };
|
|
299
304
|
|
|
@@ -353,7 +358,12 @@ export async function claimCodemodeOperation(
|
|
|
353
358
|
)
|
|
354
359
|
.returning();
|
|
355
360
|
if (!claimed) throw new Error("Codemode operation claim lost its row lock");
|
|
356
|
-
return {
|
|
361
|
+
return {
|
|
362
|
+
status: "claimed",
|
|
363
|
+
operation: mapOperation(claimed),
|
|
364
|
+
claimId: input.claimId,
|
|
365
|
+
reclaimed,
|
|
366
|
+
};
|
|
357
367
|
}),
|
|
358
368
|
);
|
|
359
369
|
}
|
|
@@ -101,7 +101,12 @@ type CodexCredentialStatusTarget = {
|
|
|
101
101
|
// onto the OLD in-flight refresh and writing stale rotated tokens over the freshly
|
|
102
102
|
// connected credential. Concurrent calls for the SAME credential still coalesce,
|
|
103
103
|
// so the one-time refresh token is never double-spent.
|
|
104
|
-
|
|
104
|
+
export type CodexCredentialTokenSnapshot = CodexTokenSnapshot & {
|
|
105
|
+
/** Exact credential-row version whose bearer is about to reach the provider. */
|
|
106
|
+
credentialVersion: number;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const inflight = new Map<string, Promise<CodexCredentialTokenSnapshot>>();
|
|
105
110
|
const CODEX_TOKEN_REFRESH_TIMEOUT_MS = 6_000;
|
|
106
111
|
|
|
107
112
|
export type CodexTokenDeadlineClock = {
|
|
@@ -235,17 +240,21 @@ export function buildCodexTokenResolver(
|
|
|
235
240
|
// clobber the newly-active account. The single-flight map needs zero change.
|
|
236
241
|
credentialId: string,
|
|
237
242
|
deps: CodexAuthDeps,
|
|
238
|
-
): {
|
|
239
|
-
|
|
243
|
+
): {
|
|
244
|
+
getToken: () => Promise<CodexCredentialTokenSnapshot>;
|
|
245
|
+
refresh: () => Promise<CodexCredentialTokenSnapshot>;
|
|
246
|
+
} {
|
|
247
|
+
const snapshot = (cred: CodexCredentialForRun): CodexCredentialTokenSnapshot => ({
|
|
240
248
|
accessToken: cred.tokens.accessToken,
|
|
241
249
|
chatgptAccountId: cred.chatgptAccountId,
|
|
242
250
|
isFedramp: cred.isFedramp,
|
|
251
|
+
credentialVersion: cred.version,
|
|
243
252
|
});
|
|
244
253
|
|
|
245
254
|
const performRefresh = async (
|
|
246
255
|
refreshDb: Database,
|
|
247
256
|
cred: CodexCredentialForRun,
|
|
248
|
-
): Promise<
|
|
257
|
+
): Promise<CodexCredentialTokenSnapshot> => {
|
|
249
258
|
try {
|
|
250
259
|
// Bound even injected/custom refresh implementations that ignore abort
|
|
251
260
|
// signals. The provider client has its own AbortController timeout; this
|
|
@@ -287,6 +296,7 @@ export function buildCodexTokenResolver(
|
|
|
287
296
|
accessToken: tokens.access_token,
|
|
288
297
|
chatgptAccountId: cred.chatgptAccountId,
|
|
289
298
|
isFedramp: cred.isFedramp,
|
|
299
|
+
credentialVersion: cred.version + 1,
|
|
290
300
|
};
|
|
291
301
|
} catch (error) {
|
|
292
302
|
if (error instanceof CodexReloginRequired) {
|
|
@@ -305,7 +315,7 @@ export function buildCodexTokenResolver(
|
|
|
305
315
|
|
|
306
316
|
// ALL refreshes — whether proactive or a 401 retry — coalesce locally and then
|
|
307
317
|
// serialize globally before any rotating refresh token reaches the provider.
|
|
308
|
-
const doRefresh = (cred: CodexCredentialForRun): Promise<
|
|
318
|
+
const doRefresh = (cred: CodexCredentialForRun): Promise<CodexCredentialTokenSnapshot> => {
|
|
309
319
|
const key = `${cred.id}:${cred.version}`;
|
|
310
320
|
const existing = inflight.get(key);
|
|
311
321
|
if (existing) {
|
|
@@ -346,7 +356,7 @@ export function buildCodexTokenResolver(
|
|
|
346
356
|
return promise;
|
|
347
357
|
};
|
|
348
358
|
|
|
349
|
-
const resolve = async (force: boolean): Promise<
|
|
359
|
+
const resolve = async (force: boolean): Promise<CodexCredentialTokenSnapshot> => {
|
|
350
360
|
const cred = await deps.loadCredential(db, settings, workspaceId, credentialId);
|
|
351
361
|
if (!cred) {
|
|
352
362
|
throw new CodexReloginRequired("No Codex subscription is connected for this workspace.");
|
|
@@ -114,6 +114,8 @@ export type ResolveConnectionCredentialResult =
|
|
|
114
114
|
| {
|
|
115
115
|
status: "ok";
|
|
116
116
|
headers: Record<string, string>;
|
|
117
|
+
/** Credential authority is owned by the embedding host, not the native connection store. */
|
|
118
|
+
authoritySource?: "host";
|
|
117
119
|
/** Present when the credential bundle or embedding host supplied explicit placements. */
|
|
118
120
|
placements?: ConnectionCredentialPlacement[];
|
|
119
121
|
connectionId: string;
|
|
@@ -133,6 +135,8 @@ export type ResolveConnectionCredentialResult =
|
|
|
133
135
|
status: "auth_needed";
|
|
134
136
|
reason: McpCredentialAuthNeededReason;
|
|
135
137
|
providerDomain: string;
|
|
138
|
+
/** Credential authority is owned by the embedding host, not the native connection store. */
|
|
139
|
+
authoritySource?: "host";
|
|
136
140
|
provider?: string;
|
|
137
141
|
connectionId?: string;
|
|
138
142
|
scopes?: string[];
|
|
@@ -158,6 +162,10 @@ export type ResolveConnectionCredentialInput = {
|
|
|
158
162
|
/** Defaults to header-only MCP transport. */
|
|
159
163
|
credentialTarget?: "mcp" | "http_api";
|
|
160
164
|
forceRefresh?: boolean;
|
|
165
|
+
/** Internal lookup mode. Preflight must not refresh credentials or record provider usage. */
|
|
166
|
+
credentialResolutionMode?: "execution" | "preflight";
|
|
167
|
+
/** Frozen provider authority generation captured by the calling integration/catalog. */
|
|
168
|
+
expectedAuthorityGeneration?: number;
|
|
161
169
|
/** Exact immutable accepted-work authority; never credential-bearing. */
|
|
162
170
|
connectionUseAuthority?: unknown;
|
|
163
171
|
/** Exact accepted attempt plus one stable physical-provider request id. */
|
|
@@ -270,6 +278,9 @@ export function buildHostConnectionTokenResolver(
|
|
|
270
278
|
serverId: input.serverId,
|
|
271
279
|
connectionRef: {
|
|
272
280
|
providerDomain: input.connectionRef.providerDomain,
|
|
281
|
+
...(input.connectionRef.authoritySource
|
|
282
|
+
? { authoritySource: input.connectionRef.authoritySource }
|
|
283
|
+
: {}),
|
|
273
284
|
...(input.connectionRef.provider ? { provider: input.connectionRef.provider } : {}),
|
|
274
285
|
...(input.connectionRef.connectionId
|
|
275
286
|
? { connectionId: input.connectionRef.connectionId }
|
|
@@ -303,6 +314,7 @@ export function buildHostConnectionTokenResolver(
|
|
|
303
314
|
status: "auth_needed",
|
|
304
315
|
reason: result.reason,
|
|
305
316
|
providerDomain: result.providerDomain,
|
|
317
|
+
authoritySource: "host",
|
|
306
318
|
...(result.provider ? { provider: result.provider } : {}),
|
|
307
319
|
...(result.connectionId ? { connectionId: result.connectionId } : {}),
|
|
308
320
|
...(result.scopes ? { scopes: [...result.scopes] } : {}),
|
|
@@ -339,6 +351,7 @@ export function buildHostConnectionTokenResolver(
|
|
|
339
351
|
return {
|
|
340
352
|
status: "ok",
|
|
341
353
|
headers,
|
|
354
|
+
authoritySource: "host",
|
|
342
355
|
...(explicitPlacements ? { placements: explicitPlacements } : {}),
|
|
343
356
|
connectionId: result.connectionId,
|
|
344
357
|
...(expiresAt !== undefined ? { expiresAt } : {}),
|
|
@@ -712,6 +725,7 @@ export function buildConnectionTokenResolver(
|
|
|
712
725
|
destinationUrl: string,
|
|
713
726
|
inputCredentialTarget: "mcp" | "http_api",
|
|
714
727
|
connectionUseAttribution?: ConnectionUseAttribution,
|
|
728
|
+
recordUsage = true,
|
|
715
729
|
): Promise<ResolveConnectionCredentialResult> => {
|
|
716
730
|
if (cred.status !== "active") {
|
|
717
731
|
return authNeededForStatus(cred, ref);
|
|
@@ -753,7 +767,9 @@ export function buildConnectionTokenResolver(
|
|
|
753
767
|
: {}),
|
|
754
768
|
};
|
|
755
769
|
}
|
|
756
|
-
|
|
770
|
+
if (recordUsage) {
|
|
771
|
+
await deps.recordUsed(db, cred.workspaceId, cred.id, cred.subjectId);
|
|
772
|
+
}
|
|
757
773
|
return {
|
|
758
774
|
status: "ok",
|
|
759
775
|
headers: material.headers,
|
|
@@ -830,11 +846,15 @@ export function buildConnectionTokenResolver(
|
|
|
830
846
|
};
|
|
831
847
|
|
|
832
848
|
return async (input) => {
|
|
849
|
+
const credentialResolutionMode = input.credentialResolutionMode ?? "execution";
|
|
833
850
|
let ref = input.connectionRef;
|
|
834
851
|
let subjectId = input.subjectId;
|
|
835
852
|
let credentialWorkspaceId = input.workspaceId;
|
|
836
|
-
let expectedAuthorityGeneration
|
|
853
|
+
let expectedAuthorityGeneration = input.expectedAuthorityGeneration;
|
|
837
854
|
let connectionUseAttribution: ConnectionUseAttribution | undefined;
|
|
855
|
+
if (ref.authoritySource === "host") {
|
|
856
|
+
return authNeeded(ref, "unsupported_auth", ref.connectionId);
|
|
857
|
+
}
|
|
838
858
|
// Repository-scoped provider bindings require a broker that can prove the
|
|
839
859
|
// selected-resource boundary. The generic standalone credential store has
|
|
840
860
|
// no provider-specific containment adapter, so it must fail closed instead
|
|
@@ -870,6 +890,16 @@ export function buildConnectionTokenResolver(
|
|
|
870
890
|
ref.connectionId,
|
|
871
891
|
);
|
|
872
892
|
}
|
|
893
|
+
if (
|
|
894
|
+
expectedAuthorityGeneration !== undefined &&
|
|
895
|
+
authorization.attribution.connectionGeneration !== expectedAuthorityGeneration
|
|
896
|
+
) {
|
|
897
|
+
return authNeeded(
|
|
898
|
+
ref,
|
|
899
|
+
authorityReasonForScope(ref.subjectScope === "subject"),
|
|
900
|
+
ref.connectionId,
|
|
901
|
+
);
|
|
902
|
+
}
|
|
873
903
|
connectionUseAttribution = authorization.attribution;
|
|
874
904
|
expectedAuthorityGeneration = authorization.attribution.connectionGeneration;
|
|
875
905
|
const expectedPersonal = authorization.attribution.scope !== "workspace";
|
|
@@ -906,6 +936,12 @@ export function buildConnectionTokenResolver(
|
|
|
906
936
|
authority.connectionId,
|
|
907
937
|
);
|
|
908
938
|
}
|
|
939
|
+
if (
|
|
940
|
+
expectedAuthorityGeneration !== undefined &&
|
|
941
|
+
authority.connectionGeneration !== expectedAuthorityGeneration
|
|
942
|
+
) {
|
|
943
|
+
return authNeeded(ref, authorityReasonForScope(expectedPersonal), authority.connectionId);
|
|
944
|
+
}
|
|
909
945
|
connectionUseAttribution = authorization.attribution;
|
|
910
946
|
expectedAuthorityGeneration = authority.connectionGeneration;
|
|
911
947
|
// Personal resources are organization-user owned and may originate in a
|
|
@@ -950,7 +986,19 @@ export function buildConnectionTokenResolver(
|
|
|
950
986
|
if (!connectionBindingMatches(cred, ref, input.destinationUrl)) {
|
|
951
987
|
return authNeeded(ref, "missing_connection", cred.id);
|
|
952
988
|
}
|
|
953
|
-
|
|
989
|
+
const now = deps.now();
|
|
990
|
+
if (
|
|
991
|
+
credentialResolutionMode === "preflight" &&
|
|
992
|
+
cred.kind === "oauth2" &&
|
|
993
|
+
(input.forceRefresh === true ||
|
|
994
|
+
(cred.expiresAt !== null && cred.expiresAt.getTime() <= now.getTime()))
|
|
995
|
+
) {
|
|
996
|
+
return authNeeded(ref, "refresh_failed", cred.id);
|
|
997
|
+
}
|
|
998
|
+
if (
|
|
999
|
+
credentialResolutionMode === "execution" &&
|
|
1000
|
+
shouldRefresh(cred, input.forceRefresh === true, now)
|
|
1001
|
+
) {
|
|
954
1002
|
try {
|
|
955
1003
|
cred = await refreshSingleFlight(cred, ref);
|
|
956
1004
|
} catch (error) {
|
|
@@ -998,6 +1046,7 @@ export function buildConnectionTokenResolver(
|
|
|
998
1046
|
input.destinationUrl,
|
|
999
1047
|
input.credentialTarget ?? "mcp",
|
|
1000
1048
|
connectionUseAttribution,
|
|
1049
|
+
credentialResolutionMode === "execution",
|
|
1001
1050
|
);
|
|
1002
1051
|
};
|
|
1003
1052
|
}
|