@southwind-ai/database 0.1.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.
@@ -0,0 +1,266 @@
1
+ import { sql } from "drizzle-orm";
2
+ import {
3
+ boolean,
4
+ check,
5
+ index,
6
+ integer,
7
+ jsonb,
8
+ pgTable,
9
+ text,
10
+ timestamp,
11
+ uniqueIndex,
12
+ varchar,
13
+ } from "drizzle-orm/pg-core";
14
+
15
+ export const durableJobs = pgTable(
16
+ "durable_jobs",
17
+ {
18
+ id: varchar("id", { length: 64 }).primaryKey(),
19
+ handlerKey: varchar("handler_key", { length: 160 }).notNull(),
20
+ queue: varchar("queue", { length: 80 }).notNull().default("default"),
21
+ status: varchar("status", { length: 32 }).notNull().default("queued"),
22
+ idempotencyKey: varchar("idempotency_key", { length: 160 }).notNull(),
23
+ payload: jsonb("payload").$type<Record<string, unknown>>().notNull(),
24
+ priority: integer("priority").notNull().default(0),
25
+ requestedBy: varchar("requested_by", { length: 64 }).notNull(),
26
+ maxAttempts: integer("max_attempts").notNull().default(3),
27
+ timeoutSeconds: integer("timeout_seconds").notNull().default(900),
28
+ availableAt: timestamp("available_at", { withTimezone: true }).notNull(),
29
+ cancelRequestedAt: timestamp("cancel_requested_at", { withTimezone: true }),
30
+ startedAt: timestamp("started_at", { withTimezone: true }),
31
+ finishedAt: timestamp("finished_at", { withTimezone: true }),
32
+ createdAt: timestamp("created_at", { withTimezone: true })
33
+ .notNull()
34
+ .defaultNow(),
35
+ updatedAt: timestamp("updated_at", { withTimezone: true })
36
+ .notNull()
37
+ .defaultNow(),
38
+ },
39
+ (table) => [
40
+ uniqueIndex("uk_durable_jobs_idempotency").on(
41
+ table.requestedBy,
42
+ table.handlerKey,
43
+ table.idempotencyKey,
44
+ ),
45
+ index("idx_durable_jobs_claim").on(
46
+ table.queue,
47
+ table.status,
48
+ table.availableAt,
49
+ table.priority,
50
+ ),
51
+ index("idx_durable_jobs_actor_created").on(
52
+ table.requestedBy,
53
+ table.createdAt,
54
+ ),
55
+ check("ck_durable_jobs_attempts", sql`${table.maxAttempts} > 0`),
56
+ check("ck_durable_jobs_timeout", sql`${table.timeoutSeconds} > 0`),
57
+ ],
58
+ );
59
+
60
+ export const durableJobStages = pgTable(
61
+ "durable_job_stages",
62
+ {
63
+ id: varchar("id", { length: 64 }).primaryKey(),
64
+ jobId: varchar("job_id", { length: 64 })
65
+ .notNull()
66
+ .references(() => durableJobs.id, { onDelete: "restrict" }),
67
+ key: varchar("key", { length: 160 }).notNull(),
68
+ ordinal: integer("ordinal").notNull(),
69
+ status: varchar("status", { length: 32 }).notNull().default("pending"),
70
+ currentAttempt: integer("current_attempt").notNull().default(0),
71
+ startedAt: timestamp("started_at", { withTimezone: true }),
72
+ finishedAt: timestamp("finished_at", { withTimezone: true }),
73
+ createdAt: timestamp("created_at", { withTimezone: true })
74
+ .notNull()
75
+ .defaultNow(),
76
+ updatedAt: timestamp("updated_at", { withTimezone: true })
77
+ .notNull()
78
+ .defaultNow(),
79
+ },
80
+ (table) => [
81
+ uniqueIndex("uk_durable_job_stages_key").on(table.jobId, table.key),
82
+ uniqueIndex("uk_durable_job_stages_ordinal").on(table.jobId, table.ordinal),
83
+ check("ck_durable_job_stages_ordinal", sql`${table.ordinal} >= 0`),
84
+ check("ck_durable_job_stages_attempt", sql`${table.currentAttempt} >= 0`),
85
+ ],
86
+ );
87
+
88
+ export const durableJobAttempts = pgTable(
89
+ "durable_job_attempts",
90
+ {
91
+ id: varchar("id", { length: 64 }).primaryKey(),
92
+ jobId: varchar("job_id", { length: 64 })
93
+ .notNull()
94
+ .references(() => durableJobs.id, { onDelete: "restrict" }),
95
+ stageId: varchar("stage_id", { length: 64 })
96
+ .notNull()
97
+ .references(() => durableJobStages.id, { onDelete: "restrict" }),
98
+ attemptNumber: integer("attempt_number").notNull(),
99
+ status: varchar("status", { length: 32 }).notNull().default("running"),
100
+ handlerKey: varchar("handler_key", { length: 160 }).notNull(),
101
+ workerId: varchar("worker_id", { length: 160 }).notNull(),
102
+ startedAt: timestamp("started_at", { withTimezone: true }).notNull(),
103
+ timeoutAt: timestamp("timeout_at", { withTimezone: true }).notNull(),
104
+ finishedAt: timestamp("finished_at", { withTimezone: true }),
105
+ createdAt: timestamp("created_at", { withTimezone: true })
106
+ .notNull()
107
+ .defaultNow(),
108
+ },
109
+ (table) => [
110
+ uniqueIndex("uk_durable_job_attempts_number").on(
111
+ table.stageId,
112
+ table.attemptNumber,
113
+ ),
114
+ index("idx_durable_job_attempts_job").on(table.jobId, table.startedAt),
115
+ check("ck_durable_job_attempts_number", sql`${table.attemptNumber} > 0`),
116
+ ],
117
+ );
118
+
119
+ export const durableJobLeases = pgTable(
120
+ "durable_job_leases",
121
+ {
122
+ id: varchar("id", { length: 64 }).primaryKey(),
123
+ jobId: varchar("job_id", { length: 64 })
124
+ .notNull()
125
+ .references(() => durableJobs.id, { onDelete: "restrict" }),
126
+ stageId: varchar("stage_id", { length: 64 })
127
+ .notNull()
128
+ .references(() => durableJobStages.id, { onDelete: "restrict" }),
129
+ attemptId: varchar("attempt_id", { length: 64 })
130
+ .notNull()
131
+ .references(() => durableJobAttempts.id, { onDelete: "restrict" }),
132
+ workerId: varchar("worker_id", { length: 160 }).notNull(),
133
+ tokenHash: varchar("token_hash", { length: 64 }).notNull(),
134
+ fencingToken: integer("fencing_token").notNull(),
135
+ status: varchar("status", { length: 24 }).notNull().default("active"),
136
+ acquiredAt: timestamp("acquired_at", { withTimezone: true }).notNull(),
137
+ heartbeatAt: timestamp("heartbeat_at", { withTimezone: true }).notNull(),
138
+ expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
139
+ releasedAt: timestamp("released_at", { withTimezone: true }),
140
+ },
141
+ (table) => [
142
+ uniqueIndex("uk_durable_job_leases_active_stage")
143
+ .on(table.stageId)
144
+ .where(sql`${table.status} = 'active'`),
145
+ index("idx_durable_job_leases_expiry").on(table.status, table.expiresAt),
146
+ check("ck_durable_job_leases_fencing", sql`${table.fencingToken} > 0`),
147
+ ],
148
+ );
149
+
150
+ export const durableJobProgress = pgTable(
151
+ "durable_job_progress",
152
+ {
153
+ id: varchar("id", { length: 64 }).primaryKey(),
154
+ jobId: varchar("job_id", { length: 64 })
155
+ .notNull()
156
+ .references(() => durableJobs.id, { onDelete: "restrict" }),
157
+ stageId: varchar("stage_id", { length: 64 })
158
+ .notNull()
159
+ .references(() => durableJobStages.id, { onDelete: "restrict" }),
160
+ attemptId: varchar("attempt_id", { length: 64 })
161
+ .notNull()
162
+ .references(() => durableJobAttempts.id, { onDelete: "restrict" }),
163
+ sequence: integer("sequence").notNull(),
164
+ completedUnits: integer("completed_units").notNull(),
165
+ totalUnits: integer("total_units"),
166
+ message: varchar("message", { length: 500 }),
167
+ metadata: jsonb("metadata").$type<Record<string, unknown>>(),
168
+ recordedAt: timestamp("recorded_at", { withTimezone: true })
169
+ .notNull()
170
+ .defaultNow(),
171
+ },
172
+ (table) => [
173
+ uniqueIndex("uk_durable_job_progress_sequence").on(
174
+ table.attemptId,
175
+ table.sequence,
176
+ ),
177
+ index("idx_durable_job_progress_job").on(table.jobId, table.recordedAt),
178
+ check("ck_durable_job_progress_sequence", sql`${table.sequence} > 0`),
179
+ check(
180
+ "ck_durable_job_progress_completed",
181
+ sql`${table.completedUnits} >= 0`,
182
+ ),
183
+ ],
184
+ );
185
+
186
+ export const durableJobArtifacts = pgTable(
187
+ "durable_job_artifacts",
188
+ {
189
+ id: varchar("id", { length: 64 }).primaryKey(),
190
+ jobId: varchar("job_id", { length: 64 })
191
+ .notNull()
192
+ .references(() => durableJobs.id, { onDelete: "restrict" }),
193
+ stageId: varchar("stage_id", { length: 64 }).references(
194
+ () => durableJobStages.id,
195
+ { onDelete: "restrict" },
196
+ ),
197
+ kind: varchar("kind", { length: 80 }).notNull(),
198
+ artifactRef: varchar("artifact_ref", { length: 37 }).notNull(),
199
+ filename: varchar("filename", { length: 240 }).notNull(),
200
+ contentType: varchar("content_type", { length: 120 }).notNull(),
201
+ byteSize: integer("byte_size").notNull(),
202
+ sha256: varchar("sha256", { length: 64 }).notNull(),
203
+ ownerId: varchar("owner_id", { length: 64 }).notNull(),
204
+ expiresAt: timestamp("expires_at", { withTimezone: true }),
205
+ createdAt: timestamp("created_at", { withTimezone: true })
206
+ .notNull()
207
+ .defaultNow(),
208
+ },
209
+ (table) => [
210
+ uniqueIndex("uk_durable_job_artifacts_kind").on(table.jobId, table.kind),
211
+ index("idx_durable_job_artifacts_expiry").on(table.expiresAt),
212
+ check("ck_durable_job_artifacts_size", sql`${table.byteSize} >= 0`),
213
+ ],
214
+ );
215
+
216
+ export const durableJobFailures = pgTable(
217
+ "durable_job_failures",
218
+ {
219
+ id: varchar("id", { length: 64 }).primaryKey(),
220
+ jobId: varchar("job_id", { length: 64 })
221
+ .notNull()
222
+ .references(() => durableJobs.id, { onDelete: "restrict" }),
223
+ stageId: varchar("stage_id", { length: 64 }).references(
224
+ () => durableJobStages.id,
225
+ { onDelete: "restrict" },
226
+ ),
227
+ attemptId: varchar("attempt_id", { length: 64 }).references(
228
+ () => durableJobAttempts.id,
229
+ { onDelete: "restrict" },
230
+ ),
231
+ code: varchar("code", { length: 120 }).notNull(),
232
+ message: text("message").notNull(),
233
+ retryable: boolean("retryable").notNull(),
234
+ details: jsonb("details").$type<Record<string, unknown>>(),
235
+ occurredAt: timestamp("occurred_at", { withTimezone: true })
236
+ .notNull()
237
+ .defaultNow(),
238
+ },
239
+ (table) => [
240
+ index("idx_durable_job_failures_job").on(table.jobId, table.occurredAt),
241
+ ],
242
+ );
243
+
244
+ export const durableJobRecoveries = pgTable(
245
+ "durable_job_recoveries",
246
+ {
247
+ id: varchar("id", { length: 64 }).primaryKey(),
248
+ jobId: varchar("job_id", { length: 64 })
249
+ .notNull()
250
+ .references(() => durableJobs.id, { onDelete: "restrict" }),
251
+ fromStatus: varchar("from_status", { length: 32 }).notNull(),
252
+ requestedBy: varchar("requested_by", { length: 64 }).notNull(),
253
+ reason: varchar("reason", { length: 500 }).notNull(),
254
+ idempotencyKey: varchar("idempotency_key", { length: 160 }).notNull(),
255
+ createdAt: timestamp("created_at", { withTimezone: true })
256
+ .notNull()
257
+ .defaultNow(),
258
+ },
259
+ (table) => [
260
+ uniqueIndex("uk_durable_job_recoveries_idempotency").on(
261
+ table.jobId,
262
+ table.idempotencyKey,
263
+ ),
264
+ index("idx_durable_job_recoveries_job").on(table.jobId, table.createdAt),
265
+ ],
266
+ );
@@ -0,0 +1,94 @@
1
+ import {
2
+ bigint,
3
+ index,
4
+ integer,
5
+ pgTable,
6
+ timestamp,
7
+ uniqueIndex,
8
+ varchar,
9
+ } from "drizzle-orm/pg-core";
10
+
11
+ export const fileUploadSessions = pgTable(
12
+ "file_upload_sessions",
13
+ {
14
+ id: varchar("id", { length: 40 }).primaryKey(),
15
+ ownerId: varchar("owner_id", { length: 64 }).notNull(),
16
+ purpose: varchar("purpose", { length: 80 }).notNull(),
17
+ filename: varchar("filename", { length: 240 }).notNull(),
18
+ extension: varchar("extension", { length: 24 }).notNull(),
19
+ mimeType: varchar("mime_type", { length: 120 }).notNull(),
20
+ expectedByteSize: bigint("expected_byte_size", {
21
+ mode: "number",
22
+ }).notNull(),
23
+ expectedSha256: varchar("expected_sha256", { length: 64 }),
24
+ receivedByteSize: bigint("received_byte_size", { mode: "number" })
25
+ .notNull()
26
+ .default(0),
27
+ nextPartNumber: integer("next_part_number").notNull().default(0),
28
+ status: varchar("status", { length: 24 }).notNull(),
29
+ expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
30
+ fileExpiresAt: timestamp("file_expires_at", { withTimezone: true }),
31
+ fileId: varchar("file_id", { length: 40 }),
32
+ createdAt: timestamp("created_at", { withTimezone: true })
33
+ .notNull()
34
+ .defaultNow(),
35
+ updatedAt: timestamp("updated_at", { withTimezone: true })
36
+ .notNull()
37
+ .defaultNow(),
38
+ },
39
+ (table) => [
40
+ index("idx_file_upload_sessions_owner_created").on(
41
+ table.ownerId,
42
+ table.createdAt,
43
+ ),
44
+ index("idx_file_upload_sessions_expiry").on(table.status, table.expiresAt),
45
+ ],
46
+ );
47
+
48
+ export const fileUploadParts = pgTable(
49
+ "file_upload_parts",
50
+ {
51
+ sessionId: varchar("session_id", { length: 40 })
52
+ .notNull()
53
+ .references(() => fileUploadSessions.id, { onDelete: "cascade" }),
54
+ partNumber: integer("part_number").notNull(),
55
+ byteOffset: bigint("byte_offset", { mode: "number" }).notNull(),
56
+ artifactRef: varchar("artifact_ref", { length: 37 }).notNull(),
57
+ byteSize: bigint("byte_size", { mode: "number" }).notNull(),
58
+ sha256: varchar("sha256", { length: 64 }).notNull(),
59
+ createdAt: timestamp("created_at", { withTimezone: true })
60
+ .notNull()
61
+ .defaultNow(),
62
+ },
63
+ (table) => [
64
+ uniqueIndex("uk_file_upload_parts_session_part").on(
65
+ table.sessionId,
66
+ table.partNumber,
67
+ ),
68
+ uniqueIndex("uk_file_upload_parts_artifact_ref").on(table.artifactRef),
69
+ ],
70
+ );
71
+
72
+ export const fileMetadata = pgTable(
73
+ "file_metadata",
74
+ {
75
+ id: varchar("id", { length: 40 }).primaryKey(),
76
+ ownerId: varchar("owner_id", { length: 64 }).notNull(),
77
+ purpose: varchar("purpose", { length: 80 }).notNull(),
78
+ filename: varchar("filename", { length: 240 }).notNull(),
79
+ extension: varchar("extension", { length: 24 }).notNull(),
80
+ mimeType: varchar("mime_type", { length: 120 }).notNull(),
81
+ artifactRef: varchar("artifact_ref", { length: 37 }).notNull(),
82
+ byteSize: bigint("byte_size", { mode: "number" }).notNull(),
83
+ sha256: varchar("sha256", { length: 64 }).notNull(),
84
+ expiresAt: timestamp("expires_at", { withTimezone: true }),
85
+ createdAt: timestamp("created_at", { withTimezone: true })
86
+ .notNull()
87
+ .defaultNow(),
88
+ },
89
+ (table) => [
90
+ uniqueIndex("uk_file_metadata_artifact_ref").on(table.artifactRef),
91
+ index("idx_file_metadata_owner_created").on(table.ownerId, table.createdAt),
92
+ index("idx_file_metadata_expiry").on(table.expiresAt),
93
+ ],
94
+ );
@@ -0,0 +1,204 @@
1
+ // @windy-module system.license begin
2
+ import { sql } from "drizzle-orm";
3
+ import { timestamp as licenseTimestamp } from "drizzle-orm/pg-core";
4
+ import { idColumn as licenseIdColumn } from "./common.js";
5
+ // @windy-module system.license end
6
+ // @windy-module system.audit begin
7
+ import { timestamp as auditTimestamp } from "drizzle-orm/pg-core";
8
+ import { idColumn as auditIdColumn } from "./common.js";
9
+ // @windy-module system.audit end
10
+ import {
11
+ boolean,
12
+ index,
13
+ jsonb,
14
+ pgTable,
15
+ text,
16
+ // @windy-module system.license begin
17
+ uniqueIndex,
18
+ // @windy-module system.license end
19
+ varchar,
20
+ } from "drizzle-orm/pg-core";
21
+ import { auditColumns, statusColumn } from "./common.js";
22
+
23
+ export const featureFlags = pgTable(
24
+ "feature_flags",
25
+ {
26
+ key: varchar("key", { length: 160 }).primaryKey(),
27
+ module: varchar("module", { length: 96 }).notNull(),
28
+ label: varchar("label", { length: 120 }).notNull(),
29
+ description: text("description"),
30
+ enabled: boolean("enabled").notNull().default(false),
31
+ visible: varchar("visible", { length: 32 }).notNull().default("visible"),
32
+ stage: varchar("stage", { length: 32 }).notNull().default("stable"),
33
+ // 旧布尔列仅为迁移兼容保留;正式 Guard 只读取 licenseKey 对应的 Entitlement。
34
+ licenseRequired: boolean("license_required").notNull().default(false),
35
+ licenseKey: varchar("license_key", { length: 160 }),
36
+ allowedLicenseVersions: jsonb("allowed_license_versions").$type<string[]>(),
37
+ dependencies: jsonb("dependencies").$type<string[]>(),
38
+ status: statusColumn().notNull().default("active"),
39
+ ...auditColumns,
40
+ },
41
+ (table) => [
42
+ index("idx_feature_flags_module").on(table.module),
43
+ index("idx_feature_flags_license").on(table.licenseKey),
44
+ ],
45
+ );
46
+
47
+ // @windy-module system.license begin
48
+ export const licenseOperators = pgTable(
49
+ "license_operators",
50
+ {
51
+ id: licenseIdColumn().primaryKey(),
52
+ username: varchar("username", { length: 80 }).notNull(),
53
+ displayName: varchar("display_name", { length: 120 }).notNull(),
54
+ tokenHash: varchar("token_hash", { length: 128 }),
55
+ passwordHash: text("password_hash"),
56
+ role: varchar("role", { length: 40 }).notNull().default("readonly"),
57
+ permissions: jsonb("permissions").$type<string[]>().notNull(),
58
+ status: statusColumn().notNull(),
59
+ lastUsedAt: licenseTimestamp("last_used_at", { withTimezone: true }),
60
+ createdAt: licenseTimestamp("created_at", { withTimezone: true })
61
+ .notNull()
62
+ .defaultNow(),
63
+ updatedAt: licenseTimestamp("updated_at", { withTimezone: true }),
64
+ updatedBy: varchar("updated_by", { length: 64 }),
65
+ disabledAt: licenseTimestamp("disabled_at", { withTimezone: true }),
66
+ disabledBy: varchar("disabled_by", { length: 64 }),
67
+ },
68
+ (table) => [
69
+ uniqueIndex("uk_license_operators_username").on(table.username),
70
+ index("idx_license_operators_status").on(table.status),
71
+ ],
72
+ );
73
+
74
+ export const licenseOperatorSessions = pgTable(
75
+ "license_operator_sessions",
76
+ {
77
+ id: licenseIdColumn().primaryKey(),
78
+ operatorId: varchar("operator_id", { length: 64 })
79
+ .notNull()
80
+ .references(() => licenseOperators.id, { onDelete: "restrict" }),
81
+ tokenHash: varchar("token_hash", { length: 128 }).notNull(),
82
+ status: statusColumn().notNull().default("active"),
83
+ issuedAt: licenseTimestamp("issued_at", { withTimezone: true })
84
+ .notNull()
85
+ .defaultNow(),
86
+ expiresAt: licenseTimestamp("expires_at", { withTimezone: true }).notNull(),
87
+ revokedAt: licenseTimestamp("revoked_at", { withTimezone: true }),
88
+ lastSeenAt: licenseTimestamp("last_seen_at", { withTimezone: true }),
89
+ ip: varchar("ip", { length: 80 }),
90
+ userAgent: text("user_agent"),
91
+ },
92
+ (table) => [
93
+ uniqueIndex("uk_license_operator_sessions_token_hash").on(table.tokenHash),
94
+ index("idx_license_operator_sessions_operator").on(table.operatorId),
95
+ index("idx_license_operator_sessions_status").on(table.status),
96
+ index("idx_license_operator_sessions_expires").on(table.expiresAt),
97
+ ],
98
+ );
99
+
100
+ export const licenseKeys = pgTable(
101
+ "license_keys",
102
+ {
103
+ id: licenseIdColumn().primaryKey(),
104
+ keyId: varchar("key_id", { length: 160 }).notNull(),
105
+ publicKeyPem: text("public_key_pem").notNull(),
106
+ algorithm: varchar("algorithm", { length: 40 })
107
+ .notNull()
108
+ .default("ED25519"),
109
+ status: statusColumn().notNull(),
110
+ rotatedFromKeyId: varchar("rotated_from_key_id", { length: 160 }),
111
+ createdAt: licenseTimestamp("created_at", { withTimezone: true }).notNull(),
112
+ createdBy: varchar("created_by", { length: 64 }).notNull(),
113
+ retiredAt: licenseTimestamp("retired_at", { withTimezone: true }),
114
+ retiredBy: varchar("retired_by", { length: 64 }),
115
+ retainUntil: licenseTimestamp("retain_until", { withTimezone: true }),
116
+ },
117
+ (table) => [
118
+ uniqueIndex("uk_license_keys_key_id").on(table.keyId),
119
+ uniqueIndex("uk_license_keys_single_active")
120
+ .on(table.status)
121
+ .where(sql`${table.status} = 'active'`),
122
+ index("idx_license_keys_status").on(table.status),
123
+ ],
124
+ );
125
+
126
+ export const licenseKeyCompatibilities = pgTable(
127
+ "license_key_compatibilities",
128
+ {
129
+ id: licenseIdColumn().primaryKey(),
130
+ productCode: varchar("product_code", { length: 120 }).notNull(),
131
+ softwareVersion: varchar("software_version", { length: 80 }).notNull(),
132
+ publicKeyId: varchar("public_key_id", { length: 160 })
133
+ .notNull()
134
+ .references(() => licenseKeys.keyId, { onDelete: "restrict" }),
135
+ declaredAt: licenseTimestamp("declared_at", { withTimezone: true })
136
+ .notNull()
137
+ .defaultNow(),
138
+ declaredBy: varchar("declared_by", { length: 64 }).notNull(),
139
+ },
140
+ (table) => [
141
+ uniqueIndex("uk_license_key_compatibility").on(
142
+ table.productCode,
143
+ table.softwareVersion,
144
+ table.publicKeyId,
145
+ ),
146
+ index("idx_license_key_compatibility_version").on(
147
+ table.productCode,
148
+ table.softwareVersion,
149
+ ),
150
+ index("idx_license_key_compatibility_key").on(table.publicKeyId),
151
+ ],
152
+ );
153
+
154
+ export const licenseGovernanceEvents = pgTable(
155
+ "license_governance_events",
156
+ {
157
+ id: licenseIdColumn().primaryKey(),
158
+ type: varchar("type", { length: 100 }).notNull(),
159
+ resourceType: varchar("resource_type", { length: 80 }).notNull(),
160
+ resourceId: varchar("resource_id", { length: 160 }).notNull(),
161
+ actorId: varchar("actor_id", { length: 64 }).notNull(),
162
+ occurredAt: licenseTimestamp("occurred_at", { withTimezone: true })
163
+ .notNull()
164
+ .defaultNow(),
165
+ payload: jsonb("payload").$type<Record<string, unknown>>().notNull(),
166
+ },
167
+ (table) => [
168
+ index("idx_license_governance_resource_time").on(
169
+ table.resourceType,
170
+ table.resourceId,
171
+ table.occurredAt,
172
+ ),
173
+ index("idx_license_governance_type_time").on(table.type, table.occurredAt),
174
+ index("idx_license_governance_actor_time").on(
175
+ table.actorId,
176
+ table.occurredAt,
177
+ ),
178
+ ],
179
+ );
180
+ // @windy-module system.license end
181
+
182
+ // @windy-module system.audit begin
183
+ export const auditLogs = pgTable(
184
+ "audit_logs",
185
+ {
186
+ id: auditIdColumn().primaryKey(),
187
+ actorId: varchar("actor_id", { length: 64 }),
188
+ action: varchar("action", { length: 120 }).notNull(),
189
+ resource: varchar("resource", { length: 160 }).notNull(),
190
+ resourceId: varchar("resource_id", { length: 160 }),
191
+ ip: varchar("ip", { length: 64 }),
192
+ userAgent: text("user_agent"),
193
+ metadata: jsonb("metadata").$type<Record<string, unknown>>(),
194
+ createdAt: auditTimestamp("created_at", { withTimezone: true })
195
+ .notNull()
196
+ .defaultNow(),
197
+ },
198
+ (table) => [
199
+ index("idx_audit_logs_actor").on(table.actorId),
200
+ index("idx_audit_logs_resource").on(table.resource, table.resourceId),
201
+ index("idx_audit_logs_created").on(table.createdAt),
202
+ ],
203
+ );
204
+ // @windy-module system.audit end
@@ -0,0 +1,90 @@
1
+ import { sql } from "drizzle-orm";
2
+ import type { LifecycleStatus } from "@southwind-ai/shared";
3
+ import {
4
+ boolean,
5
+ check,
6
+ index,
7
+ jsonb,
8
+ pgTable,
9
+ timestamp,
10
+ uniqueIndex,
11
+ varchar,
12
+ } from "drizzle-orm/pg-core";
13
+ import type { GovernedExportScopeSnapshot } from "./common.js";
14
+ import { durableJobs } from "./durable-jobs.js";
15
+
16
+ export interface GovernedExportQuerySnapshot {
17
+ q?: string;
18
+ status?: LifecycleStatus;
19
+ includeDeleted: boolean;
20
+ sort?: Array<{ field: string; order: "asc" | "desc" }>;
21
+ }
22
+
23
+ export const governedExportRequests = pgTable(
24
+ "governed_export_requests",
25
+ {
26
+ id: varchar("id", { length: 64 }).primaryKey(),
27
+ resourceType: varchar("resource_type", { length: 160 }).notNull(),
28
+ policyKey: varchar("policy_key", { length: 160 }).notNull(),
29
+ policyVersion: varchar("policy_version", { length: 40 }).notNull(),
30
+ fieldKeys: jsonb("field_keys").$type<string[]>().notNull(),
31
+ requesterId: varchar("requester_id", { length: 64 }).notNull(),
32
+ requesterName: varchar("requester_name", { length: 160 }).notNull(),
33
+ idempotencyKey: varchar("idempotency_key", { length: 160 }).notNull(),
34
+ requestFingerprint: varchar("request_fingerprint", {
35
+ length: 64,
36
+ }).notNull(),
37
+ reason: varchar("reason", { length: 500 }).notNull(),
38
+ scopeSnapshot: jsonb("scope_snapshot")
39
+ .$type<GovernedExportScopeSnapshot>()
40
+ .notNull(),
41
+ scopeHash: varchar("scope_hash", { length: 64 }).notNull(),
42
+ querySnapshot: jsonb("query_snapshot")
43
+ .$type<GovernedExportQuerySnapshot>()
44
+ .notNull(),
45
+ status: varchar("status", { length: 32 }).notNull(),
46
+ approvalRequired: boolean("approval_required").notNull(),
47
+ approvalId: varchar("approval_id", { length: 64 }),
48
+ durableJobId: varchar("durable_job_id", { length: 64 }).references(
49
+ () => durableJobs.id,
50
+ { onDelete: "restrict" },
51
+ ),
52
+ expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
53
+ createdAt: timestamp("created_at", { withTimezone: true })
54
+ .notNull()
55
+ .defaultNow(),
56
+ updatedAt: timestamp("updated_at", { withTimezone: true })
57
+ .notNull()
58
+ .defaultNow(),
59
+ },
60
+ (table) => [
61
+ uniqueIndex("uk_governed_exports_idempotency").on(
62
+ table.requesterId,
63
+ table.resourceType,
64
+ table.idempotencyKey,
65
+ ),
66
+ uniqueIndex("uk_governed_exports_job")
67
+ .on(table.durableJobId)
68
+ .where(sql`${table.durableJobId} is not null`),
69
+ index("idx_governed_exports_owner_created").on(
70
+ table.requesterId,
71
+ table.createdAt,
72
+ ),
73
+ index("idx_governed_exports_expiry").on(table.status, table.expiresAt),
74
+ check(
75
+ "ck_governed_exports_status",
76
+ sql`${table.status} in ('pending-approval', 'queued', 'running', 'succeeded', 'failed', 'cancelled', 'rejected')`,
77
+ ),
78
+ check(
79
+ "ck_governed_exports_scope_hash",
80
+ sql`${table.scopeHash} ~ '^[0-9a-f]{64}$'`,
81
+ ),
82
+ check(
83
+ "ck_governed_exports_approval_job_state",
84
+ sql`(${table.approvalRequired} or ${table.approvalId} is null)
85
+ and (${table.status} <> 'pending-approval' or (${table.approvalRequired} and ${table.durableJobId} is null))
86
+ and (${table.status} not in ('running', 'succeeded') or ${table.durableJobId} is not null)
87
+ and (${table.durableJobId} is null or not ${table.approvalRequired} or ${table.approvalId} is not null)`,
88
+ ),
89
+ ],
90
+ );