fix: remove workdir management kind

This commit is contained in:
Keisuke Hirata 2026-07-22 20:52:09 +09:00
parent fe2a39c12d
commit 44deacb3f4
No known key found for this signature in database
6 changed files with 82 additions and 172 deletions

View File

@ -123,8 +123,6 @@ pub struct BackendWorkingDirectorySummary {
pub cleanliness: Option<String>,
#[serde(default)]
pub primary_worker_id: Option<String>,
#[serde(default)]
pub management_kind: Option<String>,
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]

View File

@ -156,10 +156,6 @@ pub struct WorkingDirectorySummary {
pub cleanliness: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub primary_worker_id: Option<WorkerId>,
/// Backend projection metadata. Runtimes leave this absent; Workspace Browser
/// APIs fill it with `backend_managed` or `runtime_unmanaged`.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub management_kind: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -48,7 +48,6 @@ impl WorkingDirectory {
status: self.status.clone(),
cleanliness: None,
primary_worker_id: None,
management_kind: None,
}
}
}
@ -222,7 +221,6 @@ impl LocalGitWorktreeMaterializer {
status: WorkingDirectoryStatusKind::Corrupted,
cleanliness: Some("unknown".to_string()),
primary_worker_id: None,
management_kind: None,
},
}
}

View File

@ -1587,7 +1587,6 @@ fn create_working_directory_for_runtime(
resolved_commit: None,
materialization_status: "pending".to_string(),
cleanliness: "unknown".to_string(),
management_kind: "backend_managed".to_string(),
created_at: now_registry_timestamp(),
updated_at: now_registry_timestamp(),
};
@ -1610,12 +1609,7 @@ fn create_working_directory_for_runtime(
result.diagnostics,
));
};
let record = workdir_record_from_summary(
&api,
&runtime_id,
&working_directory.summary,
"backend_managed",
);
let record = workdir_record_from_summary(&api, &runtime_id, &working_directory.summary);
api.store.upsert_workdir_registry(&record)?;
Ok(Json(BrowserWorkingDirectoryDetailResponse {
workspace_id: api.config.workspace_id.clone(),
@ -1634,17 +1628,7 @@ fn working_directory_detail_for_runtime(
.working_directory(runtime_id, working_directory_id)
.map_err(|err| err.into_error())?;
if let Some(working_directory) = result.working_directory {
let management_kind = api
.store
.get_workdir_registry(&api.config.workspace_id, working_directory_id)?
.map(|record| record.management_kind)
.unwrap_or_else(|| "runtime_unmanaged".to_string());
let record = workdir_record_from_summary(
&api,
runtime_id,
&working_directory.summary,
management_kind.as_str(),
);
let record = workdir_record_from_summary(&api, runtime_id, &working_directory.summary);
api.store.upsert_workdir_registry(&record)?;
return Ok(Json(BrowserWorkingDirectoryDetailResponse {
workspace_id: api.config.workspace_id.clone(),
@ -1711,17 +1695,7 @@ fn cleanup_working_directory_for_runtime(
result.diagnostics,
));
};
let management_kind = api
.store
.get_workdir_registry(&api.config.workspace_id, working_directory_id)?
.map(|record| record.management_kind)
.unwrap_or_else(|| "runtime_unmanaged".to_string());
let record = workdir_record_from_summary(
&api,
runtime_id,
&working_directory.summary,
management_kind.as_str(),
);
let record = workdir_record_from_summary(&api, runtime_id, &working_directory.summary);
api.store.upsert_workdir_registry(&record)?;
Ok(Json(BrowserWorkingDirectoryDetailResponse {
workspace_id: api.config.workspace_id.clone(),
@ -3010,20 +2984,8 @@ async fn create_workspace_worker(
WorkerRegistryDisplayNamePolicy::UseProvided,
)?;
if let Some(working_directory) = worker.working_directory.as_ref() {
let management_kind = api
.store
.get_workdir_registry(
&api.config.workspace_id,
&working_directory.working_directory_id,
)?
.map(|existing| existing.management_kind)
.unwrap_or_else(|| "runtime_unmanaged".to_string());
let workdir_record = workdir_record_from_summary(
&api,
worker.runtime_id.as_str(),
working_directory,
management_kind.as_str(),
);
let workdir_record =
workdir_record_from_summary(&api, worker.runtime_id.as_str(), working_directory);
api.store.upsert_workdir_registry(&workdir_record)?;
link_worker_to_workdir(
&api,
@ -3047,7 +3009,6 @@ async fn create_workspace_worker(
&api,
worker.runtime_id.as_str(),
&status.summary,
"runtime_unmanaged",
);
api.store.upsert_workdir_registry(&record)?;
}
@ -4425,7 +4386,7 @@ fn working_directory_summaries(api: &WorkspaceApi) -> ApiResult<Vec<WorkingDirec
let _ = sync_all_runtime_workdir_observations(api);
let records = api
.store
.list_managed_workdir_registry(&api.config.workspace_id, 200)?;
.list_workdir_registry(&api.config.workspace_id, 200)?;
Ok(records
.iter()
.map(workdir_summary_from_record)
@ -4619,20 +4580,8 @@ fn sync_worker_observation(
WorkerRegistryDisplayNamePolicy::PreserveExisting,
)?;
if let Some(working_directory) = worker.working_directory.as_ref() {
let management_kind = api
.store
.get_workdir_registry(
&api.config.workspace_id,
&working_directory.working_directory_id,
)?
.map(|existing| existing.management_kind)
.unwrap_or_else(|| "runtime_unmanaged".to_string());
let workdir_record = workdir_record_from_summary(
api,
worker.runtime_id.as_str(),
working_directory,
management_kind.as_str(),
);
let workdir_record =
workdir_record_from_summary(api, worker.runtime_id.as_str(), working_directory);
api.store.upsert_workdir_registry(&workdir_record)?;
link_worker_to_workdir(api, &record, &working_directory.working_directory_id)?;
}
@ -4663,7 +4612,6 @@ fn upsert_pending_backend_workdir(
resolved_commit: None,
materialization_status: "pending".to_string(),
cleanliness: "unknown".to_string(),
management_kind: "backend_managed".to_string(),
created_at: timestamp.clone(),
updated_at: timestamp,
})?;
@ -4692,12 +4640,7 @@ fn sync_runtime_workdir_observations(
&api.config.workspace_id,
&status.summary.working_directory_id,
)?;
let management_kind = existing
.as_ref()
.map(|existing| existing.management_kind.clone())
.unwrap_or_else(|| "runtime_unmanaged".to_string());
let mut record =
workdir_record_from_summary(api, runtime_id, &status.summary, management_kind.as_str());
let mut record = workdir_record_from_summary(api, runtime_id, &status.summary);
preserve_workdir_identity_for_corrupted_summary(&mut record, existing.as_ref());
api.store.upsert_workdir_registry(&record)?;
}
@ -4719,13 +4662,8 @@ fn sync_runtime_workdir_observations(
record.workdir_id.as_str(),
)?;
} else {
let management_kind = record.management_kind.clone();
let mut updated = workdir_record_from_summary(
api,
runtime_id,
&status.summary,
management_kind.as_str(),
);
let mut updated =
workdir_record_from_summary(api, runtime_id, &status.summary);
preserve_workdir_identity_for_corrupted_summary(
&mut updated,
Some(&record),
@ -4792,17 +4730,7 @@ fn sync_linked_workdir_after_worker_stop(
.working_directory(runtime_id, link.workdir_id.as_str())
.map_err(|err| err.into_error())?;
if let Some(status) = result.working_directory {
let management_kind = api
.store
.get_workdir_registry(&api.config.workspace_id, link.workdir_id.as_str())?
.map(|record| record.management_kind)
.unwrap_or_else(|| "runtime_unmanaged".to_string());
let record = workdir_record_from_summary(
api,
runtime_id,
&status.summary,
management_kind.as_str(),
);
let record = workdir_record_from_summary(api, runtime_id, &status.summary);
api.store.upsert_workdir_registry(&record)?;
} else if let Some(mut record) = api
.store
@ -4822,7 +4750,6 @@ fn workdir_record_from_summary(
api: &WorkspaceApi,
runtime_id: &str,
summary: &WorkingDirectorySummary,
management_kind: &str,
) -> WorkdirRegistryRecord {
let timestamp = now_registry_timestamp();
WorkdirRegistryRecord {
@ -4844,7 +4771,6 @@ fn workdir_record_from_summary(
.cleanliness
.clone()
.unwrap_or_else(|| "unknown".to_string()),
management_kind: management_kind.to_string(),
created_at: timestamp.clone(),
updated_at: timestamp,
}
@ -4895,7 +4821,6 @@ fn workdir_summary_from_record(record: &WorkdirRegistryRecord) -> WorkingDirecto
status,
cleanliness: Some(record.cleanliness.clone()),
primary_worker_id: None,
management_kind: Some(record.management_kind.clone()),
}
}
@ -5517,7 +5442,6 @@ mod tests {
resolved_commit: Some("abcdef".to_string()),
materialization_status: "missing".to_string(),
cleanliness: "clean".to_string(),
management_kind: "backend_managed".to_string(),
created_at: "1".to_string(),
updated_at: "3".to_string(),
};
@ -5538,22 +5462,13 @@ mod tests {
projected.working_directory.as_ref().unwrap().status,
WorkingDirectoryStatusKind::NotFound
);
assert_eq!(
projected
.working_directory
.as_ref()
.unwrap()
.management_kind
.as_deref(),
Some("backend_managed")
);
let serialized = serde_json::to_string(&projected).unwrap();
assert!(!serialized.contains("/tmp/"));
assert!(!serialized.contains("materialized_path"));
}
#[tokio::test]
async fn workspace_managed_workdir_summaries_exclude_runtime_unmanaged_rows() {
async fn workspace_workdir_summaries_include_runtime_observed_rows() {
let dir = tempfile::tempdir().unwrap();
let api = test_api(dir.path()).await;
api.store
@ -5566,7 +5481,6 @@ mod tests {
resolved_commit: None,
materialization_status: "present".to_string(),
cleanliness: "clean".to_string(),
management_kind: "backend_managed".to_string(),
created_at: "1".to_string(),
updated_at: "1".to_string(),
})
@ -5581,29 +5495,29 @@ mod tests {
resolved_commit: None,
materialization_status: "present".to_string(),
cleanliness: "unknown".to_string(),
management_kind: "runtime_unmanaged".to_string(),
created_at: "1".to_string(),
updated_at: "2".to_string(),
})
.unwrap();
let managed = working_directory_summaries(&api)
let summaries = working_directory_summaries(&api)
.unwrap_or_else(|err| panic!("working_directory_summaries failed: {}", err.error));
assert_eq!(managed.len(), 1);
assert_eq!(managed[0].working_directory_id, "managed");
assert_eq!(
managed[0].management_kind.as_deref(),
Some("backend_managed")
);
let ids = summaries
.iter()
.map(|summary| summary.working_directory_id.as_str())
.collect::<Vec<_>>();
assert!(ids.contains(&"managed"));
assert!(ids.contains(&"runtime-direct"));
let (runtime_projection, _) =
runtime_working_directory_summaries(&api, EMBEDDED_WORKER_RUNTIME_ID).unwrap_or_else(
|err| panic!("runtime_working_directory_summaries failed: {}", err.error),
);
assert!(runtime_projection.iter().any(|summary| {
summary.working_directory_id == "runtime-direct"
&& summary.management_kind.as_deref() == Some("runtime_unmanaged")
}));
assert!(
runtime_projection
.iter()
.any(|summary| summary.working_directory_id == "runtime-direct")
);
}
#[test]
fn unmanaged_runtime_workdir_projection_is_typed_and_diagnostic_safe() {
@ -5616,7 +5530,6 @@ mod tests {
resolved_commit: None,
materialization_status: "present".to_string(),
cleanliness: "unknown".to_string(),
management_kind: "runtime_unmanaged".to_string(),
created_at: "1".to_string(),
updated_at: "2".to_string(),
};
@ -5624,10 +5537,6 @@ mod tests {
let projected = workdir_summary_from_record(&workdir);
assert_eq!(projected.status, WorkingDirectoryStatusKind::Active);
assert_eq!(
projected.management_kind.as_deref(),
Some("runtime_unmanaged")
);
let serialized = serde_json::to_string(&projected).unwrap();
assert!(!serialized.contains("/tmp/"));
assert!(!serialized.contains("materialized_path"));
@ -6227,7 +6136,6 @@ mod tests {
workdir_id: workdir_id.to_string(),
runtime_id: "runtime-test".to_string(),
repository_id: "repo-test".to_string(),
management_kind: "backend_managed".to_string(),
selector: Some("HEAD".to_string()),
resolved_commit: None,
materialization_status: status.to_string(),

View File

@ -47,6 +47,11 @@ const MIGRATIONS: &[Migration] = &[
name: "add workdir runtime observation states",
apply: add_workdir_runtime_observation_states,
},
Migration {
version: 7,
name: "remove workdir registry management kind",
apply: remove_workdir_registry_management_kind_column,
},
];
struct Migration {
@ -91,8 +96,6 @@ pub struct WorkdirRegistryRecord {
pub resolved_commit: Option<String>,
pub materialization_status: String,
pub cleanliness: String,
/// `backend_managed` rows are authored by this Backend; `runtime_unmanaged` is for diagnostics only.
pub management_kind: String,
pub created_at: String,
pub updated_at: String,
}
@ -152,11 +155,6 @@ pub trait ControlPlaneStore: Send + Sync {
workspace_id: &str,
limit: usize,
) -> Result<Vec<WorkdirRegistryRecord>>;
fn list_managed_workdir_registry(
&self,
workspace_id: &str,
limit: usize,
) -> Result<Vec<WorkdirRegistryRecord>>;
fn delete_workdir_registry(&self, workspace_id: &str, workdir_id: &str) -> Result<bool>;
fn upsert_worker_workdir_link(&self, record: &WorkerWorkdirLinkRecord) -> Result<()>;
@ -378,8 +376,8 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
conn.execute(
r#"INSERT INTO workdir_registry (
workspace_id, workdir_id, runtime_id, repository_id, selector, resolved_commit,
materialization_status, cleanliness, management_kind, created_at, updated_at
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)
materialization_status, cleanliness, created_at, updated_at
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)
ON CONFLICT(workspace_id, workdir_id) DO UPDATE SET
runtime_id = excluded.runtime_id,
repository_id = excluded.repository_id,
@ -387,7 +385,6 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
resolved_commit = excluded.resolved_commit,
materialization_status = excluded.materialization_status,
cleanliness = excluded.cleanliness,
management_kind = excluded.management_kind,
updated_at = excluded.updated_at"#,
params![
record.workspace_id,
@ -398,7 +395,6 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
record.resolved_commit,
record.materialization_status,
record.cleanliness,
record.management_kind,
record.created_at,
record.updated_at,
],
@ -442,22 +438,6 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
})
}
fn list_managed_workdir_registry(
&self,
workspace_id: &str,
limit: usize,
) -> Result<Vec<WorkdirRegistryRecord>> {
self.with_conn(|conn| {
let sql = workdir_registry_select_sql(
"WHERE workspace_id = ?1 AND management_kind = 'backend_managed' ORDER BY updated_at DESC LIMIT ?2",
);
let mut stmt = conn.prepare(sql.as_str())?;
let rows = stmt.query_map(params![workspace_id, limit as i64], read_workdir_registry_record)?;
rows.collect::<std::result::Result<Vec<_>, _>>()
.map_err(Error::from)
})
}
fn delete_workdir_registry(&self, workspace_id: &str, workdir_id: &str) -> Result<bool> {
self.with_conn(|conn| {
let changed = conn.execute(
@ -577,7 +557,7 @@ fn read_worker_registry_record(row: &rusqlite::Row<'_>) -> rusqlite::Result<Work
fn workdir_registry_select_sql(where_clause: &str) -> String {
format!(
"SELECT workspace_id, workdir_id, runtime_id, repository_id, selector, resolved_commit, \
materialization_status, cleanliness, management_kind, created_at, updated_at \
materialization_status, cleanliness, created_at, updated_at \
FROM workdir_registry {where_clause}"
)
}
@ -594,9 +574,8 @@ fn read_workdir_registry_record(
resolved_commit: row.get(5)?,
materialization_status: row.get(6)?,
cleanliness: row.get(7)?,
management_kind: row.get(8)?,
created_at: row.get(9)?,
updated_at: row.get(10)?,
created_at: row.get(8)?,
updated_at: row.get(9)?,
})
}
@ -629,7 +608,6 @@ CREATE TABLE IF NOT EXISTS workdir_registry (
resolved_commit TEXT,
materialization_status TEXT NOT NULL CHECK (materialization_status IN ('pending', 'present', 'not_found', 'corrupted', 'unknown', 'failed')),
cleanliness TEXT NOT NULL CHECK (cleanliness IN ('clean', 'dirty', 'unknown')),
management_kind TEXT NOT NULL CHECK (management_kind IN ('backend_managed', 'runtime_unmanaged')),
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
PRIMARY KEY (workspace_id, workdir_id),
@ -948,7 +926,6 @@ fn add_workdir_runtime_observation_states(conn: &Connection) -> Result<()> {
resolved_commit TEXT,
materialization_status TEXT NOT NULL CHECK (materialization_status IN ('pending', 'present', 'not_found', 'corrupted', 'unknown', 'failed')),
cleanliness TEXT NOT NULL CHECK (cleanliness IN ('clean', 'dirty', 'unknown')),
management_kind TEXT NOT NULL CHECK (management_kind IN ('backend_managed', 'runtime_unmanaged')),
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
PRIMARY KEY (workspace_id, workdir_id),
@ -956,7 +933,7 @@ fn add_workdir_runtime_observation_states(conn: &Connection) -> Result<()> {
);
INSERT OR REPLACE INTO workdir_registry_v6 (
workspace_id, workdir_id, runtime_id, repository_id, selector, resolved_commit,
materialization_status, cleanliness, management_kind, created_at, updated_at
materialization_status, cleanliness, created_at, updated_at
)
SELECT
workspace_id, workdir_id, runtime_id, repository_id, selector, resolved_commit,
@ -965,7 +942,7 @@ fn add_workdir_runtime_observation_states(conn: &Connection) -> Result<()> {
WHEN 'removed' THEN 'not_found'
ELSE materialization_status
END,
cleanliness, management_kind, created_at, updated_at
cleanliness, created_at, updated_at
FROM workdir_registry;
DROP TABLE workdir_registry;
ALTER TABLE workdir_registry_v6 RENAME TO workdir_registry;
@ -976,6 +953,48 @@ fn add_workdir_runtime_observation_states(conn: &Connection) -> Result<()> {
Ok(())
}
fn remove_workdir_registry_management_kind_column(conn: &Connection) -> Result<()> {
if !table_exists(conn, "workdir_registry")?
|| !table_columns(conn, "workdir_registry")?
.iter()
.any(|column| column == "management_kind")
{
return Ok(());
}
conn.execute_batch(
r#"
CREATE TABLE workdir_registry_v7 (
workspace_id TEXT NOT NULL,
workdir_id TEXT NOT NULL,
runtime_id TEXT NOT NULL,
repository_id TEXT NOT NULL,
selector TEXT,
resolved_commit TEXT,
materialization_status TEXT NOT NULL CHECK (materialization_status IN ('pending', 'present', 'not_found', 'corrupted', 'unknown', 'failed')),
cleanliness TEXT NOT NULL CHECK (cleanliness IN ('clean', 'dirty', 'unknown')),
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
PRIMARY KEY (workspace_id, workdir_id),
FOREIGN KEY (workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE
);
INSERT OR REPLACE INTO workdir_registry_v7 (
workspace_id, workdir_id, runtime_id, repository_id, selector, resolved_commit,
materialization_status, cleanliness, created_at, updated_at
)
SELECT
workspace_id, workdir_id, runtime_id, repository_id, selector, resolved_commit,
materialization_status, cleanliness, created_at, updated_at
FROM workdir_registry;
DROP TABLE workdir_registry;
ALTER TABLE workdir_registry_v7 RENAME TO workdir_registry;
CREATE INDEX IF NOT EXISTS idx_workdir_registry_workspace_updated
ON workdir_registry(workspace_id, updated_at DESC);
"#,
)?;
Ok(())
}
fn create_schema_v0_tables(conn: &Connection) -> Result<()> {
conn.execute_batch(
r#"
@ -1199,7 +1218,7 @@ mod tests {
let db = dir.path().join("control-plane.sqlite");
let store = SqliteWorkspaceStore::open(&db).unwrap();
assert_eq!(store.schema_version().await.unwrap(), 6);
assert_eq!(store.schema_version().await.unwrap(), 7);
let record = WorkspaceRecord {
workspace_id: "local-dev".to_string(),
@ -1211,7 +1230,7 @@ mod tests {
store.upsert_workspace(&record).await.unwrap();
let reopened = SqliteWorkspaceStore::open(&db).unwrap();
assert_eq!(reopened.schema_version().await.unwrap(), 6);
assert_eq!(reopened.schema_version().await.unwrap(), 7);
assert_eq!(
reopened.get_workspace("local-dev").await.unwrap(),
Some(record)
@ -1420,7 +1439,7 @@ mod tests {
.unwrap();
let store = SqliteWorkspaceStore::from_connection(conn).unwrap();
assert_eq!(store.schema_version().await.unwrap(), 6);
assert_eq!(store.schema_version().await.unwrap(), 7);
store
.with_conn(|conn| {
@ -1550,7 +1569,6 @@ mod tests {
resolved_commit: Some("abcdef".to_string()),
materialization_status: "not_found".to_string(),
cleanliness: "clean".to_string(),
management_kind: "backend_managed".to_string(),
created_at: "2".to_string(),
updated_at: "3".to_string(),
};
@ -1564,7 +1582,6 @@ mod tests {
resolved_commit: Some("123456".to_string()),
materialization_status: "present".to_string(),
cleanliness: "unknown".to_string(),
management_kind: "runtime_unmanaged".to_string(),
created_at: "3".to_string(),
updated_at: "4".to_string(),
};
@ -1597,12 +1614,6 @@ mod tests {
store.list_workdir_registry("local-dev", 10).unwrap(),
vec![unmanaged_workdir.clone(), workdir.clone()]
);
assert_eq!(
store
.list_managed_workdir_registry("local-dev", 10)
.unwrap(),
vec![workdir]
);
assert_eq!(
store
.list_worker_workdir_links("local-dev", "embedded", 1)

View File

@ -125,7 +125,6 @@ export type WorkingDirectorySummary = {
status: string;
cleanliness?: string | null;
primary_worker_id?: number | null;
management_kind?: "backend_managed" | "runtime_unmanaged" | string | null;
cleanup_target: {
kind: string;
working_directory_id: string;