@opengeni/db 3.6.0 → 3.7.4-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 (92) hide show
  1. package/dist/automations.d.ts +4 -0
  2. package/dist/canonical-human-identities.js +3 -3
  3. package/dist/{chunk-UGDVSQ6U.js → chunk-3ZMN4EXC.js} +2 -2
  4. package/dist/{chunk-744DFC65.js → chunk-6FIVOJ3V.js} +96 -6
  5. package/dist/chunk-6FIVOJ3V.js.map +1 -0
  6. package/dist/{chunk-UJ2EW3QI.js → chunk-DXJ2WY6W.js} +427 -85
  7. package/dist/chunk-DXJ2WY6W.js.map +1 -0
  8. package/dist/{chunk-CDMMSUWJ.js → chunk-JP6IYGYI.js} +3 -3
  9. package/dist/{chunk-Q5YUGQVK.js → chunk-JS3AWDKZ.js} +3 -3
  10. package/dist/{chunk-G3732M3T.js → chunk-LHKB62GC.js} +2 -2
  11. package/dist/{chunk-2NT476HD.js → chunk-QL4AHBYD.js} +2 -2
  12. package/dist/{chunk-IW6O6G7R.js → chunk-VDXHLVDE.js} +2 -2
  13. package/dist/{chunk-WW7AJ5IC.js → chunk-ZTSJPFDH.js} +5 -7
  14. package/dist/chunk-ZTSJPFDH.js.map +1 -0
  15. package/dist/codex-token-resolver.d.ts +12 -1
  16. package/dist/company-profile-agent-admin.d.ts +4 -2
  17. package/dist/company-profile-agent-policy.d.ts +20 -0
  18. package/dist/company-profile-schema.d.ts +573 -0
  19. package/dist/editable-artifact-durable-export.js +2 -2
  20. package/dist/editable-artifacts.js +3 -3
  21. package/dist/index.d.ts +487 -20
  22. package/dist/index.js +9489 -6041
  23. package/dist/index.js.map +1 -1
  24. package/dist/managed-auth-session-sets.js +3 -3
  25. package/dist/managed-human-provisioning.d.ts +3 -3
  26. package/dist/memory-domain.d.ts +5 -3
  27. package/dist/model-catalog.d.ts +153 -0
  28. package/dist/pr-review.d.ts +9 -0
  29. package/dist/provision-roles.js +1 -1
  30. package/dist/runtime-posture.d.ts +7 -7
  31. package/dist/schema.d.ts +1069 -6
  32. package/dist/schema.js +17 -1
  33. package/dist/session-background-commands.js +3 -3
  34. package/dist/session-control.d.ts +75 -5
  35. package/dist/session-queue-commands.d.ts +19 -0
  36. package/dist/session-tenancy.js +3 -3
  37. package/dist/user-resource-authority.d.ts +8 -0
  38. package/dist/video-generation.js +3 -3
  39. package/dist/xai-subscription.d.ts +8 -0
  40. package/drizzle/0369_local_human_personal_authority.sql +350 -0
  41. package/drizzle/0370_workspace_member_management_scope.sql +98 -0
  42. package/drizzle/0371_workspace_member_candidate_inventory.sql +128 -0
  43. package/drizzle/0372_ordered_variable_set_runtime_authority.sql +287 -0
  44. package/drizzle/0373_sandbox_shared_preparation.sql +59 -0
  45. package/drizzle/0374_session_event_cursors.sql +315 -0
  46. package/drizzle/0375_session_recovery_observability.sql +73 -0
  47. package/drizzle/0376_organization_workspace_inventory.sql +88 -0
  48. package/drizzle/0377_scoped_rig_health_projection.sql +80 -0
  49. package/drizzle/0378_scoped_rig_health_audit_timestamp.sql +77 -0
  50. package/drizzle/0379_session_event_raw_lane_activation.sql +518 -0
  51. package/drizzle/0380_autonomous_company_profile_agent_policy.sql +829 -0
  52. package/drizzle/0381_organization_codex_subscription_inheritance.sql +720 -0
  53. package/drizzle/0382_organization_api_key_provenance.sql +91 -0
  54. package/drizzle/0383_codex_cooldown_reconciliation.sql +51 -0
  55. package/drizzle/0384_codex_cooldown_revision_guard_privileges.sql +6 -0
  56. package/drizzle/0385_connected_machine_legacy_tilde_workdirs.sql +25 -0
  57. package/drizzle/0386_local_organization_administration.sql +157 -0
  58. package/drizzle/0387_bound_host_export_session_payloads.sql +165 -0
  59. package/drizzle/0388_sandbox_provider_deadline_interactions.sql +491 -0
  60. package/drizzle/0389_model_catalog_and_gateway_custom_models.sql +241 -0
  61. package/package.json +6 -6
  62. package/src/automations.ts +9 -1
  63. package/src/codex-token-resolver.ts +26 -1
  64. package/src/company-brain-governed-writes.ts +30 -1
  65. package/src/company-profile-agent-admin.ts +68 -1
  66. package/src/company-profile-agent-policy.ts +126 -0
  67. package/src/company-profile-schema.ts +115 -1
  68. package/src/database.ts +3 -5
  69. package/src/index.ts +4473 -570
  70. package/src/insights.ts +21 -7
  71. package/src/managed-human-provisioning.ts +3 -3
  72. package/src/memory-domain.ts +9 -4
  73. package/src/model-catalog.ts +682 -0
  74. package/src/organization-membership-lifecycle.ts +0 -1
  75. package/src/pr-review.ts +19 -0
  76. package/src/provision-roles.ts +83 -4
  77. package/src/runtime-posture.ts +26 -0
  78. package/src/schema.ts +259 -10
  79. package/src/session-control.ts +347 -59
  80. package/src/session-queue-commands.ts +303 -122
  81. package/src/session-tool-call-settlement.ts +34 -7
  82. package/src/user-resource-authority.ts +56 -0
  83. package/src/xai-subscription.ts +66 -14
  84. package/dist/chunk-744DFC65.js.map +0 -1
  85. package/dist/chunk-UJ2EW3QI.js.map +0 -1
  86. package/dist/chunk-WW7AJ5IC.js.map +0 -1
  87. /package/dist/{chunk-UGDVSQ6U.js.map → chunk-3ZMN4EXC.js.map} +0 -0
  88. /package/dist/{chunk-CDMMSUWJ.js.map → chunk-JP6IYGYI.js.map} +0 -0
  89. /package/dist/{chunk-Q5YUGQVK.js.map → chunk-JS3AWDKZ.js.map} +0 -0
  90. /package/dist/{chunk-G3732M3T.js.map → chunk-LHKB62GC.js.map} +0 -0
  91. /package/dist/{chunk-2NT476HD.js.map → chunk-QL4AHBYD.js.map} +0 -0
  92. /package/dist/{chunk-IW6O6G7R.js.map → chunk-VDXHLVDE.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$;
@@ -0,0 +1,165 @@
1
+ -- deployment-mode: rolling
2
+ -- Host export is an optional bounded projection. Canonical session_events keeps
3
+ -- the complete lossless payload; an oversized projection must never roll back
4
+ -- the source event or a later lifecycle transaction that closes pending work.
5
+
6
+ SET lock_timeout = '5s';
7
+ SET statement_timeout = '10min';
8
+
9
+ DO $migration$
10
+ DECLARE target_schema text := current_schema();
11
+ BEGIN
12
+ EXECUTE format($create$
13
+ CREATE OR REPLACE FUNCTION opengeni_private.enqueue_host_session_event_export()
14
+ RETURNS trigger
15
+ LANGUAGE plpgsql
16
+ SECURITY DEFINER
17
+ SET search_path = pg_catalog
18
+ AS $function$
19
+ DECLARE
20
+ v_enabled boolean;
21
+ v_initiator jsonb;
22
+ v_context jsonb := '{}'::jsonb;
23
+ v_origin text;
24
+ v_source_payload_bytes integer;
25
+ v_export_payload jsonb;
26
+ v_export_payload_codec_version smallint;
27
+ v_payload_bytes integer;
28
+ BEGIN
29
+ IF NEW.type IN (
30
+ 'agent.message.delta', 'agent.reasoning.delta',
31
+ 'sandbox.command.output.delta', 'terminal.pty.output.delta'
32
+ ) THEN
33
+ RETURN NEW;
34
+ END IF;
35
+
36
+ SELECT c.session_events_enabled INTO v_enabled
37
+ FROM %1$I.host_export_config c WHERE c.id = 1
38
+ FOR SHARE;
39
+ IF coalesce(v_enabled, false) = false THEN
40
+ RETURN NEW;
41
+ END IF;
42
+
43
+ IF NEW.turn_id IS NOT NULL THEN
44
+ SELECT
45
+ CASE WHEN octet_length(t.initiator_subject_id) <= 1024 THEN
46
+ jsonb_strip_nulls(jsonb_build_object(
47
+ 'kind', t.initiator_kind,
48
+ 'subjectId', t.initiator_subject_id,
49
+ 'label', CASE
50
+ WHEN jsonb_typeof(t.initiator_context -> 'label') = 'string'
51
+ THEN left(t.initiator_context ->> 'label', 256)
52
+ ELSE NULL
53
+ END
54
+ ))
55
+ ELSE NULL END,
56
+ jsonb_strip_nulls(jsonb_build_object(
57
+ 'label', CASE
58
+ WHEN jsonb_typeof(t.initiator_context -> 'label') = 'string'
59
+ THEN left(t.initiator_context ->> 'label', 256)
60
+ ELSE NULL
61
+ END,
62
+ 'backfill', CASE
63
+ WHEN jsonb_typeof(t.initiator_context -> 'backfill') = 'boolean'
64
+ THEN t.initiator_context -> 'backfill'
65
+ ELSE NULL
66
+ END,
67
+ 'attributionOmitted', CASE
68
+ WHEN octet_length(t.initiator_subject_id) > 1024 THEN 'subject_id_too_large'
69
+ ELSE NULL
70
+ END
71
+ )),
72
+ t.source
73
+ INTO v_initiator, v_context, v_origin
74
+ FROM %1$I.session_turns t
75
+ WHERE t.workspace_id = NEW.workspace_id AND t.id = NEW.turn_id;
76
+ ELSIF NEW.type = 'session.created' THEN
77
+ SELECT
78
+ CASE WHEN octet_length(s.created_by_subject_id) <= 1024 THEN
79
+ jsonb_strip_nulls(jsonb_build_object(
80
+ 'kind', s.created_by_kind,
81
+ 'subjectId', s.created_by_subject_id,
82
+ 'label', CASE
83
+ WHEN jsonb_typeof(s.created_by_context -> 'label') = 'string'
84
+ THEN left(s.created_by_context ->> 'label', 256)
85
+ ELSE NULL
86
+ END
87
+ ))
88
+ ELSE NULL END,
89
+ jsonb_strip_nulls(jsonb_build_object(
90
+ 'label', CASE
91
+ WHEN jsonb_typeof(s.created_by_context -> 'label') = 'string'
92
+ THEN left(s.created_by_context ->> 'label', 256)
93
+ ELSE NULL
94
+ END,
95
+ 'backfill', CASE
96
+ WHEN jsonb_typeof(s.created_by_context -> 'backfill') = 'boolean'
97
+ THEN s.created_by_context -> 'backfill'
98
+ ELSE NULL
99
+ END,
100
+ 'attributionOmitted', CASE
101
+ WHEN octet_length(s.created_by_subject_id) > 1024 THEN 'subject_id_too_large'
102
+ ELSE NULL
103
+ END
104
+ )),
105
+ NULL
106
+ INTO v_initiator, v_context, v_origin
107
+ FROM %1$I.sessions s
108
+ WHERE s.workspace_id = NEW.workspace_id AND s.id = NEW.session_id;
109
+ END IF;
110
+
111
+ v_source_payload_bytes := octet_length(NEW.payload::text);
112
+ v_export_payload := NEW.payload;
113
+ v_export_payload_codec_version := NEW.payload_codec_version;
114
+ IF v_source_payload_bytes > 65536 THEN
115
+ v_export_payload := jsonb_build_object(
116
+ '_hostExport', jsonb_build_object(
117
+ 'payloadMode', 'summary',
118
+ 'payloadTruncated', true,
119
+ 'originalBytes', v_source_payload_bytes,
120
+ 'sourceEventId', NEW.id,
121
+ 'fullPayload', 'retained in canonical session event'
122
+ ),
123
+ 'preview', '[host-export payload omitted at bounded projection boundary]'
124
+ );
125
+ -- The projection is literal PostgreSQL JSON, not an application-codec
126
+ -- envelope. Preserve codec truth only when the original payload is kept.
127
+ v_export_payload_codec_version := NULL;
128
+ END IF;
129
+
130
+ v_payload_bytes := octet_length(v_export_payload::text)
131
+ + octet_length(NEW.type)
132
+ + coalesce(octet_length(NEW.client_event_id), 0)
133
+ + coalesce(octet_length(NEW.turn_association), 0)
134
+ + coalesce(octet_length(NEW.duplicate_reason), 0)
135
+ + octet_length(coalesce(v_initiator, 'null'::jsonb)::text)
136
+ + octet_length(v_context::text)
137
+ + 768;
138
+ INSERT INTO %1$I.host_export_outbox (
139
+ export_kind, source_id, account_id, workspace_id, session_id,
140
+ turn_id, turn_generation, turn_attempt_id, session_sequence,
141
+ client_event_id, turn_association, duplicate_of_event_id, duplicate_reason,
142
+ event_type, idempotency_key, initiator, initiator_context, origin,
143
+ payload, payload_codec_version, envelope_bytes, occurred_at,
144
+ source_recorded_at, enqueued_at
145
+ ) VALUES (
146
+ 'session_event', NEW.id, NEW.account_id, NEW.workspace_id, NEW.session_id,
147
+ NEW.turn_id, NEW.turn_generation, NEW.turn_attempt_id, NEW.sequence,
148
+ NEW.client_event_id, NEW.turn_association, NEW.duplicate_of_event_id,
149
+ NEW.duplicate_reason,
150
+ NEW.type, 'session_event:' || NEW.id::text, v_initiator,
151
+ coalesce(v_context, '{}'::jsonb), v_origin, v_export_payload,
152
+ v_export_payload_codec_version, greatest(1, v_payload_bytes), NEW.occurred_at,
153
+ NEW.created_at, clock_timestamp()
154
+ )
155
+ ON CONFLICT (export_kind, source_id) DO NOTHING;
156
+ RETURN NEW;
157
+ END $function$;
158
+ $create$, target_schema);
159
+ END
160
+ $migration$;
161
+
162
+ COMMENT ON COLUMN host_export_outbox.payload IS
163
+ 'Immutable bounded host projection. Canonical session_events retains the complete payload; oversized host projections carry an explicit content-free truncation receipt.';
164
+ COMMENT ON COLUMN host_export_outbox.payload_codec_version IS
165
+ 'Codec truth for the stored host projection. NULL means literal SQL JSON, including oversized-payload projections; 1 means the original application-coded payload was retained exactly.';