Repository navigation
feat: park status, append-only amendments, and schema cleanup (v0.4.0) - #8
Merged
Merged
Conversation
Replace the never-used quarantine mechanism with a `parked` status, add `amend_task` so post-queue corrections reach the agent that will act on them, and retire dead schema. park as a status, not a directory move Quarantine relocated a task's YAML into a subdirectory no reader listed, so a quarantined task vanished from the only interface that showed it — and the confirm dialog promised a restore the UI never implemented. Making park a status dissolves that instead of needing a second feature to patch it. Parked tasks stay listed, are exempt from the TTL filter (a deliberate bookmark must not silently expire), and are skipped by the dispatcher, whose pickup loops match `submitted` and `routing-failed` only. `parked_from` records where to return to. Removed with no migration: the mechanism was never used — no `quarantine/` dir on disk, no task ever carried `status: quarantined`, no manifest granted either tool. amend_task, append-only A queued task's description was immutable, so anything learned between queuing and starting had nowhere to go and an agent trusting its brief would act on stale instructions. Amendments accumulate under `payload.amendments`; the description is never mutated. Only the source_agent or operator may amend — the target agent is rejected, the same trust boundary that makes `cancelled` operator-only. repair path for out-of-vocabulary statuses Two tasks on disk carry `complete` (not `completed`), written direct-to-YAML in May. Every mutation path rejected them. The queue has more than one writer, so tolerate the unknown rather than widen the vocabulary: `/queue/summary` buckets them, `set_task_status` can move out of one given an override plus a note. Also fixes an invalid `build-backend` that made `pip install -e ".[dev]"` fail — the documented test setup was broken. It survived because CI installed from requirements.txt and so never built the package; CI now installs from pyproject.toml. alert_state is no longer written on create (emitter removed in July); existing YAMLs keep their inert block. The README published a compose example binding 8485 on all interfaces while the same document states the port is loopback-limited and that loopback access alone can mutate the queue — corrected, with the reason stated inline. 122 tests, 91.9% coverage. Closes vikunja#293, #257, #164. agent-id: developer
Audit task-queue-park-amend-2026-08: PASS, 0 Critical/High/Medium, 2 Low, 10 Info. Both Low findings confirmed risks identified during the build rather than surfacing new ones; neither needs a code change. Recording them where the next reader will hit them instead of only in the report. unpark_task_handler outside-lock read — documented in the docstring. The target is resolved before set_task_status_handler takes the same non-reentrant fcntl lock, because holding it across both would deadlock. An illegal transition still cannot land: that handler re-reads current_status under the lock and re-validates. The residual race is a redundant-but-valid transition plus a duplicate history entry. parked joining NON_TERMINAL_STATUSES — documented above VALID_TRANSITIONS. `failed` is the only transition target defined in terms of that derived set, so adding a status silently widened it; the other three are literals and did not. The real gap is that update_task_handler has no target_agent ownership check at all, which is pre-existing and a behaviour change to fix. Tracked in vikunja#325. No functional change — comments and CHANGELOG only. agent-id: developer
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.
Closes vikunja#293, #257, #164.
What changed
parked— a status, not a directory moveQuarantine relocated a task's YAML into a subdirectory that no reader listed, so a quarantined task vanished from the only interface that showed the queue — and the UI's confirm dialog promised a restore that was never implemented. Making park a status dissolves that instead of requiring a second feature to patch it.
park_task/unpark_task+POST /tasks/{id}/{park,unpark}.parked_fromrecords the prior status and is cleared on unpark, sounpark_taskrestores it without the caller knowing it.list_tasks— a deliberate bookmark that silently expires is worse than no feature.task-dispatcher.pyas it exists today: its pickup loops matchsubmitted(L561) androuting-failed(L834) only, andarchive_expired()(L792) gates onTERMINAL_STATES. A parked task is invisible to all three, so no dispatcher change is needed.Quarantine/restore removed with no migration or compat shim: the mechanism was never used — no
quarantine/dir on disk, no task ever carriedstatus: quarantined, and no agent manifest granted either tool.amend_task— append-onlyA queued task's description was immutable, so anything learned between queuing and starting had nowhere to go, and an agent trusting its brief would act on stale instructions.
payload.amendments;payload.descriptionis never mutated.source_agentoroperator. The target agent is rejected — it must not rewrite the instructions it was handed, the same boundary that already makescancelledoperator-only.in-progress(the case that matters most); the response setsagent_may_have_started.Repair path for out-of-vocabulary statuses
Two tasks on disk carry
complete(notcompleted), written direct-to-YAML in May. Every mutation path rejected them —update_taskneeds the source inVALID_TRANSITIONS,set_task_statusneeds it inNON_TERMINAL_STATUSES, andcompleteis in neither. The queue has more than one writer, so the fix is to tolerate the unknown rather than widen the vocabulary:set_task_statuscan now move out of an unrecognised status givenallow_override=Trueand a note, recordingrepaired_from. The target must still be valid.GET /queue/summaryCounts by status behind the same
_authorized()gate. Out-of-vocabulary statuses are bucketed underunknownrather than dropped, so records from other writers stay visible.alert_stateretiredThe emitter was removed in July; nothing has read the block since. No longer written on create. Existing YAMLs keep theirs — the field is inert and rewriting hundreds of files to strip a no-op buys nothing. Deliberate, and a test pins that mutations preserve the residual block rather than silently dropping it.
Fixes
build-backendwas invalid.setuptools.backends.legacy:buildis not a real backend, sopip install -e ".[dev]"— the test setupAGENTS.mddocuments — failed withBackendUnavailable. Runtime was unaffected because the image installs fromrequirements.txt, which is exactly why it went unnoticed: CI installed fromrequirements.txttoo, so nothing ever built the package. CI now installs frompyproject.tomlso this class of breakage fails here in future.8485:8485(all interfaces) while the same document's Security section states the port is loopback-limited and that any process with loopback access can mutate the queue without the secret. Copying it verbatim exposed an unauthenticated mutation endpoint to the LAN. Now127.0.0.1:8485:8485with the reason stated inline.Also
requires-pythonraised to>=3.11— CI,pyproject.toml, the Dockerfile (3.12) and the fleet standard previously disagreed three ways.target-version = "py311"+tests/**per-file ignores, closing the fleet drift. This surfacedUP017, applied.amend_taskadded to the trust-model tool list — it is a new operator-mutating tool on the same unauthenticated loopback transport, and its source-agent check is an integrity control over a self-asserted actor, not authentication.Verification
pip install -e ".[dev]"verified in a scratch venv — broken onmaintoday.ruff check/ruff format --checkclean.Paired with cloudcli-plugin-task-queue#6, which needs this version's routes.
🤖 Generated with Claude Code