Skip to content

fix(host): taskboard_list 校验 workspaceId 与 status/urgency 值域,消除静默假阴性 - #52

Merged
cloader merged 1 commit into
cloader:mainfrom
Ausli:fix/taskboard-list-filter-contract
Oct 18, 2026
Merged

cloader merged 1 commit into
cloader:mainfrom
Ausli:fix/taskboard-list-filter-contract

Conversation

@Ausli

@Ausli Ausli commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

现象(真实环境复现)

开工查板时把工作区路径当成 workspaceId 传进去,得到「任务 0 条:无匹配任务」——与「确实没有任务」在返回文本上完全同形,模型拿不到纠正信号:

任务 0 条:无匹配任务

同一个参数在写侧是硬的:taskboard_create({ workspaceId: <路径> }) 报 not_found: unknown workspaceId。status / urgency 拼错同理,静默返回 0 条。

根因

taskboard_list 把过滤参数当成纯字符串比较(src/host/tools.ts:348-352),同一闭包里的 workspaces、asStatus、asUrgency 却都可用(create 已在用,src/host/tools.ts:462-466)。文件第 31-32 行本来就 import 了 asStatus / asUrgency,差异是纯逻辑遗漏。

fail()(src/host/tools.ts:268)会把 ToolError 原样重抛、普通 Error 包成 invalid_input,所以两条路径的错误码天然不同。

为什么会传路径:模型知道自己会话的 cwd,但工具面里没有「我属于哪个项目」的自查询,callerWorkspace() → resolveByPath(cwd) 已存在却没暴露成工具。

解法

在 taskboard_list.execute 的过滤之前补三行校验(src/host/tools.ts:348-350,与 create 共用同一组函数):

if (a.workspaceId !== undefined && workspaces.get(a.workspaceId) === undefined) throw new ToolError(ERR.notFound, `unknown workspaceId ${a.workspaceId}`)
if (a.status !== undefined) asStatus(a.status)
if (a.urgency !== undefined) asUrgency(a.urgency)

验证

新增 tests/list-filters.spec.ts(mock deps 直调 execute,不依赖宿主),覆盖:

# 调用 期望
R1 workspaceId=<未注册 uuid> not_found
R2 workspaceId=<文件系统路径> not_found
R3 status=bogus invalid_input,消息含合法状态清单
R4 urgency=bogus invalid_input,消息含 urgent, normal, relaxed
R5 空参 全量不变
R6 合法 status/urgency/真实 id 过滤结果不变
R6b 已注册但无任务的项目 仍返回 0 条,不报错
R7 任意非法输入 台账 revision 不变(只读拒绝)
  • npx tsc --noEmit 通过
  • npx vitest run 全量通过:39 files / 508 tests
  • 反证:对未打补丁的 src/host/tools.ts 跑同一 spec,5 项失败(R1–R4、R7),R5/R6/R6b 通过——证明测的是真缺陷,且合法路径不受影响。

lib/ 已用 npm run build 重建(作者在 #50 说过 lib 供 GitHub 安装使用)。

刻意不动的地方

  • 合法但属于其他项目的 workspaceId 仍返回 0 条——这是正确行为,不收紧。
  • 空参(undefined)继续全量,不在缺失时报错。
  • 客户端同源问题(src/client/controller.ts 的 loadView() 从 localStorage 恢复失效 filters.workspaceId 会把卡片滤空,见 [bug] 0.8.2 看板与项目下拉同时空白,响应体被截断/为空却报错误报为 "taskboard: HTTP 200" #42 附注)本次未处理,可作为后续单独一条。
  • defineTool 参数 schema 没有 enum 能力,值域校验只能手写进 execute;这是其余过滤参数共同的坑,本次只补已确认的同源两处。
  • 未改 changelog / README 的 release 文案,按本仓库惯例由维护者统一整理。

是否该报错,还是该给「自查询工具」

status / urgency 属于纯 bug(拼错就该报错)。workspaceId 那条涉及契约判断:另一种解法是暴露 callerWorkspace() 成只读工具,让模型直接拿到自己的工作区 id,而不是靠报错教育。我倾向两者都要:报错拦住静默假阴性,自查询降低猜的成本。如果维护者认为后者才是正解,本 PR 可以只保留 status / urgency 两条。

…ing 0

taskboard_list compared workspaceId/status/urgency as plain strings, so an unknown workspace id or a misspelled enum silently filtered to zero rows — indistinguishable from a genuinely empty project and a false negative for the check-the-board-first discipline.

An unregistered workspaceId now throws not_found (same message as taskboard_create) and status/urgency go through asStatus/asUrgency, whose plain Errors the existing fail() wraps as invalid_input. A legitimately empty project and omitted filters keep returning rows unchanged. lib/ rebuilt from src.
@cloader
cloader merged commit b8e917e into cloader:main Oct 18, 2026
1 check passed
cloader added a commit that referenced this pull request Oct 23, 2026
lib/ moves back to build output (gitignored, regenerated by npm run build); prepublishOnly now builds and tests so the npm tarball always ships fresh lib. Changelog gains the 0.9.3 entry (#52 stays in 0.9.2 — v0.9.2 tag already exists), READMEs add 0.9.3, and the version bumps to 0.9.3.
cloader pushed a commit that referenced this pull request Oct 25, 2026
#52 made an unknown `workspaceId` fail loudly instead of silently matching
nothing, but a bare `unknown workspaceId <x>` still strands the caller: the
registry keys projects by uuid, no tool answers "which project am I in", and the
error just repeats back the value that was already known to be wrong.

Name the expected shape, then hand back the calling session's own project
(`callerWorkspace()` already resolves cwd) and, when that is unavailable (cwd in
a subdirectory or in an unregistered directory), the registered projects — capped
at 8 with a `+N more` tail. That is enough to correct in one round trip.

Message-only: the rejection itself, its error code, and the read-only guarantee
are untouched, and every existing assertion in tests/list-filters.spec.ts still
holds as written.

tests: R8/R9/R10 pin the detail (own project, unaffiliated fallback, create).
@Ausli
Ausli deleted the fix/taskboard-list-filter-contract branch October 25, 2026 09:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants