feat(dispatcher): enforce depends_on with .blocked marker (Phase 2/G2)
Before claiming a session for a task, the dispatcher now: 1. Parses the task's frontmatter 2. If `depends_on: [project:task_id]` is non-empty, checks each entry against `<projectsDir>/<project>/.agent-queue/done/<task_id>.md` 3. If any dep is unresolved -> skip the task and write `<task>.md.blocked` next to it. The watchdog (G1) will resolve this marker on its next tick. The `.blocked` marker is idempotent: re-running the dispatcher does not refresh its mtime, so the watchdog can compute the blocked-since timestamp from the FIRST detection (timeout precision). Path-traversal hardening: project / task_id segments must match `[A-Za-z0-9._-]+` and cannot be `.` or `..`. A malicious frontmatter like `depends_on: ../../tmp:foo` is rejected before any filesystem lookup. assignNextTask (the doneChan path) applies the same gate so that a session freed mid-cycle cannot bypass enforcement. Tests (-race clean): - DependsOnUnresolved -> .blocked marker, no dispatch - DependsOnResolved -> normal dispatch, no marker - PartialResolution -> stay blocked - RejectPathTraversal -> blocked, not dispatched - BlockedMarker idempotent (mtime stable across passes) - NoDependsOn regression guard
This commit is contained in:
parent
47ab86eef9
commit
5cfb58c202
3 changed files with 439 additions and 9 deletions
29
VERSION.md
29
VERSION.md
|
|
@ -1,4 +1,31 @@
|
|||
# Version actuelle : 0.3.8
|
||||
# Version actuelle : 0.4.0
|
||||
|
||||
## [0.4.0] - 2026-04-16
|
||||
**Type:** Minor — Phase 2 / Chantier G2 : dispatcher enforce depends_on
|
||||
|
||||
### Ajouté
|
||||
- `internal/dispatcher/dispatcher.go` : champ `DependsOn []string` ajouté à
|
||||
`TaskFrontmatter` et nouvelles méthodes `taskBlocked`, `dependencyResolved`,
|
||||
`touchBlockedMarker`, `isSafeSegment`.
|
||||
- Avant de claim une session pour une tâche, le dispatcher parse le
|
||||
frontmatter. Si `depends_on: [project:task_id]` est non vide ET non résolu
|
||||
(sibling task pas encore dans `<projectsDir>/<project>/.agent-queue/done/`),
|
||||
la tâche est sautée et un fichier `<task>.md.blocked` est posé à côté.
|
||||
- Le marker `.blocked` est idempotent : sa mtime n'est PAS rafraîchie aux
|
||||
passes suivantes pour que le watchdog (G1) puisse compter le timeout
|
||||
depuis la première détection.
|
||||
- Sécurité : segments `project` / `task_id` validés (`[A-Za-z0-9._-]+`,
|
||||
refus de `..`, `/`, NUL) avant d'être concaténés au filesystem path.
|
||||
- `assignNextTask` (chemin du session done-channel) applique la même règle.
|
||||
|
||||
### Tests
|
||||
- `TestDispatcher_DependsOnUnresolved_DropsBlockedMarker`
|
||||
- `TestDispatcher_DependsOnResolved_Dispatches`
|
||||
- `TestDispatcher_DependsOn_PartialResolution`
|
||||
- `TestDispatcher_DependsOn_RejectPathTraversal`
|
||||
- `TestDispatcher_BlockedMarker_Idempotent`
|
||||
- `TestDispatcher_NoDependsOn_DispatchesNormally` (regression guard)
|
||||
- `-race` clean.
|
||||
|
||||
## [0.3.8] - 2026-04-16
|
||||
**Type:** Patch — Bug #1 (A3 flip+ensure inconsistency) + Bug #10 (requiredShared contract test)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue