Repository navigation
Conversation
…pec 213 S0) Pins what the UI answers before the rules behind it move in the next slice: status, Location, flashes, log lines, events and history for the comment and restore routes, and the constraints of the six write forms. No src change. With TASK_BOARD_CHARACTERIZE_DIR set the tests also write the pages with ids and dates masked, so two checkouts on one vendor can be compared byte by byte.
… 213 S1) A write path other than the UI's forms must apply the same rules, so they leave the controller and the form types: TaskCommentPolicy, TaskManager::restore() and Validation\TaskConstraints. tests/Functional/Characterization from the previous commit is unchanged and passes, and the rendered pages are byte-identical. Adds what the UI does not have and the API will need, with no caller yet: TaskBoardEraser::eraseTask() (leaf-first, so PreRemove keeps every minute; refuses a task that follows a provider), TaskBoardEditor, TaskManager::archive(), TaskDependencyManager::findBetween(), TaskFormData::fromTask().
Off by default: no route, service, listener, parameter or table for a host that
changes nothing. On: stateless routes under api.prefix, GET /v2/user and /v2/team,
and what every later endpoint goes through: the request pipeline, TaskApiGuard
(level, the person's access and where, per request), the capability table and one
{err, ECODE} shape.
The bundle stores no tokens. The hooks live in Api\Hook, the authenticator is
required and every other default denies. It fails closed when the security user is
not the token's user, as access checkers read the security token, and the build
refuses a configuration that would open every board to every token.
Audit status: re-verification pending.
The HTML coverage report ran out of 512M with the API tests: main already peaks near 440M before it. The PHPUnit ceiling goes to 1G, and the API tests compile fewer kernels: one host configuration for the pipeline and the guard, and the "off equals unset" check moves to the container builder.
Spaces, folders (none), lists, tasks with ClickUp's filters and 100 per page, a task, comments, members, tags, imported custom fields and time entries. Every endpoint goes through the guard; lists hold and count only what the token may read. Each list costs the same queries at 25 and 100 tasks (9 for a list's tasks). Read access matrix: four levels by own task, another's, another project, a board outside where, a removed member, a revoked token and the plan.
…d-moves # Conflicts: # docs/CHANGELOG.md # docs/UPGRADING.md
…audit IA) IA-M1: eraseTask() handed the host no stored-file refs. After an unlink the sync's attachment rows outlive the link (GWd-M1), so the files were never named again; on SQLite the rows stayed orphaned. The refs of the subtree are read first, passed per task in TaskErasedEvent, and the rows removed. Proof test ported from the auditor; run on SQLite and PostgreSQL 16. IA-I1: TaskBoardManageController keeps $syncLimits in its place, deprecated and ignored, so positional arguments do not move in a minor release. Also takes main (2.1.1) and the 1G PHPUnit ceiling the coverage report needs.
…t IA) IA-L1: a valid token whose user the access checker refuses keeps its 401 and now counts as a failed token, so it cannot call the authenticator unthrottled. IA-L2: with tenancy the guard asks the bundle's tenant resolver for the board's tenant, as the UI does through owns(), so a permissive workspace hook opens no other tenant; a board without a tenant is not served under tenancy. IA-I4: where's board ids grant on their own, documented as intended. IA-I2, I3, I5: notes in SECURITY and API docs, no code change.
Takes main (2.1.1), IA-M1, IA-I1, IA-L1, IA-L2 and IA-I4. The tenant check of IA-L2 also applies to spaces and to the boards a space lists.
IA-M2: a task's subtasks are those the token may open; for My tasks the checklist items of its own tasks stay, whoever created them (owner's decision). IA-I7: dependencies and links leave out tasks the token may not open. IA-L3: the tag filter runs in SQL (NOWO_TB_JSON_TEXT, with api.enabled only), so pages and last_page cost what any filter costs; budget cases added. IA-I8: the boards' columns load with the tasks, so a team's tasks cost the same queries with 1 board or 10 (9); the per-board access cost is documented. IA-I9: documented that time entries without task_id rely on the host hook. Tag filter tested on SQLite, PostgreSQL 16 and MySQL 8.4.
…13 S4) Tasks (create, update, delete with tracked time kept), comments, tags, dependencies, links and checklists as tagged subtasks, through the services the UI uses. Read writes nothing; My tasks works only on its own tasks' status, dates, description, comments and checklists; custom fields answer 409 FIELD_001; a task that follows a provider answers 409 TASK_002. Writes need an API_WRITE listener (503 AUDIT_001) and creates a QUOTA_CHECK listener (403 PLAN_002). A status change moves the task through moveToColumn(), so completion, the reopen limit and their events follow; TaskManager::update() never moves it. Idempotency-Key on every write: per token, 24 hours, same request replayed, another one 409, refusals not kept, a replay checked again. The bundle's store is a DBAL table mapped only with api.enabled; a host may replace it. Write access matrix: 18 write endpoints by four levels, own and others' tasks, another project, a board outside where, a removed member, a reader, the plan, a revoked token. API tests also run on PostgreSQL 16 and MySQL 8.4.
An API comment passed no mention ids, so TASK_MENTION and the host's mention notifications never ran, while a UI comment that names someone fires them (FR-011). Each candidate written @Label in the text is now mentioned; the comment manager checks the text again, as for the UI. docs/API.md: comments' mentions, partial writes not being one transaction, and the column-less task limitation.
…(audit IA) IA-L5: the fingerprint includes the sorted query string, so one key on ?depends_on=B and ?dependency_of=C is 409 IDEMP_002, not a false replay. IA-L6: an update asks the host's active-task limit once, for a restore and a reopening together, before anything is saved (TaskManager::activeTaskRefusal()). Tags: PUT warns about tags instead of ignoring them; create and the tag endpoint apply the task form's rules (1 to 64 characters, at most 20). Endpoints that read no body warn about one. docs/API.md: what a replay re-checks, checklist item rights, forget()'s reach.
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fifth slice of spec 213: the write API with idempotency. Stacked on #71 (and #70, #69, #67); the base is
mainbecause CI runs only on PRs to it. Audit status re-verification pending.Endpoints: create, update and delete tasks; comments; tags; custom field (409
FIELD_001); dependencies and links; checklists and their items. Thin controllers overTaskApiTaskOperationsandTaskApiChecklistOperations, through the services the UI uses (FR-011):TaskManager,TaskMemberAssigner,TaskCommentManager,TaskDependencyManager,TaskBoardEraser,TaskBoardQuota, and the forms' rules (TaskConstraints).Owner decisions applied
checklist:<name>; deleting archives them.POST …/field/{id}: 409FIELD_001.DELETE /v2/task/{id}:eraseTask(),canManageBoard, time kept; provider-linked: 409TASK_002.Before any write
API_WRITEwith no listener: 503AUDIT_001. Creates with noQUOTA_CHECKlistener: 403PLAN_002(api.quota_listener_required).TaskManager::update()(contract F9)Not inherited, so not fixed: a status change goes through
moveToColumn(), andupdate()always gets the task's current column.testUpdateMovesThroughTheColumnServiceSoCompletionFollowschecks the task completes andTASK_COMPLETED/TASK_REOPENEDfire.Idempotency
Per token, 24 hours, same request (JSON with keys sorted) replayed with
Idempotency-Replayed: true; another request with the key: 409IDEMP_002; in flight: 409IDEMP_001(taken over after 5 minutes); refusals not kept; a replay goes through the pipeline and the reach check again (404). Tabletask_board_api_idempotency, mapped only withapi.enabledand the bundle's store; purge command;forget()for revoked tokens and erased people.Write access matrix
ApiWriteMatrixTest: 18 write endpoints × the four levels × own task, another's task, another project, a board outside where, a removed member, a person who may only read, the plan without the module, a revoked token. Fresh tasks per request.Gates run locally
composer test-coverage-100(1643 tests, 100%), PHPStan, cs-check;tests/Functional/Apion PostgreSQL 16 and MySQL 8.4.Not for merge until the coordinator's audit step.
Follow-ups
TASK_MENTIONand the host's notifications run.TaskManager::activeTaskRefusal()); tags warned onPUTand limited as in the task form (64 characters, 20 tags); body-less endpoints warn about a body; docs for replays, checklist rights,forget().