mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 08:22:20 +02:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Integrations can prepare a backlog task before its assigned agent starts. > - A person can change its status or assignment during preparation. > - An unconditional promotion can replace that newer decision. > - Safe promotion needs a saved revision that includes every assignment path. > - This pull request checks that revision under the task row lock and derives policy cleanup from the locked row. > - The benefit is safe initialization recovery with the existing company, task, and run authority. ## Linked Issues or Issue Description Prerequisite #16896 has merged. This PR now targets master and contains only the conditional promotion API. The prerequisite fixed the confirmed baseline first-upload browser regression. **What happened?** A client that saves a task's initial documents and then sends an unconditional status update can override a concurrent status or assignment decision. The update validator previously dropped an expected version, so clients could not make this promotion conditional. Some direct assignment and release paths also did not advance the revision. **Expected behavior** A prepared task moves from backlog to todo only if its original status and assignment revision still matches. A stale request returns 409 before a task mutation, audit publication, or wake. Policy cleanup preserves unrelated saved settings. **Steps to reproduce** 1. Create a backlog task and save its statusVersion before preparing it. 2. Release its assignment, or change an assignee and then restore that assignee. 3. Send `{ "status": "todo", "expectedStatusVersion": <original revision> }` to its PATCH endpoint. 4. The stale promotion must return 409. A matching revision on an unchanged backlog task must succeed. Duplicate and related PR searches found no existing prepared-task promotion fix. ## What Changed - Accept an optional nonnegative expectedStatusVersion for exactly a status-only backlog-to-todo promotion. Reject extra public fields before parsing can discard them. - Check the current revision and backlog state under the issue row lock. Reject stale requests before checkout adoption or execution-control effects. - Add SQL migration 0358 to advance the revision for both assignee fields at the database boundary. Release, administrative release, direct writes, and assignment round trips now invalidate an old version. - Derive guarded policy cleanup from the locked row. Preserve review limits and protected authorization when removing a monitor. Reject derived ownership changes or review decisions. - Add schema, real HTTP/PostgreSQL, concurrency, migration replay, and policy regression coverage. Update the response type, OpenAPI, developer docs, and access map. ## Verification - Red before the initial fix: stale requests returned 200 instead of 409, validation removed the expected version, and a stale active task could adopt a checkout. - Review found two further defects. Four new release/direct-assignment tests showed unchanged revisions. The real HTTP/service test returned 422 for a valid two-field promotion after monitor cleanup. Helper tests also proved that cleanup lost valid review limits and protected authorization. All now pass after focused repairs. - Green: 124 route and policy tests, including 7 real Express/PostgreSQL cases, 37 existing route tests, and 80 policy/helper tests. The real row-lock race preserves a policy edit committed while promotion waits. - Green: all 23 database client tests, plus 9 focused assignment and replay checks. Historical migration 0227 receipt recovery cannot remove the new assignment trigger. Reapplying migration 0358 leaves existing rows unchanged and installs one trigger. Combined status/assignment writes increment once; unchanged assignees do not increment. - Earlier verification passed all 218 schema/route/service cases and the generated OpenAPI capability test. Rebased focused regressions passed all 27 selected cases. Final server TypeScript passed after all repairs; shared TypeScript passed. Migration safety/numbering, five-snapshot retention, map validation, and diff checks pass. - The baseline browser attachment case failed twice on the earlier head. Separate #16896 proved the lost-composer cause, passed 335 component tests and the real first-upload journey, and has green CI with a 5/5 review. No assertion or timeout was weakened. - Some early local checks were blocked by missing or stale sibling dependency artifacts. This worktree now has its own frozen offline install and standard SDK prerequisites. The failures and successful reruns remain in the task evidence. - Before rebasing, head `3304bb00de6ad6a248b5e046c008072ad20ac8f5` passed all CI (55 successful checks and one neutral), Greptile 5/5, and a secondary source review at 5/5 with no unresolved findings. The secondary reviewer did not execute project tests, inspect CI, or exercise a live agent journey. Earlier incomplete and must-fix assessments remain visible; both established findings are repaired above. - After #16896 merged, all five CAS commits were rebased onto master `87d9386ae637c652d1fed09d2046be1bcaeb413b`. Migration 0358 was regenerated because master added another 0357. Range comparison shows no other change to the CAS patches; inherited UI commits are absent. The original head is preserved locally. - Rebased head `1c0264e51a280b1dbf597725a7e42e72d4be1b1b` passes all 124 route/policy tests, 9 selected assignment/promotion tests, and all 24 database client/snapshot-drift tests. Database build, migration checks, generated snapshot linkage, five-snapshot retention (0354–0358), maps, and diff checks pass. Fresh-head CI and review are pending. No deployment or live agent journey was performed for this API. ## Risks - The guard supports prepared backlog promotion only. Other statuses or mixed public mutation fields return 422. A stale revision or non-backlog row returns 409. - Preserve the original version from a server that already advertises this guard before initialization. Older versions did not track every assignment path. Reconcile older or uncertain initialization instead of fetching a new revision and blindly retrying. - Migration 0358 is SQL-only and idempotent. It adds no table, column, or task backfill. Its separate trigger survives historical status-migration replay. - Existing authorization and unguarded updates remain in place. Guarded promotion does not provide a general lifecycle or review-stage control API. ## Model Used OpenAI Codex, GPT-6 family. The exact serving model ID and context window are not exposed by this session. Used tool calls, code execution, and test-first regression verification. ## 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 --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
207 lines
9.7 KiB
Markdown
207 lines
9.7 KiB
Markdown
# Task creation and lifecycle — agent access
|
|
|
|
Product map: [Task creation and lifecycle](../feature-map/tasks.md).
|
|
|
|
Review date: 2026-10-12. Source commit: `58e16dbddfc9c8d0cf60867f9d9847f0feda055b`.
|
|
Native REST transport reconciled in this change; see the
|
|
[shared transport contract](README.md#native-rest-transport).
|
|
|
|
This is a source-reviewed baseline of representative operations and known gaps.
|
|
Each capability needs operation-level review before its entire scope can be
|
|
qualified. No live agent execution is claimed. Follow the [map contract](../README.md)
|
|
for format, permission checks, evidence and maintenance.
|
|
|
|
## `create`
|
|
|
|
Review: `baseline`.
|
|
|
|
API: `present` — representative operation IDs:
|
|
|
|
- `POST /api/companies/{companyId}/issues`.
|
|
|
|
Agent access: `available` — Native create_task; REST / CLI / MCP.
|
|
|
|
Policy: Company/task visibility, assignment/change policy, active-run checkout ownership
|
|
and lifecycle rules. Ordinary Board comments preserve queued/running retries;
|
|
only a parked retry can be superseded. Explicit Stop/Interrupt retain their
|
|
existing actor and execution authority. Agent comment tools do not acquire Board
|
|
identity or cancellation authority through this behavior.
|
|
|
|
Evidence: `source-only` — [implementation](../../server/src/routes/issues.ts) at the
|
|
reviewed commit above.
|
|
|
|
Verification: `not-run` — exercise a listed permitted operation with actual agent
|
|
credentials/run context; inspect its result and applicable persistence/attribution.
|
|
Assert the relevant denied actor, owner, company or mode from Policy. Review the
|
|
remaining operations before qualifying the whole capability.
|
|
|
|
Gaps: Creation is implemented; agent/user assignment and attachments may need
|
|
narrower/different APIs.
|
|
|
|
## `organize`
|
|
|
|
`GET /api/companies/{companyId}/issues/summaries?ids=...` reads up to five exact
|
|
UUIDs and returns only navigation summaries. Normal company and task-read
|
|
permissions apply to ordinary agent REST calls; task-bridge keys are denied.
|
|
Hidden, missing, foreign and private inaccessible IDs are omitted without an
|
|
existence disclosure. No dedicated CLI/native tool is added. Embedded database
|
|
route tests cover same-company agent access and company/task-bridge denials;
|
|
this does not establish a live native `call_api` journey.
|
|
|
|
The issue-list API accepts optional `includeAgentCreatedTasks`; false restricts
|
|
independent results to human creation before search/pagination. Omission retains
|
|
existing agent/API behavior. `includeParticipatedTasks=true` requires a board user
|
|
and retains only that authenticated user's existing participation as a creation
|
|
visibility exception. Company/private-task access checks still apply. Collection
|
|
preferences and hierarchy presentation are browser state, not additional authority.
|
|
Source-reviewed behavior; live agent access verification remains separate.
|
|
|
|
All `chat_channel` origins count as bot-created for creation visibility, including
|
|
human-initiated conversations. Existing inbox participation remains independent.
|
|
|
|
Review: `baseline`.
|
|
|
|
API: `present` — representative operation IDs:
|
|
|
|
- `GET /api/companies/{companyId}/issues`.
|
|
|
|
Agent access: `available` — Native search_tasks; REST / CLI; UI grouping/navigation
|
|
state separately.
|
|
|
|
Policy: Company/task visibility, assignment/change policy, active-run checkout ownership
|
|
and lifecycle rules.
|
|
|
|
Evidence: `source-only` — [implementation](../../server/src/routes/issues.ts) at the
|
|
reviewed commit above.
|
|
|
|
Verification: `not-run` — exercise a listed permitted operation with actual agent
|
|
credentials/run context; inspect its result and applicable persistence/attribution.
|
|
Assert the relevant denied actor, owner, company or mode from Policy. Review the
|
|
remaining operations before qualifying the whole capability.
|
|
|
|
Gaps: Search/filter API exists; sorting/grouping/presentation defaults are not
|
|
necessarily shared server preferences.
|
|
|
|
## `properties`
|
|
|
|
Review: `baseline`.
|
|
|
|
Agent REST callers can promote a prepared backlog task with
|
|
`PATCH /api/issues/{id}` and exactly `{ "status": "todo", "expectedStatusVersion": N }`.
|
|
The optional nonnegative integer version comes from the current task response.
|
|
The server checks both the version and backlog state under the task row lock;
|
|
status or ownership changes, including changes back to their earlier values,
|
|
return `409` without promoting or waking the task. Mixed mutation fields return
|
|
`422`. Existing company, task, assignment and run authority still apply.
|
|
Route and real-PostgreSQL service tests cover the contract; no live agent journey
|
|
or dedicated native tool is claimed.
|
|
|
|
API: `present` — representative operation IDs:
|
|
|
|
- `PATCH /api/issues/{id}`.
|
|
- `PUT /api/issues/{id}/title`.
|
|
|
|
Agent access: `available` — Native set_task_title/reassign_task/set_dependencies and
|
|
set_task_monitor; `call_api` for REST-authorized fields; agent REST. Monitor
|
|
scheduling is a dedicated native tool, not a generic `call_api` operation.
|
|
|
|
Policy: Company/task visibility, assignment/change policy, active-run checkout ownership
|
|
and lifecycle rules. A legacy run can assign its own task to a human without
|
|
cancelling itself only with its signed run token and matching active run, agent,
|
|
company and task. The outgoing run cannot mutate its human-owned task afterward,
|
|
including through a late update already admitted before the handoff. Agent keys,
|
|
native runs, board reassignment and transfers to
|
|
another agent retain verified-stop requirements.
|
|
|
|
Native monitors require standard work mode and an agent-owned task in progress
|
|
or review. The responsible user's current permissions also apply. A malformed
|
|
request is rejected before mutation; bounded schema guidance lets the provider
|
|
correct it without exposing submitted values. Receipt replay never re-arms a
|
|
consumed schedule.
|
|
|
|
Evidence: `source-only` — [implementation](../../server/src/routes/issues.ts) at the
|
|
reviewed commit above.
|
|
|
|
Verification: `not-run` — exercise a listed permitted operation with actual agent
|
|
credentials/run context; inspect its result and applicable persistence/attribution.
|
|
Assert the relevant denied actor, owner, company or mode from Policy. Review the
|
|
remaining operations before qualifying the whole capability. Focused route
|
|
regressions in [ownership tests](../../server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts)
|
|
cover a human handoff and stop enforcement for other actors, runtimes and targets;
|
|
they use mocked services and do not claim live execution proof.
|
|
|
|
Queue and HTTP/2 sandbox callbacks admit `GET /api/issues/{id}/diagnostics/wakes`
|
|
and `POST /api/issues/{id}/monitor/check-now`. The controller still checks task
|
|
read access or monitor-management permission and assignment ownership. Recovery
|
|
resolution and scheduled-retry controls remain outside this bridge surface.
|
|
|
|
Monitor verification: submit flat scheduling fields, observe the schema rejection
|
|
and unchanged task, then retry with the nested `monitor` object and the same
|
|
idempotency key. Inspect the saved schedule and audit, yield, and verify exactly
|
|
one `issue_monitor_due` wake. [Scheduler integration coverage](../../server/src/__tests__/issue-monitor-scheduler.test.ts)
|
|
uses real PostgreSQL, the native tool authority, and the heartbeat scheduler with
|
|
a no-op process agent. It does not prove live model recovery.
|
|
|
|
Monitor deferral diagnostics: an unresolved execution-recovery hold keeps a due
|
|
schedule intact and prevents execution. Read `issue.monitor_deferred` activity
|
|
for the safe blocker cause; unexpected dispatch failures record
|
|
`issue.monitor_dispatch_failed`. Repeated identical diagnostics are throttled for
|
|
five minutes. The scheduler regression proves that a held source does not replay
|
|
and that clearing the hold permits one later wake. This is local PostgreSQL
|
|
evidence, not live provider recovery.
|
|
|
|
Gaps: Dedicated tools differ in scope and fields. Generic `call_api` forwards
|
|
status, assignment and dependency fields to existing route authorization.
|
|
|
|
## `discussion`
|
|
|
|
Review: `baseline`.
|
|
|
|
API: `present` — representative operation IDs:
|
|
|
|
- `POST /api/issues/{id}/comments`.
|
|
- `POST /api/companies/{companyId}/issues/{issueId}/attachments`.
|
|
|
|
Agent access: `available` — Native report_progress/comment_on_task/register_deliverable;
|
|
REST / CLI / MCP.
|
|
|
|
Policy: Company/task visibility, assignment/change policy, active-run checkout ownership
|
|
and lifecycle rules.
|
|
|
|
Evidence: `source-only` — [implementation](../../server/src/routes/issues.ts) at the
|
|
reviewed commit above.
|
|
|
|
Verification: `not-run` — exercise a listed permitted operation with actual agent
|
|
credentials/run context; inspect its result and applicable persistence/attribution.
|
|
Assert the relevant denied actor, owner, company or mode from Policy. Review the
|
|
remaining operations before qualifying the whole capability.
|
|
|
|
Gaps: Other-task comments and active-task progress have separate scopes. Generic
|
|
`call_api` forwards explicit reopen/resume/interrupt fields to the existing route
|
|
checks; it cannot impersonate a user.
|
|
|
|
## `completion`
|
|
|
|
Review: `baseline`.
|
|
|
|
API: `present` — representative operation IDs:
|
|
|
|
- `PATCH /api/issues/{id}`.
|
|
|
|
Agent access: `available` — Native paperclip_finish/paperclip_block through runner
|
|
protocol; agent REST / native `call_api` disposition under route permissions.
|
|
|
|
Policy: Company/task visibility, assignment/change policy, active-run checkout ownership
|
|
and lifecycle rules.
|
|
|
|
Evidence: `source-only` — [implementation](../../server/src/routes/issues.ts) at the
|
|
reviewed commit above.
|
|
|
|
Verification: `not-run` — exercise a listed permitted operation with actual agent
|
|
credentials/run context; inspect its result and applicable persistence/attribution.
|
|
Assert the relevant denied actor, owner, company or mode from Policy. Review the
|
|
remaining operations before qualifying the whole capability.
|
|
|
|
Gaps: The server governs whether a reported result becomes done/review/blocked.
|
|
Generic lifecycle PATCH uses existing REST authorization, checkout and approval rules.
|