@opengeni/db 3.6.0 → 3.7.4
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/canonical-human-identities.js +3 -3
- package/dist/{chunk-744DFC65.js → chunk-4NAR7FBA.js} +92 -6
- package/dist/chunk-4NAR7FBA.js.map +1 -0
- package/dist/{chunk-IW6O6G7R.js → chunk-5NNCGFQY.js} +2 -2
- package/dist/{chunk-G3732M3T.js → chunk-DZCYW2WT.js} +2 -2
- package/dist/{chunk-WW7AJ5IC.js → chunk-F6PBS6WE.js} +5 -7
- package/dist/chunk-F6PBS6WE.js.map +1 -0
- package/dist/{chunk-UJ2EW3QI.js → chunk-O56DM5GZ.js} +340 -85
- package/dist/chunk-O56DM5GZ.js.map +1 -0
- package/dist/{chunk-CDMMSUWJ.js → chunk-S3TDDCS2.js} +3 -3
- package/dist/{chunk-Q5YUGQVK.js → chunk-SZAWDXQC.js} +3 -3
- package/dist/{chunk-UGDVSQ6U.js → chunk-U4WPPEBH.js} +2 -2
- package/dist/{chunk-2NT476HD.js → chunk-US4IS6IV.js} +2 -2
- package/dist/codex-token-resolver.d.ts +12 -1
- package/dist/company-profile-agent-admin.d.ts +4 -2
- package/dist/company-profile-agent-policy.d.ts +20 -0
- package/dist/company-profile-schema.d.ts +573 -0
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/index.d.ts +382 -18
- package/dist/index.js +4513 -2065
- package/dist/index.js.map +1 -1
- package/dist/managed-auth-session-sets.js +3 -3
- package/dist/managed-human-provisioning.d.ts +3 -3
- package/dist/memory-domain.d.ts +5 -3
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +5 -5
- package/dist/schema.d.ts +725 -4
- package/dist/schema.js +13 -1
- package/dist/session-background-commands.js +3 -3
- package/dist/session-control.d.ts +40 -5
- package/dist/session-queue-commands.d.ts +2 -0
- package/dist/session-tenancy.js +3 -3
- package/dist/user-resource-authority.d.ts +8 -0
- package/dist/video-generation.js +3 -3
- package/drizzle/0369_local_human_personal_authority.sql +350 -0
- package/drizzle/0370_workspace_member_management_scope.sql +98 -0
- package/drizzle/0371_workspace_member_candidate_inventory.sql +128 -0
- package/drizzle/0372_ordered_variable_set_runtime_authority.sql +287 -0
- package/drizzle/0373_sandbox_shared_preparation.sql +59 -0
- package/drizzle/0374_session_event_cursors.sql +315 -0
- package/drizzle/0375_session_recovery_observability.sql +73 -0
- package/drizzle/0376_organization_workspace_inventory.sql +88 -0
- package/drizzle/0377_scoped_rig_health_projection.sql +80 -0
- package/drizzle/0378_scoped_rig_health_audit_timestamp.sql +77 -0
- package/drizzle/0379_session_event_raw_lane_activation.sql +518 -0
- package/drizzle/0380_autonomous_company_profile_agent_policy.sql +829 -0
- package/drizzle/0381_organization_codex_subscription_inheritance.sql +720 -0
- package/drizzle/0382_organization_api_key_provenance.sql +91 -0
- package/drizzle/0383_codex_cooldown_reconciliation.sql +51 -0
- package/drizzle/0384_codex_cooldown_revision_guard_privileges.sql +6 -0
- package/drizzle/0385_connected_machine_legacy_tilde_workdirs.sql +25 -0
- package/drizzle/0386_local_organization_administration.sql +157 -0
- package/package.json +5 -5
- package/src/codex-token-resolver.ts +26 -1
- package/src/company-brain-governed-writes.ts +30 -1
- package/src/company-profile-agent-admin.ts +68 -1
- package/src/company-profile-agent-policy.ts +126 -0
- package/src/company-profile-schema.ts +115 -1
- package/src/database.ts +3 -5
- package/src/index.ts +3626 -456
- package/src/managed-human-provisioning.ts +3 -3
- package/src/memory-domain.ts +9 -4
- package/src/organization-membership-lifecycle.ts +0 -1
- package/src/provision-roles.ts +83 -4
- package/src/runtime-posture.ts +22 -0
- package/src/schema.ts +161 -8
- package/src/session-control.ts +177 -59
- package/src/session-queue-commands.ts +146 -40
- package/src/session-tool-call-settlement.ts +34 -7
- package/src/user-resource-authority.ts +56 -0
- package/dist/chunk-744DFC65.js.map +0 -1
- package/dist/chunk-UJ2EW3QI.js.map +0 -1
- package/dist/chunk-WW7AJ5IC.js.map +0 -1
- /package/dist/{chunk-IW6O6G7R.js.map → chunk-5NNCGFQY.js.map} +0 -0
- /package/dist/{chunk-G3732M3T.js.map → chunk-DZCYW2WT.js.map} +0 -0
- /package/dist/{chunk-CDMMSUWJ.js.map → chunk-S3TDDCS2.js.map} +0 -0
- /package/dist/{chunk-Q5YUGQVK.js.map → chunk-SZAWDXQC.js.map} +0 -0
- /package/dist/{chunk-UGDVSQ6U.js.map → chunk-U4WPPEBH.js.map} +0 -0
- /package/dist/{chunk-2NT476HD.js.map → chunk-US4IS6IV.js.map} +0 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Organization-wide workspace authority must come from an explicitly issued
|
|
3
|
+
-- organization key, never from the historical workspace_id IS NULL shape.
|
|
4
|
+
|
|
5
|
+
SET LOCAL lock_timeout = '5s';
|
|
6
|
+
SET LOCAL statement_timeout = '5min';
|
|
7
|
+
|
|
8
|
+
ALTER TABLE api_keys
|
|
9
|
+
ADD COLUMN credential_kind text;
|
|
10
|
+
|
|
11
|
+
ALTER TABLE api_keys NO FORCE ROW LEVEL SECURITY;
|
|
12
|
+
|
|
13
|
+
UPDATE api_keys
|
|
14
|
+
SET
|
|
15
|
+
credential_kind = CASE
|
|
16
|
+
WHEN workspace_id IS NOT NULL THEN 'workspace'
|
|
17
|
+
ELSE 'legacy_account'
|
|
18
|
+
END,
|
|
19
|
+
revoked_at = CASE
|
|
20
|
+
WHEN workspace_id IS NULL THEN COALESCE(revoked_at, CURRENT_TIMESTAMP)
|
|
21
|
+
ELSE revoked_at
|
|
22
|
+
END,
|
|
23
|
+
updated_at = CASE
|
|
24
|
+
WHEN workspace_id IS NULL AND revoked_at IS NULL THEN CURRENT_TIMESTAMP
|
|
25
|
+
ELSE updated_at
|
|
26
|
+
END;
|
|
27
|
+
|
|
28
|
+
ALTER TABLE api_keys FORCE ROW LEVEL SECURITY;
|
|
29
|
+
|
|
30
|
+
CREATE FUNCTION opengeni_private.normalize_api_key_credential_kind()
|
|
31
|
+
RETURNS trigger
|
|
32
|
+
LANGUAGE plpgsql
|
|
33
|
+
SECURITY INVOKER
|
|
34
|
+
SET search_path = pg_catalog, public
|
|
35
|
+
AS $function$
|
|
36
|
+
BEGIN
|
|
37
|
+
IF NEW.workspace_id IS NOT NULL THEN
|
|
38
|
+
NEW.credential_kind := 'workspace';
|
|
39
|
+
ELSIF NEW.credential_kind IS NULL THEN
|
|
40
|
+
-- The previous application version omitted this column. Its organization-key
|
|
41
|
+
-- route is the only null-workspace insert path with this exact fixed grant.
|
|
42
|
+
IF NEW.permissions = '["account:read","workspace:create","workspace:read","workspace:admin","api_keys:manage"]'::jsonb THEN
|
|
43
|
+
NEW.credential_kind := 'organization';
|
|
44
|
+
ELSE
|
|
45
|
+
NEW.credential_kind := 'legacy_account';
|
|
46
|
+
END IF;
|
|
47
|
+
END IF;
|
|
48
|
+
|
|
49
|
+
-- Old application versions authorize every active null-workspace row by
|
|
50
|
+
-- shape alone. Keep ambiguous legacy rows unusable through that old lookup
|
|
51
|
+
-- path for the entire rolling window; only the exact previous organization
|
|
52
|
+
-- writer above may create an active null-workspace credential.
|
|
53
|
+
IF NEW.credential_kind = 'legacy_account' AND NEW.revoked_at IS NULL THEN
|
|
54
|
+
NEW.revoked_at := CURRENT_TIMESTAMP;
|
|
55
|
+
NEW.updated_at := CURRENT_TIMESTAMP;
|
|
56
|
+
END IF;
|
|
57
|
+
|
|
58
|
+
RETURN NEW;
|
|
59
|
+
END;
|
|
60
|
+
$function$;
|
|
61
|
+
|
|
62
|
+
REVOKE ALL ON FUNCTION opengeni_private.normalize_api_key_credential_kind() FROM PUBLIC;
|
|
63
|
+
|
|
64
|
+
CREATE TRIGGER api_keys_00_normalize_credential_kind
|
|
65
|
+
BEFORE INSERT OR UPDATE OF workspace_id, credential_kind, permissions, revoked_at
|
|
66
|
+
ON api_keys
|
|
67
|
+
FOR EACH ROW
|
|
68
|
+
EXECUTE FUNCTION opengeni_private.normalize_api_key_credential_kind();
|
|
69
|
+
|
|
70
|
+
ALTER TABLE api_keys
|
|
71
|
+
ALTER COLUMN credential_kind SET NOT NULL;
|
|
72
|
+
|
|
73
|
+
ALTER TABLE api_keys
|
|
74
|
+
ADD CONSTRAINT api_keys_credential_kind_check
|
|
75
|
+
CHECK (
|
|
76
|
+
(workspace_id IS NOT NULL AND credential_kind = 'workspace')
|
|
77
|
+
OR
|
|
78
|
+
(workspace_id IS NULL AND credential_kind = 'organization')
|
|
79
|
+
OR
|
|
80
|
+
(workspace_id IS NULL AND credential_kind = 'legacy_account' AND revoked_at IS NOT NULL)
|
|
81
|
+
) NOT VALID;
|
|
82
|
+
|
|
83
|
+
ALTER TABLE api_keys
|
|
84
|
+
VALIDATE CONSTRAINT api_keys_credential_kind_check;
|
|
85
|
+
|
|
86
|
+
CREATE INDEX api_keys_organization_account_idx
|
|
87
|
+
ON api_keys (account_id, created_at DESC)
|
|
88
|
+
WHERE credential_kind = 'organization';
|
|
89
|
+
|
|
90
|
+
COMMENT ON COLUMN api_keys.credential_kind IS
|
|
91
|
+
'Persisted API-key provenance. Only organization keys receive organization-wide shared-workspace authority; ambiguous legacy account keys are revoked.';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
--
|
|
3
|
+
-- Give Codex cooldowns typed provenance plus an independent revision. A fresh
|
|
4
|
+
-- provider usage read may then clear only the exact quota cooldown it observed
|
|
5
|
+
-- before the request; generic backpressure and a concurrently newer refusal
|
|
6
|
+
-- remain authoritative. Legacy writers do not know these columns, so the
|
|
7
|
+
-- trigger advances the revision and removes typed auto-clear authority whenever
|
|
8
|
+
-- they change exhausted_until alone during a rolling deployment.
|
|
9
|
+
|
|
10
|
+
ALTER TABLE "codex_subscription_credentials"
|
|
11
|
+
ADD COLUMN "exhausted_kind" text,
|
|
12
|
+
ADD COLUMN "exhausted_revision" bigint NOT NULL DEFAULT 0,
|
|
13
|
+
ADD CONSTRAINT "codex_credentials_exhausted_kind_chk" CHECK (
|
|
14
|
+
("exhausted_until" IS NULL AND "exhausted_kind" IS NULL)
|
|
15
|
+
OR (
|
|
16
|
+
"exhausted_until" IS NOT NULL
|
|
17
|
+
AND ("exhausted_kind" IS NULL OR "exhausted_kind" IN ('quota', 'rate_limit'))
|
|
18
|
+
)
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
CREATE OR REPLACE FUNCTION opengeni_private.codex_cooldown_revision_guard()
|
|
22
|
+
RETURNS trigger
|
|
23
|
+
LANGUAGE plpgsql
|
|
24
|
+
SET search_path = pg_catalog
|
|
25
|
+
AS $function$
|
|
26
|
+
BEGIN
|
|
27
|
+
IF NEW.exhausted_until IS DISTINCT FROM OLD.exhausted_until THEN
|
|
28
|
+
IF NEW.exhausted_revision = OLD.exhausted_revision THEN
|
|
29
|
+
-- Compatibility path for a pre-0382 writer: retain the cooldown but do
|
|
30
|
+
-- not let a new usage reader mistake its unknown cause for quota.
|
|
31
|
+
NEW.exhausted_kind := NULL;
|
|
32
|
+
NEW.exhausted_revision := OLD.exhausted_revision + 1;
|
|
33
|
+
ELSIF NEW.exhausted_revision <> OLD.exhausted_revision + 1 THEN
|
|
34
|
+
RAISE EXCEPTION 'Codex cooldown revision must advance exactly once';
|
|
35
|
+
END IF;
|
|
36
|
+
ELSIF NEW.exhausted_kind IS DISTINCT FROM OLD.exhausted_kind
|
|
37
|
+
OR NEW.exhausted_revision IS DISTINCT FROM OLD.exhausted_revision
|
|
38
|
+
THEN
|
|
39
|
+
IF NEW.exhausted_revision <> OLD.exhausted_revision + 1 THEN
|
|
40
|
+
RAISE EXCEPTION 'Codex cooldown metadata revision must advance exactly once';
|
|
41
|
+
END IF;
|
|
42
|
+
END IF;
|
|
43
|
+
RETURN NEW;
|
|
44
|
+
END
|
|
45
|
+
$function$;
|
|
46
|
+
|
|
47
|
+
CREATE TRIGGER codex_cooldown_revision_guard
|
|
48
|
+
BEFORE UPDATE OF exhausted_until, exhausted_kind, exhausted_revision
|
|
49
|
+
ON "codex_subscription_credentials"
|
|
50
|
+
FOR EACH ROW
|
|
51
|
+
EXECUTE FUNCTION opengeni_private.codex_cooldown_revision_guard();
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Trigger helpers are owner-internal implementation details. PostgreSQL grants
|
|
3
|
+
-- PUBLIC execute on new functions by default, so remove the inherited privilege
|
|
4
|
+
-- that exposed this guard to unrelated runtime roles.
|
|
5
|
+
|
|
6
|
+
REVOKE ALL ON FUNCTION opengeni_private.codex_cooldown_revision_guard() FROM PUBLIC;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Native Connected Machine workspace roots made tilde paths invalid. Repair
|
|
3
|
+
-- only legacy sessions whose active machine has reported an exact root; leave
|
|
4
|
+
-- ambiguous/offline legacy rows untouched rather than guessing a user home.
|
|
5
|
+
|
|
6
|
+
-- Migrations run as the NOSUPERUSER/NOBYPASSRLS table owner. Temporarily let
|
|
7
|
+
-- that owner see the joined rows; application roles remain policy-bound.
|
|
8
|
+
ALTER TABLE "sessions" NO FORCE ROW LEVEL SECURITY;
|
|
9
|
+
ALTER TABLE "sandboxes" NO FORCE ROW LEVEL SECURITY;
|
|
10
|
+
ALTER TABLE "enrollments" NO FORCE ROW LEVEL SECURITY;
|
|
11
|
+
|
|
12
|
+
UPDATE "sessions" AS session
|
|
13
|
+
SET "working_dir" =
|
|
14
|
+
rtrim(enrollment."workspace_root", '/') || '/' || substring(session."working_dir" FROM 3)
|
|
15
|
+
FROM "sandboxes" AS sandbox
|
|
16
|
+
JOIN "enrollments" AS enrollment ON enrollment."id" = sandbox."enrollment_id"
|
|
17
|
+
WHERE session."active_sandbox_id" = sandbox."id"
|
|
18
|
+
AND session."working_dir" LIKE '~/%'
|
|
19
|
+
AND enrollment."status" = 'active'
|
|
20
|
+
AND enrollment."workspace_root" IS NOT NULL
|
|
21
|
+
AND enrollment."workspace_root" <> '';
|
|
22
|
+
|
|
23
|
+
ALTER TABLE "enrollments" FORCE ROW LEVEL SECURITY;
|
|
24
|
+
ALTER TABLE "sandboxes" FORCE ROW LEVEL SECURITY;
|
|
25
|
+
ALTER TABLE "sessions" FORCE ROW LEVEL SECURITY;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
-- deployment-mode: maintenance
|
|
2
|
+
--
|
|
3
|
+
-- The canonical single-user local bootstrap already grants `dev` account-owner
|
|
4
|
+
-- authority in the application, but its durable organization membership was
|
|
5
|
+
-- left at the default member role. Converge that exact built-in identity and
|
|
6
|
+
-- admit it to organization Codex administration without broadening configured,
|
|
7
|
+
-- delegated, API-key, service, or agent principals.
|
|
8
|
+
|
|
9
|
+
SET LOCAL lock_timeout = '5s';
|
|
10
|
+
SET LOCAL statement_timeout = '10min';
|
|
11
|
+
|
|
12
|
+
CREATE OR REPLACE FUNCTION opengeni_private.assign_managed_self_organization_owner()
|
|
13
|
+
RETURNS trigger
|
|
14
|
+
LANGUAGE plpgsql
|
|
15
|
+
SECURITY DEFINER
|
|
16
|
+
SET search_path FROM CURRENT
|
|
17
|
+
AS $body$
|
|
18
|
+
BEGIN
|
|
19
|
+
IF EXISTS (
|
|
20
|
+
SELECT 1 FROM managed_accounts account
|
|
21
|
+
WHERE account.id = NEW.account_id
|
|
22
|
+
AND (
|
|
23
|
+
(
|
|
24
|
+
account.external_source = 'better-auth:user'
|
|
25
|
+
AND NEW.subject_id = 'user:' || account.external_id
|
|
26
|
+
) OR (
|
|
27
|
+
account.external_source = 'opengeni:local'
|
|
28
|
+
AND account.external_id = 'default'
|
|
29
|
+
AND NEW.subject_id = 'dev'
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
) THEN
|
|
33
|
+
NEW.role := 'owner';
|
|
34
|
+
END IF;
|
|
35
|
+
RETURN NEW;
|
|
36
|
+
END
|
|
37
|
+
$body$;
|
|
38
|
+
|
|
39
|
+
DO $pin_local_owner_trigger$
|
|
40
|
+
DECLARE data_schema text := current_schema();
|
|
41
|
+
BEGIN
|
|
42
|
+
EXECUTE format(
|
|
43
|
+
'ALTER FUNCTION opengeni_private.assign_managed_self_organization_owner() SET search_path = pg_catalog, %I, pg_temp',
|
|
44
|
+
data_schema
|
|
45
|
+
);
|
|
46
|
+
END
|
|
47
|
+
$pin_local_owner_trigger$;
|
|
48
|
+
|
|
49
|
+
ALTER TABLE organization_memberships NO FORCE ROW LEVEL SECURITY;
|
|
50
|
+
UPDATE organization_memberships membership
|
|
51
|
+
SET role = 'owner',
|
|
52
|
+
authorization_revision = membership.authorization_revision + 1,
|
|
53
|
+
updated_at = clock_timestamp()
|
|
54
|
+
FROM managed_accounts account
|
|
55
|
+
WHERE account.id = membership.account_id
|
|
56
|
+
AND account.external_source = 'opengeni:local'
|
|
57
|
+
AND account.external_id = 'default'
|
|
58
|
+
AND membership.subject_id = 'dev'
|
|
59
|
+
AND membership.status = 'active'
|
|
60
|
+
AND membership.role <> 'owner';
|
|
61
|
+
ALTER TABLE organization_memberships FORCE ROW LEVEL SECURITY;
|
|
62
|
+
|
|
63
|
+
DO $codex_scope_visibility_schema$
|
|
64
|
+
DECLARE data_schema text := current_schema();
|
|
65
|
+
BEGIN
|
|
66
|
+
EXECUTE format($ddl$
|
|
67
|
+
CREATE OR REPLACE FUNCTION opengeni_private.codex_organization_scope_visible(
|
|
68
|
+
p_account_id uuid
|
|
69
|
+
) RETURNS boolean
|
|
70
|
+
LANGUAGE plpgsql
|
|
71
|
+
SECURITY DEFINER
|
|
72
|
+
SET search_path = pg_catalog
|
|
73
|
+
AS $function$
|
|
74
|
+
DECLARE
|
|
75
|
+
workspace_value uuid := opengeni_private.current_workspace_id();
|
|
76
|
+
subject_value text := opengeni_private.current_subject_id();
|
|
77
|
+
visible boolean := false;
|
|
78
|
+
previous_lifecycle text := pg_catalog.current_setting(
|
|
79
|
+
'opengeni.organization_tenancy_lifecycle', true
|
|
80
|
+
);
|
|
81
|
+
BEGIN
|
|
82
|
+
IF p_account_id IS NULL
|
|
83
|
+
OR p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
|
|
84
|
+
THEN
|
|
85
|
+
RETURN false;
|
|
86
|
+
END IF;
|
|
87
|
+
IF workspace_value IS NOT NULL THEN
|
|
88
|
+
PERFORM pg_catalog.set_config(
|
|
89
|
+
'opengeni.organization_tenancy_lifecycle',
|
|
90
|
+
'organization_membership_lifecycle', true
|
|
91
|
+
);
|
|
92
|
+
SELECT EXISTS (
|
|
93
|
+
SELECT 1 FROM %1$I.workspaces workspace
|
|
94
|
+
WHERE workspace.account_id = p_account_id AND workspace.id = workspace_value
|
|
95
|
+
) AND NOT EXISTS (
|
|
96
|
+
SELECT 1 FROM %1$I.organization_memberships membership
|
|
97
|
+
WHERE membership.account_id = p_account_id
|
|
98
|
+
AND membership.personal_workspace_id = workspace_value
|
|
99
|
+
) INTO visible;
|
|
100
|
+
PERFORM pg_catalog.set_config(
|
|
101
|
+
'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
|
|
102
|
+
);
|
|
103
|
+
RETURN coalesce(visible, false);
|
|
104
|
+
END IF;
|
|
105
|
+
IF subject_value IS NULL OR NOT (
|
|
106
|
+
subject_value LIKE 'user:%%'
|
|
107
|
+
OR (
|
|
108
|
+
subject_value = 'dev'
|
|
109
|
+
AND EXISTS (
|
|
110
|
+
SELECT 1 FROM %1$I.managed_accounts account
|
|
111
|
+
WHERE account.id = p_account_id
|
|
112
|
+
AND account.external_source = 'opengeni:local'
|
|
113
|
+
AND account.external_id = 'default'
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
) THEN
|
|
117
|
+
RETURN false;
|
|
118
|
+
END IF;
|
|
119
|
+
PERFORM pg_catalog.set_config(
|
|
120
|
+
'opengeni.organization_tenancy_lifecycle',
|
|
121
|
+
'organization_membership_lifecycle', true
|
|
122
|
+
);
|
|
123
|
+
SELECT EXISTS (
|
|
124
|
+
SELECT 1 FROM %1$I.organization_memberships membership
|
|
125
|
+
WHERE membership.account_id = p_account_id
|
|
126
|
+
AND membership.subject_id = subject_value
|
|
127
|
+
AND membership.status = 'active'
|
|
128
|
+
AND membership.role IN ('owner', 'admin')
|
|
129
|
+
) INTO visible;
|
|
130
|
+
PERFORM pg_catalog.set_config(
|
|
131
|
+
'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
|
|
132
|
+
);
|
|
133
|
+
RETURN coalesce(visible, false);
|
|
134
|
+
EXCEPTION WHEN OTHERS THEN
|
|
135
|
+
PERFORM pg_catalog.set_config(
|
|
136
|
+
'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
|
|
137
|
+
);
|
|
138
|
+
RAISE;
|
|
139
|
+
END
|
|
140
|
+
$function$
|
|
141
|
+
$ddl$, data_schema);
|
|
142
|
+
END
|
|
143
|
+
$codex_scope_visibility_schema$;
|
|
144
|
+
|
|
145
|
+
REVOKE ALL ON FUNCTION opengeni_private.assign_managed_self_organization_owner() FROM PUBLIC;
|
|
146
|
+
REVOKE ALL ON FUNCTION opengeni_private.codex_organization_scope_visible(uuid) FROM PUBLIC;
|
|
147
|
+
|
|
148
|
+
DO $grants$
|
|
149
|
+
BEGIN
|
|
150
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
151
|
+
GRANT EXECUTE ON FUNCTION opengeni_private.assign_managed_self_organization_owner()
|
|
152
|
+
TO opengeni_app;
|
|
153
|
+
GRANT EXECUTE ON FUNCTION opengeni_private.codex_organization_scope_visible(uuid)
|
|
154
|
+
TO opengeni_app;
|
|
155
|
+
END IF;
|
|
156
|
+
END
|
|
157
|
+
$grants$;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/db",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.4",
|
|
4
4
|
"description": "OpenGeni persistence: Drizzle schema, RLS-scoped query layer, the SQL migration runner, and role provisioning.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -78,10 +78,10 @@
|
|
|
78
78
|
"prepublishOnly": "bash ../../scripts/prepublish-guard"
|
|
79
79
|
},
|
|
80
80
|
"dependencies": {
|
|
81
|
-
"@opengeni/codemode": "^0.4.
|
|
82
|
-
"@opengeni/codex": "^0.2.
|
|
83
|
-
"@opengeni/config": "^0.22.
|
|
84
|
-
"@opengeni/contracts": "^2.
|
|
81
|
+
"@opengeni/codemode": "^0.4.22",
|
|
82
|
+
"@opengeni/codex": "^0.2.20",
|
|
83
|
+
"@opengeni/config": "^0.22.5",
|
|
84
|
+
"@opengeni/contracts": "^2.9.2",
|
|
85
85
|
"@opengeni/network": "^0.2.3",
|
|
86
86
|
"drizzle-orm": "^0.45.2",
|
|
87
87
|
"postgres": "^3.4.7"
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
CODEX_REFRESH_FALLBACK_MS,
|
|
25
25
|
CODEX_REFRESH_WINDOW_MS,
|
|
26
26
|
CodexReloginRequired,
|
|
27
|
+
codexUsageConfirmsQuotaAvailable,
|
|
27
28
|
type CodexTokenSnapshot,
|
|
28
29
|
type CodexUsagePayload,
|
|
29
30
|
type CodexFetch,
|
|
@@ -43,6 +44,8 @@ export type CodexCredentialTokens = {
|
|
|
43
44
|
idToken: string;
|
|
44
45
|
};
|
|
45
46
|
|
|
47
|
+
export type CodexCredentialCooldownKind = "quota" | "rate_limit";
|
|
48
|
+
|
|
46
49
|
export type CodexCredentialForRun = {
|
|
47
50
|
id: string;
|
|
48
51
|
version: number;
|
|
@@ -56,6 +59,9 @@ export type CodexCredentialForRun = {
|
|
|
56
59
|
lastRefreshAt: Date | null;
|
|
57
60
|
status: string;
|
|
58
61
|
lastError: string | null;
|
|
62
|
+
exhaustedUntil: Date | null;
|
|
63
|
+
exhaustedKind: CodexCredentialCooldownKind | null;
|
|
64
|
+
exhaustedRevision: number;
|
|
59
65
|
};
|
|
60
66
|
|
|
61
67
|
export type CodexAccountUsageSnapshot = {
|
|
@@ -66,6 +72,12 @@ export type CodexAccountUsageSnapshot = {
|
|
|
66
72
|
checkedAt?: Date;
|
|
67
73
|
resetCreditAvailableCount?: number | null;
|
|
68
74
|
resetCreditsCheckedAt?: Date | null;
|
|
75
|
+
/**
|
|
76
|
+
* Clear only a quota cooldown with this exact revision. Set solely after a
|
|
77
|
+
* live provider response proves allowance is open; a concurrent refusal
|
|
78
|
+
* advances the revision and makes this observation stale.
|
|
79
|
+
*/
|
|
80
|
+
clearQuotaCooldownRevision?: number;
|
|
69
81
|
};
|
|
70
82
|
|
|
71
83
|
type CodexCredentialRefreshInput = {
|
|
@@ -375,7 +387,8 @@ function errorUsagePayload(reason?: "needs_relogin"): CodexUsagePayload {
|
|
|
375
387
|
* account's expired JWT from 401-ing the usage read.
|
|
376
388
|
* 2. fetch GET /wham/usage with that bearer.
|
|
377
389
|
* 3. normalize (§3) into the P2/P3 contract.
|
|
378
|
-
* 4. on any windows present, write the
|
|
390
|
+
* 4. on any windows present, write the usage cache and conditionally reconcile
|
|
391
|
+
* the exact older typed quota cooldown observed before provider I/O.
|
|
379
392
|
*
|
|
380
393
|
* A refresh that stamps needs_relogin returns { status:"error", reason } and never
|
|
381
394
|
* hits the provider; a transient refresh error returns a plain error payload.
|
|
@@ -396,6 +409,13 @@ export async function fetchCodexUsageForAccount(
|
|
|
396
409
|
return errorUsagePayload(error instanceof CodexReloginRequired ? "needs_relogin" : undefined);
|
|
397
410
|
}
|
|
398
411
|
|
|
412
|
+
// Snapshot cooldown authority immediately before provider I/O. The usage
|
|
413
|
+
// write may clear only this exact revision; any concurrent refusal advances
|
|
414
|
+
// it and wins. A metadata-read failure must not sink the usage response.
|
|
415
|
+
const observedCredential = await deps
|
|
416
|
+
.loadCredential(db, settings, workspaceId, credentialId)
|
|
417
|
+
.catch(() => null);
|
|
418
|
+
|
|
399
419
|
let normalized: CodexUsagePayload;
|
|
400
420
|
try {
|
|
401
421
|
const usage = await fetchCodexUsage(
|
|
@@ -438,6 +458,11 @@ export async function fetchCodexUsageForAccount(
|
|
|
438
458
|
checkedAt,
|
|
439
459
|
}
|
|
440
460
|
: {}),
|
|
461
|
+
...(observedCredential?.exhaustedUntil &&
|
|
462
|
+
observedCredential.exhaustedKind === "quota" &&
|
|
463
|
+
codexUsageConfirmsQuotaAvailable(normalized)
|
|
464
|
+
? { clearQuotaCooldownRevision: observedCredential.exhaustedRevision }
|
|
465
|
+
: {}),
|
|
441
466
|
...(normalized.rateLimitResetCredits
|
|
442
467
|
? {
|
|
443
468
|
resetCreditAvailableCount: normalized.rateLimitResetCredits.availableCount,
|
|
@@ -15,7 +15,11 @@ import {
|
|
|
15
15
|
import { and, eq, inArray, sql } from "drizzle-orm";
|
|
16
16
|
import type { Database } from "./database";
|
|
17
17
|
import { rawRows, setSubjectRlsContext, withRlsContext } from "./database";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
PreferenceRegistryStableKeyConflictError,
|
|
20
|
+
sanitizePreferenceDescriptorText,
|
|
21
|
+
} from "./preference-registry";
|
|
22
|
+
import { nestedPostgresSqlState, safeDatabaseErrorFacts } from "./persistence-errors";
|
|
19
23
|
import {
|
|
20
24
|
appendKnowledgeClaim,
|
|
21
25
|
appendKnowledgeClaimReview,
|
|
@@ -847,6 +851,26 @@ function isInstructionPolicyProposal(
|
|
|
847
851
|
);
|
|
848
852
|
}
|
|
849
853
|
|
|
854
|
+
function isPreferenceProposal(
|
|
855
|
+
request: CompanyBrainGovernedWriteRequestType,
|
|
856
|
+
): request is PreferenceProposalRequest {
|
|
857
|
+
return request.kind === "propose_preference" || request.kind === "promote_task_note_preference";
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
const PREFERENCE_PROPOSAL_STABLE_KEY_CONFLICT_DIAGNOSTIC =
|
|
861
|
+
"workspace preference key is bound to another proposal";
|
|
862
|
+
|
|
863
|
+
function isPreferenceProposalStableKeyConflict(error: unknown): boolean {
|
|
864
|
+
if (nestedPostgresSqlState(error) !== "23505") return false;
|
|
865
|
+
const constraint = safeDatabaseErrorFacts(error).constraint ?? "";
|
|
866
|
+
if (constraint.startsWith("preference_registry_preferences_")) return true;
|
|
867
|
+
for (let current = error, depth = 0; current instanceof Error && depth < 8; depth += 1) {
|
|
868
|
+
if (current.message.includes(PREFERENCE_PROPOSAL_STABLE_KEY_CONFLICT_DIAGNOSTIC)) return true;
|
|
869
|
+
current = current.cause;
|
|
870
|
+
}
|
|
871
|
+
return false;
|
|
872
|
+
}
|
|
873
|
+
|
|
850
874
|
async function materializeWaysOfWorkingProposal(
|
|
851
875
|
db: Database,
|
|
852
876
|
input: {
|
|
@@ -1008,6 +1032,11 @@ export async function writeCompanyBrainGovernedProposal(
|
|
|
1008
1032
|
) {
|
|
1009
1033
|
throw error;
|
|
1010
1034
|
}
|
|
1035
|
+
if (isPreferenceProposal(request) && isPreferenceProposalStableKeyConflict(error)) {
|
|
1036
|
+
throw new PreferenceRegistryStableKeyConflictError(
|
|
1037
|
+
"A preference with this stable key already exists for the workspace",
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1011
1040
|
if (error instanceof ScopedKnowledgeAuthorityError) {
|
|
1012
1041
|
throw new CompanyBrainGovernedWriteAuthorityError(error.message);
|
|
1013
1042
|
}
|
|
@@ -28,6 +28,7 @@ export class CompanyProfileAgentAdminError extends Error {
|
|
|
28
28
|
constructor(
|
|
29
29
|
readonly code:
|
|
30
30
|
| "authority_unavailable"
|
|
31
|
+
| "policy_disabled"
|
|
31
32
|
| "confirmation_unavailable"
|
|
32
33
|
| "profile_conflict"
|
|
33
34
|
| "operation_reused"
|
|
@@ -62,6 +63,13 @@ function translate(error: unknown, stage: "propose" | "confirm"): never {
|
|
|
62
63
|
{ cause: error },
|
|
63
64
|
);
|
|
64
65
|
}
|
|
66
|
+
if (state === "P1852") {
|
|
67
|
+
throw new CompanyProfileAgentAdminError(
|
|
68
|
+
"policy_disabled",
|
|
69
|
+
"Agent-authored company-profile changes are disabled by organization policy.",
|
|
70
|
+
{ cause: error },
|
|
71
|
+
);
|
|
72
|
+
}
|
|
65
73
|
if (state === "40001") {
|
|
66
74
|
throw new CompanyProfileAgentAdminError(
|
|
67
75
|
"profile_conflict",
|
|
@@ -86,6 +94,18 @@ function translate(error: unknown, stage: "propose" | "confirm"): never {
|
|
|
86
94
|
throw error;
|
|
87
95
|
}
|
|
88
96
|
|
|
97
|
+
/** Deterministic UUID-shaped id for the activation stage of one proposal operation. */
|
|
98
|
+
export function derivedCompanyProfileAutomaticActivationOperationId(operationId: string): string {
|
|
99
|
+
const bytes = createHash("sha256")
|
|
100
|
+
.update(`company-profile-agent-admin:v1:${operationId}:automatic-activation`, "utf8")
|
|
101
|
+
.digest()
|
|
102
|
+
.subarray(0, 16);
|
|
103
|
+
bytes[6] = (bytes[6]! & 0x0f) | 0x50;
|
|
104
|
+
bytes[8] = (bytes[8]! & 0x3f) | 0x80;
|
|
105
|
+
const hex = bytes.toString("hex");
|
|
106
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
89
109
|
async function withAgentAttemptContext<T>(
|
|
90
110
|
db: Database,
|
|
91
111
|
attempt: CompanyProfileAgentAttemptType,
|
|
@@ -119,10 +139,13 @@ export async function proposeCompanyProfileForAgent(
|
|
|
119
139
|
receipt_id: string;
|
|
120
140
|
revision_id: string;
|
|
121
141
|
human_input: unknown;
|
|
142
|
+
policy_mode: "suggest" | "automatic";
|
|
143
|
+
automatic_activation_receipt_id: string | null;
|
|
144
|
+
activation_event_id: string | null;
|
|
122
145
|
replayed: boolean;
|
|
123
146
|
}>(
|
|
124
147
|
scoped,
|
|
125
|
-
sql`select * from
|
|
148
|
+
sql`select * from propose_company_profile_for_attempt_v2(
|
|
126
149
|
${attempt.accountId}::uuid,
|
|
127
150
|
${attempt.workspaceId}::uuid,
|
|
128
151
|
${attempt.sessionId}::uuid,
|
|
@@ -130,6 +153,7 @@ export async function proposeCompanyProfileForAgent(
|
|
|
130
153
|
${attempt.attemptId}::uuid,
|
|
131
154
|
${attempt.executionGeneration},
|
|
132
155
|
${request.operationId}::uuid,
|
|
156
|
+
${derivedCompanyProfileAutomaticActivationOperationId(request.operationId)}::uuid,
|
|
133
157
|
${contentJson},
|
|
134
158
|
${contentHash},
|
|
135
159
|
${request.reason}
|
|
@@ -138,6 +162,48 @@ export async function proposeCompanyProfileForAgent(
|
|
|
138
162
|
if (!row) throw new Error("Company-profile proposal returned no receipt");
|
|
139
163
|
return row;
|
|
140
164
|
});
|
|
165
|
+
if (
|
|
166
|
+
result.policy_mode === "automatic" &&
|
|
167
|
+
result.automatic_activation_receipt_id &&
|
|
168
|
+
result.activation_event_id
|
|
169
|
+
) {
|
|
170
|
+
const [inventory, event] = await Promise.all([
|
|
171
|
+
listCompanyProfile(db, {
|
|
172
|
+
accountId: attempt.accountId,
|
|
173
|
+
workspaceId: attempt.workspaceId,
|
|
174
|
+
limit: 1,
|
|
175
|
+
}),
|
|
176
|
+
getCompanyProfileActivationEvent(db, {
|
|
177
|
+
accountId: attempt.accountId,
|
|
178
|
+
workspaceId: attempt.workspaceId,
|
|
179
|
+
eventId: result.activation_event_id,
|
|
180
|
+
}),
|
|
181
|
+
]);
|
|
182
|
+
if (!event?.newRevision) {
|
|
183
|
+
throw new Error("Company-profile automatic activation event is unavailable");
|
|
184
|
+
}
|
|
185
|
+
const revision = await getCompanyProfileRevision(db, {
|
|
186
|
+
accountId: attempt.accountId,
|
|
187
|
+
workspaceId: attempt.workspaceId,
|
|
188
|
+
revisionId: event.newRevision.id,
|
|
189
|
+
});
|
|
190
|
+
return CompanyProfileAgentProposalReceipt.parse({
|
|
191
|
+
status: "activated",
|
|
192
|
+
operationId: request.operationId,
|
|
193
|
+
proposalReceiptId: result.receipt_id,
|
|
194
|
+
automaticActivationReceiptId: result.automatic_activation_receipt_id,
|
|
195
|
+
policyMode: "automatic",
|
|
196
|
+
mutation: {
|
|
197
|
+
revision,
|
|
198
|
+
head: inventory.current?.revisionId === revision.id ? inventory.current : null,
|
|
199
|
+
event,
|
|
200
|
+
},
|
|
201
|
+
replayed: result.replayed,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
if (result.policy_mode !== "suggest") {
|
|
205
|
+
throw new Error("Company-profile proposal returned an incomplete automatic activation");
|
|
206
|
+
}
|
|
141
207
|
const revision = await getCompanyProfileRevision(db, {
|
|
142
208
|
accountId: attempt.accountId,
|
|
143
209
|
workspaceId: attempt.workspaceId,
|
|
@@ -148,6 +214,7 @@ export async function proposeCompanyProfileForAgent(
|
|
|
148
214
|
operationId: request.operationId,
|
|
149
215
|
proposalReceiptId: result.receipt_id,
|
|
150
216
|
revision,
|
|
217
|
+
policyMode: "suggest",
|
|
151
218
|
humanInput: CompanyProfileAgentHumanInputPrompt.parse(result.human_input),
|
|
152
219
|
confirmWith: "company_profile_confirm",
|
|
153
220
|
replayed: result.replayed,
|