@opengeni/db 4.2.2 → 4.3.2-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.
Files changed (103) hide show
  1. package/dist/canonical-human-identities.js +3 -3
  2. package/dist/{chunk-S4Y42VSX.js → chunk-AI4NC6XW.js} +212 -7
  3. package/dist/chunk-AI4NC6XW.js.map +1 -0
  4. package/dist/{chunk-SNLIKELL.js → chunk-DVSLOUEI.js} +2 -2
  5. package/dist/{chunk-6GAPAYZR.js → chunk-F4BIHJ7J.js} +562 -86
  6. package/dist/chunk-F4BIHJ7J.js.map +1 -0
  7. package/dist/{chunk-B2KR3TEM.js → chunk-I4ITNMX2.js} +2 -2
  8. package/dist/{chunk-MGVOYVPS.js → chunk-PFBUHOI5.js} +3 -3
  9. package/dist/{chunk-6Y7ZDVJ5.js → chunk-UUPY6IGP.js} +2 -2
  10. package/dist/{chunk-FPTI4NP4.js → chunk-UVBPOGA7.js} +2 -2
  11. package/dist/{chunk-7WX3R3YL.js → chunk-XBXANJQS.js} +7 -2
  12. package/dist/chunk-XBXANJQS.js.map +1 -0
  13. package/dist/{chunk-6IQ4X35Y.js → chunk-YVYBAWBU.js} +46 -1
  14. package/dist/chunk-YVYBAWBU.js.map +1 -0
  15. package/dist/connect-attempts.d.ts +60 -0
  16. package/dist/connection-setup-authority.d.ts +10 -0
  17. package/dist/connection-token-resolver.d.ts +11 -2
  18. package/dist/editable-artifact-durable-export.js +2 -2
  19. package/dist/editable-artifacts.js +3 -3
  20. package/dist/external-identities.d.ts +16 -0
  21. package/dist/external-identity-links.d.ts +74 -0
  22. package/dist/external-link-work.d.ts +51 -0
  23. package/dist/host-mcp-binding-match.d.ts +5 -0
  24. package/dist/host-mcp-bindings.d.ts +73 -0
  25. package/dist/host-mcp-task-authority.d.ts +47 -0
  26. package/dist/host-selection-identity.d.ts +6 -0
  27. package/dist/index.d.ts +60 -35
  28. package/dist/index.js +15247 -13793
  29. package/dist/index.js.map +1 -1
  30. package/dist/live-session-attempt.d.ts +275 -0
  31. package/dist/managed-auth-session-sets.js +3 -3
  32. package/dist/memory-domain.d.ts +3 -3
  33. package/dist/provision-roles.js +1 -1
  34. package/dist/retained-provider-commands.js +2 -2
  35. package/dist/runtime-posture.d.ts +6 -6
  36. package/dist/scheduled-task-revision-authority.d.ts +11 -0
  37. package/dist/schema.d.ts +1279 -9
  38. package/dist/schema.js +11 -1
  39. package/dist/session-background-commands.js +4 -4
  40. package/dist/session-command-output.js +4 -4
  41. package/dist/session-event-slices.js +2 -2
  42. package/dist/session-queue-commands.d.ts +7 -0
  43. package/dist/session-tenancy.d.ts +2 -0
  44. package/dist/session-tenancy.js +3 -3
  45. package/dist/video-generation.js +3 -3
  46. package/dist/workspace-authority.d.ts +4 -4
  47. package/dist/workspace-tool-defaults.js +4 -4
  48. package/drizzle/0437_organization_scoped_external_workspaces.sql +71 -0
  49. package/drizzle/0438_durable_connect_attempts.sql +95 -0
  50. package/drizzle/0439_external_identity_provisioning.sql +128 -0
  51. package/drizzle/0440_external_workspace_member_removal.sql +59 -0
  52. package/drizzle/0441_external_identity_membership_lifecycle.sql +163 -0
  53. package/drizzle/0442_external_owning_user_authority.sql +81 -0
  54. package/drizzle/0443_host_mcp_binding_registry.sql +93 -0
  55. package/drizzle/0444_host_mcp_delegations.sql +129 -0
  56. package/drizzle/0445_host_mcp_turn_authorities.sql +145 -0
  57. package/drizzle/0446_host_mcp_causal_continuation.sql +126 -0
  58. package/drizzle/0447_host_mcp_task_authorities.sql +264 -0
  59. package/drizzle/0448_host_mcp_child_authority.sql +115 -0
  60. package/drizzle/0449_external_identity_link_lifecycle.sql +107 -0
  61. package/drizzle/0450_external_identity_link_work.sql +179 -0
  62. package/drizzle/0451_external_link_preview_and_permission_ceiling.sql +82 -0
  63. package/drizzle/0452_external_link_scheduled_origin.sql +34 -0
  64. package/drizzle/0453_host_mcp_native_owner.sql +35 -0
  65. package/drizzle/0454_connect_origin_authority.sql +42 -0
  66. package/drizzle/0455_external_link_inventory_labels.sql +44 -0
  67. package/drizzle/0456_social_connection_versions.sql +14 -0
  68. package/drizzle/0457_canonical_session_scope_subject.sql +46 -0
  69. package/drizzle/0458_skill_review_wire_compatibility.sql +36 -0
  70. package/package.json +6 -6
  71. package/src/connect-attempts.ts +317 -0
  72. package/src/connection-authority.ts +9 -0
  73. package/src/connection-setup-authority.ts +35 -0
  74. package/src/connection-token-resolver.ts +177 -48
  75. package/src/external-identities.ts +70 -0
  76. package/src/external-identity-links.ts +364 -0
  77. package/src/external-link-work.ts +228 -0
  78. package/src/host-mcp-binding-match.ts +33 -0
  79. package/src/host-mcp-bindings.ts +741 -0
  80. package/src/host-mcp-task-authority.ts +383 -0
  81. package/src/host-selection-identity.ts +29 -0
  82. package/src/index.ts +481 -151
  83. package/src/live-session-attempt.ts +85 -0
  84. package/src/managed-human-provisioning.ts +6 -1
  85. package/src/memory-domain.ts +6 -11
  86. package/src/provision-roles.ts +3 -0
  87. package/src/runtime-posture.ts +47 -0
  88. package/src/scheduled-task-revision-authority.ts +30 -0
  89. package/src/schema.ts +233 -7
  90. package/src/session-control.ts +14 -0
  91. package/src/session-queue-commands.ts +11 -0
  92. package/src/session-tenancy.ts +7 -0
  93. package/src/workspace-artifacts.ts +10 -4
  94. package/src/workspace-authority.ts +5 -5
  95. package/dist/chunk-6GAPAYZR.js.map +0 -1
  96. package/dist/chunk-6IQ4X35Y.js.map +0 -1
  97. package/dist/chunk-7WX3R3YL.js.map +0 -1
  98. package/dist/chunk-S4Y42VSX.js.map +0 -1
  99. /package/dist/{chunk-SNLIKELL.js.map → chunk-DVSLOUEI.js.map} +0 -0
  100. /package/dist/{chunk-B2KR3TEM.js.map → chunk-I4ITNMX2.js.map} +0 -0
  101. /package/dist/{chunk-MGVOYVPS.js.map → chunk-PFBUHOI5.js.map} +0 -0
  102. /package/dist/{chunk-6Y7ZDVJ5.js.map → chunk-UUPY6IGP.js.map} +0 -0
  103. /package/dist/{chunk-FPTI4NP4.js.map → chunk-UVBPOGA7.js.map} +0 -0
@@ -0,0 +1,179 @@
1
+ -- deployment-mode: maintenance
2
+ -- Link provenance is separate from ownership and from historical creator audit.
3
+ -- An inherited revoked link remains attached: omission must never turn old work
4
+ -- into an unrestricted native-user operation.
5
+ SET LOCAL lock_timeout = '5s';
6
+ SET LOCAL statement_timeout = '5min';
7
+ DO $drain$
8
+ DECLARE roles jsonb := nullif(current_setting('opengeni.migration_application_roles', true), '')::jsonb;
9
+ BEGIN
10
+ IF roles IS NULL OR jsonb_typeof(roles) <> 'array' THEN
11
+ RAISE EXCEPTION '0450 requires application database roles' USING ERRCODE = '55000';
12
+ END IF;
13
+ IF jsonb_array_length(roles) NOT BETWEEN 1 AND 16 OR EXISTS (
14
+ SELECT 1 FROM jsonb_array_elements(roles) item WHERE jsonb_typeof(item) <> 'string'
15
+ OR btrim(item #>> '{}') = '' OR item #>> '{}' <> btrim(item #>> '{}')
16
+ OR octet_length(item #>> '{}') > 63
17
+ ) THEN RAISE EXCEPTION '0450 invalid application roles' USING ERRCODE = '55000'; END IF;
18
+ IF EXISTS (SELECT 1 FROM pg_stat_activity a JOIN jsonb_array_elements_text(roles) r ON r = a.usename
19
+ WHERE a.datname = current_database() AND a.pid <> pg_backend_pid()) THEN
20
+ RAISE EXCEPTION '0450 requires stopped application sessions' USING ERRCODE = '55000';
21
+ END IF;
22
+ END
23
+ $drain$;
24
+
25
+ CREATE TABLE external_link_turn_authorities (
26
+ turn_id uuid PRIMARY KEY REFERENCES session_turns(id) ON DELETE CASCADE,
27
+ account_id uuid NOT NULL,
28
+ workspace_id uuid NOT NULL,
29
+ session_id uuid NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
30
+ link_id uuid NOT NULL REFERENCES external_identity_links(id),
31
+ link_revision bigint NOT NULL CHECK (link_revision BETWEEN 1 AND 9007199254740991),
32
+ canonical_snapshot jsonb NOT NULL CHECK (jsonb_typeof(canonical_snapshot) = 'object' AND octet_length(canonical_snapshot::text) <= 65536),
33
+ source_kind text NOT NULL CHECK (source_kind IN ('direct','causal','child','scheduled')),
34
+ source_turn_id uuid REFERENCES session_turns(id),
35
+ source_task_id uuid REFERENCES scheduled_tasks(id),
36
+ source_task_revision bigint,
37
+ created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
38
+ FOREIGN KEY (workspace_id, account_id) REFERENCES workspaces(id, account_id),
39
+ CHECK ((source_kind = 'direct' AND source_turn_id IS NULL AND source_task_id IS NULL AND source_task_revision IS NULL)
40
+ OR (source_kind IN ('causal','child') AND source_turn_id IS NOT NULL AND source_task_id IS NULL AND source_task_revision IS NULL)
41
+ OR (source_kind = 'scheduled' AND source_turn_id IS NULL AND source_task_id IS NOT NULL AND source_task_revision > 0))
42
+ );
43
+ CREATE TABLE external_link_task_authorities (
44
+ task_id uuid NOT NULL REFERENCES scheduled_tasks(id) ON DELETE CASCADE,
45
+ task_revision bigint NOT NULL CHECK (task_revision BETWEEN 1 AND 9007199254740991),
46
+ account_id uuid NOT NULL,
47
+ workspace_id uuid NOT NULL,
48
+ link_id uuid NOT NULL REFERENCES external_identity_links(id),
49
+ link_revision bigint NOT NULL CHECK (link_revision BETWEEN 1 AND 9007199254740991),
50
+ canonical_snapshot jsonb NOT NULL CHECK (jsonb_typeof(canonical_snapshot) = 'object' AND octet_length(canonical_snapshot::text) <= 65536),
51
+ created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
52
+ PRIMARY KEY (task_id, task_revision),
53
+ FOREIGN KEY (workspace_id, account_id) REFERENCES workspaces(id, account_id)
54
+ );
55
+ ALTER TABLE external_link_turn_authorities ENABLE ROW LEVEL SECURITY;
56
+ ALTER TABLE external_link_turn_authorities FORCE ROW LEVEL SECURITY;
57
+ CREATE POLICY session_visibility_isolation ON external_link_turn_authorities AS RESTRICTIVE
58
+ USING (session_reference_visible(account_id, workspace_id, session_id))
59
+ WITH CHECK (session_reference_visible(account_id, workspace_id, session_id));
60
+ ALTER TABLE external_link_task_authorities ENABLE ROW LEVEL SECURITY;
61
+ ALTER TABLE external_link_task_authorities FORCE ROW LEVEL SECURITY;
62
+ CREATE POLICY external_link_turn_authorities_scope ON external_link_turn_authorities
63
+ USING (account_id = opengeni_private.current_account_id() AND workspace_id = opengeni_private.current_workspace_id())
64
+ WITH CHECK (account_id = opengeni_private.current_account_id() AND workspace_id = opengeni_private.current_workspace_id());
65
+ CREATE POLICY external_link_task_authorities_scope ON external_link_task_authorities
66
+ USING (account_id = opengeni_private.current_account_id() AND workspace_id = opengeni_private.current_workspace_id())
67
+ WITH CHECK (account_id = opengeni_private.current_account_id() AND workspace_id = opengeni_private.current_workspace_id());
68
+
69
+ CREATE FUNCTION opengeni_private.guard_external_link_work_snapshot() RETURNS trigger
70
+ LANGUAGE plpgsql SET search_path FROM CURRENT AS $body$
71
+ DECLARE
72
+ l external_identity_links%ROWTYPE;
73
+ t session_turns%ROWTYPE;
74
+ s sessions%ROWTYPE;
75
+ source_snapshot jsonb;
76
+ revision_authority jsonb;
77
+ BEGIN
78
+ IF TG_OP <> 'INSERT' OR NEW.account_id IS DISTINCT FROM opengeni_private.current_account_id()
79
+ OR NEW.workspace_id IS DISTINCT FROM opengeni_private.current_workspace_id() THEN
80
+ RAISE EXCEPTION 'external link work scope unavailable' USING ERRCODE = '42501';
81
+ END IF;
82
+ SELECT * INTO l FROM external_identity_links WHERE id = NEW.link_id AND account_id = NEW.account_id;
83
+ IF NOT FOUND OR l.native_subject_id IS NULL
84
+ OR NEW.canonical_snapshot #>> '{actor,accountId}' IS DISTINCT FROM NEW.account_id::text
85
+ OR NEW.canonical_snapshot #>> '{actor,linkId}' IS DISTINCT FROM NEW.link_id::text
86
+ OR NEW.canonical_snapshot #>> '{actor,linkRevision}' IS DISTINCT FROM NEW.link_revision::text
87
+ OR NEW.canonical_snapshot #>> '{actor,actingMode}' IS DISTINCT FROM 'linked_native'
88
+ OR NEW.canonical_snapshot #>> '{actor,effectiveSubjectId}' IS DISTINCT FROM l.native_subject_id
89
+ OR NEW.canonical_snapshot #>> '{actor,externalIdentityId}' IS DISTINCT FROM l.external_identity_id::text
90
+ OR NEW.canonical_snapshot #>> '{actor,externalSubjectId}' IS DISTINCT FROM l.external_subject_id
91
+ OR NEW.canonical_snapshot #>> '{actor,externalAuthorizationRevision}' IS DISTINCT FROM l.external_authorization_revision::text
92
+ OR jsonb_typeof(NEW.canonical_snapshot -> 'permissions') IS DISTINCT FROM 'array'
93
+ OR NOT (NEW.canonical_snapshot -> 'permissions') <@ l.permissions THEN
94
+ RAISE EXCEPTION 'invalid external link work snapshot' USING ERRCODE = '42501';
95
+ END IF;
96
+ IF TG_TABLE_NAME = 'external_link_task_authorities' THEN
97
+ IF NOT EXISTS (SELECT 1 FROM scheduled_tasks task WHERE task.id = NEW.task_id
98
+ AND task.account_id = NEW.account_id AND task.workspace_id = NEW.workspace_id
99
+ AND task.authority_revision = NEW.task_revision) THEN
100
+ RAISE EXCEPTION 'external link task revision unavailable' USING ERRCODE = '42501';
101
+ END IF;
102
+ revision_authority := scheduled_task_revision_authority_snapshot(NEW.account_id, NEW.workspace_id, NEW.task_id, NEW.task_revision);
103
+ IF revision_authority IS NULL OR revision_authority ->> 'subjectId' IS DISTINCT FROM l.native_subject_id
104
+ OR revision_authority ->> 'organizationMembershipId' IS DISTINCT FROM l.native_membership_id::text
105
+ OR revision_authority ->> 'membershipAuthorizationRevision' IS DISTINCT FROM l.native_authorization_revision::text THEN
106
+ RAISE EXCEPTION 'external link task owner changed' USING ERRCODE = '42501';
107
+ END IF;
108
+ RETURN NEW;
109
+ END IF;
110
+ SELECT * INTO t FROM session_turns WHERE id = NEW.turn_id AND session_id = NEW.session_id
111
+ AND account_id = NEW.account_id AND workspace_id = NEW.workspace_id;
112
+ SELECT * INTO s FROM sessions WHERE id = NEW.session_id AND account_id = NEW.account_id AND workspace_id = NEW.workspace_id;
113
+ IF t.id IS NULL OR s.id IS NULL OR t.initiating_human_subject_id IS DISTINCT FROM l.native_subject_id
114
+ OR EXISTS (SELECT 1 FROM session_turn_attempts a WHERE a.turn_id = t.id) THEN
115
+ RAISE EXCEPTION 'external link turn admission unavailable' USING ERRCODE = '42501';
116
+ END IF;
117
+ IF NEW.source_kind = 'direct' THEN
118
+ IF t.source NOT IN ('user','api') OR t.status <> 'queued' OR t.active_attempt_id IS NOT NULL
119
+ OR l.status <> 'active' OR l.revision <> NEW.link_revision
120
+ OR (l.expires_at IS NOT NULL AND l.expires_at <= clock_timestamp()) THEN
121
+ RAISE EXCEPTION 'external link direct admission unavailable' USING ERRCODE = '42501';
122
+ END IF;
123
+ ELSIF NEW.source_kind IN ('causal','child') THEN
124
+ SELECT a.canonical_snapshot INTO source_snapshot FROM external_link_turn_authorities a
125
+ WHERE a.turn_id = NEW.source_turn_id AND a.account_id = NEW.account_id AND a.workspace_id = NEW.workspace_id;
126
+ IF source_snapshot IS DISTINCT FROM NEW.canonical_snapshot THEN
127
+ RAISE EXCEPTION 'external link source snapshot changed' USING ERRCODE = '42501';
128
+ END IF;
129
+ IF NEW.source_kind = 'child' AND (s.parent_turn_id IS DISTINCT FROM NEW.source_turn_id OR t.status <> 'queued') THEN
130
+ RAISE EXCEPTION 'external link child source unavailable' USING ERRCODE = '42501';
131
+ END IF;
132
+ IF NEW.source_kind = 'child' AND NOT EXISTS (SELECT 1 FROM session_turns source
133
+ WHERE source.id = NEW.source_turn_id AND source.session_id = s.parent_session_id) THEN
134
+ RAISE EXCEPTION 'external link child parent mismatch' USING ERRCODE = '42501';
135
+ END IF;
136
+ IF NEW.source_kind = 'causal' AND (t.source NOT IN ('goal','system') OR t.status <> 'running'
137
+ OR NOT EXISTS (SELECT 1 FROM session_turns source WHERE source.id = NEW.source_turn_id AND source.session_id = t.session_id)) THEN
138
+ RAISE EXCEPTION 'external link causal source unavailable' USING ERRCODE = '42501';
139
+ END IF;
140
+ IF NEW.source_kind = 'causal' AND NOT EXISTS (SELECT 1 FROM session_system_updates u
141
+ WHERE u.account_id = NEW.account_id AND u.workspace_id = NEW.workspace_id AND u.session_id = NEW.session_id
142
+ AND u.delivered_turn_id = NEW.turn_id AND u.state = 'delivered' AND u.delivered_history_item_id IS NOT NULL
143
+ AND ((u.kind = 'goal_continuation' AND u.lineage ->> 'causalTurnId' = NEW.source_turn_id::text)
144
+ OR (u.kind IN ('child_terminal_result','child_requires_action','child_requires_action_resolved','child_paused','child_waiting_capacity','child_progress')
145
+ AND u.lineage ->> 'parentTurnId' = NEW.source_turn_id::text))) THEN
146
+ RAISE EXCEPTION 'external link exact causal delivery unavailable' USING ERRCODE = '42501';
147
+ END IF;
148
+ ELSE
149
+ SELECT a.canonical_snapshot INTO source_snapshot FROM external_link_task_authorities a
150
+ WHERE a.task_id = NEW.source_task_id AND a.task_revision = NEW.source_task_revision
151
+ AND a.account_id = NEW.account_id AND a.workspace_id = NEW.workspace_id;
152
+ IF source_snapshot IS DISTINCT FROM NEW.canonical_snapshot OR t.source <> 'system' OR t.status <> 'running' THEN
153
+ RAISE EXCEPTION 'external link scheduled source unavailable' USING ERRCODE = '42501';
154
+ END IF;
155
+ IF NOT EXISTS (SELECT 1 FROM scheduled_task_runs r WHERE r.id = t.scheduled_task_run_id
156
+ AND r.task_id = NEW.source_task_id AND r.task_authority_revision = NEW.source_task_revision
157
+ AND r.account_id = NEW.account_id AND r.workspace_id = NEW.workspace_id AND r.session_id = NEW.session_id
158
+ AND r.accepted_execution #>> '{causalHuman,subjectId}' = l.native_subject_id) THEN
159
+ RAISE EXCEPTION 'external link scheduled run mismatch' USING ERRCODE = '42501';
160
+ END IF;
161
+ END IF;
162
+ RETURN NEW;
163
+ END
164
+ $body$;
165
+ CREATE TRIGGER external_link_turn_snapshot_guard BEFORE INSERT OR UPDATE ON external_link_turn_authorities
166
+ FOR EACH ROW EXECUTE FUNCTION opengeni_private.guard_external_link_work_snapshot();
167
+ CREATE TRIGGER external_link_task_snapshot_guard BEFORE INSERT OR UPDATE ON external_link_task_authorities
168
+ FOR EACH ROW EXECUTE FUNCTION opengeni_private.guard_external_link_work_snapshot();
169
+ REVOKE ALL ON TABLE external_link_turn_authorities, external_link_task_authorities FROM PUBLIC;
170
+ REVOKE ALL ON FUNCTION opengeni_private.guard_external_link_work_snapshot() FROM PUBLIC;
171
+ DO $drain$
172
+ BEGIN
173
+ IF EXISTS (SELECT 1 FROM pg_stat_activity a
174
+ JOIN jsonb_array_elements_text(current_setting('opengeni.migration_application_roles')::jsonb) r ON r = a.usename
175
+ WHERE a.datname = current_database() AND a.pid <> pg_backend_pid()) THEN
176
+ RAISE EXCEPTION '0450 requires stopped application sessions' USING ERRCODE = '55000';
177
+ END IF;
178
+ END
179
+ $drain$;
@@ -0,0 +1,82 @@
1
+ -- deployment-mode: maintenance
2
+ SET LOCAL lock_timeout = '5s';
3
+ SET LOCAL statement_timeout = '5min';
4
+ DO $drain$
5
+ DECLARE roles jsonb := nullif(current_setting('opengeni.migration_application_roles', true), '')::jsonb;
6
+ BEGIN
7
+ IF roles IS NULL OR jsonb_typeof(roles) <> 'array' THEN
8
+ RAISE EXCEPTION '0451 requires application database roles' USING ERRCODE = '55000';
9
+ END IF;
10
+ IF jsonb_array_length(roles) NOT BETWEEN 1 AND 16 OR EXISTS (
11
+ SELECT 1 FROM jsonb_array_elements(roles) item WHERE jsonb_typeof(item) <> 'string'
12
+ OR btrim(item #>> '{}') = '' OR item #>> '{}' <> btrim(item #>> '{}')
13
+ OR octet_length(item #>> '{}') > 63
14
+ ) THEN RAISE EXCEPTION '0451 invalid application roles' USING ERRCODE = '55000'; END IF;
15
+ IF EXISTS (SELECT 1 FROM pg_stat_activity a JOIN jsonb_array_elements_text(roles) r ON r = a.usename
16
+ WHERE a.datname = current_database() AND a.pid <> pg_backend_pid()) THEN
17
+ RAISE EXCEPTION '0451 requires stopped application sessions' USING ERRCODE = '55000';
18
+ END IF;
19
+ END
20
+ $drain$;
21
+
22
+ -- Preserve all 0426 exact-work guards. Replace one asserted clause, not a
23
+ -- best-effort search, so divergence from that immutable routine aborts migration.
24
+ DO $ceiling$
25
+ DECLARE
26
+ definition text := pg_get_functiondef('opengeni_private.guard_external_link_work_snapshot()'::regprocedure);
27
+ prior text := 'OR NOT (NEW.canonical_snapshot -> ''permissions'') <@ l.permissions THEN';
28
+ replacement text := $clause$OR EXISTS (
29
+ SELECT 1 FROM jsonb_array_elements(NEW.canonical_snapshot -> 'permissions') p
30
+ WHERE jsonb_typeof(p) <> 'string' OR NOT (
31
+ l.permissions ? (p #>> '{}') OR
32
+ (l.permissions ? 'workspace:admin' AND p #>> '{}' <> 'secrets:read')
33
+ )
34
+ ) THEN$clause$;
35
+ BEGIN
36
+ IF length(definition) - length(replace(definition, prior, '')) <> length(prior) THEN
37
+ RAISE EXCEPTION '0451 expected exactly one 0426 permission clause' USING ERRCODE = '55000';
38
+ END IF;
39
+ EXECUTE replace(definition, prior, replacement);
40
+ END
41
+ $ceiling$;
42
+
43
+ -- The app role cannot SELECT external_identities directly. Expose only the
44
+ -- challenged request's display reference to an active native organization member.
45
+ CREATE FUNCTION get_external_identity_link_reference(p_account_id uuid, p_link_id uuid, p_challenge_digest text)
46
+ RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path FROM CURRENT AS $body$
47
+ DECLARE
48
+ result jsonb;
49
+ prior text := coalesce(current_setting('opengeni.organization_tenancy_lifecycle', true), '');
50
+ subject text := opengeni_private.current_subject_id();
51
+ BEGIN
52
+ IF p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
53
+ OR subject IS NULL OR subject !~ '^user:[^\r\n]+$'
54
+ OR p_challenge_digest !~ '^[0-9a-f]{64}$' THEN
55
+ RAISE EXCEPTION 'native identity link preview authority required' USING ERRCODE = '42501';
56
+ END IF;
57
+ IF NOT EXISTS (SELECT 1 FROM jsonb_array_elements(list_self_organization_memberships(subject)) m
58
+ WHERE m ->> 'organizationId' = p_account_id::text AND m ->> 'status' = 'active') THEN
59
+ RAISE EXCEPTION 'native identity link organization unavailable' USING ERRCODE = '42501';
60
+ END IF;
61
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', 'external_identity_provisioning', true);
62
+ SELECT jsonb_build_object('source', e.source, 'externalId', e.external_id) INTO result
63
+ FROM external_identity_links l JOIN external_identities e
64
+ ON e.id = l.external_identity_id AND e.account_id = l.account_id
65
+ WHERE l.account_id = p_account_id AND l.id = p_link_id AND l.challenge_digest = p_challenge_digest
66
+ AND l.status = 'pending' AND l.confirm_before > clock_timestamp()
67
+ AND (l.expires_at IS NULL OR l.expires_at > clock_timestamp())
68
+ AND e.status = 'active' AND e.authorization_revision = l.external_authorization_revision;
69
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', prior, true);
70
+ RETURN result;
71
+ END
72
+ $body$;
73
+ REVOKE ALL ON FUNCTION get_external_identity_link_reference(uuid, uuid, text) FROM PUBLIC;
74
+ DO $drain$
75
+ BEGIN
76
+ IF EXISTS (SELECT 1 FROM pg_stat_activity a
77
+ JOIN jsonb_array_elements_text(current_setting('opengeni.migration_application_roles')::jsonb) r ON r = a.usename
78
+ WHERE a.datname = current_database() AND a.pid <> pg_backend_pid()) THEN
79
+ RAISE EXCEPTION '0451 requires stopped application sessions' USING ERRCODE = '55000';
80
+ END IF;
81
+ END
82
+ $drain$;
@@ -0,0 +1,34 @@
1
+ -- deployment-mode: maintenance
2
+ SET LOCAL lock_timeout = '5s';
3
+ SET LOCAL statement_timeout = '5min';
4
+ DO $migration$
5
+ DECLARE
6
+ roles jsonb := nullif(current_setting('opengeni.migration_application_roles', true), '')::jsonb;
7
+ definition text := pg_get_functiondef('opengeni_private.guard_external_link_work_snapshot()'::regprocedure);
8
+ prior text := 'AND r.accepted_execution #>> ''{causalHuman,subjectId}'' = l.native_subject_id';
9
+ replacement text := $clause$AND r.status = 'dispatched'
10
+ AND r.accepted_execution_snapshot ->> 'causalHumanSubjectId' = l.native_subject_id
11
+ AND validate_scheduled_agent_run_live_authority(NEW.account_id, NEW.workspace_id, r.id) IS NULL$clause$;
12
+ BEGIN
13
+ IF roles IS NULL OR jsonb_typeof(roles) <> 'array' THEN
14
+ RAISE EXCEPTION '0452 requires application database roles' USING ERRCODE = '55000';
15
+ END IF;
16
+ IF jsonb_array_length(roles) NOT BETWEEN 1 AND 16 OR EXISTS (
17
+ SELECT 1 FROM jsonb_array_elements(roles) item WHERE jsonb_typeof(item) <> 'string'
18
+ OR btrim(item #>> '{}') = '' OR item #>> '{}' <> btrim(item #>> '{}')
19
+ OR octet_length(item #>> '{}') > 63
20
+ ) THEN RAISE EXCEPTION '0452 invalid application roles' USING ERRCODE = '55000'; END IF;
21
+ IF EXISTS (SELECT 1 FROM pg_stat_activity a JOIN jsonb_array_elements_text(roles) r ON r = a.usename
22
+ WHERE a.datname = current_database() AND a.pid <> pg_backend_pid()) THEN
23
+ RAISE EXCEPTION '0452 requires stopped application sessions' USING ERRCODE = '55000';
24
+ END IF;
25
+ IF length(definition) - length(replace(definition, prior, '')) <> length(prior) THEN
26
+ RAISE EXCEPTION '0452 expected exactly one scheduled origin clause' USING ERRCODE = '55000';
27
+ END IF;
28
+ EXECUTE replace(definition, prior, replacement);
29
+ IF EXISTS (SELECT 1 FROM pg_stat_activity a JOIN jsonb_array_elements_text(roles) r ON r = a.usename
30
+ WHERE a.datname = current_database() AND a.pid <> pg_backend_pid()) THEN
31
+ RAISE EXCEPTION '0452 requires stopped application sessions' USING ERRCODE = '55000';
32
+ END IF;
33
+ END
34
+ $migration$;
@@ -0,0 +1,35 @@
1
+ -- deployment-mode: maintenance
2
+ -- Host ownership follows the effective organization member. Linking creates no
3
+ -- ownership transfer: old external bindings remain external, new native bindings
4
+ -- are independent. Accepted linked work retains its separate revocable link.
5
+ SET LOCAL lock_timeout = '5s';
6
+ SET LOCAL statement_timeout = '5min';
7
+ DO $drain$
8
+ DECLARE roles jsonb := nullif(current_setting('opengeni.migration_application_roles', true), '')::jsonb;
9
+ BEGIN
10
+ IF roles IS NULL OR jsonb_typeof(roles) <> 'array' THEN
11
+ RAISE EXCEPTION '0453 requires application database roles' USING ERRCODE = '55000';
12
+ END IF;
13
+ IF jsonb_array_length(roles) NOT BETWEEN 1 AND 16 OR EXISTS (
14
+ SELECT 1 FROM jsonb_array_elements(roles) item WHERE jsonb_typeof(item) <> 'string'
15
+ OR btrim(item #>> '{}') = '' OR item #>> '{}' <> btrim(item #>> '{}')
16
+ OR octet_length(item #>> '{}') > 63
17
+ ) THEN RAISE EXCEPTION '0453 invalid application roles' USING ERRCODE = '55000'; END IF;
18
+ IF EXISTS (SELECT 1 FROM pg_stat_activity a JOIN jsonb_array_elements_text(roles) r ON r = a.usename
19
+ WHERE a.datname = current_database() AND a.pid <> pg_backend_pid()) THEN
20
+ RAISE EXCEPTION '0453 requires stopped application sessions' USING ERRCODE = '55000';
21
+ END IF;
22
+ END
23
+ $drain$;
24
+ ALTER TABLE host_mcp_bindings
25
+ DROP CONSTRAINT host_mcp_bindings_external_owner_fk,
26
+ ADD CONSTRAINT host_mcp_bindings_member_owner_fk FOREIGN KEY (account_id, owner_subject_id)
27
+ REFERENCES organization_memberships(account_id, subject_id),
28
+ ADD CONSTRAINT host_mcp_bindings_human_owner_check CHECK (
29
+ owner_subject_id ~ '^(external_user:|user:)[^\r\n]+$');
30
+ ALTER TABLE host_mcp_delegations
31
+ DROP CONSTRAINT host_mcp_delegations_external_owner_fk,
32
+ ADD CONSTRAINT host_mcp_delegations_member_owner_fk FOREIGN KEY (account_id, owner_subject_id)
33
+ REFERENCES organization_memberships(account_id, subject_id),
34
+ ADD CONSTRAINT host_mcp_delegations_human_owner_check CHECK (
35
+ owner_subject_id ~ '^(external_user:|user:)[^\r\n]+$');
@@ -0,0 +1,42 @@
1
+ -- deployment-mode: maintenance
2
+ -- Preserve the initiating external restriction separately from the effective
3
+ -- resource owner. This is credential-free provenance, never a bearer token.
4
+ SET LOCAL lock_timeout = '5s';
5
+ SET LOCAL statement_timeout = '5min';
6
+ DO $drain$
7
+ DECLARE roles jsonb := nullif(current_setting('opengeni.migration_application_roles', true), '')::jsonb;
8
+ BEGIN
9
+ IF roles IS NULL OR jsonb_typeof(roles) <> 'array' THEN
10
+ RAISE EXCEPTION '0454 requires application database roles' USING ERRCODE = '55000';
11
+ END IF;
12
+ IF jsonb_array_length(roles) NOT BETWEEN 1 AND 16 OR EXISTS (
13
+ SELECT 1 FROM jsonb_array_elements(roles) item WHERE jsonb_typeof(item) <> 'string'
14
+ OR btrim(item #>> '{}') = '' OR item #>> '{}' <> btrim(item #>> '{}')
15
+ OR octet_length(item #>> '{}') > 63
16
+ ) THEN RAISE EXCEPTION '0454 invalid application roles' USING ERRCODE = '55000'; END IF;
17
+ IF EXISTS (SELECT 1 FROM pg_stat_activity a JOIN jsonb_array_elements_text(roles) r ON r = a.usename
18
+ WHERE a.datname = current_database() AND a.pid <> pg_backend_pid()) THEN
19
+ RAISE EXCEPTION '0454 requires stopped application sessions' USING ERRCODE = '55000';
20
+ END IF;
21
+ END
22
+ $drain$;
23
+ ALTER TABLE connect_attempts ADD COLUMN external_continuation jsonb
24
+ CHECK (external_continuation IS NULL OR (jsonb_typeof(external_continuation) = 'object'
25
+ AND octet_length(external_continuation::text) <= 32768
26
+ AND external_continuation #>> '{actor,accountId}' = account_id::text
27
+ AND external_continuation #>> '{actor,effectiveSubjectId}' = subject_id));
28
+ CREATE FUNCTION opengeni_private.guard_connect_attempt_origin() RETURNS trigger
29
+ LANGUAGE plpgsql SET search_path FROM CURRENT AS $body$
30
+ BEGIN
31
+ IF ROW(NEW.id, NEW.account_id, NEW.workspace_id, NEW.subject_id, NEW.idempotency_key_hash,
32
+ NEW.request_digest, NEW.return_url, NEW.external_continuation, NEW.expires_at, NEW.created_at)
33
+ IS DISTINCT FROM ROW(OLD.id, OLD.account_id, OLD.workspace_id, OLD.subject_id, OLD.idempotency_key_hash,
34
+ OLD.request_digest, OLD.return_url, OLD.external_continuation, OLD.expires_at, OLD.created_at) THEN
35
+ RAISE EXCEPTION 'Connect origin is immutable' USING ERRCODE = '42501';
36
+ END IF;
37
+ RETURN NEW;
38
+ END
39
+ $body$;
40
+ REVOKE ALL ON FUNCTION opengeni_private.guard_connect_attempt_origin() FROM PUBLIC;
41
+ CREATE TRIGGER connect_attempt_origin_guard BEFORE UPDATE ON connect_attempts
42
+ FOR EACH ROW EXECUTE FUNCTION opengeni_private.guard_connect_attempt_origin();
@@ -0,0 +1,44 @@
1
+ -- deployment-mode: maintenance
2
+ SET LOCAL lock_timeout = '5s';
3
+ SET LOCAL statement_timeout = '5min';
4
+ DO $drain$
5
+ DECLARE roles jsonb := nullif(current_setting('opengeni.migration_application_roles', true), '')::jsonb;
6
+ BEGIN
7
+ IF roles IS NULL OR jsonb_typeof(roles) <> 'array' OR jsonb_array_length(roles) NOT BETWEEN 1 AND 16 THEN
8
+ RAISE EXCEPTION '0455 requires application database roles' USING ERRCODE = '55000';
9
+ END IF;
10
+ IF EXISTS (SELECT 1 FROM pg_stat_activity a JOIN jsonb_array_elements_text(roles) r ON r = a.usename
11
+ WHERE a.datname = current_database() AND a.pid <> pg_backend_pid()) THEN
12
+ RAISE EXCEPTION '0455 requires stopped application sessions' USING ERRCODE = '55000';
13
+ END IF;
14
+ END
15
+ $drain$;
16
+
17
+ -- No SELECT grant on external_identities. Only a current participant can resolve
18
+ -- labels for the bounded links already returned by the participant inventory.
19
+ CREATE FUNCTION get_external_identity_link_inventory_references(p_account_id uuid, p_link_ids uuid[])
20
+ RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path FROM CURRENT AS $body$
21
+ DECLARE
22
+ result jsonb;
23
+ prior text := coalesce(current_setting('opengeni.organization_tenancy_lifecycle', true), '');
24
+ subject text := opengeni_private.current_subject_id();
25
+ BEGIN
26
+ IF p_account_id IS DISTINCT FROM opengeni_private.current_account_id() OR subject IS NULL
27
+ OR p_link_ids IS NULL OR coalesce(cardinality(p_link_ids), 0) > 50 THEN
28
+ RAISE EXCEPTION 'identity link inventory authority required' USING ERRCODE = '42501';
29
+ END IF;
30
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', 'external_identity_provisioning', true);
31
+ IF NOT EXISTS (SELECT 1 FROM organization_memberships m WHERE m.account_id = p_account_id
32
+ AND m.subject_id = subject AND m.status = 'active') THEN
33
+ RAISE EXCEPTION 'identity link organization unavailable' USING ERRCODE = '42501';
34
+ END IF;
35
+ SELECT coalesce(jsonb_object_agg(l.id::text, jsonb_build_object('source', e.source, 'externalId', e.external_id)), '{}'::jsonb)
36
+ INTO result FROM external_identity_links l JOIN external_identities e
37
+ ON e.id = l.external_identity_id AND e.account_id = l.account_id
38
+ WHERE l.account_id = p_account_id AND l.id = ANY(p_link_ids)
39
+ AND (l.external_subject_id = subject OR l.native_subject_id = subject);
40
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', prior, true);
41
+ RETURN result;
42
+ END
43
+ $body$;
44
+ REVOKE ALL ON FUNCTION get_external_identity_link_inventory_references(uuid, uuid[]) FROM PUBLIC;
@@ -0,0 +1,14 @@
1
+ -- deployment-mode: maintenance
2
+ SET LOCAL lock_timeout = '5s';
3
+ SET LOCAL statement_timeout = '5min';
4
+ ALTER TABLE social_connections ADD COLUMN version integer NOT NULL DEFAULT 1 CHECK (version > 0);
5
+ CREATE FUNCTION bump_social_connection_version() RETURNS trigger
6
+ LANGUAGE plpgsql SET search_path FROM CURRENT AS $body$
7
+ BEGIN
8
+ NEW.version := OLD.version + 1;
9
+ RETURN NEW;
10
+ END
11
+ $body$;
12
+ REVOKE ALL ON FUNCTION bump_social_connection_version() FROM PUBLIC;
13
+ CREATE TRIGGER social_connection_version BEFORE UPDATE ON social_connections
14
+ FOR EACH ROW EXECUTE FUNCTION bump_social_connection_version();
@@ -0,0 +1,46 @@
1
+ -- deployment-mode: maintenance
2
+ -- Deploy with the embedding maintenance cutover: stop old API/worker writers
3
+ -- before activation and never restart a label-authority binary afterward.
4
+ -- New sessions bind agent scope to canonical authenticated user authority.
5
+ -- Do not infer a user from historical end_user labels or promote old private
6
+ -- memory rows. Old sessions remain readable under their existing visibility;
7
+ -- a null scope cannot grant cross-tree user access. Their old labels and
8
+ -- session-scoped memory records are retained as historical data only.
9
+ SET LOCAL lock_timeout = '5s';
10
+ SET LOCAL statement_timeout = '5min';
11
+
12
+ DO $drain$
13
+ DECLARE
14
+ roles jsonb := nullif(current_setting('opengeni.migration_application_roles', true), '')::jsonb;
15
+ BEGIN
16
+ IF roles IS NULL OR jsonb_typeof(roles) <> 'array' THEN
17
+ RAISE EXCEPTION '0457 requires an explicit application database role list' USING ERRCODE = '55000';
18
+ END IF;
19
+ IF jsonb_array_length(roles) NOT BETWEEN 1 AND 16 OR EXISTS (
20
+ SELECT 1 FROM jsonb_array_elements(roles) item
21
+ WHERE jsonb_typeof(item) <> 'string' OR btrim(item #>> '{}') = ''
22
+ OR item #>> '{}' <> btrim(item #>> '{}') OR octet_length(item #>> '{}') > 63
23
+ ) THEN
24
+ RAISE EXCEPTION '0457 received invalid application database roles' USING ERRCODE = '55000';
25
+ END IF;
26
+ IF EXISTS (
27
+ SELECT 1 FROM pg_stat_activity activity
28
+ JOIN jsonb_array_elements_text(roles) role_name ON role_name = activity.usename
29
+ WHERE activity.datname = current_database() AND activity.pid <> pg_backend_pid()
30
+ ) THEN
31
+ RAISE EXCEPTION '0457 requires all application database sessions stopped' USING ERRCODE = '55000';
32
+ END IF;
33
+ END
34
+ $drain$;
35
+
36
+ ALTER TABLE sessions ADD COLUMN scope_subject_id text,
37
+ ADD CONSTRAINT sessions_scope_subject_id_check CHECK (
38
+ scope_subject_id IS NULL OR (
39
+ octet_length(scope_subject_id) BETWEEN 1 AND 1024
40
+ AND scope_subject_id ~ '^(user:|external_user:).+'
41
+ )
42
+ );
43
+
44
+ CREATE INDEX sessions_workspace_scope_subject_idx
45
+ ON sessions (workspace_id, scope_subject_id)
46
+ WHERE scope_subject_id IS NOT NULL;
@@ -0,0 +1,36 @@
1
+ -- deployment-mode: rolling
2
+ -- No pending cards or historical answers are rewritten. Compatibility applies
3
+ -- only to an explicit Save/Don't save answer admitted by the canonical human
4
+ -- path, with all existing immutable-receipt, tenant, turn and revision fences.
5
+ SET LOCAL lock_timeout = '5s';
6
+ SET LOCAL statement_timeout = '1min';
7
+
8
+ DO $patch$
9
+ DECLARE
10
+ definition text := pg_get_functiondef('skill_apply_lifecycle(uuid,uuid,jsonb,jsonb)'::regprocedure);
11
+ anchor text;
12
+ replacement text;
13
+ BEGIN
14
+ -- The old generic choice serializer forced Other on. It is not consent:
15
+ -- retain a typed boolean shape, and independently forbid Other in the answer.
16
+ anchor := $old$AND coalesce(h.questions->0->'allowOther','false'::jsonb)='false'::jsonb$old$;
17
+ replacement := $new$AND coalesce(h.questions->0->'allowOther','false'::jsonb) IN ('false'::jsonb,'true'::jsonb)
18
+ AND (answer->'other' IS NULL OR answer->'other'='null'::jsonb OR answer->'other'='""'::jsonb)$new$;
19
+ IF (length(definition)-length(replace(definition,anchor,'')))/length(anchor) <> 1 THEN
20
+ RAISE EXCEPTION '0458 Skill Other compatibility anchor mismatch';
21
+ END IF;
22
+ definition := replace(definition,anchor,replacement);
23
+
24
+ -- Null optional descriptions are the same absent presentation value. Keep
25
+ -- exact option count/order/ids/labels and reject every non-null extra field.
26
+ anchor := $old$AND h.questions->0->'options'='[{"id":"save","label":"Save"},{"id":"skip","label":"Don''t save"}]'::jsonb$old$;
27
+ replacement := $new$AND jsonb_array_length(h.questions->0->'options')=2
28
+ AND ((h.questions->0->'options'->0) - 'description')='{"id":"save","label":"Save"}'::jsonb
29
+ AND ((h.questions->0->'options'->1) - 'description')='{"id":"skip","label":"Don''t save"}'::jsonb
30
+ AND coalesce(h.questions->0->'options'->0->'description','null'::jsonb)='null'::jsonb
31
+ AND coalesce(h.questions->0->'options'->1->'description','null'::jsonb)='null'::jsonb$new$;
32
+ IF (length(definition)-length(replace(definition,anchor,'')))/length(anchor) <> 1 THEN
33
+ RAISE EXCEPTION '0458 Skill option compatibility anchor mismatch';
34
+ END IF;
35
+ EXECUTE replace(definition,anchor,replacement);
36
+ END $patch$;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/db",
3
- "version": "4.2.2",
3
+ "version": "4.3.2-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": {
@@ -94,11 +94,11 @@
94
94
  "prepublishOnly": "bash ../../scripts/prepublish-guard"
95
95
  },
96
96
  "dependencies": {
97
- "@opengeni/codemode": "^0.5.3",
98
- "@opengeni/codex": "^0.2.22",
99
- "@opengeni/config": "^1.0.4",
100
- "@opengeni/contracts": "^2.15.2",
101
- "@opengeni/network": "^0.3.1",
97
+ "@opengeni/codemode": "^0.5.5-canary.0",
98
+ "@opengeni/codex": "^0.2.22-canary.7",
99
+ "@opengeni/config": "^1.1.1-canary.0",
100
+ "@opengeni/contracts": "^3.0.1-canary.0",
101
+ "@opengeni/network": "^0.3.1-canary.7",
102
102
  "drizzle-orm": "^0.45.2",
103
103
  "postgres": "^3.4.7"
104
104
  },