fix(docs): split work-product open errors into actionable reasons

The previous handler folded every null target into one error code
("WORK_PRODUCT_NOT_FOUND: Work product not found or its workspace
is unresolved"). When a user couldn't open a file we had no way to
tell whether the row was archived, the provider/external_id was
empty, the workspace had no local_path, or the external_id escaped
the root.

Widen resolveWorkProductTarget's return from (target | null) to
(target | error) where error carries one of five reasons:

  not_found             row missing for id+company
  archived              status == "archived" tombstone
  no_target             provider != filesystem and no url
  workspace_unresolved  all three workspace_id / task_id / run_id
                         lookups returned no local_path
  path_escape           external_id is absolute or steps outside root

Each reason includes the concrete detail (ids checked, paths tried)
so the toast on the renderer side tells the user exactly which slot
is null without having to open the DB.

The IPC handler maps each reason to its own failure code and passes
the detail through, so WorkProductRow's toast now reads e.g.
"workspace_id=null task_id=null run_id=null — none led to a
workspace with a local_path" instead of a generic unresolved.

Typecheck and build:verify clean.
This commit is contained in:
ZacharyZhang-NY committed 2026-04-21 14:45:59 -04:00
1 parent 9f43549606
commit 390f76fa76
2 files changed
+31 -12

No files matched your search

+21 -11
View File
@@ -351,24 +351,26 @@ export function buildDocumentMethods(
* artifact) or a concrete absolute path that downstream
* `canOpenPath` validates.
*/
resolveWorkProductTarget(workProductId: string, companyId: string): { kind: "url" | "path"; value: string } | null {
resolveWorkProductTarget(
workProductId: string,
companyId: string,
):
| { kind: "url" | "path"; value: string }
| { kind: "error"; reason: "not_found" | "archived" | "no_target" | "workspace_unresolved" | "path_escape"; detail: string } {
const row = getDb().prepare(
"SELECT provider, external_id, url, status, task_id, created_by_run_id, last_touched_by_run_id, workspace_id FROM work_products WHERE id = ? AND company_id = ?",
).get(workProductId, companyId) as
| { provider: string; external_id: string | null; url: string | null; status: string; task_id: string | null; created_by_run_id: string | null; last_touched_by_run_id: string | null; workspace_id: string | null }
| undefined;
if (!row) return null;
// Archived rows are retirement tombstones — the file no longer
// exists (or was explicitly retired) and the row is kept for
// audit only. Refuse to resolve a target so `openWorkProduct`
// can fail cleanly instead of pointing the shell at a path that
// leads nowhere.
if (row.status === "archived") return null;
if (!row) return { kind: "error", reason: "not_found", detail: `No work_product row with id=${workProductId}` };
if (row.status === "archived") return { kind: "error", reason: "archived", detail: "Work product is archived (retirement tombstone)" };
if (row.url && row.url.length > 0) {
return { kind: "url", value: row.url };
}
if (row.provider !== "filesystem" || !row.external_id) return null;
if (row.provider !== "filesystem" || !row.external_id) {
return { kind: "error", reason: "no_target", detail: `provider=${row.provider} external_id=${row.external_id ?? "null"} url=null` };
}
const workspaceRoot = resolveWorkspaceRoot(
getDb,
@@ -377,10 +379,18 @@ export function buildDocumentMethods(
row.task_id,
row.last_touched_by_run_id ?? row.created_by_run_id,
);
if (!workspaceRoot) return null;
if (!workspaceRoot) {
return {
kind: "error",
reason: "workspace_unresolved",
detail: `workspace_id=${row.workspace_id ?? "null"} task_id=${row.task_id ?? "null"} run_id=${row.last_touched_by_run_id ?? row.created_by_run_id ?? "null"} — none led to a workspace with a local_path`,
};
}
const absolute = joinWorkspaceRelative(workspaceRoot, row.external_id);
if (!absolute) return null;
if (!absolute) {
return { kind: "error", reason: "path_escape", detail: `external_id=${row.external_id} escapes workspace root ${workspaceRoot}` };
}
return { kind: "path", value: absolute };
},