@opengeni/db 1.0.2 → 1.1.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/artifact-catalog.d.ts +12 -0
- package/dist/canonical-human-identities.js +3 -3
- package/dist/capability-reconciliation.js +2 -2
- package/dist/{chunk-KEDWSSNP.js → chunk-7Z7ALFRE.js} +2 -2
- package/dist/{chunk-OQZOGCLG.js → chunk-BLUQG54M.js} +2 -2
- package/dist/{chunk-Y77PVZZD.js → chunk-BZV5ZDRY.js} +2 -2
- package/dist/{chunk-MACELCXG.js → chunk-CIGHXFPD.js} +3 -3
- package/dist/{chunk-7S53WLPI.js → chunk-EKXCF3FH.js} +4 -4
- package/dist/{chunk-F3YMVRF4.js → chunk-GLA5YCQS.js} +4 -4
- package/dist/{chunk-KH3TYDGY.js → chunk-J4WFOVBV.js} +2 -2
- package/dist/{chunk-NLSG7GGA.js → chunk-KJVOBLFC.js} +3 -3
- package/dist/{chunk-7POTLB24.js → chunk-M2YGXCCQ.js} +2 -2
- package/dist/{chunk-PQGTWFRF.js → chunk-M3SWH4MU.js} +2 -2
- package/dist/{chunk-OMHBVHX7.js → chunk-ODKYBA3C.js} +5 -5
- package/dist/{chunk-56MYWKQV.js → chunk-SKYRM7R7.js} +2 -2
- package/dist/{chunk-N5QCKHYL.js → chunk-T5NHDEZW.js} +62 -19
- package/dist/chunk-T5NHDEZW.js.map +1 -0
- package/dist/{chunk-LDTQGYD2.js → chunk-VOB6TXSJ.js} +16 -1
- package/dist/chunk-VOB6TXSJ.js.map +1 -0
- package/dist/{chunk-UEQQ2WWU.js → chunk-Z5HJXH3X.js} +3 -26
- package/dist/chunk-Z5HJXH3X.js.map +1 -0
- package/dist/connect-attempts.js +3 -3
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/index.d.ts +14 -5
- package/dist/index.js +85 -30
- package/dist/index.js.map +1 -1
- package/dist/managed-auth-session-sets.js +3 -3
- package/dist/mcp-operations.js +2 -2
- package/dist/organization-integration-policy.js +3 -3
- package/dist/provision-roles.js +1 -1
- package/dist/retained-provider-commands.js +6 -6
- package/dist/runtime-posture.d.ts +3 -2
- package/dist/schema.d.ts +93 -0
- package/dist/schema.js +3 -1
- package/dist/session-background-commands.js +4 -4
- package/dist/session-command-output.js +5 -5
- package/dist/session-event-slices.js +3 -3
- package/dist/session-mcp-credential-rotation.js +5 -5
- package/dist/session-tenancy.d.ts +6 -1
- package/dist/session-tenancy.js +3 -5
- package/dist/video-generation.js +3 -3
- package/dist/workspace-tool-defaults.js +4 -4
- package/drizzle/0610_artifact_catalog_pins.sql +165 -0
- package/drizzle/0611_universal_session_tenancy_activation.sql +166 -0
- package/drizzle/0612_scheduled_model_unavailable_refusal.sql +21 -0
- package/package.json +7 -7
- package/src/artifact-catalog.ts +31 -5
- package/src/index.ts +94 -19
- package/src/provision-roles.ts +16 -0
- package/src/runtime-posture-cli.ts +0 -2
- package/src/runtime-posture.ts +64 -27
- package/src/schema.ts +17 -0
- package/src/session-tenancy.ts +9 -31
- package/dist/chunk-LDTQGYD2.js.map +0 -1
- package/dist/chunk-N5QCKHYL.js.map +0 -1
- package/dist/chunk-UEQQ2WWU.js.map +0 -1
- /package/dist/{chunk-KEDWSSNP.js.map → chunk-7Z7ALFRE.js.map} +0 -0
- /package/dist/{chunk-OQZOGCLG.js.map → chunk-BLUQG54M.js.map} +0 -0
- /package/dist/{chunk-Y77PVZZD.js.map → chunk-BZV5ZDRY.js.map} +0 -0
- /package/dist/{chunk-MACELCXG.js.map → chunk-CIGHXFPD.js.map} +0 -0
- /package/dist/{chunk-7S53WLPI.js.map → chunk-EKXCF3FH.js.map} +0 -0
- /package/dist/{chunk-F3YMVRF4.js.map → chunk-GLA5YCQS.js.map} +0 -0
- /package/dist/{chunk-KH3TYDGY.js.map → chunk-J4WFOVBV.js.map} +0 -0
- /package/dist/{chunk-NLSG7GGA.js.map → chunk-KJVOBLFC.js.map} +0 -0
- /package/dist/{chunk-7POTLB24.js.map → chunk-M2YGXCCQ.js.map} +0 -0
- /package/dist/{chunk-PQGTWFRF.js.map → chunk-M3SWH4MU.js.map} +0 -0
- /package/dist/{chunk-OMHBVHX7.js.map → chunk-ODKYBA3C.js.map} +0 -0
- /package/dist/{chunk-56MYWKQV.js.map → chunk-SKYRM7R7.js.map} +0 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Retire per-organization session-tenancy activation. Every organization is
|
|
3
|
+
-- activated: private ("Only me") sessions, visibility transitions, forks and
|
|
4
|
+
-- personal-resource grants no longer require a version-1
|
|
5
|
+
-- session_tenancy_activations receipt, and the owner/admin Only-me setting
|
|
6
|
+
-- (organization_private_session_settings) now defaults to ENABLED when an
|
|
7
|
+
-- organization has no row. Owners/admins may still disable it; an explicit
|
|
8
|
+
-- disabled row keeps gating new Only-me chats in shared workspaces.
|
|
9
|
+
--
|
|
10
|
+
-- No receipts are written and no data is backfilled. The receipt keeps exactly
|
|
11
|
+
-- one meaning: opengeni_private.session_tenancy_account_activated (0340) still
|
|
12
|
+
-- retires the legacy_user connection and unattributed-writer compatibility
|
|
13
|
+
-- lanes only for organizations that already hold one. Organizations without a
|
|
14
|
+
-- receipt keep those lanes exactly as before this migration, so no stored
|
|
15
|
+
-- authority becomes invisible and no ownership is inferred.
|
|
16
|
+
--
|
|
17
|
+
-- Rolling: this only widens product predicates and flips a missing-row default.
|
|
18
|
+
-- A pre-0611 binary keeps working against the rewritten routines: its own
|
|
19
|
+
-- receipt probe (session_tenancy_product_activated) now answers true, its
|
|
20
|
+
-- startup interlock probe (session_tenancy_any_product_activation) answers
|
|
21
|
+
-- false regardless of the retired environment switch, and every routine keeps
|
|
22
|
+
-- its signature, owner, ACL, SECURITY DEFINER and search_path. No table is
|
|
23
|
+
-- locked or rewritten.
|
|
24
|
+
SET LOCAL lock_timeout = '5s';
|
|
25
|
+
SET LOCAL statement_timeout = '5min';
|
|
26
|
+
|
|
27
|
+
-- Remove the receipt prerequisite from every runtime predicate and default the
|
|
28
|
+
-- Only-me setting to enabled. Each rewrite replaces one exact, reviewed
|
|
29
|
+
-- fragment of the live definition; a changed shape aborts instead of silently
|
|
30
|
+
-- leaving a receipt gate behind. Owner, grants, SECURITY DEFINER and
|
|
31
|
+
-- search_path are preserved by CREATE OR REPLACE of the catalog's own
|
|
32
|
+
-- definition.
|
|
33
|
+
DO $universal_session_tenancy_predicates$
|
|
34
|
+
DECLARE
|
|
35
|
+
data_schema text := pg_catalog.current_schema();
|
|
36
|
+
target regprocedure;
|
|
37
|
+
definition text;
|
|
38
|
+
rewrite record;
|
|
39
|
+
occurrences integer;
|
|
40
|
+
-- Fragments below are routine SOURCE text matched against
|
|
41
|
+
-- pg_get_functiondef, never queries: this block reads only the catalog.
|
|
42
|
+
-- The table names are spliced in so the source is not mistaken for a
|
|
43
|
+
-- migration-time row read of a FORCE-RLS table.
|
|
44
|
+
receipts constant text := 'session_tenancy_activations';
|
|
45
|
+
settings constant text := 'organization_private_session_settings';
|
|
46
|
+
receipt_gate constant text :=
|
|
47
|
+
E' IF NOT EXISTS (\n'
|
|
48
|
+
|| E' SELECT 1 FROM ' || receipts || E' activation\n'
|
|
49
|
+
|| E' WHERE activation.account_id = p_account_id AND activation.activation_version = 1\n'
|
|
50
|
+
|| E' ) THEN\n'
|
|
51
|
+
|| E' RAISE EXCEPTION ''session tenancy product is not activated'' USING ERRCODE = ''42501'';\n'
|
|
52
|
+
|| E' END IF;\n';
|
|
53
|
+
receipt_gate_removed constant text :=
|
|
54
|
+
E' -- 0611: every organization is session-tenancy activated.\n';
|
|
55
|
+
setting_receipt_gate constant text :=
|
|
56
|
+
E' IF p_enabled AND NOT session_tenancy_product_activated(p_account_id, 1) THEN\n'
|
|
57
|
+
|| E' RAISE EXCEPTION ''session tenancy product surface is not available for this organization'' USING ERRCODE = ''55000'';\n'
|
|
58
|
+
|| E' END IF;\n';
|
|
59
|
+
BEGIN
|
|
60
|
+
FOR rewrite IN
|
|
61
|
+
SELECT * FROM (VALUES
|
|
62
|
+
(1, pg_catalog.format('%I.session_tenancy_product_activated(uuid,integer)', data_schema),
|
|
63
|
+
E' AND EXISTS (\n'
|
|
64
|
+
|| E' SELECT 1 FROM ' || receipts || E' activation\n'
|
|
65
|
+
|| E' WHERE activation.account_id = p_account_id\n'
|
|
66
|
+
|| E' AND activation.activation_version = p_activation_version\n'
|
|
67
|
+
|| E' )',
|
|
68
|
+
E' AND p_account_id IS NOT NULL\n'
|
|
69
|
+
|| E' AND p_activation_version IS NOT DISTINCT FROM 1'),
|
|
70
|
+
-- The value-free startup interlock is retired with the deployment switch.
|
|
71
|
+
-- Answering false keeps every earlier binary startable regardless of the
|
|
72
|
+
-- removed OPENGENI_ORGANIZATION_TENANCY_CANONICAL_ACTIVATION_ENABLED value.
|
|
73
|
+
(3, pg_catalog.format('%I.session_tenancy_any_product_activation()', data_schema),
|
|
74
|
+
E' SELECT EXISTS (\n'
|
|
75
|
+
|| E' SELECT 1 FROM ' || receipts || E'\n'
|
|
76
|
+
|| E' ) INTO activated;',
|
|
77
|
+
E' activated := false;'),
|
|
78
|
+
(4, pg_catalog.format(
|
|
79
|
+
'%I.list_self_user_resource_authorities(uuid,uuid,text,uuid,integer)', data_schema),
|
|
80
|
+
receipt_gate, receipt_gate_removed),
|
|
81
|
+
(5, pg_catalog.format(
|
|
82
|
+
'%I.issue_self_user_resource_grant(uuid,uuid,uuid,text,text,text,uuid,integer,boolean)',
|
|
83
|
+
data_schema),
|
|
84
|
+
receipt_gate, receipt_gate_removed),
|
|
85
|
+
(6, pg_catalog.format('%I.revoke_self_user_resource_grant(uuid,uuid,uuid)', data_schema),
|
|
86
|
+
receipt_gate, receipt_gate_removed),
|
|
87
|
+
(7, pg_catalog.format(
|
|
88
|
+
'%I.accept_turn_personal_resource_attachment(uuid,uuid,uuid,uuid,text,integer,boolean,integer)',
|
|
89
|
+
data_schema),
|
|
90
|
+
receipt_gate, receipt_gate_removed),
|
|
91
|
+
-- Only-me chats default to enabled: only an explicit disabled row gates.
|
|
92
|
+
(8, pg_catalog.format('%I.organization_private_sessions_enabled(uuid)', data_schema),
|
|
93
|
+
E' AND session_tenancy_product_activated(p_account_id, 1)\n'
|
|
94
|
+
|| E' AND EXISTS (\n'
|
|
95
|
+
|| E' SELECT 1 FROM ' || settings || E' setting\n'
|
|
96
|
+
|| E' WHERE setting.account_id = p_account_id AND setting.enabled\n'
|
|
97
|
+
|| E' )',
|
|
98
|
+
E' AND NOT EXISTS (\n'
|
|
99
|
+
|| E' SELECT 1 FROM ' || settings || E' setting\n'
|
|
100
|
+
|| E' WHERE setting.account_id = p_account_id AND NOT setting.enabled\n'
|
|
101
|
+
|| E' )'),
|
|
102
|
+
(9, pg_catalog.format(
|
|
103
|
+
'%I.get_organization_private_session_settings(uuid,text)', data_schema),
|
|
104
|
+
E' ''enabled'', coalesce(setting.enabled, false),\n',
|
|
105
|
+
E' ''enabled'', coalesce(setting.enabled, true),\n'),
|
|
106
|
+
(10, pg_catalog.format(
|
|
107
|
+
'%I.update_organization_private_session_settings(uuid,text,boolean,bigint,uuid)',
|
|
108
|
+
data_schema),
|
|
109
|
+
setting_receipt_gate, ''),
|
|
110
|
+
(11, pg_catalog.format(
|
|
111
|
+
'%I.update_organization_private_session_settings(uuid,text,boolean,bigint,uuid)',
|
|
112
|
+
data_schema),
|
|
113
|
+
E' did_change := coalesce(setting.enabled, false) IS DISTINCT FROM p_enabled;\n',
|
|
114
|
+
E' did_change := coalesce(setting.enabled, true) IS DISTINCT FROM p_enabled;\n')
|
|
115
|
+
) AS rewrites(ordinal, signature, old_fragment, new_fragment)
|
|
116
|
+
ORDER BY ordinal
|
|
117
|
+
LOOP
|
|
118
|
+
target := pg_catalog.to_regprocedure(rewrite.signature);
|
|
119
|
+
IF target IS NULL THEN
|
|
120
|
+
RAISE EXCEPTION 'universal session tenancy predicate is missing: %', rewrite.signature
|
|
121
|
+
USING ERRCODE = '55000';
|
|
122
|
+
END IF;
|
|
123
|
+
definition := pg_catalog.pg_get_functiondef(target);
|
|
124
|
+
occurrences := (
|
|
125
|
+
pg_catalog.length(definition)
|
|
126
|
+
- pg_catalog.length(pg_catalog.replace(definition, rewrite.old_fragment, ''))
|
|
127
|
+
) / pg_catalog.length(rewrite.old_fragment);
|
|
128
|
+
IF occurrences <> 1 THEN
|
|
129
|
+
RAISE EXCEPTION 'universal session tenancy predicate shape changed: % (% matches)',
|
|
130
|
+
rewrite.signature, occurrences USING ERRCODE = '55000';
|
|
131
|
+
END IF;
|
|
132
|
+
EXECUTE pg_catalog.replace(definition, rewrite.old_fragment, rewrite.new_fragment);
|
|
133
|
+
END LOOP;
|
|
134
|
+
|
|
135
|
+
-- Fail closed if any other routine still consults the receipt table. Only
|
|
136
|
+
-- the retained, now-dormant receipt writers (operator, greenfield and
|
|
137
|
+
-- additional-organization activation, plus the operator preference
|
|
138
|
+
-- enabler's lock) and the private legacy-lane retirement predicate may.
|
|
139
|
+
-- That predicate is not a product prerequisite: it closes the legacy_user
|
|
140
|
+
-- connection and unattributed-writer compatibility lanes only for an
|
|
141
|
+
-- organization that already holds a receipt.
|
|
142
|
+
IF EXISTS (
|
|
143
|
+
SELECT 1 FROM pg_catalog.pg_proc routine
|
|
144
|
+
JOIN pg_catalog.pg_namespace namespace ON namespace.oid = routine.pronamespace
|
|
145
|
+
WHERE namespace.nspname IN (data_schema, 'opengeni_private')
|
|
146
|
+
AND routine.prosrc LIKE '%session_tenancy_activations%'
|
|
147
|
+
AND routine.proname NOT IN (
|
|
148
|
+
'activate_session_tenancy_product',
|
|
149
|
+
'activate_greenfield_session_tenancy_from_setup',
|
|
150
|
+
'activate_session_tenancy_from_additional_organization',
|
|
151
|
+
'enable_organization_private_sessions_from_activation',
|
|
152
|
+
'session_tenancy_account_activated'
|
|
153
|
+
)
|
|
154
|
+
) THEN
|
|
155
|
+
RAISE EXCEPTION 'a runtime routine still requires a session tenancy activation receipt'
|
|
156
|
+
USING ERRCODE = '55000';
|
|
157
|
+
END IF;
|
|
158
|
+
END
|
|
159
|
+
$universal_session_tenancy_predicates$;
|
|
160
|
+
|
|
161
|
+
COMMENT ON FUNCTION session_tenancy_product_activated(uuid, integer) IS
|
|
162
|
+
'Universal since 0611: true for the transaction''s own organization at activation version 1. No per-organization receipt is required.';
|
|
163
|
+
COMMENT ON FUNCTION opengeni_private.session_tenancy_account_activated(uuid) IS
|
|
164
|
+
'Legacy-lane retirement predicate (0340): true when the transaction''s own organization holds a receipt. Since 0611 it gates no product surface; it only closes the legacy_user connection and unattributed-writer lanes.';
|
|
165
|
+
COMMENT ON FUNCTION session_tenancy_any_product_activation() IS
|
|
166
|
+
'Retired by 0611 with OPENGENI_ORGANIZATION_TENANCY_CANONICAL_ACTIVATION_ENABLED. Always false so no binary enforces the removed startup interlock.';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- A scheduled occurrence whose model is retired or no longer in the catalog is
|
|
3
|
+
-- refused as a visible terminal run (`scheduled_model_unavailable`) instead of
|
|
4
|
+
-- exhausting activity retries and leaving no run. Preserve the immutable 0539
|
|
5
|
+
-- refusal lifecycle (as extended by 0552), adding only this reason. Exact
|
|
6
|
+
-- anchoring refuses an unexpected deployed guard definition. Old binaries never
|
|
7
|
+
-- write the new reason, so this is rolling-safe.
|
|
8
|
+
SET LOCAL lock_timeout = '5s';
|
|
9
|
+
SET LOCAL statement_timeout = '10min';
|
|
10
|
+
|
|
11
|
+
DO $scheduled_model_refusal$
|
|
12
|
+
DECLARE
|
|
13
|
+
definition text:=pg_get_functiondef('opengeni_private.guard_scheduled_admission_refusal()'::regprocedure);
|
|
14
|
+
anchor text:='''monthly_agent_run_limit'', ''allowance_exhausted'')';
|
|
15
|
+
BEGIN
|
|
16
|
+
IF strpos(definition,anchor)=0 OR strpos(substr(definition,strpos(definition,anchor)+length(anchor)),anchor)>0 THEN
|
|
17
|
+
RAISE EXCEPTION '0612 unexpected scheduled refusal guard definition' USING ERRCODE='55000';
|
|
18
|
+
END IF;
|
|
19
|
+
EXECUTE replace(definition,anchor,
|
|
20
|
+
'''monthly_agent_run_limit'', ''allowance_exhausted'', ''scheduled_model_unavailable'')');
|
|
21
|
+
END $scheduled_model_refusal$;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/db",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.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": {
|
|
@@ -115,12 +115,12 @@
|
|
|
115
115
|
"prepublishOnly": "bash ../../scripts/prepublish-guard"
|
|
116
116
|
},
|
|
117
117
|
"dependencies": {
|
|
118
|
-
"@opengeni/codemode": "1.0
|
|
119
|
-
"@opengeni/codex": "1.0
|
|
120
|
-
"@opengeni/config": "1.0
|
|
121
|
-
"@opengeni/contracts": "1.0
|
|
122
|
-
"@opengeni/network": "1.0
|
|
123
|
-
"@opengeni/observability": "1.0
|
|
118
|
+
"@opengeni/codemode": "1.1.0",
|
|
119
|
+
"@opengeni/codex": "1.1.0",
|
|
120
|
+
"@opengeni/config": "1.1.0",
|
|
121
|
+
"@opengeni/contracts": "1.1.0",
|
|
122
|
+
"@opengeni/network": "1.1.0",
|
|
123
|
+
"@opengeni/observability": "1.1.0",
|
|
124
124
|
"drizzle-orm": "^0.45.2",
|
|
125
125
|
"postgres": "^3.4.7"
|
|
126
126
|
},
|
package/src/artifact-catalog.ts
CHANGED
|
@@ -2,7 +2,13 @@ import type { ArtifactCatalogKind, ArtifactCatalogListQuery } from "@opengeni/co
|
|
|
2
2
|
import { sql, type SQL } from "drizzle-orm";
|
|
3
3
|
import { withRlsContext, type Database } from "./database";
|
|
4
4
|
|
|
5
|
-
export type ArtifactCatalogPosition = {
|
|
5
|
+
export type ArtifactCatalogPosition = {
|
|
6
|
+
key: string;
|
|
7
|
+
kind: ArtifactCatalogKind;
|
|
8
|
+
id: string;
|
|
9
|
+
/** Legacy cursor frontiers did not carry this bit. */
|
|
10
|
+
pinned?: boolean | undefined;
|
|
11
|
+
};
|
|
6
12
|
/** Internal candidates are NOT an authorized public projection. */
|
|
7
13
|
export type ArtifactCatalogCandidate = {
|
|
8
14
|
id: string;
|
|
@@ -16,8 +22,22 @@ export type ArtifactCatalogCandidate = {
|
|
|
16
22
|
source_session_id: string | null;
|
|
17
23
|
version_id: string | null;
|
|
18
24
|
sort_key: string;
|
|
25
|
+
pinned?: boolean;
|
|
19
26
|
};
|
|
20
27
|
|
|
28
|
+
/** API callers must first verify publish permission and domain-specific read authority. */
|
|
29
|
+
export async function updateArtifactPin(
|
|
30
|
+
db: Database,
|
|
31
|
+
scope: { accountId: string; workspaceId: string },
|
|
32
|
+
input: { kind: ArtifactCatalogKind; artifactId: string; pinned: boolean },
|
|
33
|
+
): Promise<void> {
|
|
34
|
+
await withRlsContext(db, scope, async (tx) => {
|
|
35
|
+
await tx.execute(sql`select opengeni_private.update_artifact_pin(
|
|
36
|
+
${scope.accountId}::uuid, ${scope.workspaceId}::uuid,
|
|
37
|
+
${input.kind}::text, ${input.artifactId}::text, ${input.pinned}::boolean)`);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
21
41
|
/** Called only after immutable bytes have been retained by explicit publication. */
|
|
22
42
|
export async function recordSandboxFilePublication(
|
|
23
43
|
db: Database,
|
|
@@ -84,7 +104,7 @@ export async function listArtifactCatalogCandidates(
|
|
|
84
104
|
branches.push(sql`
|
|
85
105
|
SELECT p.file_id::text AS id, p.kind, p.title, 'active'::text AS status,
|
|
86
106
|
p.published_at AS created_at, p.published_at AS updated_at, p.source_session_id, NULL::text AS version_id, 'sandbox_file'::text AS origin
|
|
87
|
-
FROM opengeni_private.
|
|
107
|
+
FROM opengeni_private.list_sandbox_file_publications_pinned(${scope.accountId}::uuid, ${scope.workspaceId}::uuid,
|
|
88
108
|
${JSON.stringify({ sourceSessionId: source, q: query.q, kinds: publishedKinds, sort: query.sort, snapshotAt: input.snapshotAt, after: input.after, limit: input.limit })}::jsonb) p`);
|
|
89
109
|
if (!branches.length) return [];
|
|
90
110
|
const sort =
|
|
@@ -95,22 +115,28 @@ export async function listArtifactCatalogCandidates(
|
|
|
95
115
|
: sql`to_char(updated_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US')`;
|
|
96
116
|
const descending = query.sort !== "title";
|
|
97
117
|
const position = input.after;
|
|
98
|
-
const
|
|
118
|
+
const secondaryKeyset = !position
|
|
99
119
|
? sql`true`
|
|
100
120
|
: sql`(
|
|
101
121
|
sort_key COLLATE "C" ${descending ? sql`<` : sql`>`} ${position.key} COLLATE "C"
|
|
102
122
|
OR (sort_key = ${position.key} AND (kind COLLATE "C", id COLLATE "C") > (${position.kind}, ${position.id})))`;
|
|
123
|
+
const keyset = !position
|
|
124
|
+
? sql`true`
|
|
125
|
+
: sql`(pinned < ${position.pinned ?? false}::boolean OR
|
|
126
|
+
(pinned = ${position.pinned ?? false}::boolean AND ${secondaryKeyset}))`;
|
|
103
127
|
return withRlsContext(
|
|
104
128
|
db,
|
|
105
129
|
scope,
|
|
106
130
|
async (tx) => {
|
|
107
131
|
const result = await tx.execute(sql`
|
|
108
132
|
WITH candidates AS (${sql.join(branches, sql` UNION ALL `)}),
|
|
109
|
-
ordered AS (SELECT
|
|
133
|
+
ordered AS (SELECT candidates.*, ${sort} AS sort_key, (pins.artifact_id IS NOT NULL) AS pinned
|
|
134
|
+
FROM candidates LEFT JOIN opengeni_private.list_artifact_pins(${scope.accountId}::uuid, ${scope.workspaceId}::uuid) pins
|
|
135
|
+
ON pins.kind = candidates.kind AND pins.artifact_id = candidates.id
|
|
110
136
|
WHERE status = ${query.status} AND created_at <= ${input.snapshotAt}::timestamptz
|
|
111
137
|
${query.q ? sql`AND strpos(lower(title), lower(${query.q})) > 0` : sql``})
|
|
112
138
|
SELECT * FROM ordered WHERE ${keyset}
|
|
113
|
-
ORDER BY sort_key COLLATE "C" ${descending ? sql`DESC` : sql`ASC`}, kind COLLATE "C", id COLLATE "C"
|
|
139
|
+
ORDER BY pinned DESC, sort_key COLLATE "C" ${descending ? sql`DESC` : sql`ASC`}, kind COLLATE "C", id COLLATE "C"
|
|
114
140
|
LIMIT ${input.limit}`);
|
|
115
141
|
return (
|
|
116
142
|
Array.isArray(result) ? result : (result as { rows: ArtifactCatalogCandidate[] }).rows
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
} from "./inbox-execution-context";
|
|
7
7
|
import { parentOutboxAuthorityTx } from "./child-outbox-authority";
|
|
8
8
|
export { SubscriptionAccountChangedError } from "./subscription-account-conflict";
|
|
9
|
+
import type { GoalAdmissionPausedReason } from "@opengeni/contracts";
|
|
9
10
|
import {
|
|
10
11
|
claudeSubscriptionAccountRepository,
|
|
11
12
|
claudeSubscriptionTables,
|
|
@@ -439,7 +440,6 @@ import {
|
|
|
439
440
|
closePrivateSessionCreateCapability,
|
|
440
441
|
openPrivateChildSessionCreateCapability,
|
|
441
442
|
openPrivateSessionCreateCapability,
|
|
442
|
-
sessionTenancyProductActivated,
|
|
443
443
|
} from "./session-tenancy";
|
|
444
444
|
import {
|
|
445
445
|
completeCodemodeOperationInTransaction,
|
|
@@ -18575,6 +18575,7 @@ export type ScheduledTaskAdmissionRefusalReason =
|
|
|
18575
18575
|
| "machine_enrollment_inactive"
|
|
18576
18576
|
| "variable_set_unavailable"
|
|
18577
18577
|
| "rig_version_unavailable"
|
|
18578
|
+
| "scheduled_model_unavailable"
|
|
18578
18579
|
| "insufficient_credits"
|
|
18579
18580
|
| "allowance_exhausted"
|
|
18580
18581
|
| "monthly_model_cost_limit"
|
|
@@ -37685,11 +37686,6 @@ async function readSessionListForSubject(
|
|
|
37685
37686
|
throw new SessionListAccessError();
|
|
37686
37687
|
}
|
|
37687
37688
|
|
|
37688
|
-
const tenancyActivated = await sessionTenancyProductActivated(
|
|
37689
|
-
tx as unknown as Database,
|
|
37690
|
-
workspaceId,
|
|
37691
|
-
);
|
|
37692
|
-
|
|
37693
37689
|
const archiveMode = options.archiveStatus ?? (options.archivedOnly ? "archived" : "active");
|
|
37694
37690
|
const sortBy = options.sortBy ?? (archiveMode === "archived" ? "archivedAt" : "updatedAt");
|
|
37695
37691
|
if (options.archivedOnly && archiveMode !== "archived") {
|
|
@@ -38139,7 +38135,7 @@ async function readSessionListForSubject(
|
|
|
38139
38135
|
? { archivedAt: exactArchiveTimestamps.get(session.id)! }
|
|
38140
38136
|
: {}),
|
|
38141
38137
|
},
|
|
38142
|
-
{ subjectId: options.subjectId
|
|
38138
|
+
{ subjectId: options.subjectId },
|
|
38143
38139
|
),
|
|
38144
38140
|
...(sortBy === "updatedAt" && exactOrdinaryTimestamps.has(session.id)
|
|
38145
38141
|
? { updatedAt: exactOrdinaryTimestamps.get(session.id)! }
|
|
@@ -38395,7 +38391,6 @@ export async function getSessionForSubject(
|
|
|
38395
38391
|
const mcpServers = await sessionMcpServerMetadataForSessions(scopedDb, workspaceId, [
|
|
38396
38392
|
sessionId,
|
|
38397
38393
|
]);
|
|
38398
|
-
const tenancyActivated = await sessionTenancyProductActivated(scopedDb, workspaceId);
|
|
38399
38394
|
const failureDiagnostics = await sessionFailureDiagnostics(scopedDb, workspaceId, session);
|
|
38400
38395
|
return projectSessionForRelatedAccess(
|
|
38401
38396
|
{
|
|
@@ -38407,7 +38402,7 @@ export async function getSessionForSubject(
|
|
|
38407
38402
|
mapSessionAttention(session, row.pin),
|
|
38408
38403
|
mapSessionArchive(row.pin),
|
|
38409
38404
|
undefined,
|
|
38410
|
-
{ subjectId
|
|
38405
|
+
{ subjectId },
|
|
38411
38406
|
)),
|
|
38412
38407
|
failureDiagnostics,
|
|
38413
38408
|
},
|
|
@@ -44562,6 +44557,19 @@ export async function applyContextCompaction(
|
|
|
44562
44557
|
),
|
|
44563
44558
|
);
|
|
44564
44559
|
const supersededFrom = Math.floor(Number(maxPosition)) + 1;
|
|
44560
|
+
// Installing a successful summary is model progress, including a
|
|
44561
|
+
// maintenance turn that never enters the ordinary response stream.
|
|
44562
|
+
// Clear only provider recovery under the same locked attempt fence.
|
|
44563
|
+
if (providerRecoveryCountFromTurnMetadata(fence.turn!.metadata) > 0) {
|
|
44564
|
+
await tx
|
|
44565
|
+
.update(schema.sessionTurns)
|
|
44566
|
+
.set({
|
|
44567
|
+
metadata: metadataWithoutProviderRecoveryCount(fence.turn!.metadata),
|
|
44568
|
+
version: fence.turn!.version + 1,
|
|
44569
|
+
updatedAt: new Date(),
|
|
44570
|
+
})
|
|
44571
|
+
.where(eq(schema.sessionTurns.id, input.turnId));
|
|
44572
|
+
}
|
|
44565
44573
|
await tx
|
|
44566
44574
|
.update(schema.sessionHistoryItems)
|
|
44567
44575
|
.set({ active: false })
|
|
@@ -68341,6 +68349,16 @@ export async function updateSessionTitleWithEvent(
|
|
|
68341
68349
|
);
|
|
68342
68350
|
}
|
|
68343
68351
|
|
|
68352
|
+
/** Read-only admission at a locked paused-to-active transition. */
|
|
68353
|
+
export type SessionGoalResumeValidation = (
|
|
68354
|
+
tx: Database,
|
|
68355
|
+
session: Pick<
|
|
68356
|
+
Session,
|
|
68357
|
+
"accountId" | "workspaceId" | "model" | "codexCompactionMode" | "latencyMode"
|
|
68358
|
+
>,
|
|
68359
|
+
causalTurn: { id: string; initiatingHumanSubjectId: string | null } | null,
|
|
68360
|
+
) => Promise<void>;
|
|
68361
|
+
|
|
68344
68362
|
/**
|
|
68345
68363
|
* Status transition helper. Idempotent: requesting the current status returns
|
|
68346
68364
|
* `changed: false` so callers can skip emitting a duplicate event. `completed`
|
|
@@ -68360,6 +68378,8 @@ export async function setSessionGoalStatus(
|
|
|
68360
68378
|
reportDeliveries?: SessionGoalReportDelivery[];
|
|
68361
68379
|
reportArtifactActor?: ReportArtifactActor;
|
|
68362
68380
|
commandActor?: Extract<SessionCommandActor, { type: "agent_attempt" }>;
|
|
68381
|
+
/** Runs under the canonical control/session/goal locks, before a paused goal changes. */
|
|
68382
|
+
beforeResume?: SessionGoalResumeValidation;
|
|
68363
68383
|
},
|
|
68364
68384
|
): Promise<{
|
|
68365
68385
|
goal: SessionGoal;
|
|
@@ -68409,6 +68429,51 @@ export async function setSessionGoalStatus(
|
|
|
68409
68429
|
if (existing.status === "completed") {
|
|
68410
68430
|
throw new Error("session goal is completed; set a new goal to continue");
|
|
68411
68431
|
}
|
|
68432
|
+
if (input.status === "active" && input.beforeResume) {
|
|
68433
|
+
const [effectiveSession] = await withEffectiveSessionPolicy(scopedDb, workspaceId, [session]);
|
|
68434
|
+
// An accepted running turn owns the work the goal will continue after it
|
|
68435
|
+
// settles. An idle Resume uses the exact latest-finished causal row.
|
|
68436
|
+
const causalTurnFilter = session.activeTurnId
|
|
68437
|
+
? or(
|
|
68438
|
+
eq(schema.sessionTurns.id, session.activeTurnId),
|
|
68439
|
+
sql`${schema.sessionTurns.finishedAt} is not null`,
|
|
68440
|
+
)
|
|
68441
|
+
: sql`${schema.sessionTurns.finishedAt} is not null`;
|
|
68442
|
+
const [causalTurn] = await scopedDb
|
|
68443
|
+
.select({
|
|
68444
|
+
id: schema.sessionTurns.id,
|
|
68445
|
+
initiatingHumanSubjectId: schema.sessionTurns.initiatingHumanSubjectId,
|
|
68446
|
+
})
|
|
68447
|
+
.from(schema.sessionTurns)
|
|
68448
|
+
.where(
|
|
68449
|
+
and(
|
|
68450
|
+
eq(schema.sessionTurns.workspaceId, workspaceId),
|
|
68451
|
+
eq(schema.sessionTurns.sessionId, sessionId),
|
|
68452
|
+
causalTurnFilter,
|
|
68453
|
+
),
|
|
68454
|
+
)
|
|
68455
|
+
.orderBy(
|
|
68456
|
+
...(session.activeTurnId
|
|
68457
|
+
? [desc(sql`${schema.sessionTurns.id} = ${session.activeTurnId}`)]
|
|
68458
|
+
: []),
|
|
68459
|
+
desc(schema.sessionTurns.finishedAt),
|
|
68460
|
+
desc(schema.sessionTurns.position),
|
|
68461
|
+
desc(schema.sessionTurns.createdAt),
|
|
68462
|
+
desc(schema.sessionTurns.id),
|
|
68463
|
+
)
|
|
68464
|
+
.limit(1);
|
|
68465
|
+
await input.beforeResume(
|
|
68466
|
+
scopedDb,
|
|
68467
|
+
{
|
|
68468
|
+
accountId: session.accountId,
|
|
68469
|
+
workspaceId,
|
|
68470
|
+
model: effectiveSession!.model,
|
|
68471
|
+
latencyMode: effectiveSession!.latencyMode as Session["latencyMode"],
|
|
68472
|
+
codexCompactionMode: session.codexCompactionMode as Session["codexCompactionMode"],
|
|
68473
|
+
},
|
|
68474
|
+
causalTurn ?? null,
|
|
68475
|
+
);
|
|
68476
|
+
}
|
|
68412
68477
|
if (input.status === "completed") {
|
|
68413
68478
|
await verifyGoalReportDeliveries(scopedDb, {
|
|
68414
68479
|
accountId: session.accountId,
|
|
@@ -68502,6 +68567,7 @@ export async function setSessionGoalStatusWithEvent(
|
|
|
68502
68567
|
reportDeliveries?: SessionGoalReportDelivery[];
|
|
68503
68568
|
reportArtifactActor?: ReportArtifactActor;
|
|
68504
68569
|
commandActor?: Extract<SessionCommandActor, { type: "agent_attempt" }>;
|
|
68570
|
+
beforeResume?: SessionGoalResumeValidation;
|
|
68505
68571
|
event: SetSessionGoalStatusEvent;
|
|
68506
68572
|
},
|
|
68507
68573
|
): Promise<{
|
|
@@ -68535,6 +68601,7 @@ export async function setSessionGoalStatusWithEvent(
|
|
|
68535
68601
|
: {}),
|
|
68536
68602
|
...(input.reportArtifactActor ? { reportArtifactActor: input.reportArtifactActor } : {}),
|
|
68537
68603
|
...(input.commandActor ? { commandActor: input.commandActor } : {}),
|
|
68604
|
+
...(input.beforeResume ? { beforeResume: input.beforeResume } : {}),
|
|
68538
68605
|
...(input.evidence !== undefined ? { evidence: input.evidence } : {}),
|
|
68539
68606
|
...(input.rationale !== undefined ? { rationale: input.rationale } : {}),
|
|
68540
68607
|
...(input.pausedReason !== undefined ? { pausedReason: input.pausedReason } : {}),
|
|
@@ -68633,7 +68700,7 @@ export type GoalContinuationDecision =
|
|
|
68633
68700
|
| { decision: "queue" }
|
|
68634
68701
|
| {
|
|
68635
68702
|
decision: "paused";
|
|
68636
|
-
reason: "max_auto_continuations" | "limits" |
|
|
68703
|
+
reason: "max_auto_continuations" | "limits" | GoalAdmissionPausedReason;
|
|
68637
68704
|
goal: SessionGoal;
|
|
68638
68705
|
}
|
|
68639
68706
|
| {
|
|
@@ -68712,7 +68779,7 @@ export async function evaluateGoalContinuation(
|
|
|
68712
68779
|
// decision (before the counter bump) so a budget pause never consumes
|
|
68713
68780
|
// continuation budget.
|
|
68714
68781
|
budgetBlocked?: string | null;
|
|
68715
|
-
budgetPausedReason?: "limits" |
|
|
68782
|
+
budgetPausedReason?: "limits" | GoalAdmissionPausedReason | undefined;
|
|
68716
68783
|
},
|
|
68717
68784
|
): Promise<GoalContinuationDecision> {
|
|
68718
68785
|
return await withWorkspaceRls(
|
|
@@ -68974,7 +69041,7 @@ export async function materializeGoalContinuation(
|
|
|
68974
69041
|
workflowId: string;
|
|
68975
69042
|
defaultMaxAutoContinuations?: number | null;
|
|
68976
69043
|
budgetBlocked?: string | null;
|
|
68977
|
-
budgetPausedReason?: "limits" |
|
|
69044
|
+
budgetPausedReason?: "limits" | GoalAdmissionPausedReason | undefined;
|
|
68978
69045
|
/** Trusted worker admission, evaluated under the same session/goal locks
|
|
68979
69046
|
* as lineage materialization. Never substitutes a mutable latest human. */
|
|
68980
69047
|
admission?: (
|
|
@@ -68982,7 +69049,7 @@ export async function materializeGoalContinuation(
|
|
|
68982
69049
|
causalTurn: { id: string; initiatingHumanSubjectId: string | null } | null,
|
|
68983
69050
|
) => Promise<{
|
|
68984
69051
|
budgetBlocked: string | null;
|
|
68985
|
-
budgetPausedReason?: "limits" |
|
|
69052
|
+
budgetPausedReason?: "limits" | GoalAdmissionPausedReason;
|
|
68986
69053
|
}>;
|
|
68987
69054
|
idleBackoff?: GoalIdleBackoffPolicy | null;
|
|
68988
69055
|
policy: {
|
|
@@ -78873,6 +78940,8 @@ function metadataWithoutProviderRecoveryCount(
|
|
|
78873
78940
|
): Record<string, unknown> {
|
|
78874
78941
|
const next = { ...(metadata ?? {}) };
|
|
78875
78942
|
delete next.providerRecoveryCount;
|
|
78943
|
+
delete next.providerRecoveryStartedAt;
|
|
78944
|
+
delete next.providerRecoveryReason;
|
|
78876
78945
|
return next;
|
|
78877
78946
|
}
|
|
78878
78947
|
|
|
@@ -81315,7 +81384,15 @@ export async function requestSessionTurnRecovery(
|
|
|
81315
81384
|
}
|
|
81316
81385
|
: {}),
|
|
81317
81386
|
...(input.providerRecoveryCount !== undefined
|
|
81318
|
-
? {
|
|
81387
|
+
? {
|
|
81388
|
+
providerRecoveryCount: input.providerRecoveryCount,
|
|
81389
|
+
providerRecoveryStartedAt:
|
|
81390
|
+
providerRecoveryCountFromTurnMetadata(turn.metadata) > 0 &&
|
|
81391
|
+
typeof turn.metadata?.providerRecoveryStartedAt === "string"
|
|
81392
|
+
? turn.metadata.providerRecoveryStartedAt
|
|
81393
|
+
: now.toISOString(),
|
|
81394
|
+
providerRecoveryReason: input.reason,
|
|
81395
|
+
}
|
|
81319
81396
|
: {}),
|
|
81320
81397
|
},
|
|
81321
81398
|
updatedAt: now,
|
|
@@ -86759,7 +86836,7 @@ async function mapSessionWithControl(
|
|
|
86759
86836
|
},
|
|
86760
86837
|
archive: Pick<Session, "archived" | "archivedAt" | "archiveVersion"> = mapSessionArchive(null),
|
|
86761
86838
|
workspaceControl?: WorkspaceControlRow,
|
|
86762
|
-
tenancyViewer?: { subjectId: string
|
|
86839
|
+
tenancyViewer?: { subjectId: string },
|
|
86763
86840
|
): Promise<Session> {
|
|
86764
86841
|
const controls = await sessionControlProjections(db, row.workspaceId, [row.id], workspaceControl);
|
|
86765
86842
|
const control = controls.get(row.id);
|
|
@@ -86896,7 +86973,7 @@ function mapSession(
|
|
|
86896
86973
|
attentionVersion: 0,
|
|
86897
86974
|
},
|
|
86898
86975
|
archive: Pick<Session, "archived" | "archivedAt" | "archiveVersion"> = mapSessionArchive(null),
|
|
86899
|
-
tenancyViewer?: { subjectId: string
|
|
86976
|
+
tenancyViewer?: { subjectId: string },
|
|
86900
86977
|
): Session {
|
|
86901
86978
|
const bundledSkillIds = bundledSkillSelectionFromMetadata(row.metadata);
|
|
86902
86979
|
return {
|
|
@@ -86930,9 +87007,7 @@ function mapSession(
|
|
|
86930
87007
|
toolPolicyVersion: Number(row.toolPolicyVersion),
|
|
86931
87008
|
mcpApprovalPolicies: row.mcpApprovalPolicies,
|
|
86932
87009
|
metadata: row.metadata,
|
|
86933
|
-
...(tenancyViewer
|
|
86934
|
-
? { tenancy: mapSessionTenancy(row, tenancyViewer.subjectId) }
|
|
86935
|
-
: {}),
|
|
87010
|
+
...(tenancyViewer ? { tenancy: mapSessionTenancy(row, tenancyViewer.subjectId) } : {}),
|
|
86936
87011
|
createdBy: initiatorFromStorage(
|
|
86937
87012
|
row.createdByKind,
|
|
86938
87013
|
row.createdBySubjectId,
|
package/src/provision-roles.ts
CHANGED
|
@@ -2163,6 +2163,22 @@ BEGIN
|
|
|
2163
2163
|
REVOKE ALL ON FUNCTION opengeni_private.record_sandbox_file_publication(uuid,uuid,uuid,uuid) FROM PUBLIC;
|
|
2164
2164
|
REVOKE ALL ON FUNCTION opengeni_private.list_sandbox_file_publications(uuid,uuid,jsonb) FROM PUBLIC;
|
|
2165
2165
|
END IF;
|
|
2166
|
+
IF to_regclass('opengeni_private.artifact_catalog_pins') IS NOT NULL THEN
|
|
2167
|
+
EXECUTE format('REVOKE ALL ON TABLE opengeni_private.artifact_catalog_pins FROM %I', ${literal(role)});
|
|
2168
|
+
-- Table revocation does not remove column ACLs. Reconcile every current
|
|
2169
|
+
-- column for both the runtime and PUBLIC, including future additions.
|
|
2170
|
+
EXECUTE format('REVOKE ALL (%s) ON TABLE opengeni_private.artifact_catalog_pins FROM %I',
|
|
2171
|
+
(SELECT string_agg(quote_ident(attname),',' ORDER BY attnum) FROM pg_attribute
|
|
2172
|
+
WHERE attrelid='opengeni_private.artifact_catalog_pins'::regclass AND attnum>0 AND NOT attisdropped),
|
|
2173
|
+
${literal(role)});
|
|
2174
|
+
REVOKE ALL ON TABLE opengeni_private.artifact_catalog_pins FROM PUBLIC;
|
|
2175
|
+
EXECUTE format('REVOKE ALL (%s) ON TABLE opengeni_private.artifact_catalog_pins FROM PUBLIC',
|
|
2176
|
+
(SELECT string_agg(quote_ident(attname),',' ORDER BY attnum) FROM pg_attribute
|
|
2177
|
+
WHERE attrelid='opengeni_private.artifact_catalog_pins'::regclass AND attnum>0 AND NOT attisdropped));
|
|
2178
|
+
REVOKE ALL ON FUNCTION opengeni_private.update_artifact_pin(uuid,uuid,text,text,boolean),
|
|
2179
|
+
opengeni_private.list_artifact_pins(uuid,uuid),
|
|
2180
|
+
opengeni_private.list_sandbox_file_publications_pinned(uuid,uuid,jsonb) FROM PUBLIC;
|
|
2181
|
+
END IF;
|
|
2166
2182
|
IF to_regclass('opengeni_private.slack_file_upload_operations') IS NOT NULL THEN
|
|
2167
2183
|
-- Ordinary RLS repositories own the upload CAS, not owner capabilities.
|
|
2168
2184
|
-- Never allow deletion/truncation to erase its durable completion fence.
|
|
@@ -25,8 +25,6 @@ try {
|
|
|
25
25
|
rlsStrategy: settings.rlsStrategy,
|
|
26
26
|
expectedRole: settings.runtimeDatabaseRole,
|
|
27
27
|
targetSchema: settings.dbSchema.trim() || "public",
|
|
28
|
-
organizationTenancyCanonicalActivationEnabled:
|
|
29
|
-
settings.organizationTenancyCanonicalActivationEnabled,
|
|
30
28
|
});
|
|
31
29
|
// Structural evidence only: never print a connection string, secret, GUC, or
|
|
32
30
|
// tenant row. The command is intended for release Jobs and audit artifacts.
|