@vibedeckx/linux-x64 0.3.9 → 0.3.10

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 (2) hide show
  1. package/dist/bin.js +117 -3
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -185980,10 +185980,29 @@ var createCoreRepos = (kdb, h) => ({
185980
185980
  const row = await readQuery.executeTakeFirst();
185981
185981
  return row ? mapProject(row) : void 0;
185982
185982
  },
185983
+ /**
185984
+ * Deleting a project cascades down two independent paths: to its sessions,
185985
+ * and to `workspaces` → `workspace_checkouts`. `remote_session_mappings`
185986
+ * sits on neither — it has no key to `projects` — so once a mapping's
185987
+ * checkout is a real foreign key, leaving those rows behind would strand
185988
+ * them against a checkout the same statement just deleted.
185989
+ *
185990
+ * The dependents are therefore removed explicitly, in order, inside one
185991
+ * transaction. The scope check happens first so an unauthorized caller
185992
+ * cannot delete another tenant's mappings on the way to a no-op.
185993
+ */
185983
185994
  delete: async (id, userId) => {
185984
- let query = kdb.deleteFrom("projects").where("id", "=", id);
185985
- if (userId) query = query.where("user_id", "=", userId);
185986
- await query.execute();
185995
+ await kdb.transaction().execute(async (trx) => {
185996
+ let scope = trx.selectFrom("projects").select("id").where("id", "=", id);
185997
+ if (userId) scope = scope.where("user_id", "=", userId);
185998
+ if (!await scope.executeTakeFirst()) return;
185999
+ const mappings = await trx.selectFrom("remote_session_mappings").select(["remote_server_id", "remote_session_id"]).where("project_id", "=", id).execute();
186000
+ for (const mapping of mappings) {
186001
+ await trx.deleteFrom("notification_sync_cursors").where("remote_server_id", "=", mapping.remote_server_id).where("remote_session_id", "=", mapping.remote_session_id).execute();
186002
+ }
186003
+ await trx.deleteFrom("remote_session_mappings").where("project_id", "=", id).execute();
186004
+ await trx.deleteFrom("projects").where("id", "=", id).execute();
186005
+ });
185987
186006
  },
185988
186007
  // Deliberately unscoped: the notification importer runs without a request
185989
186008
  // context and must DERIVE ownership from the mapped local project rather
@@ -203458,6 +203477,100 @@ var createWorkspaceRegistryRepo = (kdb, h) => ({
203458
203477
  });
203459
203478
 
203460
203479
  // src/storage/sqlite.ts
203480
+ var countDanglingCheckoutBindings = (db) => {
203481
+ const scalar = (sql2) => Number(db.prepare(sql2).get().count);
203482
+ return {
203483
+ sessions: scalar(`SELECT count(*) AS count FROM agent_sessions s
203484
+ LEFT JOIN workspace_checkouts c ON c.id = s.workspace_checkout_id
203485
+ WHERE s.workspace_checkout_id IS NOT NULL AND c.id IS NULL`),
203486
+ mappings: scalar(`SELECT count(*) AS count FROM remote_session_mappings m
203487
+ LEFT JOIN workspace_checkouts c ON c.id = m.workspace_checkout_id
203488
+ WHERE m.workspace_checkout_id IS NOT NULL AND c.id IS NULL`)
203489
+ };
203490
+ };
203491
+ var tightenWorkspaceCheckoutForeignKeys = (db) => {
203492
+ const ddl = (table) => db.prepare(
203493
+ "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?"
203494
+ ).get(table)?.sql ?? "";
203495
+ const bound = (table) => /REFERENCES\s+workspace_checkouts/i.test(ddl(table));
203496
+ if (bound("agent_sessions") && bound("remote_session_mappings")) return;
203497
+ const indexDdl = (table) => db.prepare(
203498
+ "SELECT sql FROM sqlite_master WHERE type = 'index' AND tbl_name = ? AND sql IS NOT NULL"
203499
+ ).all(table).map((row) => row.sql);
203500
+ const dangling = countDanglingCheckoutBindings(db);
203501
+ if (dangling.sessions > 0 || dangling.mappings > 0) {
203502
+ console.warn(
203503
+ `[Storage] Refusing to add the workspace_checkout foreign key: ${dangling.sessions} session(s) and ${dangling.mappings} remote mapping(s) reference a checkout that no longer exists. Resolve or clear those bindings, then restart to complete the migration.`
203504
+ );
203505
+ return;
203506
+ }
203507
+ const sessionIndexes = indexDdl("agent_sessions");
203508
+ const mappingIndexes = indexDdl("remote_session_mappings");
203509
+ db.transaction(() => {
203510
+ if (!bound("agent_sessions")) {
203511
+ db.exec(`
203512
+ CREATE TABLE agent_sessions_fk_new (
203513
+ id TEXT PRIMARY KEY,
203514
+ project_id TEXT NOT NULL,
203515
+ branch TEXT NOT NULL DEFAULT '',
203516
+ workspace_checkout_id TEXT DEFAULT NULL,
203517
+ status TEXT NOT NULL DEFAULT 'running',
203518
+ permission_mode TEXT DEFAULT 'edit',
203519
+ agent_type TEXT DEFAULT 'claude-code',
203520
+ title TEXT DEFAULT NULL,
203521
+ model TEXT DEFAULT NULL,
203522
+ created_at TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%f', 'now')),
203523
+ updated_at TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%f', 'now')),
203524
+ activity_at INTEGER DEFAULT (cast((julianday('now') - 2440587.5) * 86400000 as integer)),
203525
+ last_user_message_at INTEGER DEFAULT NULL,
203526
+ last_completed_at INTEGER DEFAULT NULL,
203527
+ favorited_at INTEGER DEFAULT NULL,
203528
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
203529
+ FOREIGN KEY (workspace_checkout_id) REFERENCES workspace_checkouts(id)
203530
+ DEFERRABLE INITIALLY DEFERRED
203531
+ );
203532
+ INSERT INTO agent_sessions_fk_new
203533
+ (id, project_id, branch, workspace_checkout_id, status, permission_mode, agent_type,
203534
+ title, model, created_at, updated_at, activity_at, last_user_message_at,
203535
+ last_completed_at, favorited_at)
203536
+ SELECT id, project_id, branch, workspace_checkout_id, status, permission_mode, agent_type,
203537
+ title, model, created_at, updated_at, activity_at, last_user_message_at,
203538
+ last_completed_at, favorited_at
203539
+ FROM agent_sessions;
203540
+ DROP TABLE agent_sessions;
203541
+ ALTER TABLE agent_sessions_fk_new RENAME TO agent_sessions;
203542
+ `);
203543
+ for (const sql2 of sessionIndexes) db.exec(sql2);
203544
+ }
203545
+ if (!bound("remote_session_mappings")) {
203546
+ db.exec(`
203547
+ CREATE TABLE remote_session_mappings_fk_new (
203548
+ local_session_id TEXT PRIMARY KEY,
203549
+ project_id TEXT NOT NULL,
203550
+ remote_server_id TEXT NOT NULL,
203551
+ remote_session_id TEXT NOT NULL,
203552
+ branch TEXT,
203553
+ workspace_checkout_id TEXT DEFAULT NULL,
203554
+ title_resolved INTEGER NOT NULL DEFAULT 0,
203555
+ notification_sync_start TEXT NOT NULL DEFAULT 'from_now',
203556
+ notification_watch_until INTEGER,
203557
+ FOREIGN KEY (workspace_checkout_id) REFERENCES workspace_checkouts(id)
203558
+ DEFERRABLE INITIALLY DEFERRED
203559
+ );
203560
+ INSERT INTO remote_session_mappings_fk_new
203561
+ (local_session_id, project_id, remote_server_id, remote_session_id, branch,
203562
+ workspace_checkout_id, title_resolved, notification_sync_start, notification_watch_until)
203563
+ SELECT local_session_id, project_id, remote_server_id, remote_session_id, branch,
203564
+ workspace_checkout_id, title_resolved, notification_sync_start, notification_watch_until
203565
+ FROM remote_session_mappings;
203566
+ DROP TABLE remote_session_mappings;
203567
+ ALTER TABLE remote_session_mappings_fk_new RENAME TO remote_session_mappings;
203568
+ `);
203569
+ for (const sql2 of mappingIndexes) db.exec(sql2);
203570
+ }
203571
+ })();
203572
+ console.log("[Storage] workspace_checkout foreign keys are now enforced.");
203573
+ };
203461
203574
  var createDatabase = (dbPath) => {
203462
203575
  const db = new Database(dbPath);
203463
203576
  db.pragma("journal_mode = WAL");
@@ -204954,6 +205067,7 @@ var createDatabase = (dbPath) => {
204954
205067
  }
204955
205068
  db.exec(`CREATE INDEX IF NOT EXISTS idx_remote_session_mappings_workspace_checkout
204956
205069
  ON remote_session_mappings(workspace_checkout_id)`);
205070
+ tightenWorkspaceCheckoutForeignKeys(db);
204957
205071
  const sessionSearchCacheInfo = db.prepare("PRAGMA table_info(session_search_cache)").all();
204958
205072
  if (!sessionSearchCacheInfo.some((col) => col.name === "written_at")) {
204959
205073
  db.exec("ALTER TABLE session_search_cache ADD COLUMN written_at INTEGER");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibedeckx/linux-x64",
3
- "version": "0.3.9",
3
+ "version": "0.3.10",
4
4
  "description": "Vibedeckx platform binaries for Linux x64",
5
5
  "os": [
6
6
  "linux"