@vrplatform/kysely 1.3.45-stage.6066 → 1.3.45-stage.607

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 (97) hide show
  1. package/build/.data.tar +0 -0
  2. package/build/main/control-plane/index.d.ts +312 -0
  3. package/build/main/control-plane/index.js.map +1 -1
  4. package/build/main/index.d.ts +13 -2
  5. package/build/main/index.js +217 -23
  6. package/build/main/index.js.map +1 -1
  7. package/build/main/local/generate.js +2 -0
  8. package/build/main/local/generate.js.map +1 -1
  9. package/build/main/local/index.js +110 -8
  10. package/build/main/local/index.js.map +1 -1
  11. package/build/main/postgres-error.d.ts +3 -3
  12. package/build/main/postgres-error.js +4 -3
  13. package/build/main/postgres-error.js.map +1 -1
  14. package/build/main/query/listing/status.js +2 -6
  15. package/build/main/query/listing/status.js.map +1 -1
  16. package/build/main/query/tenant/status.d.ts +1 -1
  17. package/build/main/query/tenant/status.js +6 -4
  18. package/build/main/query/tenant/status.js.map +1 -1
  19. package/build/main/regional.d.ts +1 -0
  20. package/build/main/regional.js +1 -0
  21. package/build/main/regional.js.map +1 -1
  22. package/build/main/test/index.d.ts +23 -3
  23. package/build/main/test/index.js +35 -13
  24. package/build/main/test/index.js.map +1 -1
  25. package/build/main/v1.generated.d.ts +560 -8
  26. package/build/main/v1.generated.js.map +1 -1
  27. package/build/migrations-v2/0000000000015-durable-flow-events.ts +163 -0
  28. package/build/migrations-v2/0000000000016-billing-listing-facts.ts +539 -0
  29. package/build/migrations-v2/0000000000016-managed-team-access.ts +34 -0
  30. package/build/migrations-v2/0000000000017-drop-trial-until.ts +42 -0
  31. package/build/migrations-v2/0000000000017-webhook-secret-reencryption-failures.ts +18 -0
  32. package/build/migrations-v2/0000000000018-accounting-integrity-monitoring.ts +276 -0
  33. package/build/migrations-v2/0000000000018-durable-flow-event-sensitive-input.ts +28 -0
  34. package/build/migrations-v2/0000000000019-accounting-integrity-run-shards.ts +26 -0
  35. package/build/migrations-v2/0000000000019-asynchronous-operations.ts +736 -0
  36. package/build/migrations-v2/0000000000020-accounting-integrity-case-classification.ts +52 -0
  37. package/build/migrations-v2/0000000000021-payment-line-classification-monitor-indexes.ts +31 -0
  38. package/build/migrations-v2/0000000000022-accounting-integrity-controlled-repairs.ts +281 -0
  39. package/build/migrations-v2/0000000000023-line-classification-usage.ts +325 -0
  40. package/build/migrations-v2/0000000000024-tenant-region-migration.ts +108 -0
  41. package/build/migrations-v2/0000000000025-partner-ach-denial-default.ts +30 -0
  42. package/build/migrations-v2/0000000000026-line-classification-usage-capture.ts +399 -0
  43. package/build/migrations-v2/0000000000027-payout-replacement-chain.ts +27 -0
  44. package/build/migrations-v2/0000000000028-accounting-integrity-operator-workflow.ts +108 -0
  45. package/build/migrations-v2/0000000000029-accounting-integrity-definition-artifacts.ts +40 -0
  46. package/build/migrations-v2/0000000000030-accounting-integrity-review-metadata-compatibility.ts +49 -0
  47. package/build/migrations-v2/0000000000031-ui-permission-role-simplification.ts +70 -0
  48. package/build/migrations-v2/0000000000032-tenant-delete-ownership-cascades.ts +44 -0
  49. package/build/migrations-v2/0000000000033-reservation-issue-seed-bucket-index.ts +19 -0
  50. package/build/migrations-v2/0000000000034-retire-recent-classification-indexes.ts +29 -0
  51. package/build/migrations-v2/0000000000035-core-change-autovacuum.ts +24 -0
  52. package/build/migrations-v2/0000000000036-journal-entry-inactive-reason-constraint.ts +33 -0
  53. package/build/migrations-v2/0000000000037-journal-entry-inactive-reasons.ts +19 -0
  54. package/build/migrations-v2/0000000000038-journal-entry-group-inactive-reason.ts +14 -0
  55. package/build/migrations-v2/0000000000039-reservation-guest-breakdown.ts +19 -0
  56. package/build/migrations-v2/0000000000040-csv-import-source-identity-index.ts +23 -0
  57. package/build/migrations-v2/0000000000041-payment-returns.ts +38 -0
  58. package/build/migrations-v2/0000000000042-operation-resource-types.ts +609 -0
  59. package/build/migrations-v2/0000000000043-reservation-journal-freshness.ts +149 -0
  60. package/build/migrations-v2/0000000000044-reservation-journal-freshness-backfill.ts +254 -0
  61. package/build/migrations-v2/0000000000045-reservation-journal-freshness-revision-repair.ts +83 -0
  62. package/build/migrations-v2/0000000000046-materialized-reservation-journal-operations.ts +709 -0
  63. package/build/migrations-v2/0000000000047-public-audit-completion.ts +77 -0
  64. package/build/migrations-v2/0000000000048-reservation-journal-full-refresh-repair.ts +136 -0
  65. package/build/migrations-v2/0000000000049-queued-journal-operation-failures.ts +60 -0
  66. package/build/migrations-v2/0000000000050-connection-oauth-state.ts +43 -0
  67. package/build/migrations-v2/0000000000051-owner-statement-email-delivery.ts +134 -0
  68. package/build/migrations-v2/0000000000052-fee-journal-lock-skip-operations.ts +59 -0
  69. package/build/migrations-v2/0000000000053-fee-journal-lock-skip-null-attempt.ts +44 -0
  70. package/build/migrations-v2/0000000000054-journal-refresh-issue-codes.ts +16 -0
  71. package/build/migrations-v2/0000000000055-tenant-billing-subscription.ts +8 -0
  72. package/build/module/control-plane/index.d.ts +312 -0
  73. package/build/module/control-plane/index.js.map +1 -1
  74. package/build/module/index.d.ts +13 -2
  75. package/build/module/index.js +219 -24
  76. package/build/module/index.js.map +1 -1
  77. package/build/module/local/generate.js +2 -0
  78. package/build/module/local/generate.js.map +1 -1
  79. package/build/module/local/index.js +110 -8
  80. package/build/module/local/index.js.map +1 -1
  81. package/build/module/postgres-error.d.ts +3 -3
  82. package/build/module/postgres-error.js +4 -3
  83. package/build/module/postgres-error.js.map +1 -1
  84. package/build/module/query/listing/status.js +2 -6
  85. package/build/module/query/listing/status.js.map +1 -1
  86. package/build/module/query/tenant/status.d.ts +1 -1
  87. package/build/module/query/tenant/status.js +6 -4
  88. package/build/module/query/tenant/status.js.map +1 -1
  89. package/build/module/regional.d.ts +1 -0
  90. package/build/module/regional.js +1 -0
  91. package/build/module/regional.js.map +1 -1
  92. package/build/module/test/index.d.ts +23 -3
  93. package/build/module/test/index.js +34 -13
  94. package/build/module/test/index.js.map +1 -1
  95. package/build/module/v1.generated.d.ts +560 -8
  96. package/build/module/v1.generated.js.map +1 -1
  97. package/package.json +4 -2
@@ -0,0 +1,77 @@
1
+ import type { Kysely } from 'kysely';
2
+ import { sql } from 'kysely';
3
+
4
+ export async function up(db: Kysely<unknown>): Promise<void> {
5
+ await sql`
6
+ create type audit.actor_organization_type as enum (
7
+ 'vrplatform', 'partner', 'team', 'system'
8
+ )
9
+ `.execute(db);
10
+ await sql`
11
+ alter type audit.source_type add value if not exists 'embedded'
12
+ `.execute(db);
13
+ await sql`
14
+ alter type webhook.subscription_event_type
15
+ add value if not exists 'audit.event.changed'
16
+ `.execute(db);
17
+ await sql`
18
+ alter type webhook.event_type
19
+ add value if not exists 'audit.event.changed'
20
+ `.execute(db);
21
+ await sql`
22
+ alter type webhook.resource_type
23
+ add value if not exists 'auditEvent'
24
+ `.execute(db);
25
+ await sql`
26
+ alter table audit.action
27
+ add column actor_organization_type audit.actor_organization_type,
28
+ add column actor_organization_id uuid,
29
+ add column actor_organization_name text
30
+ `.execute(db);
31
+ await sql`
32
+ alter table audit.mutation
33
+ add column public_outcome jsonb
34
+ `.execute(db);
35
+ await sql`
36
+ alter table audit.public_event
37
+ add column actor_organization_type audit.actor_organization_type,
38
+ add column actor_organization_id uuid,
39
+ add column actor_organization_name text,
40
+ add column attempted_entity_changes jsonb not null default '[]'::jsonb,
41
+ add column blocked_by jsonb not null default '[]'::jsonb,
42
+ add column recovery jsonb not null default '[]'::jsonb
43
+ `.execute(db);
44
+ await sql`
45
+ create index if not exists public_event_tenant_occurred_idx
46
+ on audit.public_event (tenant_id, occurred_at, updated_at, id)
47
+ `.execute(db);
48
+ }
49
+
50
+ export async function down(db: Kysely<unknown>): Promise<void> {
51
+ await sql`
52
+ drop index if exists audit.public_event_tenant_occurred_idx
53
+ `.execute(db);
54
+ await sql`
55
+ alter table audit.public_event
56
+ drop column if exists recovery,
57
+ drop column if exists blocked_by,
58
+ drop column if exists attempted_entity_changes,
59
+ drop column if exists actor_organization_name,
60
+ drop column if exists actor_organization_id,
61
+ drop column if exists actor_organization_type
62
+ `.execute(db);
63
+ await sql`
64
+ alter table audit.action
65
+ drop column if exists actor_organization_name,
66
+ drop column if exists actor_organization_id,
67
+ drop column if exists actor_organization_type
68
+ `.execute(db);
69
+ await sql`
70
+ alter table audit.mutation
71
+ drop column if exists public_outcome
72
+ `.execute(db);
73
+ await sql`
74
+ drop type if exists audit.actor_organization_type
75
+ `.execute(db);
76
+ // PostgreSQL enum values cannot be removed safely without rebuilding source_type.
77
+ }
@@ -0,0 +1,136 @@
1
+ import type { Kysely } from 'kysely';
2
+ import { sql } from 'kysely';
3
+
4
+ export async function up(db: Kysely<unknown>): Promise<void> {
5
+ await sql`
6
+ create temporary table reservation_journal_full_refresh_repair_scope (
7
+ invalidation_id uuid primary key,
8
+ tenant_id uuid not null,
9
+ reservation_id uuid not null,
10
+ applied_at timestamptz not null,
11
+ covering_effect_id uuid not null
12
+ ) on commit drop
13
+ `.execute(db);
14
+
15
+ await sql`
16
+ insert into reservation_journal_full_refresh_repair_scope (
17
+ invalidation_id,
18
+ tenant_id,
19
+ reservation_id,
20
+ applied_at,
21
+ covering_effect_id
22
+ )
23
+ select
24
+ pending.id,
25
+ pending.tenant_id,
26
+ pending.reservation_id,
27
+ covering.applied_at,
28
+ covering.effect_id
29
+ from accounting.reservation_journal_invalidation as pending
30
+ inner join lateral (
31
+ select
32
+ applied.applied_at,
33
+ applied.effect_id
34
+ from accounting.reservation_journal_invalidation as applied
35
+ inner join audit.effect as effect
36
+ on effect.tenant_id = applied.tenant_id
37
+ and effect.id = applied.effect_id
38
+ where applied.tenant_id = pending.tenant_id
39
+ and applied.reservation_id = pending.reservation_id
40
+ and applied.revision >= pending.revision
41
+ and applied.applied_at is not null
42
+ and applied.covered_by_effect_id = applied.effect_id
43
+ and effect.status = 'completed'
44
+ and coalesce(effect.payload ->> 'mode', 'full') = 'full'
45
+ and (
46
+ select attempt.status
47
+ from audit.effect_attempt as attempt
48
+ where attempt.tenant_id = effect.tenant_id
49
+ and attempt.effect_id = effect.id
50
+ order by attempt.attempt_num desc
51
+ limit 1
52
+ ) is distinct from 'skipped_locked'
53
+ order by applied.revision desc
54
+ limit 1
55
+ ) as covering on true
56
+ where pending.applied_at is null
57
+ `.execute(db);
58
+
59
+ await sql`
60
+ update accounting.reservation_journal_invalidation as invalidation
61
+ set
62
+ applied_at = scope.applied_at,
63
+ covered_by_effect_id = scope.covering_effect_id
64
+ from reservation_journal_full_refresh_repair_scope as scope
65
+ where invalidation.id = scope.invalidation_id
66
+ and invalidation.applied_at is null
67
+ `.execute(db);
68
+
69
+ await sql`
70
+ select state.tenant_id
71
+ from accounting.reservation_journal_state as state
72
+ inner join (
73
+ select distinct tenant_id, reservation_id
74
+ from reservation_journal_full_refresh_repair_scope
75
+ ) as scope
76
+ on scope.tenant_id = state.tenant_id
77
+ and scope.reservation_id = state.reservation_id
78
+ order by state.tenant_id, state.reservation_id
79
+ for update of state
80
+ `.execute(db);
81
+
82
+ await sql`
83
+ with advancement as (
84
+ select
85
+ state.tenant_id,
86
+ state.reservation_id,
87
+ coalesce(
88
+ min(invalidation.revision) filter (
89
+ where invalidation.applied_at is null
90
+ ) - 1,
91
+ max(invalidation.revision),
92
+ state.applied_revision
93
+ ) as applied_revision,
94
+ min(invalidation.created_at) filter (
95
+ where invalidation.applied_at is null
96
+ ) as stale_since,
97
+ max(invalidation.applied_at) as calculated_at
98
+ from accounting.reservation_journal_state as state
99
+ inner join (
100
+ select distinct tenant_id, reservation_id
101
+ from reservation_journal_full_refresh_repair_scope
102
+ ) as scope
103
+ on scope.tenant_id = state.tenant_id
104
+ and scope.reservation_id = state.reservation_id
105
+ left join accounting.reservation_journal_invalidation as invalidation
106
+ on invalidation.tenant_id = state.tenant_id
107
+ and invalidation.reservation_id = state.reservation_id
108
+ group by
109
+ state.tenant_id,
110
+ state.reservation_id,
111
+ state.applied_revision
112
+ )
113
+ update accounting.reservation_journal_state as state
114
+ set
115
+ applied_revision = greatest(
116
+ state.applied_revision,
117
+ advancement.applied_revision
118
+ ),
119
+ stale_since = advancement.stale_since,
120
+ calculated_at = case
121
+ when advancement.applied_revision > state.applied_revision
122
+ then greatest(state.calculated_at, advancement.calculated_at)
123
+ else state.calculated_at
124
+ end,
125
+ updated_at = now()
126
+ from advancement
127
+ where state.tenant_id = advancement.tenant_id
128
+ and state.reservation_id = advancement.reservation_id
129
+ and (
130
+ state.applied_revision < advancement.applied_revision
131
+ or state.stale_since is distinct from advancement.stale_since
132
+ )
133
+ `.execute(db);
134
+ }
135
+
136
+ export async function down(_db: Kysely<unknown>): Promise<void> {}
@@ -0,0 +1,60 @@
1
+ import { type Kysely, sql } from 'kysely';
2
+
3
+ export async function up(db: Kysely<unknown>): Promise<void> {
4
+ // Preserve the source lifecycle and failure precedence; ID format is not a failure policy.
5
+ await sql`
6
+ do $$
7
+ declare
8
+ definition text;
9
+ begin
10
+ definition := pg_get_functiondef('core.refresh_operation_state(uuid)'::regprocedure);
11
+ if position('and target_public_id_format = ''action''' in definition) = 0 then
12
+ raise exception 'Journal operation failure projection has an unexpected definition';
13
+ end if;
14
+ execute replace(definition, 'and target_public_id_format = ''action''', '');
15
+ end;
16
+ $$
17
+ `.execute(db);
18
+
19
+ await sql`
20
+ select core.refresh_operation_state(operation.id)
21
+ from core.operation as operation
22
+ where operation.type = 'reservation-journal-refresh'
23
+ and operation.public_id_format = 'legacy'
24
+ and operation.status = 'failed'
25
+ and exists (
26
+ select 1 from core.operation_source as source
27
+ where source.operation_id = operation.id
28
+ and source.source_type = 'audit-action'
29
+ )
30
+ `.execute(db);
31
+ }
32
+
33
+ export async function down(db: Kysely<unknown>): Promise<void> {
34
+ await sql`
35
+ do $$
36
+ declare
37
+ definition text;
38
+ begin
39
+ definition := pg_get_functiondef('core.refresh_operation_state(uuid)'::regprocedure);
40
+ if position('and target_public_id_format = ''action''' in definition) > 0
41
+ or position('and target_action_id is not null then' in definition) = 0 then
42
+ raise exception 'Journal operation failure projection has an unexpected definition';
43
+ end if;
44
+ execute replace(
45
+ definition,
46
+ 'and target_action_id is not null then',
47
+ 'and target_public_id_format = ''action'' and target_action_id is not null then'
48
+ );
49
+ end;
50
+ $$
51
+ `.execute(db);
52
+
53
+ await sql`
54
+ select core.refresh_operation_state(operation.id)
55
+ from core.operation as operation
56
+ where operation.type = 'reservation-journal-refresh'
57
+ and operation.public_id_format = 'legacy'
58
+ and operation.status = 'failed'
59
+ `.execute(db);
60
+ }
@@ -0,0 +1,43 @@
1
+ import type { Kysely } from 'kysely';
2
+ import { sql } from 'kysely';
3
+
4
+ export async function up(db: Kysely<unknown>): Promise<void> {
5
+ await sql`set local lock_timeout = '5s'`.execute(db);
6
+ await sql`set local statement_timeout = '60s'`.execute(db);
7
+ await sql`
8
+ create table public.connection_oauth_state (
9
+ state_hash text primary key,
10
+ tenant_id uuid not null references public.tenant (id) on delete cascade,
11
+ user_id uuid not null,
12
+ app_id text not null,
13
+ connection_id uuid,
14
+ params jsonb not null,
15
+ redirect_uri text not null,
16
+ expires_at timestamptz not null,
17
+ constraint connection_oauth_state_hash_shape check (state_hash ~ '^[0-9a-f]{64}$'),
18
+ constraint connection_oauth_state_params_object check (jsonb_typeof(params) = 'object')
19
+ )
20
+ `.execute(db);
21
+ await sql`create index connection_oauth_state_expiry_idx on public.connection_oauth_state (expires_at)`.execute(
22
+ db
23
+ );
24
+ await sql`create index connection_oauth_state_tenant_idx on public.connection_oauth_state (tenant_id)`.execute(
25
+ db
26
+ );
27
+ await sql`
28
+ do $$
29
+ begin
30
+ if exists (select 1 from pg_roles where rolname = 'application') then
31
+ grant select, insert, delete on public.connection_oauth_state to application;
32
+ -- SELECT FOR UPDATE SKIP LOCKED needs UPDATE on at least one column.
33
+ -- State bindings, parameters, and expiry remain non-updatable at runtime.
34
+ grant update (state_hash) on public.connection_oauth_state to application;
35
+ end if;
36
+ end;
37
+ $$
38
+ `.execute(db);
39
+ }
40
+
41
+ export async function down(db: Kysely<unknown>): Promise<void> {
42
+ await sql`drop table public.connection_oauth_state`.execute(db);
43
+ }
@@ -0,0 +1,134 @@
1
+ import type { Kysely } from 'kysely';
2
+ import { sql } from 'kysely';
3
+
4
+ export async function up(db: Kysely<unknown>): Promise<void> {
5
+ await sql`set local lock_timeout = '5s'`.execute(db);
6
+ await sql`set local statement_timeout = '60s'`.execute(db);
7
+
8
+ await sql`
9
+ create type core.email_notification_status as enum (
10
+ 'queued', 'processing', 'completed', 'failed', 'canceled'
11
+ )
12
+ `.execute(db);
13
+ await sql`
14
+ create type core.email_delivery_status as enum (
15
+ 'queued', 'sending', 'retrying', 'accepted', 'rejected', 'failed',
16
+ 'canceled'
17
+ )
18
+ `.execute(db);
19
+ await sql`
20
+ create type core.email_delivery_failure_code as enum (
21
+ 'inactive_recipient',
22
+ 'invalid_provider_response',
23
+ 'postmark_not_configured',
24
+ 'provider_rejected',
25
+ 'task_failed',
26
+ 'transport_failed'
27
+ )
28
+ `.execute(db);
29
+
30
+ await sql`
31
+ create table core.email_notification (
32
+ id uuid primary key references audit.effect(id) on delete cascade,
33
+ tenant_id uuid not null references public.tenant(id) on delete cascade,
34
+ owner_statement_id uuid not null,
35
+ status core.email_notification_status not null default 'queued',
36
+ trigger_run_id text,
37
+ recipients_resolved_at timestamptz,
38
+ started_at timestamptz,
39
+ finished_at timestamptz,
40
+ created_at timestamptz not null default now(),
41
+ updated_at timestamptz not null default now(),
42
+ constraint email_notification_terminal_check check (
43
+ (status in ('completed', 'failed', 'canceled') and finished_at is not null)
44
+ or (status in ('queued', 'processing') and finished_at is null)
45
+ )
46
+ )
47
+ `.execute(db);
48
+ await sql`
49
+ create index email_notification_statement_history_idx
50
+ on core.email_notification (
51
+ tenant_id, owner_statement_id, created_at desc, id desc
52
+ )
53
+ `.execute(db);
54
+
55
+ await sql`
56
+ create table core.email_delivery (
57
+ id uuid primary key default gen_random_uuid(),
58
+ notification_id uuid not null
59
+ references core.email_notification(id) on delete cascade,
60
+ tenant_id uuid not null references public.tenant(id) on delete cascade,
61
+ user_id uuid not null,
62
+ status core.email_delivery_status not null default 'queued',
63
+ attempt_count integer not null default 0,
64
+ trigger_run_id text,
65
+ postmark_message_id text,
66
+ postmark_error_code integer,
67
+ http_status integer,
68
+ failure_code core.email_delivery_failure_code,
69
+ submitted_at timestamptz,
70
+ last_attempt_at timestamptz,
71
+ finished_at timestamptz,
72
+ created_at timestamptz not null default now(),
73
+ updated_at timestamptz not null default now(),
74
+ constraint email_delivery_notification_user_key
75
+ unique (notification_id, user_id),
76
+ constraint email_delivery_attempt_count_check check (attempt_count >= 0),
77
+ constraint email_delivery_postmark_error_code_check check (
78
+ postmark_error_code is null or postmark_error_code >= 0
79
+ ),
80
+ constraint email_delivery_http_status_check check (
81
+ http_status is null or http_status between 100 and 599
82
+ ),
83
+ constraint email_delivery_terminal_check check (
84
+ (status in ('accepted', 'rejected', 'failed', 'canceled')
85
+ and finished_at is not null)
86
+ or (status in ('queued', 'sending', 'retrying') and finished_at is null)
87
+ ),
88
+ constraint email_delivery_accepted_check check (
89
+ status != 'accepted'
90
+ or (
91
+ postmark_message_id is not null
92
+ and submitted_at is not null
93
+ and failure_code is null
94
+ )
95
+ ),
96
+ constraint email_delivery_failure_check check (
97
+ status not in ('rejected', 'failed') or failure_code is not null
98
+ )
99
+ )
100
+ `.execute(db);
101
+ await sql`
102
+ create index email_delivery_notification_status_idx
103
+ on core.email_delivery (notification_id, status, id)
104
+ `.execute(db);
105
+ await sql`
106
+ create index email_delivery_tenant_created_idx
107
+ on core.email_delivery (tenant_id, created_at desc, id desc)
108
+ `.execute(db);
109
+ await sql`
110
+ create unique index email_delivery_postmark_message_idx
111
+ on core.email_delivery (postmark_message_id)
112
+ where postmark_message_id is not null
113
+ `.execute(db);
114
+
115
+ await sql`
116
+ do $$
117
+ begin
118
+ if exists (select 1 from pg_roles where rolname = 'application') then
119
+ grant select, insert, update
120
+ on core.email_notification, core.email_delivery
121
+ to application;
122
+ end if;
123
+ end;
124
+ $$
125
+ `.execute(db);
126
+ }
127
+
128
+ export async function down(db: Kysely<unknown>): Promise<void> {
129
+ await sql`drop table core.email_delivery`.execute(db);
130
+ await sql`drop table core.email_notification`.execute(db);
131
+ await sql`drop type core.email_delivery_failure_code`.execute(db);
132
+ await sql`drop type core.email_delivery_status`.execute(db);
133
+ await sql`drop type core.email_notification_status`.execute(db);
134
+ }
@@ -0,0 +1,59 @@
1
+ import { type Kysely, sql } from 'kysely';
2
+
3
+ const original = `invalidation.id is null
4
+ and effect.status = 'completed'`;
5
+ const corrected = `(invalidation.id is null or (
6
+ exists (
7
+ select 1 from audit.action as action
8
+ where action.id = effect.action_id
9
+ and action.tenant_id = effect.tenant_id
10
+ and action.root_entity_type = 'recurringFee'
11
+ )
12
+ and (
13
+ select attempt.status from audit.effect_attempt as attempt
14
+ where attempt.effect_id = effect.id
15
+ and attempt.tenant_id = effect.tenant_id
16
+ order by attempt.attempt_num desc limit 1
17
+ ) = 'skipped_locked'
18
+ ))
19
+ and effect.status = 'completed'`;
20
+
21
+ async function replaceFeeLockSkipProjection<Database>(
22
+ db: Kysely<Database>,
23
+ before: string,
24
+ after: string
25
+ ) {
26
+ const { rows } = await sql<{ definition: string }>`
27
+ select pg_get_functiondef('core.refresh_audit_action_source(uuid)'::regprocedure)
28
+ as definition
29
+ `.execute(db);
30
+ const definition = rows[0]?.definition;
31
+ if (!definition || definition.split(before).length !== 2) {
32
+ throw new Error(
33
+ 'Journal operation source projection has an unexpected definition'
34
+ );
35
+ }
36
+ await sql.raw(definition.replace(before, after)).execute(db);
37
+ // Reproject derived operation state only. Preserve original attempts and journal invalidations.
38
+ await sql`
39
+ select core.refresh_audit_action_source(action.id)
40
+ from audit.action as action
41
+ where action.root_entity_type = 'recurringFee'
42
+ and exists (
43
+ select 1 from core.operation_source as source
44
+ inner join core.operation as operation on operation.id = source.operation_id
45
+ where source.source_type = 'audit-action'
46
+ and source.source_id = action.id
47
+ and operation.tenant_id = action.tenant_id
48
+ and operation.type = 'reservation-journal-refresh'
49
+ )
50
+ `.execute(db);
51
+ }
52
+
53
+ export async function up<Database>(db: Kysely<Database>): Promise<void> {
54
+ await replaceFeeLockSkipProjection(db, original, corrected);
55
+ }
56
+
57
+ export async function down<Database>(db: Kysely<Database>): Promise<void> {
58
+ await replaceFeeLockSkipProjection(db, corrected, original);
59
+ }
@@ -0,0 +1,44 @@
1
+ import { type Kysely, sql } from 'kysely';
2
+
3
+ async function replaceNullableLockSkip<Database>(
4
+ db: Kysely<Database>,
5
+ before: string,
6
+ after: string
7
+ ) {
8
+ const { rows } = await sql<{ definition: string }>`
9
+ select pg_get_functiondef('core.refresh_audit_action_source(uuid)'::regprocedure) as definition
10
+ `.execute(db);
11
+ const definition = rows[0]?.definition;
12
+ if (!definition || definition.split(before).length !== 2) {
13
+ throw new Error('Fee lock skip projection has an unexpected definition');
14
+ }
15
+ await sql.raw(definition.replace(before, after)).execute(db);
16
+ await sql`
17
+ select core.refresh_audit_action_source(action.id)
18
+ from audit.action as action
19
+ where action.root_entity_type = 'recurringFee'
20
+ and exists (
21
+ select 1 from core.operation_source as source
22
+ inner join core.operation as operation on operation.id = source.operation_id
23
+ where source.source_type = 'audit-action' and source.source_id = action.id
24
+ and operation.tenant_id = action.tenant_id
25
+ and operation.type = 'reservation-journal-refresh'
26
+ )
27
+ `.execute(db);
28
+ }
29
+
30
+ export async function up<Database>(db: Kysely<Database>): Promise<void> {
31
+ await replaceNullableLockSkip(
32
+ db,
33
+ ") = 'skipped_locked'",
34
+ ") is not distinct from 'skipped_locked'"
35
+ );
36
+ }
37
+
38
+ export async function down<Database>(db: Kysely<Database>): Promise<void> {
39
+ await replaceNullableLockSkip(
40
+ db,
41
+ ") is not distinct from 'skipped_locked'",
42
+ ") = 'skipped_locked'"
43
+ );
44
+ }
@@ -0,0 +1,16 @@
1
+ import type { Kysely } from 'kysely';
2
+ import { sql } from 'kysely';
3
+
4
+ export async function up(db: Kysely<unknown>): Promise<void> {
5
+ // Expand storage only. New codes must be readable on every channel before
6
+ // issue collectors begin writing them to shared regional databases.
7
+ await sql`alter type health.tenant_issue_code add value if not exists 'journalLocked'`.execute(
8
+ db
9
+ );
10
+ await sql`alter type health.tenant_issue_code add value if not exists 'journalConfigurationInvalid'`.execute(
11
+ db
12
+ );
13
+ await sql`alter type health.tenant_issue_code add value if not exists 'journalUnbalanced'`.execute(
14
+ db
15
+ );
16
+ }
@@ -0,0 +1,8 @@
1
+ import type { Kysely } from 'kysely';
2
+ import { sql } from 'kysely';
3
+
4
+ export async function up(db: Kysely<unknown>): Promise<void> {
5
+ await sql`alter table public.tenant add column billing_subscription_id text`.execute(
6
+ db
7
+ );
8
+ }