@kaeawc/auto-mobile 0.0.85 → 0.0.87
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/README.md +3 -3
- package/README.md.backup +3 -3
- package/dist/build-info.json +10 -10
- package/dist/src/index.js +1173 -840
- package/dist/src/utils/workers/iosBundleExtractWorker.js +3 -3
- package/node_modules/strtok3/lib/stream/StreamReader.js +6 -1
- package/node_modules/strtok3/package.json +1 -1
- package/package.json +21 -17
- package/dist/schemas/managed-slot-contract.json +0 -142
- package/dist/src/db/eventTables.ts +0 -8
- package/dist/src/db/migrations/2025_12_28_000_initial_schema.ts +0 -28
- package/dist/src/db/migrations/2025_12_30_000_performance_thresholds.ts +0 -85
- package/dist/src/db/migrations/2025_12_30_001_navigation_graph.ts +0 -223
- package/dist/src/db/migrations/2025_12_31_000_accessibility_baselines.ts +0 -34
- package/dist/src/db/migrations/2025_12_31_001_memory_audit.ts +0 -120
- package/dist/src/db/migrations/2026_01_01_000_recomposition_metrics.ts +0 -44
- package/dist/src/db/migrations/2026_01_02_000_prediction_history.ts +0 -87
- package/dist/src/db/migrations/2026_01_03_000_feature_flags.ts +0 -17
- package/dist/src/db/migrations/2026_01_03_000_test_executions.ts +0 -45
- package/dist/src/db/migrations/2026_01_05_000_tool_calls.ts +0 -31
- package/dist/src/db/migrations/2026_01_08_000_test_coverage.ts +0 -109
- package/dist/src/db/migrations/2026_01_09_000_video_recordings.ts +0 -66
- package/dist/src/db/migrations/2026_01_10_000_device_snapshots.ts +0 -54
- package/dist/src/db/migrations/2026_01_11_000_installed_apps.ts +0 -33
- package/dist/src/db/migrations/2026_01_11_000_video_recording_highlights.ts +0 -24
- package/dist/src/db/migrations/2026_01_13_000_installed_apps_session_tracking.ts +0 -27
- package/dist/src/db/migrations/2026_01_14_000_appearance_config.ts +0 -16
- package/dist/src/db/migrations/2026_01_25_000_navigation_screenshots.ts +0 -27
- package/dist/src/db/migrations/2026_01_25_001_test_run_details.ts +0 -97
- package/dist/src/db/migrations/2026_01_27_000_crash_anr_monitoring.ts +0 -204
- package/dist/src/db/migrations/2026_01_27_000_failures.ts +0 -217
- package/dist/src/db/migrations/2026_01_29_000_named_nodes.ts +0 -88
- package/dist/src/db/migrations/2026_01_30_000_performance_live_metrics.ts +0 -108
- package/dist/src/db/migrations/2026_03_15_000_telemetry_events.ts +0 -184
- package/dist/src/db/migrations/2026_03_18_000_navigation_events.ts +0 -36
- package/dist/src/db/migrations/2026_03_19_000_storage_events.ts +0 -37
- package/dist/src/db/migrations/2026_03_19_001_layout_events.ts +0 -39
- package/dist/src/db/migrations/2026_03_19_002_storage_events_previous_value.ts +0 -23
- package/dist/src/db/migrations/2026_03_19_003_layout_events_screen_name.ts +0 -23
- package/dist/src/db/migrations/2026_03_20_000_network_event_details.ts +0 -32
- package/dist/src/db/migrations/2026_04_01_000_drop_custom_events.ts +0 -41
- package/dist/src/db/migrations/2026_04_02_000_device_sessions.ts +0 -51
- package/dist/src/db/migrations/2026_07_01_000_failure_groups_signature_unique.ts +0 -162
- package/dist/src/db/migrations/2026_07_02_000_event_composite_indexes.ts +0 -62
- package/dist/src/db/migrations/2026_07_03_000_drop_redundant_device_indexes.ts +0 -66
- package/dist/src/db/migrations/2026_07_03_000_repair_datetime_now_defaults.ts +0 -278
- package/dist/src/db/migrations/2026_07_04_000_storage_events_key_lookup.ts +0 -60
- package/dist/src/db/migrations/2026_07_05_000_repair_updated_at_defaults.ts +0 -239
- package/dist/src/db/migrations/2026_07_24_000_device_locks.ts +0 -27
- package/dist/src/db/migrations/2026_07_27_000_session_tool_capabilities.ts +0 -17
- package/dist/src/db/migrations/2026_07_31_000_video_recordings_owner_session.ts +0 -39
- package/dist/src/db/migrations/2026_08_02_000_navigation_provenance.ts +0 -159
- package/dist/src/db/migrations/2026_08_17_000_emulator_loss_incidents.ts +0 -39
- package/dist/src/db/migrations/2026_08_22_000_provision_device_operations.ts +0 -21
- package/dist/src/db/migrations/2026_08_22_001_session_tool_overrides.ts +0 -106
- package/dist/src/db/migrations/2026_08_22_002_navigation_observation_eviction_index.ts +0 -67
- package/dist/src/db/migrations/2026_08_22_003_device_teardown_operations.ts +0 -19
- package/dist/src/db/migrations/2026_08_23_000_device_teardown_operation_owner.ts +0 -19
- package/dist/src/db/migrations/2026_08_23_001_default_existing_delete_device_disabled.ts +0 -26
- package/dist/src/db/migrations/2026_08_24_000_installed_apps_profile_type.ts +0 -15
- package/dist/src/db/migrations/2026_09_06_000_tool_selection_profile_provenance.ts +0 -36
- package/dist/src/db/migrations/2026_09_08_000_provision_device_operations_expiry.ts +0 -25
- package/dist/src/db/migrations/2026_09_10_000_provision_device_operation_attempt.ts +0 -25
- package/dist/src/db/migrations/2026_09_10_001_device_session_retention_index.ts +0 -14
- package/dist/src/db/migrations/2026_09_13_000_device_snapshot_vm_reclaim.ts +0 -63
- package/dist/src/db/migrations/2026_09_14_000_device_session_stable_identity.ts +0 -9
- package/dist/src/db/migrations/2026_09_14_001_device_session_identity_writer_fence.ts +0 -45
- package/dist/src/db/migrations/2026_09_16_000_device_session_liveness_owner.ts +0 -9
- package/dist/src/db/migrations/2026_09_16_001_device_session_liveness_contract.ts +0 -37
- package/dist/src/db/migrations/2026_09_17_000_device_session_liveness_writer_fence.ts +0 -48
- package/dist/src/db/migrations/2026_09_17_001_device_session_identity_fence_same_serial.ts +0 -53
- package/dist/src/db/migrations/2026_09_20_000_provision_device_lifecycle_outcome.ts +0 -32
- package/dist/src/db/migrations/2026_09_29_000_navigation_suggestion_provenance.ts +0 -41
- package/dist/src/db/migrations/2026_09_30_000_video_recording_display.ts +0 -17
- package/dist/src/db/migrations/2026_09_30_001_network_capture_fields.ts +0 -30
- package/dist/src/db/migrations/2026_10_05_000_device_locks_device_identity.ts +0 -28
- package/dist/src/db/migrations/2026_10_05_001_storage_events_key_lookup_insertion_order.ts +0 -38
- package/dist/src/db/migrations/2026_10_05_002_test_edge_coverage_edge_id_index.ts +0 -19
- package/dist/src/db/migrations/2026_10_09_000_device_resource_applications.ts +0 -22
- package/dist/src/db/migrations/2026_10_09_001_drop_provision_device_operations.ts +0 -32
- package/dist/src/db/migrations/2026_10_09_002_drop_device_teardown_operations.ts +0 -28
- package/dist/src/db/migrations/2026_10_12_000_rename_recording_tool_names.ts +0 -44
- package/dist/src/db/migrations/2026_10_12_001_video_recording_capture_teardown.ts +0 -17
- package/dist/src/db/migrations/2026_10_13_000_video_recording_recorder_daemon.ts +0 -17
- package/dist/src/index.js.map +0 -1
- package/dist/src/utils/workers/iosBundleExtractWorker.js.map +0 -1
- package/schemas/managed-slot-contract.json +0 -142
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import { type Kysely, sql } from "kysely";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Phase 1 of the (app, build) navigation model (#4837, issue #4984).
|
|
5
|
-
*
|
|
6
|
-
* Adds a build-key provenance dimension to the app-level union navigation graph.
|
|
7
|
-
* The union tables (navigation_apps / navigation_nodes / navigation_edges) are
|
|
8
|
-
* left untouched — the build key is provenance ON each node/edge, recorded as
|
|
9
|
-
* separate observation rows, NOT a separate-graph partition. A node/edge can have
|
|
10
|
-
* many observation records (reached by many builds/devices/sessions).
|
|
11
|
-
*
|
|
12
|
-
* Backward-compat (AC4): existing single-build rows are backfilled to a DEFAULT
|
|
13
|
-
* build key per app (version_code=0, content_hash='') with non-null legacy
|
|
14
|
-
* sentinels for device_id/session_uuid, so today's behavior falls out as the
|
|
15
|
-
* degenerate one-build case with no data loss.
|
|
16
|
-
*/
|
|
17
|
-
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
18
|
-
// Build-key dimension: (packageId=app_id, versionCode, contentHash). Normalized
|
|
19
|
-
// so observation rows carry a cheap int reference instead of repeating strings.
|
|
20
|
-
await db.schema
|
|
21
|
-
.createTable("navigation_build_keys")
|
|
22
|
-
.ifNotExists()
|
|
23
|
-
.addColumn("id", "integer", (col) => col.primaryKey().autoIncrement())
|
|
24
|
-
.addColumn("app_id", "text", (col) =>
|
|
25
|
-
col.notNull().references("navigation_apps.app_id").onDelete("cascade"),
|
|
26
|
-
)
|
|
27
|
-
.addColumn("version_code", "integer", (col) => col.notNull())
|
|
28
|
-
.addColumn("content_hash", "text", (col) => col.notNull())
|
|
29
|
-
.addColumn("created_at", "text", (col) => col.notNull().defaultTo(sql`(datetime('now'))`))
|
|
30
|
-
.execute();
|
|
31
|
-
|
|
32
|
-
await db.schema
|
|
33
|
-
.createIndex("idx_navigation_build_keys_unique")
|
|
34
|
-
.ifNotExists()
|
|
35
|
-
.on("navigation_build_keys")
|
|
36
|
-
.columns(["app_id", "version_code", "content_hash"])
|
|
37
|
-
.unique()
|
|
38
|
-
.execute();
|
|
39
|
-
|
|
40
|
-
// Per-node observation records: {buildKey, deviceId, sessionUuid, firstSeen, lastSeen}.
|
|
41
|
-
await db.schema
|
|
42
|
-
.createTable("navigation_node_observations")
|
|
43
|
-
.ifNotExists()
|
|
44
|
-
.addColumn("id", "integer", (col) => col.primaryKey().autoIncrement())
|
|
45
|
-
.addColumn("node_id", "integer", (col) =>
|
|
46
|
-
col.notNull().references("navigation_nodes.id").onDelete("cascade"),
|
|
47
|
-
)
|
|
48
|
-
.addColumn("build_key_id", "integer", (col) =>
|
|
49
|
-
col.notNull().references("navigation_build_keys.id").onDelete("cascade"),
|
|
50
|
-
)
|
|
51
|
-
.addColumn("device_id", "text", (col) => col.notNull())
|
|
52
|
-
.addColumn("session_uuid", "text", (col) => col.notNull())
|
|
53
|
-
.addColumn("first_seen_at", "integer", (col) => col.notNull())
|
|
54
|
-
.addColumn("last_seen_at", "integer", (col) => col.notNull())
|
|
55
|
-
.addColumn("created_at", "text", (col) => col.notNull().defaultTo(sql`(datetime('now'))`))
|
|
56
|
-
.execute();
|
|
57
|
-
|
|
58
|
-
await db.schema
|
|
59
|
-
.createIndex("idx_navigation_node_observations_unique")
|
|
60
|
-
.ifNotExists()
|
|
61
|
-
.on("navigation_node_observations")
|
|
62
|
-
.columns(["node_id", "build_key_id", "device_id", "session_uuid"])
|
|
63
|
-
.unique()
|
|
64
|
-
.execute();
|
|
65
|
-
|
|
66
|
-
await db.schema
|
|
67
|
-
.createIndex("idx_navigation_node_observations_build")
|
|
68
|
-
.ifNotExists()
|
|
69
|
-
.on("navigation_node_observations")
|
|
70
|
-
.column("build_key_id")
|
|
71
|
-
.execute();
|
|
72
|
-
|
|
73
|
-
await db.schema
|
|
74
|
-
.createIndex("idx_navigation_node_observations_device")
|
|
75
|
-
.ifNotExists()
|
|
76
|
-
.on("navigation_node_observations")
|
|
77
|
-
.column("device_id")
|
|
78
|
-
.execute();
|
|
79
|
-
|
|
80
|
-
// Per-edge observation records (symmetric to nodes).
|
|
81
|
-
await db.schema
|
|
82
|
-
.createTable("navigation_edge_observations")
|
|
83
|
-
.ifNotExists()
|
|
84
|
-
.addColumn("id", "integer", (col) => col.primaryKey().autoIncrement())
|
|
85
|
-
.addColumn("edge_id", "integer", (col) =>
|
|
86
|
-
col.notNull().references("navigation_edges.id").onDelete("cascade"),
|
|
87
|
-
)
|
|
88
|
-
.addColumn("build_key_id", "integer", (col) =>
|
|
89
|
-
col.notNull().references("navigation_build_keys.id").onDelete("cascade"),
|
|
90
|
-
)
|
|
91
|
-
.addColumn("device_id", "text", (col) => col.notNull())
|
|
92
|
-
.addColumn("session_uuid", "text", (col) => col.notNull())
|
|
93
|
-
.addColumn("first_seen_at", "integer", (col) => col.notNull())
|
|
94
|
-
.addColumn("last_seen_at", "integer", (col) => col.notNull())
|
|
95
|
-
.addColumn("created_at", "text", (col) => col.notNull().defaultTo(sql`(datetime('now'))`))
|
|
96
|
-
.execute();
|
|
97
|
-
|
|
98
|
-
await db.schema
|
|
99
|
-
.createIndex("idx_navigation_edge_observations_unique")
|
|
100
|
-
.ifNotExists()
|
|
101
|
-
.on("navigation_edge_observations")
|
|
102
|
-
.columns(["edge_id", "build_key_id", "device_id", "session_uuid"])
|
|
103
|
-
.unique()
|
|
104
|
-
.execute();
|
|
105
|
-
|
|
106
|
-
await db.schema
|
|
107
|
-
.createIndex("idx_navigation_edge_observations_build")
|
|
108
|
-
.ifNotExists()
|
|
109
|
-
.on("navigation_edge_observations")
|
|
110
|
-
.column("build_key_id")
|
|
111
|
-
.execute();
|
|
112
|
-
|
|
113
|
-
await db.schema
|
|
114
|
-
.createIndex("idx_navigation_edge_observations_device")
|
|
115
|
-
.ifNotExists()
|
|
116
|
-
.on("navigation_edge_observations")
|
|
117
|
-
.column("device_id")
|
|
118
|
-
.execute();
|
|
119
|
-
|
|
120
|
-
// --- Backfill (AC4): one DEFAULT build key per app, one observation per row. ---
|
|
121
|
-
// Kysely's SqliteAdapter reports supportsTransactionalDdl = false, so this up()
|
|
122
|
-
// is NOT wrapped in a transaction. If a later statement fails after an earlier
|
|
123
|
-
// insert, the migration is not recorded and reruns from the top — so every
|
|
124
|
-
// backfill uses INSERT OR IGNORE (safe against its UNIQUE index) to stay
|
|
125
|
-
// idempotent and retry-safe rather than wedging on a duplicate-key violation.
|
|
126
|
-
await sql`
|
|
127
|
-
INSERT OR IGNORE INTO navigation_build_keys (app_id, version_code, content_hash)
|
|
128
|
-
SELECT app_id, 0, '' FROM navigation_apps
|
|
129
|
-
`.execute(db);
|
|
130
|
-
|
|
131
|
-
// Normalize with min/max: legacy getOrCreateNode replaced last_seen_at
|
|
132
|
-
// unconditionally, so out-of-order commits could leave first_seen_at > last_seen_at
|
|
133
|
-
// on existing rows. Copying those verbatim would carry the inversion into the
|
|
134
|
-
// observation window (the runtime upserts only keep FUTURE writes monotonic).
|
|
135
|
-
await sql`
|
|
136
|
-
INSERT OR IGNORE INTO navigation_node_observations
|
|
137
|
-
(node_id, build_key_id, device_id, session_uuid, first_seen_at, last_seen_at)
|
|
138
|
-
SELECT n.id, bk.id, 'legacy', 'legacy',
|
|
139
|
-
min(n.first_seen_at, n.last_seen_at), max(n.first_seen_at, n.last_seen_at)
|
|
140
|
-
FROM navigation_nodes n
|
|
141
|
-
JOIN navigation_build_keys bk
|
|
142
|
-
ON bk.app_id = n.app_id AND bk.version_code = 0 AND bk.content_hash = ''
|
|
143
|
-
`.execute(db);
|
|
144
|
-
|
|
145
|
-
await sql`
|
|
146
|
-
INSERT OR IGNORE INTO navigation_edge_observations
|
|
147
|
-
(edge_id, build_key_id, device_id, session_uuid, first_seen_at, last_seen_at)
|
|
148
|
-
SELECT e.id, bk.id, 'legacy', 'legacy', e.timestamp, e.timestamp
|
|
149
|
-
FROM navigation_edges e
|
|
150
|
-
JOIN navigation_build_keys bk
|
|
151
|
-
ON bk.app_id = e.app_id AND bk.version_code = 0 AND bk.content_hash = ''
|
|
152
|
-
`.execute(db);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
156
|
-
await db.schema.dropTable("navigation_edge_observations").execute();
|
|
157
|
-
await db.schema.dropTable("navigation_node_observations").execute();
|
|
158
|
-
await db.schema.dropTable("navigation_build_keys").execute();
|
|
159
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { type Kysely, sql } from "kysely";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Durable, bounded postmortem records for Android emulator loss. The JSON
|
|
5
|
-
* payload retains the evolving recovery attempts while the indexed columns make
|
|
6
|
-
* incident lookup and retention pruning cheap.
|
|
7
|
-
*/
|
|
8
|
-
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
9
|
-
await db.schema
|
|
10
|
-
.createTable("emulator_loss_incidents")
|
|
11
|
-
.ifNotExists()
|
|
12
|
-
.addColumn("id", "integer", (col) => col.primaryKey().autoIncrement())
|
|
13
|
-
.addColumn("incident_id", "text", (col) => col.notNull().unique())
|
|
14
|
-
.addColumn("device_id", "text", (col) => col.notNull())
|
|
15
|
-
.addColumn("observed_at_ms", "integer", (col) => col.notNull())
|
|
16
|
-
.addColumn("updated_at_ms", "integer", (col) => col.notNull())
|
|
17
|
-
.addColumn("revision", "integer", (col) => col.notNull().defaultTo(0))
|
|
18
|
-
.addColumn("incident_json", "text", (col) => col.notNull())
|
|
19
|
-
.addColumn("created_at", "text", (col) => col.notNull().defaultTo(sql`(datetime('now'))`))
|
|
20
|
-
.execute();
|
|
21
|
-
|
|
22
|
-
await db.schema
|
|
23
|
-
.createIndex("idx_emulator_loss_incidents_observed")
|
|
24
|
-
.ifNotExists()
|
|
25
|
-
.on("emulator_loss_incidents")
|
|
26
|
-
.columns(["observed_at_ms", "id"])
|
|
27
|
-
.execute();
|
|
28
|
-
|
|
29
|
-
await db.schema
|
|
30
|
-
.createIndex("idx_emulator_loss_incidents_device")
|
|
31
|
-
.ifNotExists()
|
|
32
|
-
.on("emulator_loss_incidents")
|
|
33
|
-
.column("device_id")
|
|
34
|
-
.execute();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
38
|
-
await db.schema.dropTable("emulator_loss_incidents").execute();
|
|
39
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { type Kysely, sql } from "kysely";
|
|
2
|
-
|
|
3
|
-
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
4
|
-
await db.schema
|
|
5
|
-
.createTable("provision_device_operations")
|
|
6
|
-
.ifNotExists()
|
|
7
|
-
.addColumn("operation_id", "text", (column) => column.primaryKey())
|
|
8
|
-
.addColumn("request_fingerprint", "text", (column) => column.notNull())
|
|
9
|
-
.addColumn("status", "text", (column) => column.notNull())
|
|
10
|
-
.addColumn("result_json", "text")
|
|
11
|
-
.addColumn("error_code", "text")
|
|
12
|
-
.addColumn("error_message", "text")
|
|
13
|
-
.addColumn("creation_started", "integer", (column) => column.notNull().defaultTo(0))
|
|
14
|
-
.addColumn("created_at", "text", (column) => column.notNull().defaultTo(sql`(datetime('now'))`))
|
|
15
|
-
.addColumn("updated_at", "text", (column) => column.notNull().defaultTo(sql`(datetime('now'))`))
|
|
16
|
-
.execute();
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
20
|
-
await db.schema.dropTable("provision_device_operations").execute();
|
|
21
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { type Kysely, sql } from "kysely";
|
|
2
|
-
|
|
3
|
-
const TOOLS_BY_LEGACY_CAPABILITY: Readonly<Record<string, readonly string[]>> = {
|
|
4
|
-
clipboard: ["clipboard", "selectAllText"],
|
|
5
|
-
"advanced-interaction": ["openLink", "imeAction", "dragAndDrop", "pinchOn", "shake", "rotate"],
|
|
6
|
-
"app-permissions": ["getAppPermissions", "setAppPermissions"],
|
|
7
|
-
"device-settings": ["changeLocalization", "getDeviceState", "setDeviceState"],
|
|
8
|
-
"device-control": ["provisionDevice"],
|
|
9
|
-
"app-data-interop": [
|
|
10
|
-
"putAppFile",
|
|
11
|
-
"getPreference",
|
|
12
|
-
"setPreference",
|
|
13
|
-
"sqlQuery",
|
|
14
|
-
"setKeyValue",
|
|
15
|
-
"removeKeyValue",
|
|
16
|
-
"clearKeyValueFile",
|
|
17
|
-
],
|
|
18
|
-
notifications: [
|
|
19
|
-
"systemTray",
|
|
20
|
-
"postNotification",
|
|
21
|
-
"getNotificationPolicy",
|
|
22
|
-
"setNotificationPolicy",
|
|
23
|
-
],
|
|
24
|
-
telephony: ["phoneCall", "sendSms"],
|
|
25
|
-
"accessibility-tools": ["accessibility", "accessibilityFocus"],
|
|
26
|
-
"screen-artifacts": ["videoRecording", "deviceSnapshot", "highlight"],
|
|
27
|
-
"test-authoring": [
|
|
28
|
-
"executePlan",
|
|
29
|
-
"startTestRecording",
|
|
30
|
-
"exportPlan",
|
|
31
|
-
"recordSteps",
|
|
32
|
-
"barrier",
|
|
33
|
-
"criticalSection",
|
|
34
|
-
],
|
|
35
|
-
"network-inspection": ["network", "mockNetwork", "clearMockNetwork", "getNetworkGraph"],
|
|
36
|
-
"app-routing": ["getDeepLinks"],
|
|
37
|
-
"navigation-modeling": ["navigateTo", "getNavigationGraph", "explore"],
|
|
38
|
-
"biometric-auth": ["biometricAuth"],
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
interface LegacyCapabilityRow {
|
|
42
|
-
session_uuid: string;
|
|
43
|
-
capability: string;
|
|
44
|
-
enabled: number;
|
|
45
|
-
updated_at: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
49
|
-
await db.schema
|
|
50
|
-
.createTable("session_tool_overrides")
|
|
51
|
-
.ifNotExists()
|
|
52
|
-
.addColumn("session_uuid", "text", (column) => column.notNull())
|
|
53
|
-
.addColumn("tool_name", "text", (column) => column.notNull())
|
|
54
|
-
.addColumn("enabled", "integer", (column) => column.notNull())
|
|
55
|
-
.addColumn("updated_at", "text", (column) => column.notNull().defaultTo(sql`(datetime('now'))`))
|
|
56
|
-
.addPrimaryKeyConstraint("session_tool_overrides_pk", ["session_uuid", "tool_name"])
|
|
57
|
-
.execute();
|
|
58
|
-
|
|
59
|
-
const oldTable = await sql<{ name: string }>`
|
|
60
|
-
select name from sqlite_master
|
|
61
|
-
where type = 'table' and name = 'session_tool_capabilities'
|
|
62
|
-
`.execute(db);
|
|
63
|
-
if (oldTable.rows.length === 0) {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const legacyRows = await sql<LegacyCapabilityRow>`
|
|
68
|
-
select session_uuid, capability, enabled, updated_at
|
|
69
|
-
from session_tool_capabilities
|
|
70
|
-
`.execute(db);
|
|
71
|
-
for (const row of legacyRows.rows) {
|
|
72
|
-
for (const toolName of TOOLS_BY_LEGACY_CAPABILITY[row.capability] ?? []) {
|
|
73
|
-
await sql`
|
|
74
|
-
insert into session_tool_overrides (
|
|
75
|
-
session_uuid,
|
|
76
|
-
tool_name,
|
|
77
|
-
enabled,
|
|
78
|
-
updated_at
|
|
79
|
-
) values (
|
|
80
|
-
${row.session_uuid},
|
|
81
|
-
${toolName},
|
|
82
|
-
${row.enabled},
|
|
83
|
-
${row.updated_at}
|
|
84
|
-
)
|
|
85
|
-
on conflict(session_uuid, tool_name) do update set
|
|
86
|
-
enabled = excluded.enabled,
|
|
87
|
-
updated_at = excluded.updated_at
|
|
88
|
-
`.execute(db);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
await db.schema.dropTable("session_tool_capabilities").execute();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
96
|
-
await db.schema
|
|
97
|
-
.createTable("session_tool_capabilities")
|
|
98
|
-
.ifNotExists()
|
|
99
|
-
.addColumn("session_uuid", "text", (column) => column.notNull())
|
|
100
|
-
.addColumn("capability", "text", (column) => column.notNull())
|
|
101
|
-
.addColumn("enabled", "integer", (column) => column.notNull())
|
|
102
|
-
.addColumn("updated_at", "text", (column) => column.notNull().defaultTo(sql`(datetime('now'))`))
|
|
103
|
-
.addPrimaryKeyConstraint("session_tool_capabilities_pk", ["session_uuid", "capability"])
|
|
104
|
-
.execute();
|
|
105
|
-
await db.schema.dropTable("session_tool_overrides").ifExists().execute();
|
|
106
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import type { Kysely } from "kysely";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* `(last_seen_at, id)` eviction index for the navigation observation tables
|
|
5
|
-
* (#5309, follow-up to #4986 Phase 3 retention).
|
|
6
|
-
*
|
|
7
|
-
* The size-cap eviction pass in `src/db/navigationRetention.ts`
|
|
8
|
-
* (`collectOldestEvictable` → `buildOldestNodeEvictableQuery` /
|
|
9
|
-
* `buildOldestEdgeEvictableQuery`) reads the `limit` oldest rows of
|
|
10
|
-
* `navigation_node_observations` / `navigation_edge_observations`
|
|
11
|
-
* `ORDER BY last_seen_at ASC, id ASC` — GLOBALLY across every build key (there is
|
|
12
|
-
* no `build_key_id = ?` equality; app scope, when present, is a
|
|
13
|
-
* `build_key_id IN (<app's build keys>)` filter, not an equality). Up to the
|
|
14
|
-
* global cap of ~500k rows, the 6-hourly background pass full-scans and temp-sorts
|
|
15
|
-
* these tables to satisfy that ordering.
|
|
16
|
-
*
|
|
17
|
-
* The ordering column is `last_seen_at`, so the index that removes the sort must
|
|
18
|
-
* lead with `last_seen_at`. `(last_seen_at, id)` lets the planner read rows in
|
|
19
|
-
* `last_seen_at ASC, id ASC` order straight from the index and stop at `limit` —
|
|
20
|
-
* an index range scan instead of scan + temp B-tree sort — for all three query
|
|
21
|
-
* shapes the pass issues:
|
|
22
|
-
* - global eviction (`appId === null`): covering index scan, no sort.
|
|
23
|
-
* - app-scoped eviction: index scan in `last_seen_at` order, `build_key_id IN`
|
|
24
|
-
* applied as a filter, no sort.
|
|
25
|
-
* - active-row exclusion (`protectedIds` non-empty): the outer ordering is still
|
|
26
|
-
* served by this index; the correlated `MAX(last_seen_at) WHERE build_key_id=?`
|
|
27
|
-
* subquery seeks via the retained single-column build index (below).
|
|
28
|
-
*
|
|
29
|
-
* A leading-`build_key_id` composite such as `(build_key_id, last_seen_at)` does
|
|
30
|
-
* NOT remove the sort here: because the outer query orders by `last_seen_at`
|
|
31
|
-
* across build keys (not within one), the planner would still emit
|
|
32
|
-
* `USE TEMP B-TREE FOR ORDER BY`. See #5309 review; the planner test compiles the
|
|
33
|
-
* real query builders and asserts the sort is gone.
|
|
34
|
-
*
|
|
35
|
-
* No DESC in the index columns: the eviction scan orders ASC (oldest-first), and
|
|
36
|
-
* SQLite reads a plain ascending index in order; a plain composite suffices
|
|
37
|
-
* (mirrors 2026_07_02_000_event_composite_indexes.ts). `id` is the tiebreaker the
|
|
38
|
-
* query orders on, so including it keeps the global scan covering (no row lookup).
|
|
39
|
-
*
|
|
40
|
-
* Additive and forward-only. The #4986 single-column
|
|
41
|
-
* `idx_navigation_<t>_observations_build (build_key_id)` is deliberately RETAINED:
|
|
42
|
-
* it still seeks the correlated per-build-key `MAX(last_seen_at)` exclusion
|
|
43
|
-
* subquery, and is not a prefix of this `(last_seen_at, id)` index.
|
|
44
|
-
*
|
|
45
|
-
* `.ifNotExists()` / `.ifExists()` so the migration survives #2785's
|
|
46
|
-
* destructive-recovery replay (drop-all then replay every migration on an empty
|
|
47
|
-
* DB) in unusual orders. Sorts after 2026_08_02_000_navigation_provenance.ts, so
|
|
48
|
-
* both observation tables exist when it runs during a full replay.
|
|
49
|
-
*/
|
|
50
|
-
const TABLES = ["navigation_node_observations", "navigation_edge_observations"] as const;
|
|
51
|
-
|
|
52
|
-
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
53
|
-
for (const table of TABLES) {
|
|
54
|
-
await db.schema
|
|
55
|
-
.createIndex(`idx_${table}_seen_id`)
|
|
56
|
-
.ifNotExists()
|
|
57
|
-
.on(table as never)
|
|
58
|
-
.columns(["last_seen_at", "id"] as never[])
|
|
59
|
-
.execute();
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
64
|
-
for (const table of TABLES) {
|
|
65
|
-
await db.schema.dropIndex(`idx_${table}_seen_id`).ifExists().execute();
|
|
66
|
-
}
|
|
67
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { type Kysely, sql } from "kysely";
|
|
2
|
-
|
|
3
|
-
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
4
|
-
await db.schema
|
|
5
|
-
.createTable("device_teardown_operations")
|
|
6
|
-
.ifNotExists()
|
|
7
|
-
.addColumn("operation_id", "text", (column) => column.primaryKey())
|
|
8
|
-
.addColumn("request_fingerprint", "text", (column) => column.notNull())
|
|
9
|
-
.addColumn("status", "text", (column) => column.notNull())
|
|
10
|
-
.addColumn("result_json", "text")
|
|
11
|
-
.addColumn("expires_at_ms", "integer", (column) => column.notNull())
|
|
12
|
-
.addColumn("created_at", "text", (column) => column.notNull().defaultTo(sql`(datetime('now'))`))
|
|
13
|
-
.addColumn("updated_at", "text", (column) => column.notNull().defaultTo(sql`(datetime('now'))`))
|
|
14
|
-
.execute();
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
18
|
-
await db.schema.dropTable("device_teardown_operations").execute();
|
|
19
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { type Kysely, sql } from "kysely";
|
|
2
|
-
|
|
3
|
-
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
4
|
-
const existingColumn = await sql<{ name: string }>`
|
|
5
|
-
SELECT name FROM pragma_table_info('device_teardown_operations')
|
|
6
|
-
WHERE name = 'owner_token'
|
|
7
|
-
`.execute(db);
|
|
8
|
-
|
|
9
|
-
if (existingColumn.rows.length === 0) {
|
|
10
|
-
await db.schema
|
|
11
|
-
.alterTable("device_teardown_operations")
|
|
12
|
-
.addColumn("owner_token", "text", (column) => column.notNull().defaultTo(sql`''`))
|
|
13
|
-
.execute();
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
18
|
-
await db.schema.alterTable("device_teardown_operations").dropColumn("owner_token").execute();
|
|
19
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { type Kysely, sql } from "kysely";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Existing `provisionDevice` overrides predate the destructive `deleteDevice`
|
|
5
|
-
* tool. Preserve those profiles without granting a new destructive capability;
|
|
6
|
-
* callers must explicitly enable deleteDevice after this migration.
|
|
7
|
-
*/
|
|
8
|
-
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
9
|
-
await sql`
|
|
10
|
-
insert into session_tool_overrides (
|
|
11
|
-
session_uuid,
|
|
12
|
-
tool_name,
|
|
13
|
-
enabled,
|
|
14
|
-
updated_at
|
|
15
|
-
)
|
|
16
|
-
select session_uuid, 'deleteDevice', 0, updated_at
|
|
17
|
-
from session_tool_overrides
|
|
18
|
-
where tool_name = 'provisionDevice'
|
|
19
|
-
on conflict(session_uuid, tool_name) do nothing
|
|
20
|
-
`.execute(db);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
24
|
-
// Preserve explicit post-migration choices; the created disabled row is safe to retain.
|
|
25
|
-
void db;
|
|
26
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { sql, type Kysely } from "kysely";
|
|
2
|
-
|
|
3
|
-
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
4
|
-
const existingColumn = await sql<{ name: string }>`
|
|
5
|
-
SELECT name FROM pragma_table_info('installed_apps') WHERE name = 'profile_type'
|
|
6
|
-
`.execute(db);
|
|
7
|
-
|
|
8
|
-
if (existingColumn.rows.length === 0) {
|
|
9
|
-
await db.schema.alterTable("installed_apps").addColumn("profile_type", "text").execute();
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
14
|
-
await db.schema.alterTable("installed_apps").dropColumn("profile_type").execute();
|
|
15
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { type Kysely, sql } from "kysely";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Durable provenance for daemon-minted tool-selection profiles (issue #6225,
|
|
5
|
-
* follow-up to #6148/#6213).
|
|
6
|
-
*
|
|
7
|
-
* `ToolSelectionProfileRegistry` (`src/server/toolSelectionProfileRegistry.ts`)
|
|
8
|
-
* is the sole signal `setToolEnabled` trusts to tell "a crypto-random profile
|
|
9
|
-
* uuid this daemon process itself minted via
|
|
10
|
-
* `createAndBindToolSelectionProfile`" apart from a fabricated or
|
|
11
|
-
* merely-echoed-header value. Before this migration that signal lived only in
|
|
12
|
-
* an in-memory `Set`, so a daemon restart/upgrade lost every minted profile: a
|
|
13
|
-
* client reaffirming its still-valid profile failed the provenance check and
|
|
14
|
-
* had to re-mint. This table lets the registry reload its membership at
|
|
15
|
-
* startup so a legitimately-minted profile survives a restart, while a value
|
|
16
|
-
* that was never minted is still rejected (it was never written here either).
|
|
17
|
-
*
|
|
18
|
-
* `profile_uuid` alone is the primary key: the table is a pure membership set,
|
|
19
|
-
* no other field is ever read back to decide provenance (parity with the
|
|
20
|
-
* in-memory `Set<string>` it backs). `created_at` is kept for observability
|
|
21
|
-
* only (e.g. a future eviction pass), matching the unpruned-growth tradeoff
|
|
22
|
-
* already accepted for `session_tool_overrides` (one entry per anonymous
|
|
23
|
-
* `setToolEnabled` call across the daemon's life).
|
|
24
|
-
*/
|
|
25
|
-
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
26
|
-
await db.schema
|
|
27
|
-
.createTable("tool_selection_profile_provenance")
|
|
28
|
-
.ifNotExists()
|
|
29
|
-
.addColumn("profile_uuid", "text", (col) => col.primaryKey())
|
|
30
|
-
.addColumn("created_at", "text", (col) => col.notNull().defaultTo(sql`(datetime('now'))`))
|
|
31
|
-
.execute();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
35
|
-
await db.schema.dropTable("tool_selection_profile_provenance").ifExists().execute();
|
|
36
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { type Kysely, sql } from "kysely";
|
|
2
|
-
|
|
3
|
-
// #6652: without an expiry, `provision_device_operations` never sheds rows, so
|
|
4
|
-
// it grows one row per distinct operationId for the life of the database.
|
|
5
|
-
// Existing rows predate this column and get `expires_at_ms` defaulted to 0 --
|
|
6
|
-
// already-expired -- so the very next `begin()` call's prune sweep
|
|
7
|
-
// (`ProvisionDeviceOperationRepository.begin()`) reclaims the pre-existing
|
|
8
|
-
// backlog too, not just newly inserted rows.
|
|
9
|
-
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
10
|
-
const existingColumn = await sql<{ name: string }>`
|
|
11
|
-
SELECT name FROM pragma_table_info('provision_device_operations')
|
|
12
|
-
WHERE name = 'expires_at_ms'
|
|
13
|
-
`.execute(db);
|
|
14
|
-
|
|
15
|
-
if (existingColumn.rows.length === 0) {
|
|
16
|
-
await db.schema
|
|
17
|
-
.alterTable("provision_device_operations")
|
|
18
|
-
.addColumn("expires_at_ms", "integer", (column) => column.notNull().defaultTo(0))
|
|
19
|
-
.execute();
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
24
|
-
await db.schema.alterTable("provision_device_operations").dropColumn("expires_at_ms").execute();
|
|
25
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { type Kysely, sql } from "kysely";
|
|
2
|
-
|
|
3
|
-
// Fence every mutation of a `provision_device_operations` row to the attempt
|
|
4
|
-
// that owns it. Without it, `complete()`/`fail()`/`markDeviceCreationStarted()`
|
|
5
|
-
// match on `operation_id` alone, so a wedged attempt whose row was legitimately
|
|
6
|
-
// reclaimed (expiry sweep, or a retry after a failure) stamps its stale result
|
|
7
|
-
// over the replacement attempt's row. Pre-existing rows default to the empty
|
|
8
|
-
// token: no live attempt can present it, so their first `begin()` claims them.
|
|
9
|
-
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
10
|
-
const existingColumn = await sql<{ name: string }>`
|
|
11
|
-
SELECT name FROM pragma_table_info('provision_device_operations')
|
|
12
|
-
WHERE name = 'attempt_id'
|
|
13
|
-
`.execute(db);
|
|
14
|
-
|
|
15
|
-
if (existingColumn.rows.length === 0) {
|
|
16
|
-
await db.schema
|
|
17
|
-
.alterTable("provision_device_operations")
|
|
18
|
-
.addColumn("attempt_id", "text", (column) => column.notNull().defaultTo(""))
|
|
19
|
-
.execute();
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
24
|
-
await db.schema.alterTable("provision_device_operations").dropColumn("attempt_id").execute();
|
|
25
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { Kysely } from "kysely";
|
|
2
|
-
|
|
3
|
-
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
4
|
-
await db.schema
|
|
5
|
-
.createIndex("idx_device_sessions_released_at_ms")
|
|
6
|
-
.ifNotExists()
|
|
7
|
-
.on("device_sessions")
|
|
8
|
-
.column("released_at_ms")
|
|
9
|
-
.execute();
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
13
|
-
await db.schema.dropIndex("idx_device_sessions_released_at_ms").ifExists().execute();
|
|
14
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { sql, type Kysely } from "kysely";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* VM snapshot accounting and reclaim (issue #6490, parent #6371).
|
|
5
|
-
*
|
|
6
|
-
* `size_bytes` is NOT NULL DEFAULT 0 and SQLite cannot relax that in place, so
|
|
7
|
-
* "unknown size" is carried by a sibling flag rather than a nullable column: a
|
|
8
|
-
* `vm` record whose in-AVD payload could not be located is stored as
|
|
9
|
-
* `size_bytes = 0, size_unknown = 1` and is reported as unsized instead of being
|
|
10
|
-
* silently budgeted as zero.
|
|
11
|
-
*
|
|
12
|
-
* `pending_reclaim` marks a row whose in-AVD snapshot could not be deleted
|
|
13
|
-
* because its emulator was offline. The row is kept (not dropped "for free")
|
|
14
|
-
* so a later session can finish the reclaim when that AVD is next seen live.
|
|
15
|
-
*
|
|
16
|
-
* ALL THREE COLUMNS ARE ADDED IN ONE TRANSACTION. Kysely's SqliteAdapter
|
|
17
|
-
* reports `supportsTransactionalDdl === false`, so the migrator does NOT wrap
|
|
18
|
-
* these in one — statement by statement, a crash or a failure after the first
|
|
19
|
-
* `ALTER TABLE` committed would leave `size_unknown` in the schema with no
|
|
20
|
-
* ledger row, and every later startup would replay from the top and die on
|
|
21
|
-
* `duplicate column name: size_unknown`, wedging the daemon until someone
|
|
22
|
-
* repaired the schema by hand (#6490 review). SQLite itself is perfectly happy
|
|
23
|
-
* to roll DDL back, so one explicit transaction makes the trio all-or-nothing.
|
|
24
|
-
* `down()` gets the same treatment for the same reason.
|
|
25
|
-
*
|
|
26
|
-
* Existing Android `vm` rows are BACKFILLED as unsized in the same transaction.
|
|
27
|
-
* A pre-change build sized a `vm` record by measuring the archive directory,
|
|
28
|
-
* which holds none of its bytes, so every upgraded row carries a number
|
|
29
|
-
* (normally 0) that describes nothing. Nothing re-imports a row that already
|
|
30
|
-
* exists, so defaulting them to "size known" would keep multi-gigabyte in-AVD
|
|
31
|
-
* payloads outside the budget forever while the archive reported zero unsized
|
|
32
|
-
* records; flagged unknown, they are re-measured at their real location before
|
|
33
|
-
* the next budget pass (#6891 review).
|
|
34
|
-
*/
|
|
35
|
-
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
36
|
-
await db.transaction().execute(async (trx) => {
|
|
37
|
-
await trx.schema
|
|
38
|
-
.alterTable("device_snapshots")
|
|
39
|
-
.addColumn("size_unknown", "integer", (col) => col.notNull().defaultTo(0))
|
|
40
|
-
.execute();
|
|
41
|
-
await trx.schema
|
|
42
|
-
.alterTable("device_snapshots")
|
|
43
|
-
.addColumn("pending_reclaim", "integer", (col) => col.notNull().defaultTo(0))
|
|
44
|
-
.execute();
|
|
45
|
-
await trx.schema
|
|
46
|
-
.alterTable("device_snapshots")
|
|
47
|
-
.addColumn("pending_reclaim_reason", "text")
|
|
48
|
-
.execute();
|
|
49
|
-
await sql`
|
|
50
|
-
UPDATE device_snapshots
|
|
51
|
-
SET size_unknown = 1
|
|
52
|
-
WHERE platform = 'android' AND snapshot_type = 'vm'
|
|
53
|
-
`.execute(trx);
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
58
|
-
await db.transaction().execute(async (trx) => {
|
|
59
|
-
await trx.schema.alterTable("device_snapshots").dropColumn("pending_reclaim_reason").execute();
|
|
60
|
-
await trx.schema.alterTable("device_snapshots").dropColumn("pending_reclaim").execute();
|
|
61
|
-
await trx.schema.alterTable("device_snapshots").dropColumn("size_unknown").execute();
|
|
62
|
-
});
|
|
63
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { Kysely } from "kysely";
|
|
2
|
-
|
|
3
|
-
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
4
|
-
await db.schema.alterTable("device_sessions").addColumn("stable_device_id", "text").execute();
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
8
|
-
await db.schema.alterTable("device_sessions").dropColumn("stable_device_id").execute();
|
|
9
|
-
}
|