@opengeni/db 5.0.1 → 6.0.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 +0 -2
- package/dist/canonical-human-identities.d.ts +4 -0
- package/dist/canonical-human-identities.js +3 -3
- package/dist/capability-components.d.ts +5 -13
- package/dist/capability-integrations.d.ts +23 -8
- package/dist/capability-reconciliation.d.ts +5 -0
- package/dist/capability-reconciliation.js +21 -0
- package/dist/capability-reconciliation.js.map +1 -0
- package/dist/{chunk-6T2GGYWE.js → chunk-4UJCXBR2.js} +4 -5800
- package/dist/chunk-4UJCXBR2.js.map +1 -0
- package/dist/{chunk-MWMXWHJD.js → chunk-7JCDSMX2.js} +2 -2
- package/dist/chunk-AJA2ZIUW.js +219 -0
- package/dist/chunk-AJA2ZIUW.js.map +1 -0
- package/dist/chunk-CDAT6EKW.js +247 -0
- package/dist/chunk-CDAT6EKW.js.map +1 -0
- package/dist/{chunk-DPONPYJD.js → chunk-CJI6G254.js} +70 -18
- package/dist/chunk-CJI6G254.js.map +1 -0
- package/dist/chunk-G32NVMRJ.js +276 -0
- package/dist/chunk-G32NVMRJ.js.map +1 -0
- package/dist/chunk-GABXCG3N.js +5811 -0
- package/dist/chunk-GABXCG3N.js.map +1 -0
- package/dist/{chunk-ERKVSPDX.js → chunk-LVY33EWL.js} +3 -3
- package/dist/{chunk-45CP2YKY.js → chunk-MAMBI2HR.js} +9 -3
- package/dist/{chunk-45CP2YKY.js.map → chunk-MAMBI2HR.js.map} +1 -1
- package/dist/{chunk-F73UKJY6.js → chunk-MN5PZMEK.js} +91 -108
- package/dist/chunk-MN5PZMEK.js.map +1 -0
- package/dist/chunk-PJI4MUZ7.js +131 -0
- package/dist/chunk-PJI4MUZ7.js.map +1 -0
- package/dist/{chunk-T6BWIGWJ.js → chunk-RIYHP2WU.js} +2 -2
- package/dist/{chunk-7HMK3B45.js → chunk-RL2MUXM3.js} +16 -7
- package/dist/chunk-RL2MUXM3.js.map +1 -0
- package/dist/{chunk-HQDVI2DB.js → chunk-SYKHLPTK.js} +4 -3
- package/dist/{chunk-HQDVI2DB.js.map → chunk-SYKHLPTK.js.map} +1 -1
- package/dist/{chunk-T6YLHJLS.js → chunk-XIXVVBW2.js} +198 -51
- package/dist/chunk-XIXVVBW2.js.map +1 -0
- package/dist/chunk-ZPJ6V2VG.js +156 -0
- package/dist/chunk-ZPJ6V2VG.js.map +1 -0
- package/dist/codex-capacity-recovery.d.ts +12 -0
- package/dist/codex-failure-eligibility.d.ts +9 -0
- package/dist/company-profile-schema.d.ts +4 -2
- package/dist/connect-attempts.d.ts +15 -0
- package/dist/connect-attempts.js +25 -0
- package/dist/connect-attempts.js.map +1 -0
- package/dist/connection-authority.d.ts +7 -63
- package/dist/connection-refresh-lock.d.ts +4 -0
- package/dist/connection-token-resolver.d.ts +8 -43
- package/dist/database-timing.d.ts +10 -0
- package/dist/database.d.ts +4 -0
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.d.ts +9 -0
- package/dist/editable-artifacts.js +7 -5
- package/dist/index.d.ts +447 -78
- package/dist/index.js +12705 -12872
- package/dist/index.js.map +1 -1
- package/dist/insights-usage-bundle.d.ts +3 -0
- package/dist/integration-bindings.d.ts +1 -1
- package/dist/integration-facets.d.ts +18 -1
- package/dist/knowledge-entries-schema.d.ts +2 -0
- package/dist/knowledge-entries.d.ts +1 -0
- package/dist/knowledge-indexing.d.ts +1 -0
- package/dist/live-session-attempt.d.ts +35 -137
- package/dist/managed-auth-session-sets.js +3 -3
- package/dist/mcp-account-bindings.d.ts +4 -0
- package/dist/mcp-oauth.d.ts +11 -0
- package/dist/mcp-operations.js +2 -2
- package/dist/migrate.js +1 -1
- package/dist/organization-integration-policy.d.ts +51 -0
- package/dist/organization-integration-policy.js +18 -0
- package/dist/organization-integration-policy.js.map +1 -0
- package/dist/organization-usage.d.ts +20 -0
- package/dist/plugin-packages.d.ts +9 -12
- package/dist/pr-review.d.ts +0 -11
- package/dist/preference-registry-schema.d.ts +17 -0
- package/dist/provision-roles.js +1 -1
- package/dist/retained-provider-commands.d.ts +124 -6
- package/dist/retained-provider-commands.js +29 -97
- package/dist/retained-provider-commands.js.map +1 -1
- package/dist/retired-session-create-metadata.d.ts +4 -0
- package/dist/runtime-posture.d.ts +7 -5
- package/dist/schema.d.ts +876 -656
- package/dist/schema.js +5 -7
- package/dist/session-admission-block.d.ts +32 -0
- package/dist/session-attempt-writers.d.ts +4 -0
- package/dist/session-background-commands.js +5 -4
- package/dist/session-command-output.js +8 -126
- package/dist/session-command-output.js.map +1 -1
- package/dist/session-control.d.ts +5 -1
- package/dist/session-event-slices.d.ts +10 -0
- package/dist/session-event-slices.js +8 -222
- package/dist/session-event-slices.js.map +1 -1
- package/dist/session-file-attachments.d.ts +28 -0
- package/dist/session-goal-reports.d.ts +43 -0
- package/dist/session-mcp-approval.d.ts +4 -0
- package/dist/session-mcp-credential-rotation.js +5 -4
- package/dist/session-mcp-credential-rotation.js.map +1 -1
- package/dist/session-message-search.d.ts +22 -0
- package/dist/session-queue-commands.d.ts +2 -6
- package/dist/session-retry.d.ts +36 -0
- package/dist/session-tenancy.js +3 -3
- package/dist/skill-source-release-impact.d.ts +18 -0
- package/dist/skill-source-release.d.ts +9 -0
- package/dist/skills.d.ts +7 -1
- package/dist/user-resource-authority.d.ts +0 -8
- package/dist/video-generation.js +3 -3
- package/dist/workspace-tool-defaults.js +5 -4
- package/dist/workspace-tool-defaults.js.map +1 -1
- package/drizzle/0468_knowledge_relationship_projection.sql +30 -0
- package/drizzle/0469_knowledge_source_discovery.sql +85 -0
- package/drizzle/0470_organization_integration_policy.sql +151 -0
- package/drizzle/0471_usage_events_account_recent_index.sql +4 -0
- package/drizzle/0472_usage_events_workspace_recent_index.sql +4 -0
- package/drizzle/0473_organization_usage_analytical_capability.sql +237 -0
- package/drizzle/0474_goal_report_requirements.sql +156 -0
- package/drizzle/0475_session_event_history_statistics.sql +15 -0
- package/drizzle/0476_session_event_history_policy_planning.sql +29 -0
- package/drizzle/0477_managed_sign_in_methods.sql +303 -0
- package/drizzle/0478_sender_owned_connections.sql +2772 -0
- package/drizzle/0479_connection_create_idempotency.sql +16 -0
- package/drizzle/0480_private_session_organization_key_administration.sql +154 -0
- package/drizzle/0481_personal_connection_visibility_cleanup.sql +45 -0
- package/drizzle/0482_remove_packs.sql +417 -0
- package/drizzle/0483_preclaim_admission_block.sql +85 -0
- package/drizzle/0484_insights_usage_projection.sql +211 -0
- package/drizzle/0485_inherited_mcp_approval_policies.sql +6 -0
- package/drizzle/0486_reasoning_configuration_message_forks.sql +35 -0
- package/drizzle/0487_skill_catalog_message_forks.sql +26 -0
- package/drizzle/0488_permanent_skill_removal.sql +207 -0
- package/drizzle/0489_modal_command_byte_offsets.sql +17 -0
- package/drizzle/0490_periodic_capture_attempt_cadence.sql +6 -0
- package/drizzle/0491_warm_capture_holder_reclamation.sql +57 -0
- package/drizzle/0492_codex_accepted_source_authority.sql +249 -0
- package/drizzle/0493_stopping_command_error_containment.sql +58 -0
- package/drizzle/0494_mcp_account_bindings.sql +334 -0
- package/drizzle/0495_consented_sandbox_recovery.sql +220 -0
- package/drizzle/0496_supervised_command_settlement.sql +386 -0
- package/drizzle/0497_modal_inventory_owner_read_capability.sql +112 -0
- package/drizzle/0498_enrollment_membership_fence.sql +87 -0
- package/drizzle/0499_session_attachment_access.sql +224 -0
- package/drizzle/0500_session_execution_epoch.sql +37 -0
- package/drizzle/0501_session_sharing_execution.sql +591 -0
- package/drizzle/0502_active_message_boundary_forks.sql +30 -0
- package/package.json +17 -5
- package/src/automations.ts +17 -73
- package/src/canonical-human-identities.ts +17 -5
- package/src/capability-components.ts +7 -24
- package/src/capability-integrations.ts +301 -21
- package/src/capability-reconciliation.ts +19 -0
- package/src/codex-capacity-recovery.ts +47 -0
- package/src/codex-failure-eligibility.ts +93 -0
- package/src/connect-attempts.ts +49 -18
- package/src/connection-authority.ts +12 -158
- package/src/connection-refresh-lock.ts +22 -0
- package/src/connection-token-resolver.ts +58 -530
- package/src/database-timing.ts +43 -0
- package/src/database.ts +61 -26
- package/src/editable-artifacts.ts +1 -1
- package/src/index.ts +3578 -1273
- package/src/insights-model-bundle.ts +19 -7
- package/src/insights-usage-bundle.ts +16 -19
- package/src/integration-bindings.ts +2 -7
- package/src/integration-facets.ts +73 -6
- package/src/knowledge-entries.ts +8 -1
- package/src/live-session-attempt.ts +0 -22
- package/src/mcp-account-bindings.ts +12 -0
- package/src/mcp-oauth.ts +26 -0
- package/src/organization-integration-policy.ts +235 -0
- package/src/organization-usage.ts +83 -0
- package/src/plugin-packages.ts +226 -42
- package/src/pr-review.ts +7 -69
- package/src/preference-registry-schema.ts +1 -0
- package/src/provision-roles.ts +50 -37
- package/src/retained-provider-commands.ts +286 -9
- package/src/retired-session-create-metadata.ts +16 -0
- package/src/runtime-posture.ts +217 -13
- package/src/schema.ts +104 -120
- package/src/session-admission-block.ts +29 -0
- package/src/session-attempt-writers.ts +48 -0
- package/src/session-control.ts +21 -5
- package/src/session-event-slices.ts +76 -26
- package/src/session-file-attachments.ts +66 -0
- package/src/session-goal-reports.ts +255 -0
- package/src/session-mcp-approval.ts +30 -0
- package/src/session-message-search.ts +348 -0
- package/src/session-queue-commands.ts +60 -36
- package/src/session-retry.ts +319 -0
- package/src/skill-config-migration.ts +11 -2
- package/src/skill-source-release-impact.ts +33 -0
- package/src/skill-source-release.ts +67 -26
- package/src/skills.ts +44 -6
- package/src/user-resource-authority.ts +0 -56
- package/dist/chunk-6T2GGYWE.js.map +0 -1
- package/dist/chunk-7HMK3B45.js.map +0 -1
- package/dist/chunk-DPONPYJD.js.map +0 -1
- package/dist/chunk-F73UKJY6.js.map +0 -1
- package/dist/chunk-T6YLHJLS.js.map +0 -1
- package/dist/host-mcp-binding-match.d.ts +0 -5
- package/dist/host-mcp-bindings.d.ts +0 -79
- package/dist/host-mcp-resolvers.d.ts +0 -47
- package/dist/host-mcp-task-authority.d.ts +0 -43
- package/dist/host-selection-identity.d.ts +0 -6
- package/dist/pack-components.d.ts +0 -83
- package/dist/pack-installations.d.ts +0 -86
- package/src/host-mcp-binding-match.ts +0 -34
- package/src/host-mcp-bindings.ts +0 -777
- package/src/host-mcp-resolvers.ts +0 -266
- package/src/host-mcp-task-authority.ts +0 -384
- package/src/host-selection-identity.ts +0 -29
- package/src/pack-components.ts +0 -980
- package/src/pack-installations.ts +0 -950
- /package/dist/{chunk-MWMXWHJD.js.map → chunk-7JCDSMX2.js.map} +0 -0
- /package/dist/{chunk-ERKVSPDX.js.map → chunk-LVY33EWL.js.map} +0 -0
- /package/dist/{chunk-T6BWIGWJ.js.map → chunk-RIYHP2WU.js.map} +0 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Additive storage only. Old workers cannot honor a parked admission: complete
|
|
3
|
+
-- the worker rollout before relying on this fence (see docs/run-lifecycle.md).
|
|
4
|
+
SET LOCAL lock_timeout = '5s';
|
|
5
|
+
SET LOCAL statement_timeout = '1min';
|
|
6
|
+
ALTER TABLE sessions ADD COLUMN admission_block jsonb;
|
|
7
|
+
ALTER TABLE sessions ADD CONSTRAINT sessions_admission_block_shape CHECK (
|
|
8
|
+
admission_block IS NULL OR (
|
|
9
|
+
jsonb_typeof(admission_block) = 'object'
|
|
10
|
+
AND admission_block ?& ARRAY['reason','sqlState','retryPolicy','blockedAt','attemptId','fence','previousStatus']
|
|
11
|
+
AND admission_block - ARRAY['reason','sqlState','retryPolicy','blockedAt','attemptId','fence','previousStatus'] = '{}'::jsonb
|
|
12
|
+
AND admission_block->>'previousStatus' IN ('queued','idle','running','recovering','waiting_capacity','requires_action')
|
|
13
|
+
AND admission_block->>'reason' IN ('database_claim_rejected','initiator_membership_required','personal_resource_grant_required')
|
|
14
|
+
AND admission_block->>'retryPolicy' = 'explicit_recheck'
|
|
15
|
+
AND (admission_block->'sqlState' = 'null'::jsonb OR admission_block->>'sqlState' ~ '^[0-9A-Z]{5}$')
|
|
16
|
+
AND jsonb_typeof(admission_block->'blockedAt') = 'string'
|
|
17
|
+
AND jsonb_typeof(admission_block->'attemptId') = 'string'
|
|
18
|
+
AND admission_block->>'attemptId' ~ '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
|
|
19
|
+
AND jsonb_typeof(admission_block->'fence') = 'object'
|
|
20
|
+
AND admission_block->'fence' ?& ARRAY['lastSequence','controlVersion']
|
|
21
|
+
AND (admission_block->'fence') - ARRAY['lastSequence','controlVersion'] = '{}'::jsonb
|
|
22
|
+
AND jsonb_typeof(admission_block#>'{fence,lastSequence}') = 'number'
|
|
23
|
+
AND jsonb_typeof(admission_block#>'{fence,controlVersion}') = 'number'
|
|
24
|
+
AND admission_block#>>'{fence,lastSequence}' ~ '^[0-9]+$'
|
|
25
|
+
AND admission_block#>>'{fence,controlVersion}' ~ '^[0-9]+$'
|
|
26
|
+
) IS TRUE
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
-- Older application binaries do not read the new field. They may continue
|
|
30
|
+
-- retrying until rolled out, but cannot admit an attempt through a durable block.
|
|
31
|
+
CREATE FUNCTION guard_session_admission_block() RETURNS trigger
|
|
32
|
+
LANGUAGE plpgsql SET search_path FROM CURRENT AS $body$
|
|
33
|
+
BEGIN
|
|
34
|
+
IF EXISTS (SELECT 1 FROM sessions s WHERE s.id = NEW.session_id
|
|
35
|
+
AND s.workspace_id = NEW.workspace_id AND s.account_id = NEW.account_id
|
|
36
|
+
AND s.admission_block IS NOT NULL) THEN
|
|
37
|
+
RAISE EXCEPTION 'session admission requires an explicit recheck' USING ERRCODE = 'OG003';
|
|
38
|
+
END IF;
|
|
39
|
+
RETURN NEW;
|
|
40
|
+
END
|
|
41
|
+
$body$;
|
|
42
|
+
REVOKE ALL ON FUNCTION guard_session_admission_block() FROM PUBLIC;
|
|
43
|
+
CREATE TRIGGER session_attempt_admission_block BEFORE INSERT ON session_turn_attempts
|
|
44
|
+
FOR EACH ROW EXECUTE FUNCTION guard_session_admission_block();
|
|
45
|
+
|
|
46
|
+
-- Give only these two reviewed guards stable typed causes. Never infer their
|
|
47
|
+
-- meaning from arbitrary P0002/42501 errors or parse driver error messages.
|
|
48
|
+
-- A temporary installer keeps quoted function source distinct from migration
|
|
49
|
+
-- data preflights. This reads pg_proc only; no membership row is read or changed.
|
|
50
|
+
CREATE FUNCTION pg_temp.install_typed_admission_causes() RETURNS void
|
|
51
|
+
LANGUAGE plpgsql AS $typed_admission_causes$
|
|
52
|
+
DECLARE
|
|
53
|
+
function_oid regprocedure := pg_catalog.to_regprocedure(pg_catalog.format(
|
|
54
|
+
'%I.admit_session_attempt_personal_resources()', current_schema()));
|
|
55
|
+
definition text;
|
|
56
|
+
membership_selection constant text := $selection$ SELECT membership.* INTO STRICT member_row
|
|
57
|
+
FROM organization_memberships membership
|
|
58
|
+
WHERE membership.account_id = NEW.account_id
|
|
59
|
+
AND membership.subject_id = initiating_subject
|
|
60
|
+
AND membership.status = 'active'
|
|
61
|
+
AND membership.revoked_at IS NULL
|
|
62
|
+
FOR SHARE;$selection$;
|
|
63
|
+
grant_rejection constant text := $rejection$RAISE EXCEPTION 'matching personal-resource grant required'
|
|
64
|
+
USING ERRCODE = '42501';$rejection$;
|
|
65
|
+
BEGIN
|
|
66
|
+
IF function_oid IS NULL THEN
|
|
67
|
+
RAISE EXCEPTION '0483 requires personal-resource admission' USING ERRCODE = '55000';
|
|
68
|
+
END IF;
|
|
69
|
+
definition := pg_catalog.pg_get_functiondef(function_oid);
|
|
70
|
+
IF (length(definition) - length(replace(definition, membership_selection, ''))) / length(membership_selection) <> 1
|
|
71
|
+
OR (length(definition) - length(replace(definition, grant_rejection, ''))) / length(grant_rejection) <> 1 THEN
|
|
72
|
+
RAISE EXCEPTION '0483 personal-resource guard source drift' USING ERRCODE = '55000';
|
|
73
|
+
END IF;
|
|
74
|
+
definition := replace(definition, membership_selection,
|
|
75
|
+
' BEGIN' || chr(10) || membership_selection || chr(10) ||
|
|
76
|
+
' EXCEPTION WHEN no_data_found THEN' || chr(10) ||
|
|
77
|
+
' RAISE EXCEPTION ''personal-resource initiating membership required'' USING ERRCODE = ''OG001'';' || chr(10) ||
|
|
78
|
+
' END;');
|
|
79
|
+
definition := replace(definition, grant_rejection,
|
|
80
|
+
replace(grant_rejection, '''42501''', '''OG002'''));
|
|
81
|
+
EXECUTE definition;
|
|
82
|
+
END
|
|
83
|
+
$typed_admission_causes$;
|
|
84
|
+
SELECT pg_temp.install_typed_admission_causes();
|
|
85
|
+
DROP FUNCTION pg_temp.install_typed_admission_causes();
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- The bundle consumes four columns. A PL/pgSQL SETOF usage_events function
|
|
3
|
+
-- constructs/spills the entire row before its caller can project those columns.
|
|
4
|
+
-- Keep the 0359 authorization/window/filter/capability protocol unchanged and
|
|
5
|
+
-- expose a separate narrow result so existing full-row callers keep working.
|
|
6
|
+
DO $insights_usage_projection$
|
|
7
|
+
DECLARE
|
|
8
|
+
data_schema text := pg_catalog.current_schema();
|
|
9
|
+
BEGIN
|
|
10
|
+
EXECUTE pg_catalog.format($ddl$
|
|
11
|
+
CREATE FUNCTION opengeni_private.visible_workspace_insights_usage_projection(
|
|
12
|
+
p_workspace_id uuid,
|
|
13
|
+
p_since timestamp with time zone,
|
|
14
|
+
p_until timestamp with time zone,
|
|
15
|
+
p_event_types text[]
|
|
16
|
+
) RETURNS TABLE (
|
|
17
|
+
event_type text,
|
|
18
|
+
quantity bigint,
|
|
19
|
+
occurred_at timestamp with time zone,
|
|
20
|
+
source_resource_id text
|
|
21
|
+
)
|
|
22
|
+
LANGUAGE plpgsql
|
|
23
|
+
VOLATILE
|
|
24
|
+
SECURITY DEFINER
|
|
25
|
+
SET search_path = pg_catalog, %1$I, opengeni_private, pg_temp
|
|
26
|
+
AS $function$
|
|
27
|
+
DECLARE
|
|
28
|
+
context_account_id uuid;
|
|
29
|
+
context_workspace_id uuid;
|
|
30
|
+
context_subject_id text;
|
|
31
|
+
context_initiating_human_subject_id text;
|
|
32
|
+
BEGIN
|
|
33
|
+
PERFORM opengeni_private.session_variable_set_attachments_protocol_v1_active();
|
|
34
|
+
BEGIN
|
|
35
|
+
context_account_id := nullif(
|
|
36
|
+
pg_catalog.current_setting('opengeni.account_id', true), ''
|
|
37
|
+
)::uuid;
|
|
38
|
+
context_workspace_id := nullif(
|
|
39
|
+
pg_catalog.current_setting('opengeni.workspace_id', true), ''
|
|
40
|
+
)::uuid;
|
|
41
|
+
EXCEPTION WHEN invalid_text_representation THEN
|
|
42
|
+
RAISE EXCEPTION 'Insights RLS context is malformed'
|
|
43
|
+
USING ERRCODE = '42501';
|
|
44
|
+
END;
|
|
45
|
+
context_subject_id := nullif(
|
|
46
|
+
pg_catalog.current_setting('opengeni.subject_id', true), ''
|
|
47
|
+
);
|
|
48
|
+
context_initiating_human_subject_id := nullif(
|
|
49
|
+
pg_catalog.current_setting('opengeni.initiating_human_subject_id', true), ''
|
|
50
|
+
);
|
|
51
|
+
IF context_account_id IS NULL
|
|
52
|
+
OR context_workspace_id IS NULL
|
|
53
|
+
OR context_workspace_id IS DISTINCT FROM p_workspace_id
|
|
54
|
+
THEN
|
|
55
|
+
RAISE EXCEPTION 'Insights RLS context does not match the requested workspace'
|
|
56
|
+
USING ERRCODE = '42501';
|
|
57
|
+
END IF;
|
|
58
|
+
IF p_since IS NULL
|
|
59
|
+
OR p_until IS NULL
|
|
60
|
+
OR p_since IN (
|
|
61
|
+
'-infinity'::timestamp with time zone,
|
|
62
|
+
'infinity'::timestamp with time zone
|
|
63
|
+
)
|
|
64
|
+
OR p_until = '-infinity'::timestamp with time zone
|
|
65
|
+
OR p_until < p_since
|
|
66
|
+
OR (
|
|
67
|
+
p_until <> 'infinity'::timestamp with time zone
|
|
68
|
+
AND p_until - p_since > interval '370 days'
|
|
69
|
+
)
|
|
70
|
+
OR (
|
|
71
|
+
p_until = 'infinity'::timestamp with time zone
|
|
72
|
+
AND p_since < CURRENT_TIMESTAMP - interval '370 days'
|
|
73
|
+
)
|
|
74
|
+
THEN
|
|
75
|
+
RAISE EXCEPTION 'Insights fact window must be non-negative and at most 370 days'
|
|
76
|
+
USING ERRCODE = '22023';
|
|
77
|
+
END IF;
|
|
78
|
+
IF p_event_types IS NULL
|
|
79
|
+
OR pg_catalog.array_ndims(p_event_types) <> 1
|
|
80
|
+
OR pg_catalog.cardinality(p_event_types) < 1
|
|
81
|
+
OR pg_catalog.cardinality(p_event_types) > 16
|
|
82
|
+
OR EXISTS (
|
|
83
|
+
SELECT 1
|
|
84
|
+
FROM pg_catalog.unnest(p_event_types) event_type(value)
|
|
85
|
+
WHERE event_type.value IS NULL
|
|
86
|
+
OR pg_catalog.btrim(event_type.value) = ''
|
|
87
|
+
OR pg_catalog.octet_length(event_type.value) > 256
|
|
88
|
+
)
|
|
89
|
+
OR (
|
|
90
|
+
SELECT pg_catalog.count(DISTINCT event_type.value COLLATE "C")
|
|
91
|
+
FROM pg_catalog.unnest(p_event_types) event_type(value)
|
|
92
|
+
) <> pg_catalog.cardinality(p_event_types)
|
|
93
|
+
THEN
|
|
94
|
+
RAISE EXCEPTION 'Insights usage event types must contain 1-16 unique bounded values'
|
|
95
|
+
USING ERRCODE = '22023';
|
|
96
|
+
END IF;
|
|
97
|
+
IF p_until = p_since THEN
|
|
98
|
+
RETURN;
|
|
99
|
+
END IF;
|
|
100
|
+
|
|
101
|
+
INSERT INTO opengeni_private.insights_fact_read_runtime_capabilities (
|
|
102
|
+
backend_pid,
|
|
103
|
+
transaction_id,
|
|
104
|
+
capability_kind,
|
|
105
|
+
account_id,
|
|
106
|
+
workspace_id,
|
|
107
|
+
subject_id,
|
|
108
|
+
initiating_human_subject_id
|
|
109
|
+
) VALUES (
|
|
110
|
+
pg_catalog.pg_backend_pid(),
|
|
111
|
+
pg_catalog.pg_current_xact_id(),
|
|
112
|
+
'usage_events',
|
|
113
|
+
context_account_id,
|
|
114
|
+
context_workspace_id,
|
|
115
|
+
context_subject_id,
|
|
116
|
+
context_initiating_human_subject_id
|
|
117
|
+
);
|
|
118
|
+
BEGIN
|
|
119
|
+
RETURN QUERY
|
|
120
|
+
WITH visible_sessions AS MATERIALIZED (
|
|
121
|
+
SELECT
|
|
122
|
+
id, account_id, workspace_id, visibility,
|
|
123
|
+
owner_organization_membership_id, owner_subject_id
|
|
124
|
+
FROM %1$I.sessions
|
|
125
|
+
WHERE account_id = context_account_id
|
|
126
|
+
AND workspace_id = p_workspace_id
|
|
127
|
+
)
|
|
128
|
+
SELECT usage_row.event_type, usage_row.quantity,
|
|
129
|
+
usage_row.occurred_at, usage_row.source_resource_id
|
|
130
|
+
FROM %1$I.usage_events usage_row
|
|
131
|
+
LEFT JOIN visible_sessions session_row
|
|
132
|
+
ON session_row.account_id = usage_row.account_id
|
|
133
|
+
AND session_row.workspace_id = usage_row.workspace_id
|
|
134
|
+
AND session_row.id = usage_row.session_id
|
|
135
|
+
WHERE usage_row.account_id = context_account_id
|
|
136
|
+
AND usage_row.workspace_id = p_workspace_id
|
|
137
|
+
AND usage_row.event_type = ANY (p_event_types)
|
|
138
|
+
AND usage_row.occurred_at >= p_since
|
|
139
|
+
AND usage_row.occurred_at < p_until
|
|
140
|
+
AND (
|
|
141
|
+
usage_row.session_id IS NULL
|
|
142
|
+
OR (
|
|
143
|
+
session_row.id IS NOT NULL
|
|
144
|
+
AND (
|
|
145
|
+
context_subject_id IS NULL
|
|
146
|
+
OR session_row.visibility = 'workspace_shared'
|
|
147
|
+
OR %1$I.session_private_actor_visible(
|
|
148
|
+
session_row.account_id,
|
|
149
|
+
session_row.workspace_id,
|
|
150
|
+
session_row.owner_organization_membership_id,
|
|
151
|
+
session_row.owner_subject_id
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
)
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
DELETE FROM opengeni_private.insights_fact_read_runtime_capabilities
|
|
158
|
+
WHERE backend_pid = pg_catalog.pg_backend_pid()
|
|
159
|
+
AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
|
|
160
|
+
AND capability_kind = 'usage_events';
|
|
161
|
+
RETURN;
|
|
162
|
+
EXCEPTION WHEN OTHERS THEN
|
|
163
|
+
DELETE FROM opengeni_private.insights_fact_read_runtime_capabilities
|
|
164
|
+
WHERE backend_pid = pg_catalog.pg_backend_pid()
|
|
165
|
+
AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
|
|
166
|
+
AND capability_kind = 'usage_events';
|
|
167
|
+
RAISE;
|
|
168
|
+
END;
|
|
169
|
+
END
|
|
170
|
+
$function$;
|
|
171
|
+
REVOKE ALL ON FUNCTION opengeni_private.visible_workspace_insights_usage_projection(
|
|
172
|
+
uuid, timestamptz, timestamptz, text[]
|
|
173
|
+
) FROM PUBLIC;
|
|
174
|
+
$ddl$, data_schema);
|
|
175
|
+
END
|
|
176
|
+
$insights_usage_projection$;
|
|
177
|
+
|
|
178
|
+
DO $insights_usage_projection_acl$
|
|
179
|
+
DECLARE
|
|
180
|
+
role_name text;
|
|
181
|
+
data_schema text := pg_catalog.current_schema();
|
|
182
|
+
BEGIN
|
|
183
|
+
-- Strip owner-default grants before admitting only configured application
|
|
184
|
+
-- roles that already have the existing full-row analytical authority.
|
|
185
|
+
FOR role_name IN
|
|
186
|
+
SELECT DISTINCT r.rolname FROM pg_catalog.pg_proc p
|
|
187
|
+
CROSS JOIN LATERAL pg_catalog.aclexplode(
|
|
188
|
+
coalesce(p.proacl, pg_catalog.acldefault('f', p.proowner))
|
|
189
|
+
) privilege
|
|
190
|
+
JOIN pg_catalog.pg_roles r ON r.oid = privilege.grantee
|
|
191
|
+
WHERE p.oid = 'opengeni_private.visible_workspace_insights_usage_projection(uuid,timestamptz,timestamptz,text[])'::regprocedure
|
|
192
|
+
AND privilege.grantee <> p.proowner
|
|
193
|
+
LOOP
|
|
194
|
+
EXECUTE pg_catalog.format('REVOKE ALL ON FUNCTION opengeni_private.visible_workspace_insights_usage_projection(uuid,timestamptz,timestamptz,text[]) FROM %I', role_name);
|
|
195
|
+
END LOOP;
|
|
196
|
+
FOR role_name IN
|
|
197
|
+
SELECT r.rolname
|
|
198
|
+
FROM pg_catalog.jsonb_array_elements_text(
|
|
199
|
+
pg_catalog.current_setting('opengeni.migration_application_roles')::jsonb
|
|
200
|
+
) configured(value)
|
|
201
|
+
JOIN pg_catalog.pg_roles r ON r.rolname = configured.value
|
|
202
|
+
WHERE pg_catalog.has_function_privilege(r.rolname,
|
|
203
|
+
'opengeni_private.visible_workspace_insights_usage_events(uuid,timestamptz,timestamptz,text[])', 'EXECUTE')
|
|
204
|
+
AND pg_catalog.has_table_privilege(r.rolname, pg_catalog.format('%I.usage_events', data_schema), 'SELECT')
|
|
205
|
+
AND pg_catalog.has_table_privilege(r.rolname, pg_catalog.format('%I.sessions', data_schema), 'SELECT')
|
|
206
|
+
LOOP
|
|
207
|
+
EXECUTE pg_catalog.format('GRANT USAGE ON SCHEMA opengeni_private TO %I', role_name);
|
|
208
|
+
EXECUTE pg_catalog.format('GRANT EXECUTE ON FUNCTION opengeni_private.visible_workspace_insights_usage_projection(uuid,timestamptz,timestamptz,text[]) TO %I', role_name);
|
|
209
|
+
END LOOP;
|
|
210
|
+
END
|
|
211
|
+
$insights_usage_projection_acl$;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Session-local approval settings do not copy or grant capability definitions.
|
|
3
|
+
SET LOCAL lock_timeout = '5s';
|
|
4
|
+
SET LOCAL statement_timeout = '5min';
|
|
5
|
+
ALTER TABLE sessions ADD COLUMN mcp_approval_policies jsonb NOT NULL DEFAULT '{}'::jsonb
|
|
6
|
+
CHECK (jsonb_typeof(mcp_approval_policies) = 'object');
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Configuration controls occupy fractional positions but are not compaction.
|
|
3
|
+
-- Rewrite only the message-boundary overload's rejection predicate, retaining
|
|
4
|
+
-- ordered JSON copying and all authority/provenance checks installed previously.
|
|
5
|
+
DO $migration$
|
|
6
|
+
DECLARE
|
|
7
|
+
candidate oid;
|
|
8
|
+
definition text;
|
|
9
|
+
updated text;
|
|
10
|
+
BEGIN
|
|
11
|
+
SELECT oid INTO STRICT candidate FROM pg_proc
|
|
12
|
+
WHERE proname = 'fork_session_content'
|
|
13
|
+
AND pronamespace = current_schema()::regnamespace
|
|
14
|
+
AND pronargs = 11;
|
|
15
|
+
definition := pg_get_functiondef(candidate);
|
|
16
|
+
updated := replace(definition,
|
|
17
|
+
'history.position <> trunc(history.position)',
|
|
18
|
+
$predicate$(history.position <> trunc(history.position) AND (
|
|
19
|
+
history.item ->> 'type' = 'unknown'
|
|
20
|
+
AND history.item #> '{opengeniReasoningConfiguration,version}' = '1'::jsonb
|
|
21
|
+
AND history.item #>> '{opengeniReasoningConfiguration,baselineEffort}'
|
|
22
|
+
IN ('low', 'medium', 'high', 'xhigh', 'max')
|
|
23
|
+
AND history.item #>> '{opengeniReasoningConfiguration,effort}'
|
|
24
|
+
IN ('low', 'medium', 'high', 'xhigh', 'max')
|
|
25
|
+
AND jsonb_typeof(history.item #> '{opengeniReasoningConfiguration,turnId}') = 'string'
|
|
26
|
+
AND history.item #>> '{providerData,type}' = 'configuration_update'
|
|
27
|
+
AND history.item #>> '{providerData,reasoning,effort}' =
|
|
28
|
+
history.item #>> '{opengeniReasoningConfiguration,effort}'
|
|
29
|
+
) IS NOT TRUE)$predicate$);
|
|
30
|
+
IF updated = definition THEN
|
|
31
|
+
RAISE EXCEPTION 'reasoning configuration fork predicate rewrite did not match';
|
|
32
|
+
END IF;
|
|
33
|
+
EXECUTE updated;
|
|
34
|
+
END
|
|
35
|
+
$migration$;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Durable catalog updates precede accepted input at fractional positions.
|
|
3
|
+
-- Permit only their marked developer messages; keep compaction and authority
|
|
4
|
+
-- checks, ordered JSON copying, and reasoning-control validation unchanged.
|
|
5
|
+
DO $migration$
|
|
6
|
+
DECLARE candidate oid; definition text; updated text;
|
|
7
|
+
BEGIN
|
|
8
|
+
SELECT oid INTO STRICT candidate FROM pg_proc
|
|
9
|
+
WHERE proname = 'fork_session_content'
|
|
10
|
+
AND pronamespace = current_schema()::regnamespace AND pronargs = 11;
|
|
11
|
+
definition := pg_get_functiondef(candidate);
|
|
12
|
+
updated := replace(definition,
|
|
13
|
+
'history.position <> trunc(history.position) AND (',
|
|
14
|
+
$predicate$history.position <> trunc(history.position) AND NOT ((
|
|
15
|
+
history.item ->> 'type' = 'message'
|
|
16
|
+
AND history.item ->> 'role' = 'developer'
|
|
17
|
+
AND jsonb_typeof(history.item -> 'content') = 'string'
|
|
18
|
+
AND starts_with(history.item ->> 'content', E'<opengeni_skill_catalog>\n')
|
|
19
|
+
AND right(history.item ->> 'content', length(E'\n</opengeni_skill_catalog>')) = E'\n</opengeni_skill_catalog>'
|
|
20
|
+
) IS TRUE) AND ($predicate$);
|
|
21
|
+
IF updated = definition THEN
|
|
22
|
+
RAISE EXCEPTION 'skill catalog fork predicate rewrite did not match';
|
|
23
|
+
END IF;
|
|
24
|
+
EXECUTE updated;
|
|
25
|
+
END
|
|
26
|
+
$migration$;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
-- deployment-mode: maintenance
|
|
2
|
+
-- Drain old API/control/turn workers: old review UIs do not describe deletion.
|
|
3
|
+
SET LOCAL lock_timeout = '5s';
|
|
4
|
+
SET LOCAL statement_timeout = '1min';
|
|
5
|
+
DO $drain$
|
|
6
|
+
DECLARE roles jsonb := nullif(current_setting('opengeni.migration_application_roles',true),'')::jsonb;
|
|
7
|
+
BEGIN
|
|
8
|
+
IF roles IS NULL OR jsonb_typeof(roles)<>'array' OR jsonb_array_length(roles) NOT BETWEEN 1 AND 16
|
|
9
|
+
OR EXISTS(SELECT 1 FROM jsonb_array_elements(roles) r WHERE jsonb_typeof(r)<>'string' OR length(btrim(r#>>'{}')) NOT BETWEEN 1 AND 63)
|
|
10
|
+
THEN RAISE EXCEPTION 'Skill removal requires explicit application database roles' USING ERRCODE='55000'; END IF;
|
|
11
|
+
IF EXISTS(SELECT 1 FROM pg_stat_activity a JOIN jsonb_array_elements_text(roles) r ON a.usename=r.value
|
|
12
|
+
WHERE a.datname=current_database() AND a.pid<>pg_backend_pid())
|
|
13
|
+
THEN RAISE EXCEPTION 'Skill removal requires drained application sessions' USING ERRCODE='55000'; END IF;
|
|
14
|
+
END $drain$;
|
|
15
|
+
|
|
16
|
+
ALTER TABLE preference_registry_revisions ADD COLUMN IF NOT EXISTS skill_removal_operation_id uuid;
|
|
17
|
+
-- Keep content-free operation receipts: forgetting a create/save operation would
|
|
18
|
+
-- allow its delayed retry to recreate the deleted Skill. Referential cleanup
|
|
19
|
+
-- also reaches personal-Skill receipts written through another workspace.
|
|
20
|
+
ALTER TABLE skill_write_receipts DROP CONSTRAINT skill_write_receipts_activation_event_id_fkey;
|
|
21
|
+
ALTER TABLE skill_write_receipts ADD CONSTRAINT skill_write_receipts_activation_event_id_fkey
|
|
22
|
+
FOREIGN KEY (activation_event_id) REFERENCES preference_registry_events(id) ON DELETE SET NULL;
|
|
23
|
+
-- No content is copied to a tombstone. Existing foreign keys remain restrictive
|
|
24
|
+
-- for surviving cross-scope references; removal deletes only its scoped audit
|
|
25
|
+
-- rows that hold live references. Content-free removal receipts remain replayable.
|
|
26
|
+
|
|
27
|
+
-- The anchored function DDL below contains DML text. Keep the owner-visible
|
|
28
|
+
-- window explicit for the migration guard; no table data is changed here.
|
|
29
|
+
ALTER TABLE capability_facet_installations NO FORCE ROW LEVEL SECURITY;
|
|
30
|
+
ALTER TABLE preference_registry_events NO FORCE ROW LEVEL SECURITY;
|
|
31
|
+
ALTER TABLE preference_registry_preferences NO FORCE ROW LEVEL SECURITY;
|
|
32
|
+
ALTER TABLE preference_registry_revisions NO FORCE ROW LEVEL SECURITY;
|
|
33
|
+
ALTER TABLE skill_write_receipts NO FORCE ROW LEVEL SECURITY;
|
|
34
|
+
ALTER TABLE company_brain_preference_proposal_receipts NO FORCE ROW LEVEL SECURITY;
|
|
35
|
+
ALTER TABLE capability_component_owners NO FORCE ROW LEVEL SECURITY;
|
|
36
|
+
ALTER TABLE skill_source_bindings NO FORCE ROW LEVEL SECURITY;
|
|
37
|
+
DO $patch$
|
|
38
|
+
DECLARE definition text; anchor text; replacement text; pair jsonb;
|
|
39
|
+
BEGIN
|
|
40
|
+
definition := pg_get_functiondef('opengeni_private.guard_workspace_owned_skill_head_delete()'::regprocedure);
|
|
41
|
+
anchor := $a$IF TG_OP <> 'DELETE' OR pg_trigger_depth() <= 1 OR OLD.scope <> 'workspace' THEN$a$;
|
|
42
|
+
replacement := $r$IF TG_OP='DELETE'
|
|
43
|
+
AND current_user=pg_get_userbyid((SELECT relowner FROM pg_class WHERE oid=TG_RELID))
|
|
44
|
+
AND current_setting('opengeni.skill_remove_head',true)=OLD.id::text
|
|
45
|
+
AND nullif(current_setting('opengeni.account_id',true),'')::uuid=OLD.account_id
|
|
46
|
+
AND (OLD.scope='user' OR (OLD.scope='workspace' AND OLD.scope_workspace_id=nullif(current_setting('opengeni.workspace_id',true),'')::uuid))
|
|
47
|
+
THEN RETURN OLD; END IF;
|
|
48
|
+
IF TG_OP <> 'DELETE' OR pg_trigger_depth() <= 1 OR OLD.scope <> 'workspace' THEN$r$;
|
|
49
|
+
IF (length(definition)-length(replace(definition,anchor,'')))/length(anchor)<>1 THEN RAISE EXCEPTION 'Skill delete guard anchor mismatch'; END IF;
|
|
50
|
+
EXECUTE replace(definition,anchor,replacement);
|
|
51
|
+
|
|
52
|
+
definition := pg_get_functiondef('opengeni_private.guard_workspace_owned_skill_history_delete()'::regprocedure);
|
|
53
|
+
anchor := $a$IF TG_OP = 'DELETE' AND pg_trigger_depth() > 1 THEN RETURN OLD; END IF;$a$;
|
|
54
|
+
replacement := $r$IF TG_TABLE_NAME='skill_write_receipts' AND TG_OP='UPDATE' AND pg_trigger_depth()>1 THEN
|
|
55
|
+
IF OLD.activation_event_id IS NOT NULL AND NEW.activation_event_id IS NULL
|
|
56
|
+
AND (to_jsonb(NEW)-'activation_event_id')=(to_jsonb(OLD)-'activation_event_id')
|
|
57
|
+
THEN RETURN NEW; END IF;
|
|
58
|
+
END IF;
|
|
59
|
+
IF TG_OP='DELETE'
|
|
60
|
+
AND current_user=pg_get_userbyid((SELECT relowner FROM pg_class WHERE oid=TG_RELID))
|
|
61
|
+
AND nullif(current_setting('opengeni.skill_remove_head',true),'') IS NOT NULL
|
|
62
|
+
AND nullif(current_setting('opengeni.account_id',true),'')::uuid=OLD.account_id THEN
|
|
63
|
+
IF TG_TABLE_NAME='company_brain_preference_proposal_receipts' THEN
|
|
64
|
+
IF OLD.workspace_id=nullif(current_setting('opengeni.workspace_id',true),'')::uuid
|
|
65
|
+
AND OLD.preference_id::text=current_setting('opengeni.skill_remove_head',true) THEN RETURN OLD; END IF;
|
|
66
|
+
END IF;
|
|
67
|
+
END IF;
|
|
68
|
+
IF TG_OP = 'DELETE' AND pg_trigger_depth() > 1 THEN RETURN OLD; END IF;$r$;
|
|
69
|
+
IF (length(definition)-length(replace(definition,anchor,'')))/length(anchor)<>1 THEN RAISE EXCEPTION 'Skill audit guard anchor mismatch'; END IF;
|
|
70
|
+
EXECUTE replace(definition,anchor,replacement);
|
|
71
|
+
|
|
72
|
+
definition := pg_get_functiondef('skill_guard_legacy_activation()'::regprocedure);
|
|
73
|
+
anchor := 'RETURN NEW;';
|
|
74
|
+
replacement := $r$IF NEW.active_revision_id IS NOT NULL AND EXISTS(
|
|
75
|
+
SELECT 1 FROM preference_registry_revisions WHERE id=NEW.active_revision_id AND skill_removal_operation_id IS NOT NULL)
|
|
76
|
+
THEN RAISE EXCEPTION 'A removal proposal cannot be activated as Skill content' USING ERRCODE='42501'; END IF;
|
|
77
|
+
RETURN NEW;$r$;
|
|
78
|
+
IF (length(definition)-length(replace(definition,anchor,'')))/length(anchor)<>1 THEN RAISE EXCEPTION 'Skill activation guard anchor mismatch'; END IF;
|
|
79
|
+
EXECUTE replace(definition,anchor,replacement);
|
|
80
|
+
|
|
81
|
+
definition := pg_get_functiondef('skill_apply_lifecycle(uuid,uuid,jsonb,jsonb)'::regprocedure);
|
|
82
|
+
-- Exact anchored edits preserve the existing accepted Learning/actor resolver,
|
|
83
|
+
-- live attempt locks, operation replay, human proof and head/scope CAS.
|
|
84
|
+
FOR pair IN SELECT value FROM jsonb_array_elements(jsonb_build_array(
|
|
85
|
+
jsonb_build_array($a$'save','install','approve','reject','restore','confirm_response'$a$,
|
|
86
|
+
$r$'save','install','approve','reject','restore','confirm_response','remove'$r$),
|
|
87
|
+
jsonb_build_array($a$AND h.questions->0->>'label'='Save this Skill?'$a$,
|
|
88
|
+
$r$AND h.questions->0->>'label'=CASE WHEN skill_review ? 'removalOperationId' THEN 'Permanently delete this Skill?' ELSE 'Save this Skill?' END$r$),
|
|
89
|
+
jsonb_build_array($a$AND h.questions->0->>'helpText'='Review the complete files before saving. Saving activates this revision immediately.'$a$,
|
|
90
|
+
$r$AND h.questions->0->>'helpText'=CASE WHEN skill_review ? 'removalOperationId' THEN 'This cannot be undone. All stored Skill revisions will be deleted; conversations remain unchanged.' ELSE 'Review the complete files before saving. Saving activates this revision immediately.' END$r$),
|
|
91
|
+
jsonb_build_array($a$AND h.questions->0->>'prompt'='Save this exact Skill revision for this workspace?'$a$,
|
|
92
|
+
$r$AND h.questions->0->>'prompt'=CASE WHEN skill_review ? 'removalOperationId' THEN 'Permanently delete this Skill and all its stored revisions?' ELSE 'Save this exact Skill revision for this workspace?' END$r$),
|
|
93
|
+
jsonb_build_array($a$AND ((h.questions->0->'options'->0) - 'description')='{"id":"save","label":"Save"}'::jsonb$a$,
|
|
94
|
+
$r$AND ((h.questions->0->'options'->0) - 'description')=CASE WHEN skill_review ? 'removalOperationId' THEN '{"id":"save","label":"Permanently delete"}'::jsonb ELSE '{"id":"save","label":"Save"}'::jsonb END$r$),
|
|
95
|
+
jsonb_build_array($a$AND ((h.questions->0->'options'->1) - 'description')='{"id":"skip","label":"Don''t save"}'::jsonb$a$,
|
|
96
|
+
$r$AND ((h.questions->0->'options'->1) - 'description')=CASE WHEN skill_review ? 'removalOperationId' THEN '{"id":"skip","label":"Keep Skill"}'::jsonb ELSE '{"id":"skip","label":"Don''t save"}'::jsonb END$r$)
|
|
97
|
+
)) LOOP
|
|
98
|
+
anchor:=pair->>0; replacement:=pair->>1;
|
|
99
|
+
IF (length(definition)-length(replace(definition,anchor,'')))/length(anchor)<>1 THEN RAISE EXCEPTION 'Skill removal lifecycle anchor mismatch: %',anchor; END IF;
|
|
100
|
+
definition:=replace(definition,anchor,replacement);
|
|
101
|
+
END LOOP;
|
|
102
|
+
anchor := $a$ SELECT * INTO head FROM preference_registry_preferences h WHERE h.id=skill_id AND h.account_id=p_account_id FOR UPDATE;$a$;
|
|
103
|
+
replacement := $r$ -- Distribution removals acquire facet/owner rows before the Skill head.
|
|
104
|
+
-- NOWAIT also prevents a publication-lock/facet-lock cycle with installation.
|
|
105
|
+
-- Contention aborts atomically; the caller can retry the same operation.
|
|
106
|
+
IF operation='remove' OR p_request ? 'removalOperationId' THEN
|
|
107
|
+
PERFORM fi.id FROM capability_facet_installations fi JOIN skill_source_bindings b
|
|
108
|
+
ON b.skill_facet_id=fi.facet_id AND b.workspace_id=fi.workspace_id
|
|
109
|
+
WHERE b.preference_id=skill_id AND b.account_id=p_account_id AND b.workspace_id=p_workspace_id
|
|
110
|
+
ORDER BY fi.id FOR UPDATE OF fi NOWAIT;
|
|
111
|
+
PERFORM o.id FROM capability_component_owners o JOIN capability_facet_installations fi ON fi.id=o.facet_installation_id
|
|
112
|
+
JOIN skill_source_bindings b ON b.skill_facet_id=fi.facet_id AND b.workspace_id=fi.workspace_id
|
|
113
|
+
WHERE b.preference_id=skill_id AND b.account_id=p_account_id AND b.workspace_id=p_workspace_id
|
|
114
|
+
ORDER BY o.id FOR UPDATE OF o NOWAIT;
|
|
115
|
+
END IF;
|
|
116
|
+
SELECT * INTO head FROM preference_registry_preferences h WHERE h.id=skill_id AND h.account_id=p_account_id FOR UPDATE;$r$;
|
|
117
|
+
IF (length(definition)-length(replace(definition,anchor,'')))/length(anchor)<>1 THEN RAISE EXCEPTION 'Skill removal lock anchor mismatch'; END IF;
|
|
118
|
+
definition:=replace(definition,anchor,replacement);
|
|
119
|
+
anchor := $a$ IF outcome IS NULL THEN
|
|
120
|
+
IF operation IN ('approve','restore','reject') THEN$a$;
|
|
121
|
+
replacement := $r$ -- Marked proposals are never ordinary approvals/restores. The reviewer must
|
|
122
|
+
-- explicitly supply the immutable removal operation id displayed by the UI.
|
|
123
|
+
SELECT * INTO rev FROM preference_registry_revisions r
|
|
124
|
+
WHERE r.account_id=p_account_id AND r.preference_id=skill_id
|
|
125
|
+
AND r.id=(p_request->>'revisionId')::uuid;
|
|
126
|
+
IF rev.skill_removal_operation_id IS NOT NULL AND operation<>'remove' THEN
|
|
127
|
+
IF operation NOT IN ('approve','reject')
|
|
128
|
+
OR rev.skill_removal_operation_id IS DISTINCT FROM (p_request->>'removalOperationId')::uuid THEN
|
|
129
|
+
RAISE EXCEPTION 'Removal requires explicit exact deletion approval' USING ERRCODE='42501'; END IF;
|
|
130
|
+
SELECT * INTO confirmation_source FROM skill_write_receipts r
|
|
131
|
+
WHERE r.account_id=p_account_id AND r.workspace_id=p_workspace_id
|
|
132
|
+
AND r.operation_id=rev.skill_removal_operation_id;
|
|
133
|
+
IF NOT FOUND OR confirmation_source.receipt->>'outcome'<>'pending'
|
|
134
|
+
OR confirmation_source.receipt#>>'{skillReview,revisionId}' IS DISTINCT FROM rev.id::text
|
|
135
|
+
OR (confirmation_source.receipt#>>'{skillReview,expectedRevisionId}')::uuid IS DISTINCT FROM head.active_revision_id
|
|
136
|
+
OR (confirmation_source.receipt#>>'{skillReview,expectedScopeVersion}')::integer IS DISTINCT FROM head.scope_version
|
|
137
|
+
OR EXISTS(SELECT 1 FROM preference_registry_revisions r WHERE r.preference_id=skill_id AND r.revision>rev.revision)
|
|
138
|
+
OR EXISTS(SELECT 1 FROM preference_registry_events e WHERE e.preference_id=skill_id AND e.new_revision_id=rev.id AND e.type='rejected')
|
|
139
|
+
THEN RAISE EXCEPTION 'Skill removal proposal changed or settled' USING ERRCODE='40001'; END IF;
|
|
140
|
+
ELSIF p_request ? 'removalOperationId' THEN
|
|
141
|
+
RAISE EXCEPTION 'Removal approval does not match a removal proposal' USING ERRCODE='42501';
|
|
142
|
+
END IF;
|
|
143
|
+
IF operation='remove' OR rev.skill_removal_operation_id IS NOT NULL THEN
|
|
144
|
+
IF head.scope NOT IN ('workspace','user') THEN
|
|
145
|
+
RAISE EXCEPTION 'Permanent removal does not manage organization Skills' USING ERRCODE='42501'; END IF;
|
|
146
|
+
IF operation='remove' THEN
|
|
147
|
+
SELECT * INTO rev FROM preference_registry_revisions r WHERE r.preference_id=skill_id AND r.account_id=p_account_id
|
|
148
|
+
ORDER BY r.revision DESC LIMIT 1;
|
|
149
|
+
IF NOT FOUND THEN RAISE EXCEPTION 'Skill revision unavailable' USING ERRCODE='40001'; END IF;
|
|
150
|
+
END IF;
|
|
151
|
+
revision_id:=rev.id;
|
|
152
|
+
IF operation='remove' AND mode<>'automatic' THEN
|
|
153
|
+
-- A distinct immutable proposal binds the complete revision inventory.
|
|
154
|
+
INSERT INTO preference_registry_revisions(account_id,preference_id,title,description,content,content_hash,
|
|
155
|
+
conflict_strategy,provenance_source,provenance_source_id,trust,created_by_subject_id,
|
|
156
|
+
corrects_revision_id,skill_files,skill_activation_mode,skill_removal_operation_id)
|
|
157
|
+
VALUES(p_account_id,skill_id,rev.title,rev.description,rev.content,rev.content_hash,
|
|
158
|
+
'override','agent',p_actor->>'attemptId','untrusted_proposal',actor_subject,
|
|
159
|
+
head.active_revision_id,rev.skill_files,rev.skill_activation_mode,operation_id) RETURNING id INTO revision_id;
|
|
160
|
+
outcome:='pending';
|
|
161
|
+
ELSIF operation='reject' THEN
|
|
162
|
+
SELECT coalesce(max(e.version),0)+1 INTO next_event FROM preference_registry_events e WHERE e.preference_id=skill_id;
|
|
163
|
+
INSERT INTO preference_registry_events(account_id,preference_id,type,version,new_revision_id,actor_subject_id,reason)
|
|
164
|
+
VALUES(p_account_id,skill_id,'rejected',next_event,revision_id,actor_subject,p_request->>'reason');
|
|
165
|
+
outcome:='preserved';
|
|
166
|
+
ELSE
|
|
167
|
+
-- Shared/composite distribution cannot be silently dismantled. Direct
|
|
168
|
+
-- installations are scoped to this one facet; other facets remain intact.
|
|
169
|
+
IF EXISTS(SELECT 1 FROM skill_source_bindings b JOIN capability_facet_installations fi
|
|
170
|
+
ON fi.facet_id=b.skill_facet_id AND fi.workspace_id=b.workspace_id
|
|
171
|
+
JOIN capability_component_owners o ON o.facet_installation_id=fi.id
|
|
172
|
+
WHERE b.preference_id=skill_id AND (o.owner_kind<>'direct' OR NOT o.removable)) THEN
|
|
173
|
+
RAISE EXCEPTION 'Skill has another distribution owner; release it before permanent removal' USING ERRCODE='42501'; END IF;
|
|
174
|
+
DELETE FROM capability_facet_installations fi USING skill_source_bindings b
|
|
175
|
+
WHERE b.preference_id=skill_id AND b.account_id=p_account_id AND b.workspace_id=p_workspace_id
|
|
176
|
+
AND fi.facet_id=b.skill_facet_id AND fi.account_id=p_account_id AND fi.workspace_id=p_workspace_id;
|
|
177
|
+
PERFORM set_config('opengeni.skill_remove_head',skill_id::text,true);
|
|
178
|
+
DELETE FROM company_brain_preference_proposal_receipts r
|
|
179
|
+
WHERE r.account_id=p_account_id AND r.workspace_id=p_workspace_id AND r.preference_id=skill_id;
|
|
180
|
+
DELETE FROM preference_registry_preferences WHERE id=skill_id AND account_id=p_account_id;
|
|
181
|
+
PERFORM set_config('opengeni.skill_remove_head','',true);
|
|
182
|
+
outcome:='applied';
|
|
183
|
+
END IF;
|
|
184
|
+
result:=jsonb_build_object('operationId',operation_id,'skillId',skill_id,'revisionId',revision_id,
|
|
185
|
+
'outcome',outcome,'removed',outcome='applied','removedScope',head.scope,'replayed',false);
|
|
186
|
+
IF outcome='pending' THEN result:=result||jsonb_build_object('pendingReason','approval','skillReview',
|
|
187
|
+
jsonb_build_object('sourceOperationId',operation_id,'removalOperationId',operation_id,'skillId',skill_id,
|
|
188
|
+
'revisionId',revision_id,'expectedRevisionId',head.active_revision_id,'expectedScopeVersion',head.scope_version)); END IF;
|
|
189
|
+
IF operation='reject' THEN result:=result||jsonb_build_object('decision','rejected'); END IF;
|
|
190
|
+
INSERT INTO skill_write_receipts(account_id,workspace_id,operation_id,fingerprint,actor,receipt)
|
|
191
|
+
VALUES(p_account_id,p_workspace_id,operation_id,fingerprint,p_actor,result);
|
|
192
|
+
RETURN result;
|
|
193
|
+
END IF;
|
|
194
|
+
|
|
195
|
+
IF outcome IS NULL THEN
|
|
196
|
+
IF operation IN ('approve','restore','reject') THEN$r$;
|
|
197
|
+
IF (length(definition)-length(replace(definition,anchor,'')))/length(anchor)<>1 THEN RAISE EXCEPTION 'Skill removal branch anchor mismatch'; END IF;
|
|
198
|
+
EXECUTE replace(definition,anchor,replacement);
|
|
199
|
+
END $patch$;
|
|
200
|
+
ALTER TABLE capability_facet_installations FORCE ROW LEVEL SECURITY;
|
|
201
|
+
ALTER TABLE preference_registry_events FORCE ROW LEVEL SECURITY;
|
|
202
|
+
ALTER TABLE preference_registry_preferences FORCE ROW LEVEL SECURITY;
|
|
203
|
+
ALTER TABLE preference_registry_revisions FORCE ROW LEVEL SECURITY;
|
|
204
|
+
ALTER TABLE skill_write_receipts FORCE ROW LEVEL SECURITY;
|
|
205
|
+
ALTER TABLE company_brain_preference_proposal_receipts FORCE ROW LEVEL SECURITY;
|
|
206
|
+
ALTER TABLE capability_component_owners FORCE ROW LEVEL SECURITY;
|
|
207
|
+
ALTER TABLE skill_source_bindings FORCE ROW LEVEL SECURITY;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
-- deployment-mode: maintenance
|
|
2
|
+
-- Old readers cannot interpret router byte offsets. Drain old worker owners
|
|
3
|
+
-- before activating new starts. Existing legacy locators remain unchanged and
|
|
4
|
+
-- are reconciled by the explicit legacy reader; never reinterpret their IDs.
|
|
5
|
+
ALTER TABLE sandbox_retained_processes
|
|
6
|
+
DROP CONSTRAINT sandbox_retained_processes_provider_command_chk;
|
|
7
|
+
ALTER TABLE sandbox_retained_processes
|
|
8
|
+
ADD CONSTRAINT sandbox_retained_processes_provider_command_chk CHECK (
|
|
9
|
+
provider_command IS NULL OR ((
|
|
10
|
+
provider_backend = 'modal'
|
|
11
|
+
AND jsonb_typeof(provider_command) = 'object'
|
|
12
|
+
AND provider_command->>'kind' IN ('modal-control-v1', 'modal-router-v1')
|
|
13
|
+
AND provider_command->>'sandboxId' = provider_instance_id
|
|
14
|
+
AND length(provider_command->>'taskId') > 0
|
|
15
|
+
AND length(provider_command->>'execId') > 0
|
|
16
|
+
) IS TRUE)
|
|
17
|
+
);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Retain the cadence clock independently of an in-flight capture claim or a
|
|
3
|
+
-- successful archive. Failed provider attempts must not re-pause the workspace
|
|
4
|
+
-- on every heartbeat. Nullable expansion is compatible with old readers.
|
|
5
|
+
ALTER TABLE sandbox_leases
|
|
6
|
+
ADD COLUMN archive_capture_last_attempt_at timestamptz;
|