@@ -0,0 +1,142 @@
|
||||
---
|
||||
created: 2026-09-22
|
||||
status: implemented
|
||||
requirements:
|
||||
- REQ-TASKS-MCP-MOVE-RESULTS-001
|
||||
system_design:
|
||||
- ../../specs/tasks/system-design/mcp-task-move-results.md
|
||||
legacy_specs:
|
||||
- ../../specs/workflow-step-move-overrides/spec.md
|
||||
---
|
||||
|
||||
# Implementation plan: Same-step MCP move results
|
||||
|
||||
## Overview
|
||||
|
||||
[Issue #3872](https://github.com/kdlbs/kandev/issues/3872) reports repeated deferred moves to a task's current step.
|
||||
One sequential work order adds a validated no-op result, regression coverage, and matching tool documentation.
|
||||
Implementation and targeted verification are complete.
|
||||
|
||||
## Evidence and root cause
|
||||
|
||||
Investigation used commit `c4262c644d4` and the canonical issue. The issue contains no image attachments or comments.
|
||||
The authenticated GitHub user is `carlosflorencio`; issue assignment succeeded on 2026-09-22.
|
||||
|
||||
`handleMoveTask` routes every RUNNING or STARTING primary session to `deferMoveTask`.
|
||||
`deferMoveTask` validates entry options against step equality but does not complete an option-less equal-step request.
|
||||
It calls `SetPendingMove`, creates a move identifier, and returns `deferred`.
|
||||
At turn end, `applyPendingMove` recognizes the equal target and skips the transition.
|
||||
|
||||
A temporary `TestIssue3872SameStepRepro` used the existing envelope fixture with a SQLite task service and recording queue.
|
||||
The task and requested destination both used `wf-envd` / `step-work`; the primary session was RUNNING.
|
||||
No entry options were supplied. The expected result was `applied` with no pending move.
|
||||
|
||||
```text
|
||||
go test ./internal/mcp/handlers -run '^TestIssue3872SameStepRepro$' -count=1
|
||||
FAIL: expected "applied", actual "deferred"
|
||||
FAIL: expected empty pendingMoves, actual one record targeting step-work
|
||||
```
|
||||
|
||||
The reproduction ran from `apps/backend` and failed for both expected assertions.
|
||||
The temporary test and generator were removed. No production or permanent test files changed.
|
||||
|
||||
Current orchestrator code includes equal-target record consumption and stale-move recovery.
|
||||
The historical nine-row leak reported for v0.95.1 was not reproduced against a live database.
|
||||
This package prevents new unnecessary records. It does not claim to repair every historical row.
|
||||
|
||||
## Requirements and assumptions
|
||||
|
||||
The task system owns this contract because it owns task placement and pending transitions.
|
||||
Adjacent agent and plugin specifications do not own this MCP task result.
|
||||
|
||||
The existing AC-TASKS-KANBAN-TASK-REORDERING-001.28 already forbids position changes for a move to the current step.
|
||||
The new [result requirement](../../specs/tasks/requirements/mcp-task-move-results.md) supplies the missing observable completion semantics.
|
||||
The legacy move-overrides specification continues to own normalization and one-time entry behavior.
|
||||
|
||||
The selected result is `applied`, one of the issue's proposed solutions and an existing protocol value.
|
||||
This avoids adding a new disposition. No material product question blocks this package.
|
||||
Existing queued work is preserved because a destination no-op is not a cancellation request.
|
||||
|
||||
## Scope
|
||||
|
||||
### In scope
|
||||
|
||||
- Validated equal-workflow, equal-step completion before session routing.
|
||||
- Stored task response, unchanged position, and no new queue or transition effects.
|
||||
- Regression tests, both MCP tool descriptions, and public protocol documentation.
|
||||
|
||||
### Out of scope
|
||||
|
||||
- Historical database cleanup, cancellation semantics, and global queue redesign.
|
||||
- Changes to real cross-step moves, HTTP moves, WIP admission, or rendered UI.
|
||||
- New dispositions, migrations, runtime flags, and prompt-only retry workarounds.
|
||||
|
||||
## Technical approach
|
||||
|
||||
Follow [the system design](../../specs/tasks/system-design/mcp-task-move-results.md).
|
||||
Add a small validated completion helper in `internal/mcp/handlers` before the session lookup.
|
||||
Preserve existing write authorization through `AuthorizeWorkspaceScope` rather than relying on read permission alone.
|
||||
Return the existing move envelope without calling either mutation path.
|
||||
|
||||
Update both tool registrations and `docs/public/agent-communication.md` in the implementation work order.
|
||||
Public docs remain unchanged during this design turn because the runtime fix is not implemented.
|
||||
Existing move-overrides plans remain historical records; their implementation results are not rerun or rewritten by this package.
|
||||
|
||||
## Tests
|
||||
|
||||
All new named tests below belong to `config_task_handlers_same_step_test.go` unless another path is given.
|
||||
|
||||
| Acceptance | Planned regression |
|
||||
| --- | --- |
|
||||
| AC-TASKS-MCP-MOVE-RESULTS-001.1 | `TestHandleMoveTask_SameStepApplied`: RUNNING, STARTING, idle, absent, and mixed sibling sessions |
|
||||
| AC-TASKS-MCP-MOVE-RESULTS-001.2 | `TestHandleMoveTask_SameStepReturnsStoredTask`: omitted and differing requested positions |
|
||||
| AC-TASKS-MCP-MOVE-RESULTS-001.3 | `TestHandleMoveTask_SameStepPreservesEffects`: 47 repeats, pending moves, prompts, metadata, session state, and event counts |
|
||||
| AC-TASKS-MCP-MOVE-RESULTS-001.4 | `TestHandleMoveTask_SameStepValidation`: archive, access scope, missing targets, operational lookup failures, dependency failures, and entry options |
|
||||
| AC-TASKS-MCP-MOVE-RESULTS-001.5 | Existing `TestHandleMoveTask` / `TestDeferMoveTask` cases and server `TestMoveTaskToolSchemasExposeEntryOptions` |
|
||||
|
||||
The first regression must fail on the current code before implementation.
|
||||
Empty options normalize to no options. Non-empty instructions, reset, skip, and legacy prompt remain invalid for equal-step requests.
|
||||
The existing `TestPendingMove_EqualTargetRecordsAppliedMoveID` protects downstream defense behavior.
|
||||
|
||||
## E2E tests
|
||||
|
||||
The changed interface is MCP, with no rendered UI change. No Playwright test is required.
|
||||
Add `TestHandleMoveTask_SameStepPersistentQueue` with the real task service and queue repository.
|
||||
Send 47 requests through the registered WebSocket MCP action and inspect responses, task state, and persisted pending rows.
|
||||
For a seeded unrelated move, compare its identity and payload before and after the requests.
|
||||
Add `TestMoveTaskSameStepResponseForwarding` in `internal/mcp/server/config_handlers_test.go` for both MCP modes.
|
||||
Together these cover the public tool adapter, backend dispatch, and persistence boundaries.
|
||||
|
||||
## Work orders
|
||||
|
||||
- [x] [Task 01: Complete same-step MCP requests](task-01-complete-same-step-moves.md)
|
||||
|
||||
## Verification results
|
||||
|
||||
Design validation on 2026-09-22:
|
||||
|
||||
- `python3 scripts/list-docs.py validate`: passed (299 decisions, 1114 specifications).
|
||||
- `python3 scripts/lint-spec-files.test.py`: passed (36 tests).
|
||||
- `python3 scripts/lint-spec-files.py --all`: passed.
|
||||
- `git diff --check -- docs/specs docs/plans/mcp-same-step-move`: passed.
|
||||
- Catalog discovery includes both new specifications. All four package files were inspected before staging.
|
||||
|
||||
Implementation validation on 2026-09-23:
|
||||
|
||||
- Handler regressions, including the same-step matrix, passed. The new regression failed before the fix with `deferred` and one pending move, as expected.
|
||||
- MCP handler, MCP server, workflow move, and orchestrator equal-target regression commands in the work order passed.
|
||||
- `golangci-lint run ./internal/mcp/handlers ./internal/mcp/server`: passed with 0 issues.
|
||||
- `go build -o /tmp/kandev-issue3872-final ./cmd/kandev` from `apps/backend`: passed.
|
||||
- Public docs validation tests passed (62 tests); public docs validation passed (47 pages).
|
||||
- `python3 scripts/list-docs.py validate`: passed (299 decisions, 1114 specifications).
|
||||
- `python3 scripts/lint-spec-files.py --all`: passed.
|
||||
- `git diff --check`: passed.
|
||||
- PR review follow-up: missing and inaccessible targets retain validation errors; operational workflow or step lookup failures now return `internal_error` without exposing repository details.
|
||||
- Handler, task repository, workflow repository, workflow service, MCP server, workflow move, and orchestrator regressions passed after the follow-up. Lint passed for the four changed Go packages, the backend binary built, and catalog/specification validation passed.
|
||||
|
||||
## Risks
|
||||
|
||||
- An early return can bypass authorization, target checks, or entry-option validation unless their tests cover it.
|
||||
- Reusing the requested position in the response can misrepresent stored order.
|
||||
- Removing pending work can cancel an unrelated accepted transition.
|
||||
- A concurrent independent move can change the task after the no-op snapshot. This response does not reserve the destination.
|
||||
@@ -0,0 +1,122 @@
|
||||
---
|
||||
id: "01-complete-same-step-moves"
|
||||
title: "Complete same-step MCP requests"
|
||||
status: done
|
||||
wave: 1
|
||||
depends_on: []
|
||||
plan: "plan.md"
|
||||
requirements:
|
||||
- REQ-TASKS-MCP-MOVE-RESULTS-001
|
||||
acceptance_criteria:
|
||||
- AC-TASKS-MCP-MOVE-RESULTS-001.1
|
||||
- AC-TASKS-MCP-MOVE-RESULTS-001.2
|
||||
- AC-TASKS-MCP-MOVE-RESULTS-001.3
|
||||
- AC-TASKS-MCP-MOVE-RESULTS-001.4
|
||||
- AC-TASKS-MCP-MOVE-RESULTS-001.5
|
||||
system_design:
|
||||
- ../../specs/tasks/system-design/mcp-task-move-results.md
|
||||
---
|
||||
|
||||
# Task 01: Complete same-step MCP requests
|
||||
|
||||
## Summary
|
||||
|
||||
Implement the validated no-op branch before session routing.
|
||||
Prove that repeated calls return completion without changing task state or queued work.
|
||||
|
||||
## In scope
|
||||
|
||||
- The handler helper and regression matrix in the plan.
|
||||
- Both tool registrations, response forwarding tests, and public reference wording.
|
||||
- A persistent-queue integration case with repeated MCP backend dispatch.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Production database cleanup, queue cancellation, and orchestrator rewrites.
|
||||
- HTTP, plugin, frontend, or position-reorder behavior changes.
|
||||
|
||||
## Acceptance
|
||||
|
||||
1. The named handler regressions fail before the correction and pass after it, including authorization, mixed-session, and operational target lookup cases.
|
||||
2. A valid no-op preserves the complete stored task, existing queue state, and all transition effects across 47 requests.
|
||||
3. Both tool modes expose the same result semantics, and all commands below pass.
|
||||
|
||||
## Implementation sequence
|
||||
|
||||
1. Read the requirement, design, scoped backend guidance, and existing envelope and validation fixtures.
|
||||
2. Add the same-step regression first. Record its expected failure.
|
||||
3. Implement the small handler helper described in the design.
|
||||
4. Add validation, preservation, and persistent-queue coverage from the plan's test matrix.
|
||||
5. Update tool descriptions and public reference text. Preserve the existing result envelope.
|
||||
6. Run the commands below. Record results here and synchronize the plan.
|
||||
|
||||
The validation matrix includes nonexistent tasks, unauthorized tasks, read-only access, archived tasks, missing steps, and mismatched workflows.
|
||||
It also includes cross-workspace targets, task-read failures, missing validation dependencies, and option normalization errors.
|
||||
The positive cases include absent or unavailable queue support because a no-op does not use the queue.
|
||||
|
||||
## Verification
|
||||
|
||||
Run from the repository root:
|
||||
|
||||
```bash
|
||||
(cd apps/backend && go test ./internal/mcp/handlers -run 'TestHandleMoveTask|TestDeferMoveTask|TestMoveTaskErrorMessage' -count=1)
|
||||
(cd apps/backend && go test ./internal/mcp/server -run 'TestMoveTask' -count=1)
|
||||
(cd apps/backend && go test ./internal/workflow/move -count=1)
|
||||
(cd apps/backend && go test ./internal/orchestrator -run '^TestPendingMove_EqualTargetRecordsAppliedMoveID$' -count=1)
|
||||
(cd apps/backend && go test ./internal/task/repository ./internal/workflow/repository ./internal/workflow/service -count=1)
|
||||
(cd apps/backend && golangci-lint run ./internal/mcp/handlers ./internal/task/repository ./internal/workflow/repository ./internal/workflow/service)
|
||||
(cd apps/backend && go build -o /tmp/kandev-issue3872-fixup ./cmd/kandev)
|
||||
node --test scripts/validate-public-docs.test.mjs
|
||||
node scripts/validate-public-docs.mjs
|
||||
python3 scripts/list-docs.py validate
|
||||
python3 scripts/lint-spec-files.py --all
|
||||
git diff --check
|
||||
```
|
||||
|
||||
## Files likely touched
|
||||
|
||||
- `apps/backend/internal/mcp/handlers/config_task_handlers.go`
|
||||
- `apps/backend/internal/mcp/handlers/config_task_handlers_same_step_test.go` (new)
|
||||
- `apps/backend/internal/mcp/handlers/handlers_test.go` (shared fixture)
|
||||
- `apps/backend/internal/task/repository/repoerrors/errors.go`
|
||||
- `apps/backend/internal/task/repository/sqlite/workflow.go`
|
||||
- `apps/backend/internal/task/repository/workflow_repository_test.go`
|
||||
- `apps/backend/internal/workflow/models/errors.go`
|
||||
- `apps/backend/internal/workflow/repository/sqlite.go`
|
||||
- `apps/backend/internal/workflow/repository/sqlite_test.go`
|
||||
- `apps/backend/internal/workflow/service/access.go`
|
||||
- `apps/backend/internal/workflow/service/access_test.go`
|
||||
- `apps/backend/internal/mcp/server/server.go`
|
||||
- `apps/backend/internal/mcp/server/config_handlers.go`
|
||||
- `apps/backend/internal/mcp/server/config_handlers_test.go`
|
||||
- `docs/public/agent-communication.md`
|
||||
|
||||
## Dependencies
|
||||
|
||||
None.
|
||||
|
||||
## Risks
|
||||
|
||||
The current fixtures sometimes omit workflow controllers. Do not preserve that omission as a production validation bypass.
|
||||
Existing task-write authorization must run even though this branch performs no write.
|
||||
Existing queued work must survive the no-op unchanged.
|
||||
|
||||
## Parallelism
|
||||
|
||||
`sequential`
|
||||
|
||||
## Inputs
|
||||
|
||||
- [Requirement](../../specs/tasks/requirements/mcp-task-move-results.md)
|
||||
- [System design](../../specs/tasks/system-design/mcp-task-move-results.md)
|
||||
- [Plan and diagnostic evidence](plan.md)
|
||||
- Existing `pendingMoveRecordingQueuer`, `newTestTaskService`, and `seedRunningTask` fixtures.
|
||||
- Existing `TestHandleMoveTask_DeferredReturnsMoveResultEnvelope` and `TestPendingMove_EqualTargetRecordsAppliedMoveID`.
|
||||
|
||||
## Results
|
||||
|
||||
Implemented and verified on 2026-09-23. The regression was first confirmed red: the same-step request returned `deferred`, included a move ID, and recorded one pending move. After the fix, the handler matrix, 47-request preservation cases, persisted queue dispatch, response forwarding, and tool-description checks passed. The validated no-op returns the stored task and position without changing task, queue, session, prompt, metadata, event, or transition state.
|
||||
|
||||
All verification commands above passed. Additional checks passed: `golangci-lint run ./internal/mcp/handlers ./internal/mcp/server` (0 issues), backend binary build, public documentation validation (62 tests and 47 pages), catalog validation (299 decisions and 1114 specifications), specification lint, and `git diff --check`.
|
||||
|
||||
PR review follow-up: typed workflow and step not-found errors now preserve validation behavior, while operational repository/controller failures are logged and return `internal_error` without leaking database details. The new workflow and step failure regressions failed before the correction with `VALIDATION_ERROR` and pass with `INTERNAL_ERROR`. Handler, MCP server, task repository, workflow repository/service, workflow move, and orchestrator tests passed. Changed-package lint reported 0 issues; the backend binary build, catalog validation, specification lint, and diff check passed.
|
||||
@@ -237,7 +237,9 @@ Task B processes each incoming message as a normal turn. When it receives Agent
|
||||
}
|
||||
```
|
||||
|
||||
The server normalizes this object once and omits empty optional strings. Reset is additive with the destination reset policy, and instructions are appended once after the normal destination prompt. When `skip_step_prompt` is set, the destination step's configured prompt and its task-description fallback are suppressed for this entry: with instructions the agent starts a turn carrying only those instructions, and without instructions no turn starts and the task lands idle. The destination and options are validated together. Every call returns a move-result envelope: `disposition` is `"deferred"` when the current agent is running (the options persist through the turn boundary, WIP queue promotion, and backend restart before they are applied) or `"applied"` when an idle move committed immediately, `task` is the moved (or target-step) task, and an optioned move also returns a `move_id` plus the accepted `entry_options` so you can correlate the retained one-shot override with the eventual step entry. The legacy top-level `prompt` argument remains accepted as an alias for `entry_options.instructions`; when both are non-empty, validation fails. Pull-request draft/readiness is not a generic move option.
|
||||
The server normalizes this object once and omits empty optional strings. Reset is additive with the destination reset policy, and instructions are appended once after the normal destination prompt. When `skip_step_prompt` is set, the destination step's configured prompt and its task-description fallback are suppressed for this entry: with instructions the agent starts a turn carrying only those instructions, and without instructions no turn starts and the task lands idle. The destination and options are validated together.
|
||||
|
||||
Every call returns a move-result envelope. `disposition` is `"deferred"` when the source session is RUNNING or STARTING and an actual step change is recorded for turn-end. It is `"applied"` when a step change commits immediately or when the task already has the requested workflow and step and no entry options were supplied. In the same-step case, `task` is the stored task and no retry is needed. An optioned step change also returns a `move_id` plus the accepted `entry_options` so you can correlate the retained one-shot override with the eventual step entry. The legacy top-level `prompt` argument remains accepted as an alias for `entry_options.instructions`; when both are non-empty, validation fails. The server determines task position when it enters a step, and a same-step request preserves its stored position. Pull-request draft/readiness is not a generic move option.
|
||||
|
||||
**No secrets or large dumps.** Messages are coordination, not a code-delivery channel. Do not send credentials, private keys, or large file contents through cross-task messages. Reference files by path; share access via the repository, not the message.
|
||||
|
||||
|
||||
@@ -226,7 +226,7 @@ Creation and immediate launch are not one rollback boundary. If the task is crea
|
||||
|
||||
Task states on the wire are `TODO`, `CREATED`, `SCHEDULING`, `IN_PROGRESS`, `REVIEW`, `BLOCKED`, `WAITING_FOR_INPUT`, `COMPLETED`, `FAILED`, and `CANCELLED`. Use `task.move` to change the workflow step and `task.state` to change runtime state; these are separate operations.
|
||||
|
||||
`task.move` accepts an optional one-shot `entry_options` object. Its normalized fields are `reset_context`, `instructions`, and `skip_step_prompt`; empty optional strings are omitted. Reset is additive, and instructions are appended after the destination step's normal prompt. When `skip_step_prompt` is set, the destination step's prompt and its task-description fallback are suppressed for this entry: with instructions the agent starts a turn carrying only those instructions, and without instructions no turn starts and the task lands idle. The values do not mutate workflow defaults. A successful response includes, when supplied, the normalized `entry_options`, plus a `move_id` correlating options retained for a deferred move. The same options are accepted by `move_task_kandev`; its legacy top-level `prompt` is an alias for `entry_options.instructions`, and conflicting non-empty values are rejected. Moves requested by an active agent use the deferred MCP path and persist the complete options through turn completion, WIP promotion, and backend restart before the retained move is applied.
|
||||
`task.move` accepts an optional one-shot `entry_options` object. Its normalized fields are `reset_context`, `instructions`, and `skip_step_prompt`; empty optional strings are omitted. Reset is additive, and instructions are appended after the destination step's normal prompt. When `skip_step_prompt` is set, the destination step's prompt and its task-description fallback are suppressed for this entry: with instructions the agent starts a turn carrying only those instructions, and without instructions no turn starts and the task lands idle. The values do not mutate workflow defaults. A successful response includes, when supplied, the normalized `entry_options`, plus a `move_id` correlating options retained for a deferred move. The same options are accepted by `move_task_kandev`; its legacy top-level `prompt` is an alias for `entry_options.instructions`, and conflicting non-empty values are rejected. Actual workflow-step changes requested through `move_task_kandev` from a RUNNING or STARTING session use the deferred MCP path and persist their complete options through turn completion, WIP promotion, and backend restart. A valid same-step MCP request without entry options returns `disposition: "applied"` with the stored task, so the caller does not need to retry.
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
status: draft
|
||||
system: tasks
|
||||
created: 2026-09-22
|
||||
owners:
|
||||
- kandev
|
||||
---
|
||||
|
||||
# MCP task move results
|
||||
|
||||
## Overview
|
||||
|
||||
Agents need to distinguish a completed destination request from a move that waits for turn end.
|
||||
The task system owns this result because it owns workflow placement and deferred moves.
|
||||
|
||||
The existing [ordering requirement](kanban-task-reordering.md),
|
||||
AC-TASKS-KANBAN-TASK-REORDERING-001.28, already preserves position for moves to the current step.
|
||||
This document defines the MCP result for that case. It does not replace the ordering contract.
|
||||
|
||||
## Requirements
|
||||
|
||||
### REQ-TASKS-MCP-MOVE-RESULTS-001: Completed destination requests
|
||||
|
||||
**Intent:** An agent can recognize success without submitting the same destination request again.
|
||||
|
||||
#### Acceptance criteria
|
||||
|
||||
- **AC-TASKS-MCP-MOVE-RESULTS-001.1:** When a valid request names the current workflow and step without entry options, the system shall return `disposition: "applied"`.
|
||||
This result shall apply with no session, an idle session, or any running or starting session.
|
||||
- **AC-TASKS-MCP-MOVE-RESULTS-001.2:** The result shall contain the stored task, including its actual position.
|
||||
The system shall not create a move identifier or report a new workflow entry for this no-op.
|
||||
- **AC-TASKS-MCP-MOVE-RESULTS-001.3:** The no-op shall leave task state, position, metadata, session state, prompts, and pending moves unchanged.
|
||||
It shall not run step actions or create transition history. Repeated requests shall have the same behavior.
|
||||
- **AC-TASKS-MCP-MOVE-RESULTS-001.4:** The no-op shall retain task-write authorization, archive restrictions, and workflow and step validation.
|
||||
Missing or inaccessible targets shall remain validation failures; repository and controller failures shall return an internal error without exposing implementation details.
|
||||
Invalid requests shall fail without side effects. Non-empty normalized entry options shall remain invalid without a step change.
|
||||
- **AC-TASKS-MCP-MOVE-RESULTS-001.5:** A valid request to another step shall retain the existing immediate or deferred behavior.
|
||||
Both task-mode and configuration-mode tool descriptions shall explain that `applied` also means the task already occupies the requested destination.
|
||||
The descriptions shall state that callers need no retry for that result.
|
||||
|
||||
## Compatibility and exclusions
|
||||
|
||||
The result reuses the existing `applied` value and response envelope. It adds no disposition value or request field.
|
||||
Empty entry options retain their existing normalization. The legacy `prompt` remains an alias for entry instructions.
|
||||
|
||||
Existing queued work remains independently eligible. Success describes the destination at the time of the task read, not a reservation against later moves.
|
||||
|
||||
Historical pending-row repair, move cancellation, position reordering, HTTP behavior, and workflow recovery are outside this capability.
|
||||
|
||||
## Implementation plans
|
||||
|
||||
- [Same-step MCP move fix](../../../plans/mcp-same-step-move/plan.md)
|
||||
@@ -0,0 +1,86 @@
|
||||
---
|
||||
status: draft
|
||||
system: tasks
|
||||
requirements:
|
||||
- REQ-TASKS-MCP-MOVE-RESULTS-001
|
||||
---
|
||||
|
||||
# MCP task move results
|
||||
|
||||
## Purpose and boundaries
|
||||
|
||||
The MCP backend handler owns destination-result classification before it selects immediate or deferred execution.
|
||||
The task service remains the source of task data and authorization.
|
||||
The queue and orchestrator retain ownership of real deferred transitions.
|
||||
|
||||
| Requirement | Design section |
|
||||
| --- | --- |
|
||||
| REQ-TASKS-MCP-MOVE-RESULTS-001 | Classification and response |
|
||||
|
||||
## Classification and response
|
||||
|
||||
`Handlers.handleMoveTask` in `internal/mcp/handlers/config_task_handlers.go` first parses required fields and normalizes entry options.
|
||||
A small helper then reads the task through `taskSvc.GetTask` and compares both workflow and step identifiers.
|
||||
For different destinations, normal routing continues. No active-session permission is broadened.
|
||||
|
||||
For an equal destination, the helper validates the candidate before returning success:
|
||||
|
||||
1. Enforce `authz.ScopeTaskWrite` through the existing `AuthorizeWorkspaceScope` service method.
|
||||
2. Reject archived tasks.
|
||||
3. Resolve the workflow and validate workspace membership through existing service accessors.
|
||||
4. Resolve the step through `workflowCtrl.GetStep` and validate workflow membership.
|
||||
5. Use `workflowmove.ValidateEntryOptions` with `MoveChangePositionOnly`.
|
||||
|
||||
Target lookups preserve typed not-found errors. A missing or inaccessible workflow or step remains a validation error; operational repository and controller failures are logged and return `internal_error` without exposing the underlying error to the MCP caller.
|
||||
|
||||
Production requires the task and workflow dependencies. A missing validation dependency must fail closed, without a fabricated success.
|
||||
Handler fixtures for this branch must supply real workflow steps or the existing workflow controller fixture.
|
||||
|
||||
The helper returns `dto.MoveTaskResponse` with `dto.FromTask(task)` and `moveDispositionApplied`.
|
||||
It leaves `MoveID`, `EntryOptions`, and `WorkflowEntryIdentity` empty.
|
||||
It does not use `synthesizeMovedTaskDTO`, because that function can substitute requested values.
|
||||
It completes before `lookupSession`, `deferMoveTask`, and `applyMoveTaskImmediate`.
|
||||
Thus a valid no-op does not depend on queue availability or session liveness.
|
||||
|
||||
Task-mode registration in `internal/mcp/server/server.go` and configuration-mode registration in `config_handlers.go` describe the same result.
|
||||
Their move and prompt descriptions must limit deferral language to actual step changes.
|
||||
Their position descriptions must agree with the existing server-owned ordering contract.
|
||||
`moveTaskHandler` continues forwarding the existing response envelope.
|
||||
|
||||
## Concurrency, persistence, and recovery
|
||||
|
||||
The no-op performs no writes and requires no schema change or queue admission.
|
||||
Its success refers to the authorized task snapshot. A later independent transition can still change that task.
|
||||
The handler must not call the generic move service for this branch: that path checks active sessions and performs writes.
|
||||
|
||||
The no-op neither replaces nor consumes an existing pending move, including one for another destination.
|
||||
It does not cancel previously accepted work or erase entry markers.
|
||||
|
||||
`orchestrator.applyPendingMove` retains its equal-target guard for older records and destinations reached after admission.
|
||||
That guard already records applied move identity and consumes the matching record through the applicable queue path.
|
||||
This change does not add a database sweep or infer why an older deployment retained a particular row.
|
||||
|
||||
## Failure and security
|
||||
|
||||
Malformed payloads and conflicting prompt aliases fail during existing normalization.
|
||||
Task read failures, target lookup failures, authorization failures, and archive restrictions must not become `applied` responses.
|
||||
Missing or inaccessible targets retain validation errors. Operational lookup failures return `internal_error`, and response messages must not expose database or controller details.
|
||||
Responses must not reveal inaccessible task data.
|
||||
Both workflow and step equality are required. Matching only the step identifier is insufficient.
|
||||
|
||||
## Validation and observability
|
||||
|
||||
Handler tests cover SQLite task state and a recording queue, with repeated requests and mixed session states.
|
||||
A handler integration case uses the real message queue repository to prove that pending rows do not grow.
|
||||
Existing deferred-move and immediate-move tests prove that actual transitions retain their behavior.
|
||||
Tool tests cover both registrations and MCP response forwarding.
|
||||
|
||||
The no-op emits neither `pending move recorded` nor workflow transition events.
|
||||
No new metric or logging stream is necessary.
|
||||
|
||||
## Related contracts
|
||||
|
||||
- [Kanban task ordering](kanban-task-reordering.md)
|
||||
- [Workflow move overrides](../../workflow-step-move-overrides/spec.md)
|
||||
- [Move override decision](../../../decisions/2026-08-13-workflow-move-overrides.md)
|
||||
- [Implementation plan](../../../plans/mcp-same-step-move/plan.md)
|
||||
Reference in New Issue
Block a user