Skip to content

Separate recurring task instances into their own store - #19

Open
janitorr wants to merge 5 commits into
mainfrom
separate-recurring-task-instances
Open

Separate recurring task instances into their own store#19
janitorr wants to merge 5 commits into
mainfrom
separate-recurring-task-instances

Conversation

@janitorr

Copy link
Copy Markdown
Owner

Implements the separate-recurring-task-instances OpenSpec change.

Summary

  • Add a dedicated recurring_task_instances store/table and remove RecurringTaskId from one-shot tasks.
  • Make the recurring template the aggregate root: every /tasks/recurring/{id}/... endpoint resolves {id} as the template id. complete marks the template's current active instance done and creates the next.
  • Strip recurrence logic from the one-shot complete handler so it depends only on ITaskStore and IClock.
  • Morning report: add a type discriminator (one-shot | recurring) to each item, report recurring under the template id, and bump schemaVersion to "3".
  • Update MCP complete_recurring_task to take the template id from list_recurring_tasks and return the completed recurring instance.

Verification

  • dotnet build Nagger.slnx succeeds.
  • dotnet test Nagger.slnx: 134 tests pass (74 Core + 60 Host).
  • Core mutation testing: 79.65% (above the 75% break threshold); newly-introduced mutants are all killed.

Note: this change builds on add-seven-day-report-window, which is already archived on main.

@janitorr

Copy link
Copy Markdown
Owner Author

Reviewed. No logic bugs found in the core change; the design and implementation are coherent with the OpenSpec proposal. A few actionable issues:

1. USAGE.md is now stale (Medium — docs)
AGENTS.md points to USAGE.md as the canonical API reference, but the breaking semantics aren't reflected there:

  • USAGE.md:174 still says POST /tasks/recurring/{id}/complete takes "the instance id" (now the template id).
  • USAGE.md:179 still says "Completing a non-recurring task ... returns 400" — that path no longer exists.
  • USAGE.md:185 still says to use /tasks/one-shot "to discover the generated instance ids" — recurring instances are no longer in one_shot_tasks.
  • USAGE.md:205 still shows "schemaVersion": "2" and item objects without the new required type field.

2. Canonical specs not synced (Medium — consistency)
openspec/specs/recurring-task-creation/spec.md and openspec/specs/recurring-task-lifecycle/spec.md still describe the old behavior ("creates a one-shot task instance", "complete ... for a recurring task instance", "Reject completion of a non-recurring task"). The deltas exist under openspec/changes/..., but the canonical specs now disagree with the implementation. Expected pre-archive for OpenSpec, but confirm whether /opsx-sync should run here or after merge.

3. Migration drops RecurringTaskId with no data relocation (Medium — data loss risk, acknowledged non-goal)
20260817213759_SeparateRecurringTaskInstances.cs does DropColumn on one_shot_tasks and creates the new table without moving rows. Any existing nagger.db will have its recurring instances orphaned as plain one-shot tasks (still active) and its templates left with zero instances, so complete returns the "no active instance" validation error. The design explicitly declares "no production data exists" as a non-goal, which is fine, but SQLite is the canonical local store and persists between runs — worth confirming no reviewer/dev has data that would silently degrade.

4. stryker-config.json change is unrelated to this feature (Low — scope)
The reporters change (HtmlMarkdown/Json) is a separate commit (43cc01a) bundled into this branch. It has nothing to do with the instance-store change and should ideally land as its own PR.

5. IRecurringTaskInstanceStore.GetByIdAsync is unused (Low — nit)
Declared in Ports.cs and implemented in SqliteRecurringTaskInstanceStore, but no handler calls it (complete/pause/resume/cancel all use GetByTemplateIdAsync). The design lists it, so this may be intentional for future instance-by-id endpoints; if not, it's dead code.

Core handlers, status mapping, morning-report classification, and exception-to-404 mapping all look correct, and the added tests cover the breaking cases (complete-by-template, no-active-instance, missing-template).

@janitorr

Copy link
Copy Markdown
Owner Author

Thanks for the thorough review. Addressing each point:

1. USAGE.md stale — fixed. Updated the recurring-task section (complete now takes the template id and returns the recurring instance; removed the non-recurring/instance-id references; list no longer points at /tasks/one-shot) and the morning-report example (schemaVersion "3" and the new required type field on each item, including a recurring example).

2. Canonical specs not synced — fixed. Ran the OpenSpec sync workflow: recurring-task-creation, recurring-task-lifecycle, one-shot-task-lifecycle, and morning-task-report main specs now reflect the new behavior. openspec validate --all passes (12/12). The deltas still live in the change; the canonical specs now agree with the implementation.

3. Migration drops RecurringTaskId with no data relocation — intentional. This is an explicit non-goal in design.md (No data migration of existing rows (no production data exists)). Agreed it's worth flagging: anyone with an existing nagger.db that created recurring tasks before this change would lose those instances on upgrade. No change here per the design.

4. stryker-config.json commit unrelated — intentional. The reporters change (HtmlMarkdown/Json) was made deliberately to make Stryker output machine-parseable for future mutation-testing runs; it's bundled on this branch on purpose.

5. GetByIdAsync unused — intentional. The design specifies the IRecurringTaskInstanceStore port with GetByIdAsync for future instance-by-id endpoints; all current handlers use GetByTemplateIdAsync.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant