Hide archived work_products from the visible surface
Retirement set `status = "archived"` but none of the downstream
readers honoured it. The snapshot pulled every row, the task-scoped
listing returned every row, the DocumentCenter rendered every row,
and `resolveWorkProductTarget` would happily hand a deleted file's
path to `shell.openPath`. The promise that archived rows "stop
surfacing" was only true inside the projector's own doc comment.
Pin the visibility gate at the three DB read paths:
- `listSnapshot().workProducts` — the canonical renderer source —
excludes archived;
- `listWorkProductsForTask` (task API + thread view) excludes
archived;
- `listWorkProductsForCompany` excludes archived.
`resolveWorkProductTarget` now refuses to resolve an archived row, so
`openWorkProduct` fails with `WORK_PRODUCT_NOT_FOUND` instead of
routing the OS shell at a dead path. Archived rows stay in the table
as retirement tombstones — operators still have the audit trail;
they just no longer leak into the operator UI.
This commit is contained in:
1 parent
88dfe1a01f
commit
f0c4a66cc1
2 files changed
+23
-9
No files matched your search
@@ -353,11 +353,17 @@ export function buildDocumentMethods(
|
||||
*/
|
||||
resolveWorkProductTarget(workProductId: string, companyId: string): { kind: "url" | "path"; value: string } | null {
|
||||
const row = getDb().prepare(
|
||||
"SELECT provider, external_id, url, task_id, created_by_run_id, last_touched_by_run_id, workspace_id FROM work_products WHERE id = ? AND company_id = ?",
|
||||
"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; task_id: string | null; created_by_run_id: string | null; last_touched_by_run_id: string | null; workspace_id: string | null }
|
||||
| { 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.url && row.url.length > 0) {
|
||||
return { kind: "url", value: row.url };
|
||||
@@ -378,6 +384,10 @@ export function buildDocumentMethods(
|
||||
return { kind: "path", value: absolute };
|
||||
},
|
||||
|
||||
// Task-scoped visible listing. Archived rows stay in the table as
|
||||
// retirement tombstones; the task API and the thread view only
|
||||
// surface currently-active deliverables so a deleted file stops
|
||||
// appearing in the task's artifact strip.
|
||||
listWorkProductsForTask(taskId: string, companyId: string) {
|
||||
return readAll<{
|
||||
id: string; company_id: string; project_id: string | null; task_id: string;
|
||||
@@ -388,7 +398,7 @@ export function buildDocumentMethods(
|
||||
created_by_run_id: string | null; last_touched_by_run_id: string | null;
|
||||
created_at: string; updated_at: string;
|
||||
}>(
|
||||
"SELECT * FROM work_products WHERE task_id = ? AND company_id = ? ORDER BY is_primary DESC, updated_at DESC",
|
||||
"SELECT * FROM work_products WHERE task_id = ? AND company_id = ? AND status <> 'archived' ORDER BY is_primary DESC, updated_at DESC",
|
||||
taskId, companyId,
|
||||
).map((r) => ({
|
||||
id: r.id, companyId: r.company_id, projectId: r.project_id, taskId: r.task_id,
|
||||
@@ -405,10 +415,10 @@ export function buildDocumentMethods(
|
||||
/**
|
||||
* Company-scoped work-product listing, newest first. Used by the
|
||||
* renderer snapshot so the DocumentCenter can surface filesystem
|
||||
* artifacts (type = "file") the agents produce via bash/write/edit
|
||||
* — these are the deliverables operators expect to see alongside
|
||||
* prose documents, so the page stops looking empty when every
|
||||
* artifact of the night lives in `work_products`.
|
||||
* artifacts (type = "file") the agents produce via bash/write/edit.
|
||||
* Archived rows are excluded so retired files stop showing in the
|
||||
* operator UI; callers that need the full audit trail go through
|
||||
* `listArchivedWorkProductsForCompany`.
|
||||
*/
|
||||
listWorkProductsForCompany(companyId: string, limit = 500) {
|
||||
return readAll<{
|
||||
@@ -420,7 +430,7 @@ export function buildDocumentMethods(
|
||||
created_by_run_id: string | null; last_touched_by_run_id: string | null;
|
||||
created_at: string; updated_at: string;
|
||||
}>(
|
||||
"SELECT * FROM work_products WHERE company_id = ? ORDER BY updated_at DESC LIMIT ?",
|
||||
"SELECT * FROM work_products WHERE company_id = ? AND status <> 'archived' ORDER BY updated_at DESC LIMIT ?",
|
||||
companyId, limit,
|
||||
).map((r) => ({
|
||||
id: r.id, companyId: r.company_id, projectId: r.project_id, taskId: r.task_id,
|
||||
|
||||
@@ -129,8 +129,12 @@ export function buildSnapshotMethods(
|
||||
goalId: r.goal_id, parentDocId: r.parent_doc_id, version: r.version, status: r.status,
|
||||
tagsJson: r.tags_json, latestRevisionId: r.latest_revision_id ?? null, createdAt: r.created_at, updatedAt: r.updated_at,
|
||||
})),
|
||||
// Archived rows stay in the table as tombstones for audit but
|
||||
// never surface as deliverables — the DocumentCenter and every
|
||||
// downstream consumer read this snapshot, so the filter is
|
||||
// pinned here as the canonical visibility gate.
|
||||
workProducts: readAll<SqliteRow>(
|
||||
`select id, company_id, project_id, task_id, workspace_id, type, provider, external_id, title, url, status, review_state, is_primary, summary, metadata_json, created_by_run_id, last_touched_by_run_id, created_at, updated_at from work_products${companyWhere} order by updated_at desc limit 500`,
|
||||
`select id, company_id, project_id, task_id, workspace_id, type, provider, external_id, title, url, status, review_state, is_primary, summary, metadata_json, created_by_run_id, last_touched_by_run_id, created_at, updated_at from work_products${companyWhere}${companyWhere ? " and" : " where"} status <> 'archived' order by updated_at desc limit 500`,
|
||||
...companyArgs,
|
||||
).map((r) => ({
|
||||
id: r.id, companyId: r.company_id, projectId: r.project_id, taskId: r.task_id,
|
||||
|
||||
Reference in new issue
Block a user