@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.
Files changed (87) hide show
  1. package/README.md +3 -3
  2. package/README.md.backup +3 -3
  3. package/dist/build-info.json +10 -10
  4. package/dist/src/index.js +1173 -840
  5. package/dist/src/utils/workers/iosBundleExtractWorker.js +3 -3
  6. package/node_modules/strtok3/lib/stream/StreamReader.js +6 -1
  7. package/node_modules/strtok3/package.json +1 -1
  8. package/package.json +21 -17
  9. package/dist/schemas/managed-slot-contract.json +0 -142
  10. package/dist/src/db/eventTables.ts +0 -8
  11. package/dist/src/db/migrations/2025_12_28_000_initial_schema.ts +0 -28
  12. package/dist/src/db/migrations/2025_12_30_000_performance_thresholds.ts +0 -85
  13. package/dist/src/db/migrations/2025_12_30_001_navigation_graph.ts +0 -223
  14. package/dist/src/db/migrations/2025_12_31_000_accessibility_baselines.ts +0 -34
  15. package/dist/src/db/migrations/2025_12_31_001_memory_audit.ts +0 -120
  16. package/dist/src/db/migrations/2026_01_01_000_recomposition_metrics.ts +0 -44
  17. package/dist/src/db/migrations/2026_01_02_000_prediction_history.ts +0 -87
  18. package/dist/src/db/migrations/2026_01_03_000_feature_flags.ts +0 -17
  19. package/dist/src/db/migrations/2026_01_03_000_test_executions.ts +0 -45
  20. package/dist/src/db/migrations/2026_01_05_000_tool_calls.ts +0 -31
  21. package/dist/src/db/migrations/2026_01_08_000_test_coverage.ts +0 -109
  22. package/dist/src/db/migrations/2026_01_09_000_video_recordings.ts +0 -66
  23. package/dist/src/db/migrations/2026_01_10_000_device_snapshots.ts +0 -54
  24. package/dist/src/db/migrations/2026_01_11_000_installed_apps.ts +0 -33
  25. package/dist/src/db/migrations/2026_01_11_000_video_recording_highlights.ts +0 -24
  26. package/dist/src/db/migrations/2026_01_13_000_installed_apps_session_tracking.ts +0 -27
  27. package/dist/src/db/migrations/2026_01_14_000_appearance_config.ts +0 -16
  28. package/dist/src/db/migrations/2026_01_25_000_navigation_screenshots.ts +0 -27
  29. package/dist/src/db/migrations/2026_01_25_001_test_run_details.ts +0 -97
  30. package/dist/src/db/migrations/2026_01_27_000_crash_anr_monitoring.ts +0 -204
  31. package/dist/src/db/migrations/2026_01_27_000_failures.ts +0 -217
  32. package/dist/src/db/migrations/2026_01_29_000_named_nodes.ts +0 -88
  33. package/dist/src/db/migrations/2026_01_30_000_performance_live_metrics.ts +0 -108
  34. package/dist/src/db/migrations/2026_03_15_000_telemetry_events.ts +0 -184
  35. package/dist/src/db/migrations/2026_03_18_000_navigation_events.ts +0 -36
  36. package/dist/src/db/migrations/2026_03_19_000_storage_events.ts +0 -37
  37. package/dist/src/db/migrations/2026_03_19_001_layout_events.ts +0 -39
  38. package/dist/src/db/migrations/2026_03_19_002_storage_events_previous_value.ts +0 -23
  39. package/dist/src/db/migrations/2026_03_19_003_layout_events_screen_name.ts +0 -23
  40. package/dist/src/db/migrations/2026_03_20_000_network_event_details.ts +0 -32
  41. package/dist/src/db/migrations/2026_04_01_000_drop_custom_events.ts +0 -41
  42. package/dist/src/db/migrations/2026_04_02_000_device_sessions.ts +0 -51
  43. package/dist/src/db/migrations/2026_07_01_000_failure_groups_signature_unique.ts +0 -162
  44. package/dist/src/db/migrations/2026_07_02_000_event_composite_indexes.ts +0 -62
  45. package/dist/src/db/migrations/2026_07_03_000_drop_redundant_device_indexes.ts +0 -66
  46. package/dist/src/db/migrations/2026_07_03_000_repair_datetime_now_defaults.ts +0 -278
  47. package/dist/src/db/migrations/2026_07_04_000_storage_events_key_lookup.ts +0 -60
  48. package/dist/src/db/migrations/2026_07_05_000_repair_updated_at_defaults.ts +0 -239
  49. package/dist/src/db/migrations/2026_07_24_000_device_locks.ts +0 -27
  50. package/dist/src/db/migrations/2026_07_27_000_session_tool_capabilities.ts +0 -17
  51. package/dist/src/db/migrations/2026_07_31_000_video_recordings_owner_session.ts +0 -39
  52. package/dist/src/db/migrations/2026_08_02_000_navigation_provenance.ts +0 -159
  53. package/dist/src/db/migrations/2026_08_17_000_emulator_loss_incidents.ts +0 -39
  54. package/dist/src/db/migrations/2026_08_22_000_provision_device_operations.ts +0 -21
  55. package/dist/src/db/migrations/2026_08_22_001_session_tool_overrides.ts +0 -106
  56. package/dist/src/db/migrations/2026_08_22_002_navigation_observation_eviction_index.ts +0 -67
  57. package/dist/src/db/migrations/2026_08_22_003_device_teardown_operations.ts +0 -19
  58. package/dist/src/db/migrations/2026_08_23_000_device_teardown_operation_owner.ts +0 -19
  59. package/dist/src/db/migrations/2026_08_23_001_default_existing_delete_device_disabled.ts +0 -26
  60. package/dist/src/db/migrations/2026_08_24_000_installed_apps_profile_type.ts +0 -15
  61. package/dist/src/db/migrations/2026_09_06_000_tool_selection_profile_provenance.ts +0 -36
  62. package/dist/src/db/migrations/2026_09_08_000_provision_device_operations_expiry.ts +0 -25
  63. package/dist/src/db/migrations/2026_09_10_000_provision_device_operation_attempt.ts +0 -25
  64. package/dist/src/db/migrations/2026_09_10_001_device_session_retention_index.ts +0 -14
  65. package/dist/src/db/migrations/2026_09_13_000_device_snapshot_vm_reclaim.ts +0 -63
  66. package/dist/src/db/migrations/2026_09_14_000_device_session_stable_identity.ts +0 -9
  67. package/dist/src/db/migrations/2026_09_14_001_device_session_identity_writer_fence.ts +0 -45
  68. package/dist/src/db/migrations/2026_09_16_000_device_session_liveness_owner.ts +0 -9
  69. package/dist/src/db/migrations/2026_09_16_001_device_session_liveness_contract.ts +0 -37
  70. package/dist/src/db/migrations/2026_09_17_000_device_session_liveness_writer_fence.ts +0 -48
  71. package/dist/src/db/migrations/2026_09_17_001_device_session_identity_fence_same_serial.ts +0 -53
  72. package/dist/src/db/migrations/2026_09_20_000_provision_device_lifecycle_outcome.ts +0 -32
  73. package/dist/src/db/migrations/2026_09_29_000_navigation_suggestion_provenance.ts +0 -41
  74. package/dist/src/db/migrations/2026_09_30_000_video_recording_display.ts +0 -17
  75. package/dist/src/db/migrations/2026_09_30_001_network_capture_fields.ts +0 -30
  76. package/dist/src/db/migrations/2026_10_05_000_device_locks_device_identity.ts +0 -28
  77. package/dist/src/db/migrations/2026_10_05_001_storage_events_key_lookup_insertion_order.ts +0 -38
  78. package/dist/src/db/migrations/2026_10_05_002_test_edge_coverage_edge_id_index.ts +0 -19
  79. package/dist/src/db/migrations/2026_10_09_000_device_resource_applications.ts +0 -22
  80. package/dist/src/db/migrations/2026_10_09_001_drop_provision_device_operations.ts +0 -32
  81. package/dist/src/db/migrations/2026_10_09_002_drop_device_teardown_operations.ts +0 -28
  82. package/dist/src/db/migrations/2026_10_12_000_rename_recording_tool_names.ts +0 -44
  83. package/dist/src/db/migrations/2026_10_12_001_video_recording_capture_teardown.ts +0 -17
  84. package/dist/src/db/migrations/2026_10_13_000_video_recording_recorder_daemon.ts +0 -17
  85. package/dist/src/index.js.map +0 -1
  86. package/dist/src/utils/workers/iosBundleExtractWorker.js.map +0 -1
  87. 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
- }