docs(plans): Vikunja probe record; lead decision 51
Researcher ran P1-P7 on a scratch Vikunja 2.7.0. Scope names in addendum A are wrong, polling on updated misses column moves and deletions, and If-Match isn't enforced. Decision 51 rules on each. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
# Vikunja 2.7.0 probes P1 to P7
|
||||
|
||||
Date: 2026-10-04. Seat: Researcher. Assignment: Sage, under lead decision 49 (A4).
|
||||
Scope: probes P1 to P7 from section 9 of `agents/darkwing/work/slice1-data-model-addendum-2026-10-04.md`, run against a scratch Vikunja. P8 (broker push) is not part of this run.
|
||||
|
||||
## Summary
|
||||
|
||||
| Probe | Result |
|
||||
|---|---|
|
||||
| P1 | Bot create: `POST /api/v2/user/bots {"username":"bot-…","name":"…"}` gives 201. Project share: `POST /api/v2/projects/{id}/users {"username","permission"}` gives 201. Permission update is `PUT`, not `PATCH`. |
|
||||
| P2 | `expires_at` in the past is accepted at creation (201). The token is refused at use (401). A missing `expires_at` is 422. |
|
||||
| P3 | `PATCH` with `bucket_id` returns 200 and echoes the value, but the task does not move. The move is `PUT /projects/{p}/views/{v}/buckets/{b}/tasks {"task_id"}`, scope `projects.views_buckets_tasks`. A move between non-done buckets does not bump `updated`. |
|
||||
| P4 | `PATCH` merges. `PUT` replaces and clears omitted fields, including labels and assignees. `labels` inside a `PATCH` body replaces the label set. |
|
||||
| P5 | Adding or removing one label bumps `updated`. A comment create or delete bumps it. Bulk label set does not bump it. Assignee add and remove bump it. |
|
||||
| P6 | Lists, single GET and by-index never return a soft-deleted task. Delete does not bump `updated`, so a poll on `updated` cannot see a deletion. |
|
||||
| P7 | A coder token refused on task create gets 401 with a v1-style body (`code` 11), the same status and body as an expired token. A project ACL refusal is 403 with an RFC 9457 body. |
|
||||
|
||||
Three results change the addendum as written: the bucket-move scope and route (P3), the missing `updated` bump on moves and deletes (P3, P6), and the 401-for-scope-refusal status (P7). Details and addendum corrections are in the section after the probes.
|
||||
|
||||
## Environment
|
||||
|
||||
- Image: `vikunja/vikunja:2.7.0`, pinned by digest `vikunja/vikunja@sha256:e2204a1c1c6a81e833c2b3a5442be182ca2335b54c2e7e37578cc3fe12a27cfc` (image id `sha256:21b04bd954a2c88f83c180d4d7dd73d3ba6f581457fd934c978b45a12943e7fb`). `/api/v1/info` reported `v2.7.0`.
|
||||
- SQLite, container name `rsrch-vikunja-probe`, port `127.0.0.1:34567` to container 3456, run as `--user 1000:1000`.
|
||||
- Data directory `/tmp/rsrch-vikunja-data` (mode 700), bind-mounted. No named volumes. No licence key. `VIKUNJA_WEBHOOKS_ENABLED=false`, registration disabled.
|
||||
- Scratch owner `rsrchowner` (id 1), created through the CLI. Bot `bot-coder` (id 2), owned by the owner. Project id 2 (`Probe project`), kanban view id 8 with buckets To-Do 4, Doing 5, Done 6.
|
||||
- Token values were never printed. Every output below was filtered or read from files kept only in the scratch directory. Tokens that worked: control token (200 on `GET /tasks`), 8-second token (200 before expiry), coder token (read, PATCH, comment create), `move1` token (bucket move). A grep of the container log for `tk_` plus 40 hex found 0 matches.
|
||||
|
||||
## Commands
|
||||
|
||||
Setup (the first start failed because `chmod 600 $D/*` also stripped the execute bit from `db/` and `files/`; fixed with `chmod 700` and a restart):
|
||||
|
||||
```
|
||||
docker pull vikunja/vikunja:2.7.0
|
||||
IMG=vikunja/vikunja@sha256:e2204a1c1c6a81e833c2b3a5442be182ca2335b54c2e7e37578cc3fe12a27cfc
|
||||
D=/tmp/rsrch-vikunja-data; mkdir -p $D/db $D/files; chmod 700 $D $D/db $D/files
|
||||
openssl rand -hex 32 > $D/svc.secret; openssl rand -hex 12 > $D/owner.pw; chmod 600 $D/svc.secret $D/owner.pw
|
||||
docker run -d --name rsrch-vikunja-probe --user 1000:1000 -p 127.0.0.1:34567:3456 \
|
||||
-v $D/db:/db -v $D/files:/app/vikunja/files \
|
||||
-e VIKUNJA_SERVICE_PUBLICURL=http://127.0.0.1:34567/ -e VIKUNJA_SERVICE_SECRET=$(cat $D/svc.secret) \
|
||||
-e VIKUNJA_DATABASE_TYPE=sqlite -e VIKUNJA_DATABASE_PATH=/db/vikunja.db \
|
||||
-e VIKUNJA_WEBHOOKS_ENABLED=false -e VIKUNJA_SERVICE_ENABLEREGISTRATION=false $IMG
|
||||
docker exec rsrch-vikunja-probe /app/vikunja/vikunja user create -u rsrchowner -e [email protected] -p "$(cat $D/owner.pw)"
|
||||
curl -s -X POST $B/api/v2/login -H 'Content-Type: application/json' -d '{"username":"rsrchowner","password":"<from owner.pw>"}' # JWT stored in $D/jwt
|
||||
curl -s $B/api/v2/openapi.json > $D/openapi.json
|
||||
```
|
||||
|
||||
Helper used for every call (`B=http://127.0.0.1:34567/api/v2`; the auth file is the owner JWT unless a token file is named):
|
||||
|
||||
```
|
||||
api(){ local m=$1 p=$2 b=${3:-} ct=${4:-application/json} af=${5:-$D/jwt}
|
||||
curl -s -X $m "$B$p" -H "Authorization: Bearer $(cat $af)" -H "Content-Type: $ct" ${b:+-d "$b"} -w '\n__HTTP %{http_code}\n'; }
|
||||
```
|
||||
|
||||
`ct` is `application/merge-patch+json` for the PATCH calls unless the probe says otherwise. Between timing-sensitive calls I used `sleep 2`, because `updated` has one-second resolution (see below).
|
||||
|
||||
## P1. Request bodies for bot create and project share
|
||||
|
||||
Settles: report 3.4 item 1 (exact v2 bodies). Addendum section 3.
|
||||
|
||||
| Call | Request | Status | Response fields |
|
||||
|---|---|---|---|
|
||||
| Create bot | `POST /api/v2/user/bots` `{"username":"bot-coder","name":"Coder bot"}` | 201 | `id` 2, `username`, `name`, `bot_owner_id` 1, `status` 0, `created`, `updated` |
|
||||
| Create bot, no prefix | same route, `{"username":"coderx","name":"x"}` | 400 | `code` 1034, "Bot usernames must start with the 'bot-' prefix." |
|
||||
| Create project | `POST /api/v2/projects` `{"title":"Probe project"}` | 201 | `id` 2; 4 default views (list, gantt, table, kanban) |
|
||||
| Share with bot | `POST /api/v2/projects/2/users` `{"username":"bot-coder","permission":1}` | 201 | `id`, `username`, `permission` 1, `created`, `updated` |
|
||||
| List shares | `GET /api/v2/projects/2/users` | 200 | pagination envelope `items,total,page,per_page,total_pages`; each item has `id`, `username`, `bot_owner_id`, `permission` |
|
||||
| Change permission | `PUT /api/v2/projects/2/users/bot-coder` `{"permission":0}` | 200 | `PATCH` on the same route returned 405; the spec lists only `delete` and `put` |
|
||||
| Mint token for bot | `POST /api/v2/tokens` `{"title","owner_id":2,"expires_at","permissions":{group:[verbs]}}` | 201 | `id`, `token` (shown once), `permissions`, `expires_at`, `owner_id` 2 |
|
||||
|
||||
Permission values from the spec: 0 read, 1 read and write, 2 admin. The bot must be shared to a project before its token sees that project's tasks (a read-only share gave 403 on task PATCH; see P7).
|
||||
|
||||
Addendum correction: the example scope in addendum section 2 names `projects_buckets` and `projects_views_tasks: ["update"]`. Neither exists. `POST /tokens` rejects an unknown group or verb at mint time with 400, `code` 14002 (checked for `projects_buckets`, `tasks:["bogus"]` and `projects_views_tasks:["update"]`). `projects_views_tasks` has only `read_all`. So a wrong scope file fails loudly when the broker mints.
|
||||
|
||||
## P2. Token with `expires_at` in the past
|
||||
|
||||
Settles: report 3.4 item 3 (past `expires_at`). Item 2 (maximum lifetime) only partly: 2030-01-01, about 3 years 3 months ahead, was accepted. I did not test further out.
|
||||
|
||||
| Request | Status | Notes |
|
||||
|---|---|---|
|
||||
| `POST /tokens` `{"title":"p2-past","owner_id":2,"expires_at":"2020-01-01T00:00:00Z","permissions":{"tasks":["read_all"]}}` | 201 | Returned the token and `expires_at` 2020-01-01. No warning. |
|
||||
| Same, without `owner_id` (owner's own token) | 201 | `owner_id` 1. Same behaviour. |
|
||||
| Same, `expires_at` omitted | 422 | `errors[0]` location `body.expires_at`, "non zero value required" |
|
||||
| `GET /tasks`, `GET /projects`, `GET /token/test` with the past token | 401 each | Body `{"code":11,"message":"missing, malformed, expired or otherwise invalid token provided"}` |
|
||||
| Control: same permissions, `expires_at` 2030-01-01, `GET /tasks` | 200 | Empty list, so the 401 above is the expiry and not the scope. |
|
||||
| Live expiry: token with `expires_at` now+8 s | 200 before, 401 after | Expiry is enforced at use, to the second. |
|
||||
|
||||
Consequence: the broker must check `expires_at` itself after minting. Vikunja will hand back an already-dead token without complaint.
|
||||
|
||||
## P3. `bucket_id` in PATCH versus the view route
|
||||
|
||||
Settles: addendum section 2 (the `bucket_id` question) and the scope for a bucket move.
|
||||
|
||||
Findings:
|
||||
- `PATCH /tasks/3` `{"bucket_id":5}` (merge-patch) returns 200 and the response body shows `bucket_id` 5. A fresh `GET /tasks/3` shows `bucket_id` 0, and `GET /projects/2/views/8/buckets/tasks` still lists the task under To-Do. The task did not move. The field is accepted and ignored.
|
||||
- `GET /tasks/{id}` and `GET /projects/{p}/views/{v}/tasks` return `bucket_id` 0 for every task I created through the API, including tasks that sit in To-Do. Bucket membership is readable through `GET /projects/{p}/views/{v}/buckets/tasks` (items are buckets, each with `count` and `tasks`; `tasks` is `null` when empty).
|
||||
- The move that works: `PUT /api/v2/projects/2/views/8/buckets/5/tasks` `{"task_id":3}` returns 200 with `bucket_id`, `bucket`, `task_id`, `project_view_id`, `task`. After it, the bucket listing shows the task in Doing (count 1).
|
||||
- Scope for the move: group `projects`, verb `views_buckets_tasks` (route table: `POST /api/v1/projects/:project/views/:view/buckets/:bucket/tasks`). A token with only that verb plus `projects.read_one` and `tasks.read_one` moved a task (200). A token with `projects.views_buckets_put` and `projects_views_tasks.read_all` got 401. The coder token from P7, which lacks `views_buckets_tasks`, got 401 on the move.
|
||||
- Move into the Done bucket (6) set `done` true and `done_at`, and bumped `updated` (20:00:15). Move out of Done to Doing (5) set `done` false and bumped `updated` (20:00:17).
|
||||
- Moves between To-Do and Doing did not bump `updated`. Two runs of To-Do to Doing to To-Do on task 1, with 2-second sleeps, left `updated` unchanged each time (`2026-10-04T19:59:27Z` before, after and after again). Task 3's first move to Doing also left `updated` at 20:00:04.
|
||||
|
||||
Consequence: the adapter must use the view route for state changes, the token needs `projects.views_buckets_tasks`, and a poll on `updated` does not see a plain bucket move. The adapter needs a second read (the view's `buckets/tasks` listing) to detect state changes by people, or a periodic full reconcile.
|
||||
|
||||
## P4. PATCH merge versus replace
|
||||
|
||||
Settles: addendum section 6 (PATCH-only writes).
|
||||
|
||||
Fresh task 3 created with description, priority 3, `due_date`, `percent_done` 0.5, `hex_color`, one label and one assignee. Calls on `/tasks/3`:
|
||||
|
||||
| Request | Status | Result |
|
||||
|---|---|---|
|
||||
| `PATCH` merge-patch `{"title":"C renamed"}` | 200 | Only the title changed. Description, priority, due date, percent done, colour, label and assignee all kept. This is a merge. |
|
||||
| `PATCH` with `Content-Type: application/json` `{"title":"C renamed2"}` | 200 | Same result. The spec lists `application/merge-patch+json`, `application/json-patch+json` and `application/merge-patch+shorthand`, but plain JSON is accepted. |
|
||||
| `PATCH` `[{"op":"replace","path":"/title","value":"C jsonpatch"}]`, `application/json-patch+json` | 200 | Works. |
|
||||
| `PATCH` `{"description":null}` | 200 | Description became `""`. Null clears. |
|
||||
| `PATCH` `{"priority":0}` | 200 | Priority became 0. A zero value is written, not skipped. |
|
||||
| `PATCH` `{"done":true}`, then `{"done":false}` | 200 | `done_at` set, then reset to `0001-01-01T00:00:00Z`. |
|
||||
| `PATCH` `{"labels":[]}` (task 2) | 200 | The task's labels were cleared. A `labels` key in a PATCH body replaces the label set. |
|
||||
| `PATCH` `{"bogus":1}` | 422 | `errors[0]` location `body.bogus`, "unexpected property". The error body echoes the whole task in `value`. |
|
||||
| `PUT /tasks/2` `{"title":"B put"}` | 200 | Replace. `due_date`, `percent_done`, `hex_color`, labels and assignees were all cleared. |
|
||||
|
||||
Consequence: the adapter's "PATCH only owned fields" rule is sound, provided it never sends `labels` or `assignees` in a PATCH unless it means to replace them, and never calls `PUT /tasks/{id}`. Unknown fields are refused with 422, so a typo in a field name fails loudly.
|
||||
|
||||
## P5. Does a label change or a comment bump `updated`?
|
||||
|
||||
Settles: addendum section 5, lines 263 and 265 (both marked unverified).
|
||||
|
||||
`updated` is one-second resolution in responses (`2026-10-04T20:00:40Z`, no fraction). I put `sleep 2` between each call and read `GET /tasks/1` after each. Starting `updated` 19:59:27.
|
||||
|
||||
| Action | Status | `updated` after | Bumped? |
|
||||
|---|---|---|---|
|
||||
| `POST /tasks/1/labels` `{"label_id":1}` | 201 | 20:00:40 | yes |
|
||||
| `POST /tasks/1/labels` `{"label_id":2}` | 201 | 20:00:42 | yes |
|
||||
| `DELETE /tasks/1/labels/2` | 204 | 20:00:44 | yes |
|
||||
| `PUT /tasks/1/labels/bulk` `{"labels":[{"id":2}]}` | 200 | 20:00:44 | no |
|
||||
| `POST /tasks/1/comments` `{"comment":"owner note"}` | 201 | 20:00:48 | yes |
|
||||
| `POST /tasks/1/comments` as the coder token | 201 | 20:00:50 | yes |
|
||||
| `POST /tasks/1/assignees` `{"user_id":2}` | 201 | 20:00:52 | yes |
|
||||
| `PATCH /tasks/1` `{"title":"Seed A v2"}` (control) | 200 | 20:00:54 | yes |
|
||||
| Bulk labels `[1]`, `[1,2]`, `[]`, one after the other | 200 each | 20:00:54 | no, three times; the label sets did change (`["lbl-one"]`, `["lbl-one","lbl-two"]`, `[]`) |
|
||||
| `DELETE /tasks/1/assignees/2` | 204 | 20:01:10 | yes |
|
||||
| `DELETE /tasks/1/comments/1` | 204 | 20:01:12 | yes |
|
||||
|
||||
Consequence: single-label, comment, and assignee changes show up in an `updated` cursor. A bulk label set does not. If anything in Mosaic or a human's client uses the bulk route, the poll misses it until the next write to the task. Also noted: `DELETE` of a label that is not on the task returned 403, not 404.
|
||||
|
||||
Comment list shape: `GET /tasks/{id}/comments` returns the pagination envelope; items have `id`, `comment`, `author`, `created`, `updated`.
|
||||
|
||||
## P6. Are soft-deleted tasks returned by lists?
|
||||
|
||||
Settles: report 3.4 item 4 (polling and soft-deleted tasks).
|
||||
|
||||
Task 2 was deleted with `DELETE /tasks/2` (204). Before the delete, the project list returned ids 1, 2, 3.
|
||||
|
||||
| Read | Result |
|
||||
|---|---|
|
||||
| `GET /tasks/2` | 404, `code` 4002 "This task does not exist" |
|
||||
| `GET /projects/2/tasks` | ids 1 and 3, `total` 2 |
|
||||
| `GET /tasks` | ids 1 and 3 |
|
||||
| `GET /projects/2/views/5/tasks` (list view) | ids 3 and 1 |
|
||||
| `GET /projects/2/views/8/buckets/tasks` (kanban) | ids 1 and 3 |
|
||||
| `GET /projects/2/tasks/by-index/2` | 404, `code` 4002 |
|
||||
| `filter=deleted_at > 2000-01-01` | 400, `code` 4016 "The task field 'deleted_at' is invalid." |
|
||||
| `expand=deleted` | 422; allowed values are `subtasks, buckets, reactions, comments, comment_count, time_entries_count, is_unread` |
|
||||
| `filter=updated > 2026-10-04T19:59:00Z` after the delete | ids 1 and 3 only |
|
||||
|
||||
SQLite check, read-only (`sqlite3 -readonly db/vikunja.db "select id,title,deleted_at,updated from tasks"`): row 2 has `deleted_at` 2026-10-04 20:01:21 and `updated` 2026-10-04 19:59:41. The row is kept (soft delete). The delete did not touch `updated`.
|
||||
|
||||
Consequence: no list, filter, or expand option returns a soft-deleted task, and a delete leaves no trace on an `updated` cursor. The adapter cannot learn about a deletion by polling `updated`. It must compare the set of ids it holds against the full id list for the project (a periodic reconcile), or use events. Report 3.4 item 4 is settled: polling does not return soft-deleted tasks.
|
||||
|
||||
## P7. Scope probe: coder token refused on task create
|
||||
|
||||
Settles: addendum section 3 (scope probe) and the refusal's status code.
|
||||
|
||||
Coder token for bot 2: `projects: [read_one, read_all]`, `tasks: [read_all, read_one, update]`, `tasks_comments: [create, read_all]`, `tasks_labels: [create, delete, read_all]`, `labels: [read_all]`; `expires_at` 2030-01-01. Project 2 shared with the bot at permission 1.
|
||||
|
||||
| Request with the coder token | Status | Body |
|
||||
|---|---|---|
|
||||
| `POST /api/v2/projects/2/tasks` `{"title":"should be refused"}` | 401 | `{"code":11,"message":"missing, malformed, expired or otherwise invalid token provided"}` |
|
||||
| `DELETE /tasks/1` | 401 | same |
|
||||
| `POST /projects` | 401 | same |
|
||||
| `PUT /api/v1/projects/2/tasks` | 401 | not recorded |
|
||||
| `PUT /projects/2/views/8/buckets/4/tasks` (no `views_buckets_tasks`) | 401 | not recorded |
|
||||
| `GET /tasks/1` | 200 | in scope |
|
||||
| `PATCH /tasks/1` (read-write share) | 200 | in scope |
|
||||
|
||||
ACL contrast: with the share set to permission 0 (`PUT /projects/2/users/bot-coder {"permission":0}`), the same coder token's `PATCH /tasks/1` got 403 with an RFC 9457 body (`title` "Forbidden", `status` 403). After restoring permission 1, PATCH returned 200.
|
||||
|
||||
Consequences:
|
||||
- A scope refusal is 401 with a v1-style body, not 403 and not RFC 9457. It is the same status and body as an expired or invalid token. The status alone cannot tell the broker "token expired" from "scope refuses this verb". To tell them apart, send a verb the token is known to hold with the same token (a `GET /tasks`) and compare.
|
||||
- A project ACL refusal is 403 with an RFC 9457 body. 401 means token or scope; 403 means the share.
|
||||
- The scope probe passes: a coder token without `tasks.create` cannot create tasks.
|
||||
- On `/api/v2` routes the auth middleware answers with the old error shape. An adapter that parses RFC 9457 bodies only will fail to parse a 401.
|
||||
|
||||
## Extras found while running (not on the P-list)
|
||||
|
||||
- ETag: `GET /api/v2/tasks/{id}` returns `Etag: "1791144072000000000-2"` and `Cache-Control: no-store`. The first number is the task's `updated` as Unix nanoseconds: 1791144072 decodes to 2026-10-04T20:01:12Z, the `updated` that task 1 had after the comment delete in P5, and the ETag read after the later PUT decoded to 20:01:57Z, the time of that PUT. The meaning of the `-2` suffix is unverified (it was `-2` on both reads). `If-None-Match` with the current value returned 304. After a PATCH, the same value returned 200.
|
||||
- `If-Match` is not enforced on writes. `PATCH /tasks/1` with a stale `If-Match`, and with `If-Match: "bogus"`, returned 200. `PUT /tasks/1` with `If-Match: "bogus"` returned 200. This tested one task on one version only. Addendum section 6's compare-before-write (read, compare `updated` and digest, then write) is the only conflict check available. It leaves a window between the read and the write.
|
||||
- Task creation response shows `bucket_id` 0. Tasks land in the default bucket (To-Do, id 4) of the kanban view with no `bucket_id` supplied.
|
||||
- `updated` on the wire has one-second resolution, so two writes in one second share a timestamp. That supports the addendum's `{T, seen}` cursor with `updated >= T`.
|
||||
|
||||
## What this changes in the addendum
|
||||
|
||||
1. Section 2 example scope: replace `projects_buckets` and `projects_views_tasks: ["update"]` with `projects: ["views_buckets_tasks", …]` for roles that move tasks. Unknown groups fail at mint with 400 `14002`.
|
||||
2. Section 5 (sync): a poll on `updated` misses (a) bucket moves between non-done buckets, (b) bulk label sets, and (c) deletions. Add a periodic reconcile: list all task ids, list bucket membership via `buckets/tasks`, and compare. Comment and single-label changes are visible.
|
||||
3. Section 6 (concurrency): there is no server-side precondition on writes. Keep compare-before-write and document the window.
|
||||
4. Section 3 (scope probe): treat 401 as "token or scope", 403 as "share". Identify scope refusal by a control request, not by status.
|
||||
5. Section 4/broker: after minting, check `expires_at` yourself.
|
||||
|
||||
## Report 3.4 status after these probes
|
||||
|
||||
| 3.4 item | Status |
|
||||
|---|---|
|
||||
| Exact v2 bodies for bot create and project share | Settled (P1) |
|
||||
| Maximum token lifetime | Partly: 2030-01-01 accepted; further out untested |
|
||||
| Past `expires_at` rejected at creation | Settled: not rejected (P2) |
|
||||
| Polling returns soft-deleted tasks | Settled: it does not (P6) |
|
||||
| PKCE option, external bearer JWT, headless owner JWT with OIDC-only, Gitea 2FA basic auth, AGPL, Pocket ID governance, `vikunja_groups` claim | Not touched by these probes; still unverified |
|
||||
|
||||
## Unverified
|
||||
|
||||
- Meaning of the ETag suffix, and whether `If-Match` is enforced in any other case (different route, weak ETag form).
|
||||
- Token lifetime beyond 2030-01-01.
|
||||
- Whether `updated` bumps on a bucket move made through the web UI or the v1 route; I used only the v2 route.
|
||||
- The 401 bodies of `PUT /api/v1/projects/2/tasks` and the failed move were recorded as status only.
|
||||
- One scratch instance, one owner, small data (three tasks). No load, no concurrency, no restart persistence.
|
||||
|
||||
## Cleanup
|
||||
|
||||
- `docker rm -f -v rsrch-vikunja-probe`, then `rm -rf /tmp/rsrch-vikunja-data` (database, files, secrets, JWT, token files, spec copy).
|
||||
- Confirmed afterwards: `docker ps -a --filter name=rsrch-vikunja-` lists 0 containers, `/tmp/rsrch-vikunja-data` does not exist, nothing listens on port 34567. The run used bind mounts, so it created no Docker volume.
|
||||
- Left in place: the pulled image `vikunja/vikunja:2.7.0` in the local Docker cache (remove with `docker rmi vikunja/vikunja:2.7.0` if wanted), and `/tmp/rsrch/` from the first assignment (source clones and doc extracts, no secrets). A file `/tmp/vikunja_token.py` also exists; I did not create or read it and did not touch it.
|
||||
- Nothing in the repository changed except this file. No commits, pushes, Gitea posts, or messages to other seats.
|
||||
@@ -477,3 +477,4 @@ are never rewritten or removed; corrections are new entries.
|
||||
2026-10-04T20:25:29Z | Dewey (T3 Claude Code) | row 5 CHAT-03 I1 round-1 rework | Darkwing B1-B2 and Filbert B1-B6 fixed; conversation 152/152, other suites green; 60 of 61 mutants killed (r2-B5b survives, recorded); manifest 2b48e333; round 2 requested, comment 26689 on #1507 (revs 56-58); nothing committed
|
||||
2026-10-04T20:42:10Z | Filbert (claude, T3 9cb9731e) | row 5 CHAT-03 I1 round 2 review (#1507) | approve: B1-B6 fixed (probes rerun, round 1 mutants killed; freeze path read, code waits for frozen 1 before members); follow-ups F1 (enumeration-under-freeze test, C4 and r2-B5b survive), F2 (takeover+Enter in one chunk), F3 (preArgs override reaches Pi unsealed); comment 26694; queue rev 60 committed ddd9cf36 (not pushed); review file agents/filbert/work/chat-03-i1-review-r2-2026-10-04.md sha256 2f88ccd9 (uncommitted)
|
||||
2026-10-04T20:47:45Z | Sage (T3 Claude Code, thread 1ef1e4f8) | row 5 CHAT-03 I1 commit | both round 2 approvals (26690, 26694); manifest 2b48e333 checked; all suites green on an export; candidate, packets and Filbert's review record committed; follow-ups in DEFERRED; row 5 to waiting-on-jason for Gate E
|
||||
2026-10-04T23:31:47Z | Sage (T3 Claude Code, thread 1ef1e4f8) | Vikunja probes received | Researcher's P1-P7 record (ef0beec6) committed; scratch container confirmed gone; lead decision 51; Darkwing asked for addendum B (scope map, poll gaps)
|
||||
|
||||
@@ -824,3 +824,27 @@ which stay with him. Each item names who decided it and what happened.
|
||||
- The three views: accepted as demonstrations. Counting a person's
|
||||
edit in the same second as a broker write as external is the
|
||||
cautious side, and stays.
|
||||
51. **Sage's rulings on the Vikunja probes (2026-10-04).** Source:
|
||||
Researcher, `agents/researcher/work/2026-10-04_vikunja-probes.md`
|
||||
(sha256 ef0beec6…), probes P1 to P7 against a scratch
|
||||
`vikunja/vikunja:2.7.0` (digest e2204a1c…), since removed. Where the
|
||||
probes and addendum A disagree, the probes win.
|
||||
- Token scopes: addendum A's example group names don't exist, and
|
||||
Vikunja refuses unknown groups with 400. Darkwing rewrites the
|
||||
scope map from the real route groups in addendum B.
|
||||
- Polling on `updated` misses bucket moves between columns that
|
||||
aren't done, bulk label sets and deletions. The hourly reconcile
|
||||
alone would leave a person's column move unseen for up to an hour.
|
||||
Darkwing proposes in addendum B how the 30-second poll catches moves
|
||||
and deletions too, for example by also listing the task ids in each
|
||||
bucket of the project's kanban view. At slice 1 scale that costs a
|
||||
few requests per poll.
|
||||
- Vikunja doesn't enforce `If-Match`. The broker compares before it
|
||||
writes and the race window stays, as 6.8 already says. The broker
|
||||
writes with `PATCH` of owned fields only, never `PUT`, which
|
||||
clears omitted fields. Labels change only through the single add and
|
||||
remove routes, never as a `labels` key in a `PATCH` body.
|
||||
- A 401 means either an expired token or one out of scope. The broker
|
||||
checks `expires_at` itself, both after minting (Vikunja accepts a
|
||||
past date) and before each use, then treats any 401 as a refusal
|
||||
and logs both possible causes.
|
||||
|
||||
Reference in New Issue
Block a user