@kaeawc/auto-mobile 0.0.49 → 0.0.51
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/schemas/tool-definitions.json +1271 -642
- package/dist/src/db/migrations/2026_07_31_000_video_recordings_owner_session.ts +12 -5
- package/dist/src/db/migrations/2026_08_02_000_navigation_provenance.ts +159 -0
- package/dist/src/index.js +404 -399
- package/dist/src/index.js.map +1 -1
- package/package.json +21 -15
- package/schemas/tool-definitions.json +1271 -642
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { sql, type Kysely } from "kysely";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Session-scope the video recording archive (issue #4752).
|
|
@@ -12,10 +12,17 @@ import type { Kysely } from "kysely";
|
|
|
12
12
|
* existing archive.
|
|
13
13
|
*/
|
|
14
14
|
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
15
|
-
await
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
const existingColumn = await sql<{ name: string }>`
|
|
16
|
+
SELECT name FROM pragma_table_info('video_recordings')
|
|
17
|
+
WHERE name = 'owner_session_uuid'
|
|
18
|
+
`.execute(db);
|
|
19
|
+
|
|
20
|
+
if (existingColumn.rows.length === 0) {
|
|
21
|
+
await db.schema
|
|
22
|
+
.alterTable("video_recordings")
|
|
23
|
+
.addColumn("owner_session_uuid", "text")
|
|
24
|
+
.execute();
|
|
25
|
+
}
|
|
19
26
|
|
|
20
27
|
await db.schema
|
|
21
28
|
.createIndex("idx_video_recordings_owner_session")
|
|
@@ -0,0 +1,159 @@
|
|
|
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
|
+
}
|