mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-10 12:37:55 +01:00
fix(api-catalog): mark task-tree controls board-only and name the Pause subtree page (#20252)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents read the API catalog (`server/src/routes/openapi.ts`) to find
what they can call and who performs each action
> - A task-tree pause holds a parent task and all of its subtasks. Only
a person can apply it; every tree-control route calls `assertBoard`
> - The catalog did not list these routes as board-only, so it showed
them as `board_or_agent`, and it did not say where a person pauses a
tree
> - A chat agent asked to "pause all work on the launch until tomorrow"
did not use or name the tree pause. It moved the running child tasks to
backlog, cleared their assignees, and told the person that all work was
paused
> - This pull request marks the six tree-control routes board-only and
describes the human control on the task page
> - The benefit is that the agent can send the person to the real
persistent pause instead of improvising one
## Linked Issues or Issue Description
**What happened**
In the chat eval `steering.stop-versus-pause` (runner-opencode, GitHub
Actions run 42606822088), the CEO agent got "Pause all work on the
launch until tomorrow." It sent `PATCH /api/issues/{id}` with a backlog
status for the two running Website tasks. That cleared their execution
assignments. It replied "All launch work is paused." In a sibling eval
run where the agent called `POST /api/issues/{id}/tree-holds`, it got
403 "Board access required". The catalog showed that route as callable
by agents.
**Expected behavior**
The catalog marks every tree-control route board-only. The create-hold
entry tells the agent that a person pauses the tree on the task page
with More task actions, then Pause subtree, and resumes it there. It
also says that backlog or reassignment is not a tree pause.
**Steps to reproduce**
1. Run `buildOpenApiSpec()` on master.
2. Read
`paths["/api/issues/{id}/tree-holds"].post["x-paperclip-authorization"]`.
3. It is `{ actor: "board_or_agent" }`, but the route calls
`assertBoard`.
## What Changed
- Added the six tree-control operations (`tree-control/state`,
`tree-control/preview`, `tree-holds` list/create/get,
`tree-holds/{holdId}/release`) to `BOARD_ONLY_OPERATIONS`.
- Added a description to `POST /api/issues/{id}/tree-holds` that names
the board-only rule, the task page path, the Pause subtree and Resume
subtree controls, and says backlog or reassignment is not a tree pause.
- Added an `openapi-routes.test.ts` case for the board-only
authorization and the description.
## Verification
- `pnpm exec vitest run server/src/__tests__/openapi-routes.test.ts -t
"task-tree controls"` fails before the change and passes after it.
- `server/src/__tests__/openapi-routes.test.ts` and
`server/src/services/native-runtime/runner-api.test.ts` pass (2111
tests).
- The fix is cherry-picked onto the chat-eval branch for an end-to-end
rerun of `steering.stop-versus-pause` and
`task-coordination.pause-tree`.
## Risks
- Low risk. Catalog metadata only. The routes already reject agents, so
no runtime permission changes.
- Agents now see these operations as board-only. That matches the
existing 403.
## Model Used
- Anthropic Claude Opus 5.5 (`claude-opus-5-5`), tool use and code
execution in Claude Code.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ba75b44c3c
commit
7044ff96e3
2 files changed
+27
No files matched your search
@@ -390,6 +390,26 @@ describe("openapi routes", () => {
|
||||
for (const code of ["200", "400", "401", "403", "404"]) expect(operation.responses[code]).toBeDefined();
|
||||
});
|
||||
|
||||
it("documents task-tree controls as board-only and names the human page", () => {
|
||||
const spec = buildOpenApiSpec() as any;
|
||||
const boardSecurity = [{ BoardSessionAuth: [] }, { BoardApiKeyAuth: [] }];
|
||||
for (const [path, method] of [
|
||||
["/api/issues/{id}/tree-control/state", "get"],
|
||||
["/api/issues/{id}/tree-control/preview", "post"],
|
||||
["/api/issues/{id}/tree-holds", "get"],
|
||||
["/api/issues/{id}/tree-holds", "post"],
|
||||
["/api/issues/{id}/tree-holds/{holdId}", "get"],
|
||||
["/api/issues/{id}/tree-holds/{holdId}/release", "post"],
|
||||
] as const) {
|
||||
const operation = spec.paths[path][method];
|
||||
expect(operation.security).toEqual(boardSecurity);
|
||||
expect(operation["x-paperclip-authorization"]).toEqual({ actor: "board" });
|
||||
}
|
||||
const create = spec.paths["/api/issues/{id}/tree-holds"].post.description as string;
|
||||
expect(create).toContain("Pause subtree");
|
||||
expect(create).toContain("/{prefix}/issues/{identifier}");
|
||||
});
|
||||
|
||||
it("documents owned conversation reads and exact revision access separately", () => {
|
||||
const spec = buildOpenApiSpec() as any;
|
||||
const boardSecurity = [{ BoardSessionAuth: [] }, { BoardApiKeyAuth: [] }];
|
||||
|
||||
@@ -1581,6 +1581,12 @@ const BOARD_ONLY_OPERATIONS = new Set([
|
||||
"POST /api/chat-identity-links/request-access",
|
||||
"POST /api/chat-endpoints/{endpointId}/publications/{publicationId}/resolve",
|
||||
"POST /api/chat-endpoints/{endpointId}/actions/{actionId}/resolve",
|
||||
"GET /api/issues/{id}/tree-control/state",
|
||||
"POST /api/issues/{id}/tree-control/preview",
|
||||
"GET /api/issues/{id}/tree-holds",
|
||||
"POST /api/issues/{id}/tree-holds",
|
||||
"GET /api/issues/{id}/tree-holds/{holdId}",
|
||||
"POST /api/issues/{id}/tree-holds/{holdId}/release",
|
||||
]);
|
||||
|
||||
const INSTANCE_ADMIN_OPERATIONS = new Set([
|
||||
@@ -8544,6 +8550,7 @@ registry.registerPath({
|
||||
path: "/api/issues/{id}/tree-holds",
|
||||
tags: ["issues"],
|
||||
summary: "Create an issue tree hold",
|
||||
description: "Board-only. An agent cannot pause, cancel or resume a task tree, and this denial is final for agents. A person pauses the parent task and all of its subtasks on the company-prefixed task page /{prefix}/issues/{identifier}: More task actions, then Pause subtree. The person resumes it later on the same page with Resume subtree. Link that task page. Moving tasks to backlog or changing assignees is not a tree pause.",
|
||||
request: {
|
||||
params: z.object({ id: z.string() }),
|
||||
body: jsonBody(createIssueTreeHoldSchema),
|
||||
|
||||
Reference in new issue
Block a user