feat: add waits-for dependency type for fanout gates (bd-xo1o.2)

Adds 'waits-for' dependency type for dynamic molecule bonding:
- DepWaitsFor blocks an issue until spawner's children are closed
- Two gate types: all-children (wait for all) or any-children (first)
- Updated blocked_cache.go CTE to handle waits-for dependencies
- Added --waits-for and --waits-for-gate flags to bd create command
- Added WaitsForMeta struct for gate metadata storage
- Full test coverage for all gate types and dynamic child scenarios

This enables patrol molecules to wait for dynamically-bonded arms to
complete before proceeding (Christmas Ornament pattern).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-23 03:59:43 -08:00
parent 37fd1ce614
commit fa9a88e2a3
7 changed files with 328 additions and 5 deletions

View File

@@ -72,6 +72,9 @@ type CreateArgs struct {
EstimatedMinutes *int `json:"estimated_minutes,omitempty"` // Time estimate in minutes
Labels []string `json:"labels,omitempty"`
Dependencies []string `json:"dependencies,omitempty"`
// Waits-for dependencies (bd-xo1o.2)
WaitsFor string `json:"waits_for,omitempty"` // Spawner issue ID to wait for
WaitsForGate string `json:"waits_for_gate,omitempty"` // Gate type: all-children or any-children
// Messaging fields (bd-kwro)
Sender string `json:"sender,omitempty"` // Who sent this (for messages)
Wisp bool `json:"wisp,omitempty"` // Wisp = ephemeral vapor from the Steam Engine; bulk-deleted when closed

View File

@@ -309,6 +309,46 @@ func (s *Server) handleCreate(req *Request) Response {
}
}
// Add waits-for dependency if specified (bd-xo1o.2)
if createArgs.WaitsFor != "" {
// Validate gate type
gate := createArgs.WaitsForGate
if gate == "" {
gate = types.WaitsForAllChildren
}
if gate != types.WaitsForAllChildren && gate != types.WaitsForAnyChildren {
return Response{
Success: false,
Error: fmt.Sprintf("invalid waits_for_gate value '%s' (valid: all-children, any-children)", gate),
}
}
// Create metadata JSON
meta := types.WaitsForMeta{
Gate: gate,
}
metaJSON, err := json.Marshal(meta)
if err != nil {
return Response{
Success: false,
Error: fmt.Sprintf("failed to serialize waits-for metadata: %v", err),
}
}
dep := &types.Dependency{
IssueID: issue.ID,
DependsOnID: createArgs.WaitsFor,
Type: types.DepWaitsFor,
Metadata: string(metaJSON),
}
if err := store.AddDependency(ctx, dep, s.reqActor(req)); err != nil {
return Response{
Success: false,
Error: fmt.Sprintf("failed to add waits-for dependency %s -> %s: %v", issue.ID, createArgs.WaitsFor, err),
}
}
}
// Emit mutation event for event-driven daemon
s.emitMutation(MutationCreate, issue.ID)