fix(tasks): cover syncProject wiring and allow deleting orphans after file removal

Review follow-up:

- runtime.test.js: add syncProject wiring tests with a real temp-dir
  project and real project-config runtime — asserts reconcileLoopTasks is
  driven with the discovered loops when the project path is known (task
  created, nextRunAt computed) and that plain listing is used when the
  path cannot be resolved (reconcile not called).
- service.js: DELETE on a loop-owned task is rejected with a 400 only
  while its loop file still exists on disk; once the file is gone the
  orphan task can be deleted directly instead of waiting for the next
  reconcile. Tests use real temp files for both branches.
- DOCUMENTATION.md: delete semantics updated accordingly.
- PR description refreshed for the final HEAD (test counts, reconciliation
  contract, evidence wording).
This commit is contained in:
makeittech
2026-08-06 09:49:31 +03:00
parent 59a6c1b70d
commit 9b6b90504c
4 changed files with 156 additions and 21 deletions
@@ -1,3 +1,4 @@
import fs from 'node:fs';
import path from 'node:path';
import { OpenChamberControlError } from '../openchamber-control/error.js';
@@ -80,10 +81,12 @@ export const createScheduledTaskService = (dependencies) => {
if (!normalizedTaskID) throw new OpenChamberControlError('taskId is required', 400);
const current = await projectConfigRuntime.listScheduledTasks(projectID);
const existing = current.find((task) => task.id === normalizedTaskID) || null;
if (existing?.loopFile) {
if (existing?.loopFile && fs.existsSync(existing.loopFile)) {
// Loop tasks are owned by their `.agents/loops` markdown file: deleting
// the JSON row would be silently undone by the next reconcile. The file
// itself is the removal surface.
// the JSON row would be silently undone by the next reconcile while the
// file exists. The file itself is the removal surface. Once the file is
// gone (the task is an orphan that the next sync would remove anyway),
// deleting the row is safe and allowed.
throw new OpenChamberControlError(
'Loop task is managed by its .agents/loops markdown file; delete the file to remove the task',
400,