workspace: remove worker lifecycle state

This commit is contained in:
Keisuke Hirata 2026-07-11 13:33:49 +09:00
parent 13f9e0fab8
commit b877c92975
No known key found for this signature in database
6 changed files with 142 additions and 52 deletions

View File

@ -1,8 +1,8 @@
--- ---
title: 'Remove durable worker lifecycle_state' title: 'Remove durable worker lifecycle_state'
state: 'inprogress' state: 'done'
created_at: '2026-07-11T04:08:12Z' created_at: '2026-07-11T04:08:12Z'
updated_at: '2026-07-11T04:09:06Z' updated_at: '2026-07-11T04:33:35Z'
assignee: null assignee: null
queued_by: 'yoi ticket' queued_by: 'yoi ticket'
queued_at: '2026-07-11T04:09:06Z' queued_at: '2026-07-11T04:09:06Z'

View File

@ -39,4 +39,38 @@ Ticket を `yoi ticket` が queued にしました。
State changed to `inprogress`. State changed to `inprogress`.
---
<!-- event: implementation_report author: hare at: 2026-07-11T04:33:35Z -->
## Implementation report
Removed durable worker lifecycle state from the Backend worker registry.
- `WorkerRegistryRecord` no longer has `lifecycle_state`.
- Active worker_registry CREATE/INSERT/SELECT/read paths no longer include lifecycle state.
- Added schema migration v4 to rebuild legacy worker_registry tables without the old lifecycle column.
- Registry-only Worker projection now reports `archived` instead of stale `idle`/`running`/`stopped` values.
- Live Worker merge no longer lets registry data overwrite Runtime status/state.
- Cleanup planning uses live Runtime workers for running-linked detection; registry records only provide retention/link metadata.
- Removed lifecycle_state from frontend cleanup candidate types/display.
Validation:
- cargo test -q -p yoi-workspace-server
- cd web/workspace && deno task check
- cd web/workspace && deno task test
- cargo test -q
- git diff --check
- nix build .#yoi --no-link
---
<!-- event: state_changed author: "yoi ticket" at: 2026-07-11T04:33:35Z from: inprogress to: done reason: cli_state field: state -->
## State changed
State changed to `done`.
--- ---

View File

@ -601,7 +601,6 @@ pub struct CleanupWorkerCandidate {
pub blocking_reason: Option<String>, pub blocking_reason: Option<String>,
pub pinned: bool, pub pinned: bool,
pub retention_state: String, pub retention_state: String,
pub lifecycle_state: String,
pub linked_workdir_ids: Vec<String>, pub linked_workdir_ids: Vec<String>,
pub running_linked: bool, pub running_linked: bool,
pub estimated_reclaim_bytes: Option<u64>, pub estimated_reclaim_bytes: Option<u64>,
@ -1579,7 +1578,13 @@ fn build_runtime_cleanup_plan(
api: &WorkspaceApi, api: &WorkspaceApi,
runtime_id: &str, runtime_id: &str,
) -> ApiResult<RuntimeCleanupPlanResponse> { ) -> ApiResult<RuntimeCleanupPlanResponse> {
let _ = workers_response(api.clone()); let workers = workers_response(api.clone())?;
let live_running_worker_ids: HashSet<String> = workers
.items
.iter()
.filter(|worker| worker.status == "running" || worker.state == "running")
.map(|worker| backend_worker_id(worker.runtime_id.as_str(), worker.worker_id.as_str()))
.collect();
let (workdir_summaries, mut diagnostics) = let (workdir_summaries, mut diagnostics) =
match runtime_working_directory_summaries(api, runtime_id) { match runtime_working_directory_summaries(api, runtime_id) {
Ok(result) => result, Ok(result) => result,
@ -1618,7 +1623,7 @@ fn build_runtime_cleanup_plan(
let links = api let links = api
.store .store
.list_worker_workdir_links(&api.config.workspace_id, record.worker_id.as_str())?; .list_worker_workdir_links(&api.config.workspace_id, record.worker_id.as_str())?;
let is_running = record.lifecycle_state == "running"; let is_running = live_running_worker_ids.contains(&record.worker_id);
let pinned = record.retention_state == "pinned"; let pinned = record.retention_state == "pinned";
let blocking_reason = if pinned { let blocking_reason = if pinned {
Some("worker is pinned".to_string()) Some("worker is pinned".to_string())
@ -1642,7 +1647,6 @@ fn build_runtime_cleanup_plan(
blocking_reason, blocking_reason,
pinned, pinned,
retention_state: record.retention_state.clone(), retention_state: record.retention_state.clone(),
lifecycle_state: record.lifecycle_state.clone(),
linked_workdir_ids: links.iter().map(|link| link.workdir_id.clone()).collect(), linked_workdir_ids: links.iter().map(|link| link.workdir_id.clone()).collect(),
running_linked: is_running, running_linked: is_running,
estimated_reclaim_bytes: None, estimated_reclaim_bytes: None,
@ -1667,7 +1671,7 @@ fn build_runtime_cleanup_plan(
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let linked_running_worker_ids = linked_workers let linked_running_worker_ids = linked_workers
.iter() .iter()
.filter(|worker| worker.lifecycle_state == "running") .filter(|worker| live_running_worker_ids.contains(&worker.worker_id))
.map(|worker| worker.worker_id.clone()) .map(|worker| worker.worker_id.clone())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let pinned_linked = linked_workers let pinned_linked = linked_workers
@ -2913,13 +2917,10 @@ async fn stop_runtime_worker(
.stop_worker(&runtime_id, &worker_id, request) .stop_worker(&runtime_id, &worker_id, request)
.map_err(|err| err.into_error())?; .map_err(|err| err.into_error())?;
let backend_id = backend_worker_id(&runtime_id, &worker_id); let backend_id = backend_worker_id(&runtime_id, &worker_id);
if let Some(mut record) = api if let Some(record) = api
.store .store
.get_worker_registry(&api.config.workspace_id, backend_id.as_str())? .get_worker_registry(&api.config.workspace_id, backend_id.as_str())?
{ {
record.lifecycle_state = "stopped".to_string();
record.updated_at = now_registry_timestamp();
api.store.upsert_worker_registry(&record)?;
sync_linked_workdir_after_worker_stop(&api, &runtime_id, &record)?; sync_linked_workdir_after_worker_stop(&api, &runtime_id, &record)?;
} }
Ok(Json(result)) Ok(Json(result))
@ -3879,7 +3880,6 @@ fn record_worker_summary(
runtime_worker_id: worker.worker_id.as_str().to_string(), runtime_worker_id: worker.worker_id.as_str().to_string(),
display_name: display_name.to_string(), display_name: display_name.to_string(),
profile, profile,
lifecycle_state: worker.status.clone(),
retention_state: existing retention_state: existing
.as_ref() .as_ref()
.map(|record| record.retention_state.clone()) .map(|record| record.retention_state.clone())
@ -3909,8 +3909,8 @@ fn worker_summary_from_registry(record: &WorkerRegistryRecord) -> WorkerSummary
host_id: "backend-registry".to_string(), host_id: "backend-registry".to_string(),
role: None, role: None,
label: record.display_name.clone(), label: record.display_name.clone(),
status: record.lifecycle_state.clone(), status: "archived".to_string(),
state: record.lifecycle_state.clone(), state: "archived".to_string(),
last_seen_at: Some(record.updated_at.clone()), last_seen_at: Some(record.updated_at.clone()),
pinned: record.retention_state == "pinned", pinned: record.retention_state == "pinned",
retention_state: record.retention_state.clone(), retention_state: record.retention_state.clone(),
@ -3949,8 +3949,6 @@ fn merge_worker_registry_projection(
.cloned() .cloned()
.unwrap_or_else(|| worker_summary_from_registry(record)); .unwrap_or_else(|| worker_summary_from_registry(record));
summary.label = record.display_name.clone(); summary.label = record.display_name.clone();
summary.status = record.lifecycle_state.clone();
summary.state = record.lifecycle_state.clone();
summary.profile = record.profile.clone(); summary.profile = record.profile.clone();
summary.pinned = record.retention_state == "pinned"; summary.pinned = record.retention_state == "pinned";
summary.retention_state = record.retention_state.clone(); summary.retention_state = record.retention_state.clone();
@ -4759,7 +4757,6 @@ mod tests {
runtime_worker_id: "worker-1".to_string(), runtime_worker_id: "worker-1".to_string(),
display_name: "Archived Worker".to_string(), display_name: "Archived Worker".to_string(),
profile: Some("builtin:coder".to_string()), profile: Some("builtin:coder".to_string()),
lifecycle_state: "stopped".to_string(),
retention_state: "pinned".to_string(), retention_state: "pinned".to_string(),
transcript_ref: Some("runtime://embedded/workers/worker-1/transcript".to_string()), transcript_ref: Some("runtime://embedded/workers/worker-1/transcript".to_string()),
session_ref: None, session_ref: None,
@ -4792,7 +4789,8 @@ mod tests {
let projected = merge_worker_registry_projection(None, &worker, vec![link], &[workdir]); let projected = merge_worker_registry_projection(None, &worker, vec![link], &[workdir]);
assert_eq!(projected.status, "stopped"); assert_eq!(projected.status, "archived");
assert_eq!(projected.state, "archived");
assert_eq!( assert_eq!(
projected.working_directory.as_ref().unwrap().status, projected.working_directory.as_ref().unwrap().status,
WorkingDirectoryStatusKind::Removed WorkingDirectoryStatusKind::Removed
@ -5341,7 +5339,6 @@ mod tests {
fn seed_cleanup_worker( fn seed_cleanup_worker(
api: &WorkspaceApi, api: &WorkspaceApi,
runtime_worker_id: &str, runtime_worker_id: &str,
lifecycle_state: &str,
retention_state: &str, retention_state: &str,
) -> String { ) -> String {
let worker_id = backend_worker_id("runtime-test", runtime_worker_id); let worker_id = backend_worker_id("runtime-test", runtime_worker_id);
@ -5354,7 +5351,6 @@ mod tests {
runtime_worker_id: runtime_worker_id.to_string(), runtime_worker_id: runtime_worker_id.to_string(),
display_name: runtime_worker_id.to_string(), display_name: runtime_worker_id.to_string(),
profile: None, profile: None,
lifecycle_state: lifecycle_state.to_string(),
retention_state: retention_state.to_string(), retention_state: retention_state.to_string(),
transcript_ref: None, transcript_ref: None,
session_ref: None, session_ref: None,
@ -5525,12 +5521,12 @@ mod tests {
let workspace = tempfile::tempdir().unwrap(); let workspace = tempfile::tempdir().unwrap();
init_clean_git_workspace(workspace.path()); init_clean_git_workspace(workspace.path());
let api = test_api(workspace.path()).await; let api = test_api(workspace.path()).await;
let pinned = seed_cleanup_worker(&api, "worker-pinned", "stopped", "pinned"); let pinned = seed_cleanup_worker(&api, "worker-pinned", "pinned");
let running = seed_cleanup_worker(&api, "worker-running", "running", "normal"); let unobserved = seed_cleanup_worker(&api, "worker-unobserved", "normal");
seed_cleanup_workdir(&api, "workdir-dirty", "present", "dirty"); seed_cleanup_workdir(&api, "workdir-dirty", "present", "dirty");
seed_cleanup_workdir(&api, "workdir-removed", "removed", "clean"); seed_cleanup_workdir(&api, "workdir-removed", "removed", "clean");
seed_cleanup_link(&api, pinned.as_str(), "workdir-dirty"); seed_cleanup_link(&api, pinned.as_str(), "workdir-dirty");
seed_cleanup_link(&api, running.as_str(), "workdir-removed"); seed_cleanup_link(&api, unobserved.as_str(), "workdir-removed");
let plan = build_runtime_cleanup_plan(&api, "runtime-test") let plan = build_runtime_cleanup_plan(&api, "runtime-test")
.unwrap_or_else(|err| panic!("cleanup plan: {}", err.error)); .unwrap_or_else(|err| panic!("cleanup plan: {}", err.error));
@ -5553,11 +5549,8 @@ mod tests {
running_linked_workdir.action, running_linked_workdir.action,
CleanupTargetKind::WorkdirRecordDelete CleanupTargetKind::WorkdirRecordDelete
); );
assert!(running_linked_workdir.running_linked); assert!(!running_linked_workdir.running_linked);
assert_eq!( assert_eq!(running_linked_workdir.blocking_reason.as_deref(), None);
running_linked_workdir.blocking_reason.as_deref(),
Some("workdir is linked to a running Worker")
);
let dirty_workdir = plan let dirty_workdir = plan
.workdirs .workdirs
.iter() .iter()
@ -5574,7 +5567,7 @@ mod tests {
let workspace = tempfile::tempdir().unwrap(); let workspace = tempfile::tempdir().unwrap();
init_clean_git_workspace(workspace.path()); init_clean_git_workspace(workspace.path());
let api = test_api(workspace.path()).await; let api = test_api(workspace.path()).await;
let worker = seed_cleanup_worker(&api, "worker-pinned", "stopped", "pinned"); let worker = seed_cleanup_worker(&api, "worker-pinned", "pinned");
let plan = build_runtime_cleanup_plan(&api, "runtime-test") let plan = build_runtime_cleanup_plan(&api, "runtime-test")
.unwrap_or_else(|err| panic!("cleanup plan: {}", err.error)); .unwrap_or_else(|err| panic!("cleanup plan: {}", err.error));
let target = plan let target = plan

View File

@ -32,6 +32,11 @@ const MIGRATIONS: &[Migration] = &[
name: "backend worker workdir registry schema", name: "backend worker workdir registry schema",
apply: create_worker_workdir_registry_tables, apply: create_worker_workdir_registry_tables,
}, },
Migration {
version: 4,
name: "remove durable worker lifecycle state",
apply: remove_worker_registry_legacy_live_state_column,
},
]; ];
struct Migration { struct Migration {
@ -58,7 +63,6 @@ pub struct WorkerRegistryRecord {
pub runtime_worker_id: String, pub runtime_worker_id: String,
pub display_name: String, pub display_name: String,
pub profile: Option<String>, pub profile: Option<String>,
pub lifecycle_state: String,
/// Retention state is explicit so `pinned` can be represented before prune exists. /// Retention state is explicit so `pinned` can be represented before prune exists.
pub retention_state: String, pub retention_state: String,
pub transcript_ref: Option<String>, pub transcript_ref: Option<String>,
@ -244,15 +248,14 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
conn.execute( conn.execute(
r#"INSERT INTO worker_registry ( r#"INSERT INTO worker_registry (
workspace_id, worker_id, runtime_id, runtime_worker_id, display_name, profile, workspace_id, worker_id, runtime_id, runtime_worker_id, display_name, profile,
lifecycle_state, retention_state, transcript_ref, session_ref, summary_ref, retention_state, transcript_ref, session_ref, summary_ref,
diagnostics_ref, created_at, updated_at diagnostics_ref, created_at, updated_at
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14) ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)
ON CONFLICT(workspace_id, worker_id) DO UPDATE SET ON CONFLICT(workspace_id, worker_id) DO UPDATE SET
runtime_id = excluded.runtime_id, runtime_id = excluded.runtime_id,
runtime_worker_id = excluded.runtime_worker_id, runtime_worker_id = excluded.runtime_worker_id,
display_name = excluded.display_name, display_name = excluded.display_name,
profile = excluded.profile, profile = excluded.profile,
lifecycle_state = excluded.lifecycle_state,
retention_state = CASE retention_state = CASE
WHEN worker_registry.retention_state = 'pinned' AND excluded.retention_state = 'normal' WHEN worker_registry.retention_state = 'pinned' AND excluded.retention_state = 'normal'
THEN worker_registry.retention_state THEN worker_registry.retention_state
@ -270,7 +273,6 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
record.runtime_worker_id, record.runtime_worker_id,
record.display_name, record.display_name,
record.profile, record.profile,
record.lifecycle_state,
record.retention_state, record.retention_state,
record.transcript_ref, record.transcript_ref,
record.session_ref, record.session_ref,
@ -543,7 +545,7 @@ fn read_worker_workdir_link_record(
fn worker_registry_select_sql(where_clause: &str) -> String { fn worker_registry_select_sql(where_clause: &str) -> String {
format!( format!(
"SELECT workspace_id, worker_id, runtime_id, runtime_worker_id, display_name, profile, \ "SELECT workspace_id, worker_id, runtime_id, runtime_worker_id, display_name, profile, \
lifecycle_state, retention_state, transcript_ref, session_ref, summary_ref, diagnostics_ref, \ retention_state, transcript_ref, session_ref, summary_ref, diagnostics_ref, \
created_at, updated_at FROM worker_registry {where_clause}" created_at, updated_at FROM worker_registry {where_clause}"
) )
} }
@ -556,14 +558,13 @@ fn read_worker_registry_record(row: &rusqlite::Row<'_>) -> rusqlite::Result<Work
runtime_worker_id: row.get(3)?, runtime_worker_id: row.get(3)?,
display_name: row.get(4)?, display_name: row.get(4)?,
profile: row.get(5)?, profile: row.get(5)?,
lifecycle_state: row.get(6)?, retention_state: row.get(6)?,
retention_state: row.get(7)?, transcript_ref: row.get(7)?,
transcript_ref: row.get(8)?, session_ref: row.get(8)?,
session_ref: row.get(9)?, summary_ref: row.get(9)?,
summary_ref: row.get(10)?, diagnostics_ref: row.get(10)?,
diagnostics_ref: row.get(11)?, created_at: row.get(11)?,
created_at: row.get(12)?, updated_at: row.get(12)?,
updated_at: row.get(13)?,
}) })
} }
@ -603,7 +604,6 @@ CREATE TABLE IF NOT EXISTS worker_registry (
runtime_worker_id TEXT NOT NULL, runtime_worker_id TEXT NOT NULL,
display_name TEXT NOT NULL, display_name TEXT NOT NULL,
profile TEXT, profile TEXT,
lifecycle_state TEXT NOT NULL,
retention_state TEXT NOT NULL CHECK (retention_state IN ('normal', 'pinned')), retention_state TEXT NOT NULL CHECK (retention_state IN ('normal', 'pinned')),
transcript_ref TEXT, transcript_ref TEXT,
session_ref TEXT, session_ref TEXT,
@ -787,6 +787,54 @@ fn rename_legacy_table(conn: &Connection, table_name: &str, legacy_name: &str) -
Ok(()) Ok(())
} }
fn remove_worker_registry_legacy_live_state_column(conn: &Connection) -> Result<()> {
if !table_exists(conn, "worker_registry")?
|| !table_columns(conn, "worker_registry")?
.iter()
.any(|column| column == "lifecycle_state")
{
return Ok(());
}
conn.execute_batch(
r#"
CREATE TABLE worker_registry_v4 (
workspace_id TEXT NOT NULL,
worker_id TEXT NOT NULL,
runtime_id TEXT NOT NULL,
runtime_worker_id TEXT NOT NULL,
display_name TEXT NOT NULL,
profile TEXT,
retention_state TEXT NOT NULL CHECK (retention_state IN ('normal', 'pinned')),
transcript_ref TEXT,
session_ref TEXT,
summary_ref TEXT,
diagnostics_ref TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
PRIMARY KEY (workspace_id, worker_id),
UNIQUE (workspace_id, runtime_id, runtime_worker_id),
FOREIGN KEY (workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE
);
INSERT INTO worker_registry_v4 (
workspace_id, worker_id, runtime_id, runtime_worker_id, display_name, profile,
retention_state, transcript_ref, session_ref, summary_ref, diagnostics_ref,
created_at, updated_at
)
SELECT
workspace_id, worker_id, runtime_id, runtime_worker_id, display_name, profile,
retention_state, transcript_ref, session_ref, summary_ref, diagnostics_ref,
created_at, updated_at
FROM worker_registry;
DROP TABLE worker_registry;
ALTER TABLE worker_registry_v4 RENAME TO worker_registry;
CREATE INDEX IF NOT EXISTS idx_worker_registry_workspace_updated
ON worker_registry(workspace_id, updated_at DESC);
"#,
)?;
Ok(())
}
fn create_schema_v0_tables(conn: &Connection) -> Result<()> { fn create_schema_v0_tables(conn: &Connection) -> Result<()> {
conn.execute_batch( conn.execute_batch(
r#" r#"
@ -1010,7 +1058,7 @@ mod tests {
let db = dir.path().join("control-plane.sqlite"); let db = dir.path().join("control-plane.sqlite");
let store = SqliteWorkspaceStore::open(&db).unwrap(); let store = SqliteWorkspaceStore::open(&db).unwrap();
assert_eq!(store.schema_version().await.unwrap(), 3); assert_eq!(store.schema_version().await.unwrap(), 4);
let record = WorkspaceRecord { let record = WorkspaceRecord {
workspace_id: "local-dev".to_string(), workspace_id: "local-dev".to_string(),
@ -1022,7 +1070,7 @@ mod tests {
store.upsert_workspace(&record).await.unwrap(); store.upsert_workspace(&record).await.unwrap();
let reopened = SqliteWorkspaceStore::open(&db).unwrap(); let reopened = SqliteWorkspaceStore::open(&db).unwrap();
assert_eq!(reopened.schema_version().await.unwrap(), 3); assert_eq!(reopened.schema_version().await.unwrap(), 4);
assert_eq!( assert_eq!(
reopened.get_workspace("local-dev").await.unwrap(), reopened.get_workspace("local-dev").await.unwrap(),
Some(record) Some(record)
@ -1137,6 +1185,25 @@ mod tests {
"caused_by_event_id", "caused_by_event_id",
], ],
); );
assert_columns(
&conn,
"worker_registry",
[
"workspace_id",
"worker_id",
"runtime_id",
"runtime_worker_id",
"display_name",
"profile",
"retention_state",
"transcript_ref",
"session_ref",
"summary_ref",
"diagnostics_ref",
"created_at",
"updated_at",
],
);
assert_columns( assert_columns(
&conn, &conn,
"artifacts", "artifacts",
@ -1213,7 +1280,7 @@ mod tests {
.unwrap(); .unwrap();
let store = SqliteWorkspaceStore::from_connection(conn).unwrap(); let store = SqliteWorkspaceStore::from_connection(conn).unwrap();
assert_eq!(store.schema_version().await.unwrap(), 3); assert_eq!(store.schema_version().await.unwrap(), 4);
store store
.with_conn(|conn| { .with_conn(|conn| {
@ -1319,7 +1386,6 @@ mod tests {
runtime_worker_id: "browser-1".to_string(), runtime_worker_id: "browser-1".to_string(),
display_name: "Browser 1".to_string(), display_name: "Browser 1".to_string(),
profile: Some("builtin:companion".to_string()), profile: Some("builtin:companion".to_string()),
lifecycle_state: "idle".to_string(),
retention_state: "pinned".to_string(), retention_state: "pinned".to_string(),
transcript_ref: Some("runtime://embedded/workers/browser-1/transcript".to_string()), transcript_ref: Some("runtime://embedded/workers/browser-1/transcript".to_string()),
session_ref: None, session_ref: None,
@ -1330,12 +1396,10 @@ mod tests {
}; };
store.upsert_worker_registry(&worker).unwrap(); store.upsert_worker_registry(&worker).unwrap();
let mut runtime_sync_worker = worker.clone(); let mut runtime_sync_worker = worker.clone();
runtime_sync_worker.lifecycle_state = "running".to_string();
runtime_sync_worker.retention_state = "normal".to_string(); runtime_sync_worker.retention_state = "normal".to_string();
runtime_sync_worker.updated_at = "5".to_string(); runtime_sync_worker.updated_at = "5".to_string();
store.upsert_worker_registry(&runtime_sync_worker).unwrap(); store.upsert_worker_registry(&runtime_sync_worker).unwrap();
let mut expected_worker = worker.clone(); let mut expected_worker = worker.clone();
expected_worker.lifecycle_state = "running".to_string();
expected_worker.updated_at = "5".to_string(); expected_worker.updated_at = "5".to_string();
let workdir = WorkdirRegistryRecord { let workdir = WorkdirRegistryRecord {

View File

@ -163,7 +163,6 @@ export type CleanupWorkerCandidate = {
blocking_reason?: string | null; blocking_reason?: string | null;
pinned: boolean; pinned: boolean;
retention_state: string; retention_state: string;
lifecycle_state: string;
linked_workdir_ids: string[]; linked_workdir_ids: string[];
running_linked: boolean; running_linked: boolean;
estimated_reclaim_bytes?: number | null; estimated_reclaim_bytes?: number | null;

View File

@ -127,7 +127,7 @@
/> />
<span> <span>
<strong>Delete Worker registry row:</strong> <code>{candidate.runtime_worker_id}</code> <strong>Delete Worker registry row:</strong> <code>{candidate.runtime_worker_id}</code>
<small>{candidate.lifecycle_state}; {candidate.retention_state}; linked Workdirs {candidate.linked_workdir_ids.length}</small> <small>{candidate.retention_state}; linked Workdirs {candidate.linked_workdir_ids.length}</small>
{#if candidate.blocking_reason}<small class="error">Blocked: {candidate.blocking_reason}</small>{/if} {#if candidate.blocking_reason}<small class="error">Blocked: {candidate.blocking_reason}</small>{/if}
</span> </span>
</label> </label>