@opengeni/db 0.19.0 → 0.22.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.
@@ -0,0 +1,218 @@
1
+ -- deployment-mode: rolling
2
+ -- Attempt-frozen connector action policy and secret-free execution evidence.
3
+
4
+ CREATE TABLE connector_action_policies (
5
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
6
+ account_id uuid NOT NULL REFERENCES managed_accounts(id) ON DELETE CASCADE,
7
+ workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
8
+ connection_id text NOT NULL,
9
+ server_id text NOT NULL,
10
+ tool_name text NOT NULL,
11
+ action_name text NOT NULL,
12
+ policy text NOT NULL,
13
+ version integer NOT NULL DEFAULT 1,
14
+ created_by_subject_id text NOT NULL,
15
+ updated_by_subject_id text NOT NULL,
16
+ created_at timestamptz NOT NULL DEFAULT now(),
17
+ updated_at timestamptz NOT NULL DEFAULT now(),
18
+ CONSTRAINT connector_action_policies_connection_id_chk
19
+ CHECK (octet_length(btrim(connection_id)) BETWEEN 1 AND 512),
20
+ CONSTRAINT connector_action_policies_server_id_chk
21
+ CHECK (octet_length(btrim(server_id)) BETWEEN 1 AND 256),
22
+ CONSTRAINT connector_action_policies_tool_name_chk
23
+ CHECK (octet_length(btrim(tool_name)) BETWEEN 1 AND 512),
24
+ CONSTRAINT connector_action_policies_action_name_chk
25
+ CHECK (octet_length(btrim(action_name)) BETWEEN 1 AND 512),
26
+ CONSTRAINT connector_action_policies_policy_chk
27
+ CHECK (policy IN ('allow', 'ask', 'block')),
28
+ CONSTRAINT connector_action_policies_version_chk CHECK (version > 0),
29
+ CONSTRAINT connector_action_policies_creator_chk
30
+ CHECK (octet_length(btrim(created_by_subject_id)) BETWEEN 1 AND 1024),
31
+ CONSTRAINT connector_action_policies_updater_chk
32
+ CHECK (octet_length(btrim(updated_by_subject_id)) BETWEEN 1 AND 1024),
33
+ CONSTRAINT connector_action_policies_workspace_account_fk
34
+ FOREIGN KEY (workspace_id, account_id)
35
+ REFERENCES workspaces(id, account_id) ON DELETE CASCADE,
36
+ CONSTRAINT connector_action_policies_scope_uq
37
+ UNIQUE (workspace_id, connection_id, server_id, tool_name, action_name)
38
+ );
39
+
40
+ CREATE INDEX connector_action_policies_workspace_connection_idx
41
+ ON connector_action_policies(workspace_id, connection_id, server_id);
42
+
43
+ ALTER TABLE session_turn_attempts
44
+ ADD COLUMN connector_action_policies jsonb NOT NULL DEFAULT '[]'::jsonb,
45
+ ADD CONSTRAINT session_turn_attempts_connector_action_policies_chk
46
+ CHECK (
47
+ jsonb_typeof(connector_action_policies) = 'array'
48
+ AND jsonb_array_length(connector_action_policies) <= 2048
49
+ );
50
+
51
+ CREATE TABLE connector_action_requests (
52
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
53
+ account_id uuid NOT NULL REFERENCES managed_accounts(id) ON DELETE CASCADE,
54
+ workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
55
+ session_id uuid NOT NULL,
56
+ turn_id uuid NOT NULL,
57
+ creation_attempt_id uuid NOT NULL,
58
+ creation_execution_generation integer NOT NULL,
59
+ execution_attempt_id uuid,
60
+ execution_attempt_generation integer,
61
+ approval_id text NOT NULL,
62
+ initiator_kind text NOT NULL,
63
+ initiator_subject_id text NOT NULL,
64
+ connection_id text NOT NULL,
65
+ connection_version integer,
66
+ server_id text NOT NULL,
67
+ tool_name text NOT NULL,
68
+ action_name text NOT NULL,
69
+ -- Snapshot provenance intentionally has no FK: a policy may be changed or
70
+ -- deleted after claim, while the attempt-frozen decision remains executable.
71
+ policy_id uuid,
72
+ policy_version integer,
73
+ policy_source text NOT NULL,
74
+ policy_decision text NOT NULL,
75
+ action_fingerprint text NOT NULL,
76
+ status text NOT NULL,
77
+ decision text,
78
+ decision_by_subject_id text,
79
+ -- Durable provenance UUID; session-event retention must not rewrite a settled
80
+ -- connector decision or trip the immutable-decision trigger.
81
+ decision_event_id uuid,
82
+ decided_at timestamptz,
83
+ execution_started_at timestamptz,
84
+ execution_finished_at timestamptz,
85
+ outcome text,
86
+ created_at timestamptz NOT NULL DEFAULT now(),
87
+ updated_at timestamptz NOT NULL DEFAULT now(),
88
+ CONSTRAINT connector_action_requests_creation_attempt_fk
89
+ FOREIGN KEY (account_id, workspace_id, session_id, turn_id, creation_attempt_id)
90
+ REFERENCES session_turn_attempts(account_id, workspace_id, session_id, turn_id, id)
91
+ ON DELETE CASCADE,
92
+ CONSTRAINT connector_action_requests_execution_attempt_fk
93
+ FOREIGN KEY (account_id, workspace_id, session_id, turn_id, execution_attempt_id)
94
+ REFERENCES session_turn_attempts(account_id, workspace_id, session_id, turn_id, id)
95
+ ON DELETE CASCADE,
96
+ CONSTRAINT connector_action_requests_identity_uq
97
+ UNIQUE (workspace_id, session_id, turn_id, approval_id),
98
+ CONSTRAINT connector_action_requests_creation_generation_chk
99
+ CHECK (creation_execution_generation > 0),
100
+ CONSTRAINT connector_action_requests_execution_attempt_chk
101
+ CHECK (
102
+ (execution_attempt_id IS NULL AND execution_attempt_generation IS NULL)
103
+ OR
104
+ (execution_attempt_id IS NOT NULL AND execution_attempt_generation > 0)
105
+ ),
106
+ CONSTRAINT connector_action_requests_approval_id_chk
107
+ CHECK (octet_length(btrim(approval_id)) BETWEEN 1 AND 1024),
108
+ CONSTRAINT connector_action_requests_initiator_kind_chk
109
+ CHECK (initiator_kind IN ('subject', 'service')),
110
+ CONSTRAINT connector_action_requests_initiator_subject_chk
111
+ CHECK (octet_length(btrim(initiator_subject_id)) BETWEEN 1 AND 1024),
112
+ CONSTRAINT connector_action_requests_connection_id_chk
113
+ CHECK (octet_length(btrim(connection_id)) BETWEEN 1 AND 512),
114
+ CONSTRAINT connector_action_requests_connection_version_chk
115
+ CHECK (connection_version IS NULL OR connection_version > 0),
116
+ CONSTRAINT connector_action_requests_server_id_chk
117
+ CHECK (octet_length(btrim(server_id)) BETWEEN 1 AND 256),
118
+ CONSTRAINT connector_action_requests_tool_name_chk
119
+ CHECK (octet_length(btrim(tool_name)) BETWEEN 1 AND 512),
120
+ CONSTRAINT connector_action_requests_action_name_chk
121
+ CHECK (octet_length(btrim(action_name)) BETWEEN 1 AND 512),
122
+ CONSTRAINT connector_action_requests_policy_version_chk
123
+ CHECK (policy_version IS NULL OR policy_version > 0),
124
+ CONSTRAINT connector_action_requests_policy_source_chk
125
+ CHECK (policy_source IN ('explicit', 'ambiguous')),
126
+ CONSTRAINT connector_action_requests_policy_decision_chk
127
+ CHECK (policy_decision IN ('allow', 'ask', 'block')),
128
+ CONSTRAINT connector_action_requests_fingerprint_chk
129
+ CHECK (action_fingerprint ~ '^[0-9a-f]{64}$'),
130
+ CONSTRAINT connector_action_requests_status_chk
131
+ CHECK (status IN (
132
+ 'pending', 'approved', 'rejected', 'blocked', 'executing',
133
+ 'completed', 'failed', 'uncertain'
134
+ )),
135
+ CONSTRAINT connector_action_requests_decision_chk
136
+ CHECK (decision IS NULL OR decision IN ('approve', 'reject')),
137
+ CONSTRAINT connector_action_requests_decision_consistency_chk
138
+ CHECK (
139
+ (decision IS NULL AND decision_by_subject_id IS NULL AND decision_event_id IS NULL AND decided_at IS NULL)
140
+ OR
141
+ (decision IS NOT NULL AND decision_by_subject_id IS NOT NULL AND decision_event_id IS NOT NULL AND decided_at IS NOT NULL)
142
+ )
143
+ );
144
+
145
+ CREATE INDEX connector_action_requests_attempt_status_idx
146
+ ON connector_action_requests(workspace_id, creation_attempt_id, status);
147
+ CREATE INDEX connector_action_requests_session_created_idx
148
+ ON connector_action_requests(workspace_id, session_id, created_at DESC, id DESC);
149
+
150
+ CREATE OR REPLACE FUNCTION opengeni_private.prevent_connector_action_request_identity_mutation()
151
+ RETURNS trigger
152
+ LANGUAGE plpgsql
153
+ AS $$
154
+ BEGIN
155
+ IF NEW.account_id IS DISTINCT FROM OLD.account_id
156
+ OR NEW.workspace_id IS DISTINCT FROM OLD.workspace_id
157
+ OR NEW.session_id IS DISTINCT FROM OLD.session_id
158
+ OR NEW.turn_id IS DISTINCT FROM OLD.turn_id
159
+ OR NEW.creation_attempt_id IS DISTINCT FROM OLD.creation_attempt_id
160
+ OR NEW.creation_execution_generation IS DISTINCT FROM OLD.creation_execution_generation
161
+ OR NEW.approval_id IS DISTINCT FROM OLD.approval_id
162
+ OR NEW.initiator_kind IS DISTINCT FROM OLD.initiator_kind
163
+ OR NEW.initiator_subject_id IS DISTINCT FROM OLD.initiator_subject_id
164
+ OR NEW.connection_id IS DISTINCT FROM OLD.connection_id
165
+ OR NEW.connection_version IS DISTINCT FROM OLD.connection_version
166
+ OR NEW.server_id IS DISTINCT FROM OLD.server_id
167
+ OR NEW.tool_name IS DISTINCT FROM OLD.tool_name
168
+ OR NEW.action_name IS DISTINCT FROM OLD.action_name
169
+ OR NEW.policy_id IS DISTINCT FROM OLD.policy_id
170
+ OR NEW.policy_version IS DISTINCT FROM OLD.policy_version
171
+ OR NEW.policy_source IS DISTINCT FROM OLD.policy_source
172
+ OR NEW.policy_decision IS DISTINCT FROM OLD.policy_decision
173
+ OR NEW.action_fingerprint IS DISTINCT FROM OLD.action_fingerprint
174
+ THEN
175
+ RAISE EXCEPTION 'connector action request identity is immutable' USING ERRCODE = '23514';
176
+ END IF;
177
+ IF OLD.execution_attempt_id IS NOT NULL AND (
178
+ NEW.execution_attempt_id IS DISTINCT FROM OLD.execution_attempt_id
179
+ OR NEW.execution_attempt_generation IS DISTINCT FROM OLD.execution_attempt_generation
180
+ ) THEN
181
+ RAISE EXCEPTION 'connector action execution attempt is immutable' USING ERRCODE = '23514';
182
+ END IF;
183
+ IF OLD.decision IS NOT NULL AND (
184
+ NEW.decision IS DISTINCT FROM OLD.decision
185
+ OR NEW.decision_by_subject_id IS DISTINCT FROM OLD.decision_by_subject_id
186
+ OR NEW.decision_event_id IS DISTINCT FROM OLD.decision_event_id
187
+ OR NEW.decided_at IS DISTINCT FROM OLD.decided_at
188
+ ) THEN
189
+ RAISE EXCEPTION 'connector action approval decision is immutable' USING ERRCODE = '23514';
190
+ END IF;
191
+ RETURN NEW;
192
+ END;
193
+ $$;
194
+
195
+ CREATE TRIGGER connector_action_requests_identity_immutable
196
+ BEFORE UPDATE ON connector_action_requests
197
+ FOR EACH ROW
198
+ EXECUTE FUNCTION opengeni_private.prevent_connector_action_request_identity_mutation();
199
+
200
+ ALTER TABLE connector_action_policies ENABLE ROW LEVEL SECURITY;
201
+ ALTER TABLE connector_action_policies FORCE ROW LEVEL SECURITY;
202
+ CREATE POLICY connector_action_policies_workspace_isolation ON connector_action_policies
203
+ USING (opengeni_private.workspace_rls_visible(account_id, workspace_id))
204
+ WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id));
205
+
206
+ ALTER TABLE connector_action_requests ENABLE ROW LEVEL SECURITY;
207
+ ALTER TABLE connector_action_requests FORCE ROW LEVEL SECURITY;
208
+ CREATE POLICY connector_action_requests_workspace_isolation ON connector_action_requests
209
+ USING (opengeni_private.workspace_rls_visible(account_id, workspace_id))
210
+ WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id));
211
+
212
+ DO $$
213
+ BEGIN
214
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
215
+ GRANT SELECT, INSERT, UPDATE, DELETE ON connector_action_policies TO opengeni_app;
216
+ GRANT SELECT, INSERT, UPDATE, DELETE ON connector_action_requests TO opengeni_app;
217
+ END IF;
218
+ END $$;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/db",
3
- "version": "0.19.0",
3
+ "version": "0.22.1",
4
4
  "description": "OpenGeni persistence: Drizzle schema, RLS-scoped query layer, the SQL migration runner, and role provisioning.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -51,8 +51,8 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "@opengeni/codex": "^0.2.9",
54
- "@opengeni/config": "^0.9.1",
55
- "@opengeni/contracts": "^0.28.1",
54
+ "@opengeni/config": "^0.10.0",
55
+ "@opengeni/contracts": "^0.31.0",
56
56
  "@opengeni/network": "^0.1.1",
57
57
  "drizzle-orm": "^0.45.2",
58
58
  "postgres": "^3.4.7"
@@ -506,6 +506,9 @@ export function buildConnectionTokenResolver(
506
506
 
507
507
  return async (input) => {
508
508
  const ref = input.connectionRef;
509
+ if (ref.subjectScope === "subject" && !input.subjectId) {
510
+ return authNeeded(ref, "personal_authority_unavailable", ref.connectionId);
511
+ }
509
512
  // Repository-scoped provider bindings require a broker that can prove the
510
513
  // selected-resource boundary. The generic standalone credential store has
511
514
  // no provider-specific containment adapter, so it must fail closed instead