diff --git a/.beads/beads.jsonl b/.beads/beads.jsonl index 67871cea..c865829b 100644 --- a/.beads/beads.jsonl +++ b/.beads/beads.jsonl @@ -40,6 +40,7 @@ {"id":"bd-19er","content_hash":"17f69b5bb6b1d48e99ea46d79ffc6fd025207ae3ca15472caa947835194ca38c","title":"Create backup and restore procedures","description":"Disaster recovery procedures for Agent Mail data.\n\nAcceptance Criteria:\n- Automated daily snapshots (GCP persistent disk)\n- SQLite backup script\n- Git repository backup\n- Restore procedure documentation\n- Test restore from backup\n\nFile: deployment/agent-mail/backup.sh","status":"closed","priority":3,"issue_type":"task","created_at":"2025-11-07T22:43:43.417403-08:00","updated_at":"2025-11-24T11:31:04.095966-08:00","closed_at":"2025-11-23T23:39:00.061047-08:00","source_repo":".","dependencies":[{"issue_id":"bd-19er","depends_on_id":"bd-z3s3","type":"blocks","created_at":"2025-11-07T23:04:28.122501-08:00","created_by":"daemon"}]} {"id":"bd-1a6j","content_hash":"16f978c58b9988457aeb1eaff37fb17f12e91325549b38be10362a08923e9a2d","title":"Test issue 2","description":"","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-07T19:07:12.24632-08:00","updated_at":"2025-11-07T19:07:12.24632-08:00","source_repo":"."} {"id":"bd-1b0a","content_hash":"57d0a0ca69b2c95554ed7afa95c366187f0a9b53beebe2391b7aa49a3436f470","title":"Add transaction helper to replace manual COMMIT/ROLLBACK","description":"Create tx.go with withTx helper that handles transaction lifecycle. Replace manual transaction blocks in create/insert/update paths.","notes":"Refactoring complete:\n- Created withTx() helper in util.go\n- Added ExecInTransaction() as deprecated wrapper for backward compatibility\n- Refactored all manual transaction blocks to use withTx():\n - events.go: AddComment\n - dirty.go: MarkIssuesDirty, ClearDirtyIssuesByID\n - labels.go: executeLabelOperation\n - dependencies.go: AddDependency, RemoveDependency\n - compact.go: ApplyCompaction\n- All tests pass successfully","status":"closed","priority":1,"issue_type":"task","created_at":"2025-11-01T11:41:14.823323-07:00","updated_at":"2025-11-02T12:41:45.827688-08:00","closed_at":"2025-11-02T12:41:45.827688-08:00","source_repo":"."} +{"id":"bd-1c4h","content_hash":"8bf2c604a10fefa2713bf334656a4f4c8f7d1f36bfd04f9bb824f904dc3a6b0d","title":"Bug: Transaction operations don't invalidate blocked_issues_cache","description":"## Problem\n\nTransaction operations in `transaction.go` do NOT call `invalidateBlockedCache()`, but the non-transaction equivalents in `queries.go` and `dependencies.go` do.\n\n## Affected Methods\n\n- `AddDependency` (lines 543-650) - missing when `dep.Type == DepBlocks || DepParentChild`\n- `RemoveDependency` (lines 653-688) - missing check for blocking type removal\n- `CloseIssue` (lines 470-503) - missing entirely\n- `UpdateIssue` (lines 334-418) - missing when status changes\n\n## Impact\n\nWhen using transactions to:\n- Add/remove blocking dependencies → `GetReadyWork` returns stale results\n- Close issues that are blockers → Issues remain incorrectly marked as blocked\n- Change issue status → Blocked cache becomes inconsistent\n\n## Evidence\n\n```bash\ngrep invalidateBlockedCache transaction.go # Returns: No matches found\n```\n\nCompare to `dependencies.go:156` and `queries.go:523,697` which properly call it.\n\n## Fix\n\nNeed to add cache invalidation. Complexity: current `invalidateBlockedCache` expects `*sql.Tx` but transaction uses `*sql.Conn`. Will need refactoring to support both (or extract common interface).","status":"open","priority":1,"issue_type":"bug","created_at":"2025-11-24T12:29:33.781937-08:00","updated_at":"2025-11-24T12:29:33.781937-08:00","source_repo":"."} {"id":"bd-1c63eb84","content_hash":"ffb879c48e5d99f98d0cf6efcb0e7c6940820e8936eabea009c8d365af5f9524","title":"Investigate jujutsu integration for beads","description":"Research and document how beads could integrate with jujutsu (jj), the next-generation VCS. Key areas to explore:\n- How jj's operation model differs from git (immutable operations, working-copy-as-commit)\n- JSONL sync strategy with jj's conflict resolution model\n- Daemon compatibility with jj's more frequent rewrites\n- Whether auto-import/export needs changes for jj workflows\n- Example configurations and documentation updates needed","status":"closed","priority":3,"issue_type":"task","created_at":"2025-10-23T09:23:23.582009-07:00","updated_at":"2025-11-05T14:30:10.416881-08:00","closed_at":"2025-11-05T14:26:17.967073-08:00","source_repo":"."} {"id":"bd-1c77","content_hash":"49c554748a8f61dc99eb6a942c620f5856f4c0d240678022f6ae998a102d591e","title":"Implement filesystem shims for WASM","description":"WASM needs JS shims for filesystem access. Child of epic bd-44d0.\n\n## Tasks\n- [ ] Implement file read/write shims\n- [ ] Map WASM syscalls to Node.js fs API\n- [ ] Handle .beads/ directory discovery\n- [ ] Test with real JSONL files\n- [ ] Support both absolute and relative paths\n\n## Technical Notes\n- Use Node.js fs module via syscall/js\n- Consider MEMFS for in-memory option","status":"closed","priority":0,"issue_type":"task","created_at":"2025-11-02T18:33:31.280464-08:00","updated_at":"2025-11-05T00:55:48.756428-08:00","closed_at":"2025-11-05T00:55:48.756432-08:00","source_repo":".","dependencies":[{"issue_id":"bd-1c77","depends_on_id":"bd-197b","type":"blocks","created_at":"2025-11-02T18:33:31.281134-08:00","created_by":"daemon"}]} {"id":"bd-1ece","content_hash":"95ec39ad0bf8c9514bc500b929a5996d026936b0fc037e19a99d2234e5315770","title":"Remove obsolete renumber.go command (hash IDs eliminated need)","description":"","status":"closed","priority":2,"issue_type":"chore","created_at":"2025-10-31T21:27:05.559328-07:00","updated_at":"2025-10-31T21:27:11.426941-07:00","closed_at":"2025-10-31T21:27:11.426941-07:00","source_repo":"."} @@ -198,7 +199,7 @@ {"id":"bd-6hji","content_hash":"6da407d81b32c439e93754b0d5322a6ba2a4377569b9f7a425d02c6b1b1987dc","title":"Test exclusive file reservations with two agents","description":"Simulate two agents racing to claim the same issue and verify that exclusive reservations prevent collision.\n\nAcceptance Criteria:\n- Agent A reserves bd-123 → succeeds\n- Agent B tries to reserve bd-123 → fails with clear error message\n- Agent B can see who has the reservation\n- Reservation expires after TTL\n- Agent B can claim after expiration","notes":"Successfully tested file reservations:\n- Agent BrownBear reserved bd-123 → granted\n- Agent ChartreuseHill tried same → conflicts returned\n- System correctly prevents collision","status":"closed","priority":0,"issue_type":"task","created_at":"2025-11-07T22:41:59.963468-08:00","updated_at":"2025-11-08T00:03:18.004972-08:00","closed_at":"2025-11-08T00:03:18.004972-08:00","source_repo":".","dependencies":[{"issue_id":"bd-6hji","depends_on_id":"bd-muls","type":"blocks","created_at":"2025-11-07T23:03:52.897843-08:00","created_by":"daemon"},{"issue_id":"bd-6hji","depends_on_id":"bd-27xm","type":"blocks","created_at":"2025-11-07T23:20:21.911222-08:00","created_by":"daemon"},{"issue_id":"bd-6hji","depends_on_id":"bd-spmx","type":"parent-child","created_at":"2025-11-08T00:02:47.904652-08:00","created_by":"daemon"}]} {"id":"bd-6ku3","content_hash":"44f4b7c866bd65391dccc5aadee556a7be9b07661e355018c6cb8906b73e3ab3","title":"Fix TestMigrateHashIDs test failure","description":"Test failure in cmd/bd/migrate_hash_ids_test.go:100 - New ID bd-09970281 for bd-1 is not a hash ID. This test is validating the hash ID migration but the generated ID doesn't match the expected format.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-11-06T18:52:58.114046-08:00","updated_at":"2025-11-06T19:04:58.804373-08:00","closed_at":"2025-11-06T19:04:58.804373-08:00","source_repo":"."} {"id":"bd-6mjj","content_hash":"2226a78fdb09302679f4fd9424d4e8c8fbdef1ef374bdd3789471b4c0868358d","title":"Split test suites: fast vs. integration","description":"Reorganize tests into separate packages/files for fast unit tests vs slow integration tests.\n\nBenefits:\n- Clear separation of concerns\n- Easier to run just fast tests during development\n- Can parallelize CI jobs better\n\nFiles to organize:\n- beads_hash_multiclone_test.go (slow integration tests)\n- beads_integration_test.go (medium-speed integration tests)\n- Other test files (fast unit tests)","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-04T01:24:21.040347-08:00","updated_at":"2025-11-04T10:38:12.408674-08:00","closed_at":"2025-11-04T10:38:12.408674-08:00","source_repo":".","dependencies":[{"issue_id":"bd-6mjj","depends_on_id":"bd-l5gq","type":"blocks","created_at":"2025-11-04T01:24:21.041228-08:00","created_by":"daemon"}]} -{"id":"bd-6pul","content_hash":"438d1300af1d2c9073daaef3327785dd0ce5d4e412ed91fa849673ab7f35602c","title":"Epic: Transaction Support for Atomic Operations","description":"Add RunInTransaction API to enable atomic multi-operation workflows.\n\nThis epic implements transaction support in the Beads storage layer, allowing clients (like VC) to execute multiple storage operations atomically. Without this, operations like plan approval that create issues, dependencies, and labels cannot guarantee all-or-nothing semantics.\n\n## Problem\n- Each storage method manages its own transaction\n- External callers cannot coordinate multiple operations atomically\n- Attempting to use BeginTx externally causes deadlocks (methods try to acquire separate connections)\n\n## Solution\nAdd `RunInTransaction(func(tx Transaction) error)` API where Transaction interface exposes key storage methods that execute within a single database transaction.\n\n## Success Criteria\n- WHEN multiple issues created in transaction THEN all succeed or all rollback\n- WHEN transaction commits THEN all changes are durable\n- WHEN transaction rolls back THEN no partial state remains\n- WHEN concurrent transactions compete THEN proper serialization via IMMEDIATE mode\n\nSee bd-9dbq for detailed design notes.","design":"Architecture:\n1. Add Transaction interface to storage/storage.go\n2. Add RunInTransaction to Storage interface \n3. Implement sqliteTxStorage in sqlite package\n4. Refactor internal helpers to accept *sql.Conn\n\nKey design decisions:\n- Use BEGIN IMMEDIATE for write lock acquisition\n- Dedicated connection per transaction (prevents pool contention)\n- Transaction interface mirrors Storage methods (subset)\n- Panic-safe with deferred rollback","status":"open","priority":1,"issue_type":"epic","created_at":"2025-11-24T11:36:18.525875-08:00","updated_at":"2025-11-24T11:36:18.525875-08:00","source_repo":"."} +{"id":"bd-6pul","content_hash":"438d1300af1d2c9073daaef3327785dd0ce5d4e412ed91fa849673ab7f35602c","title":"Epic: Transaction Support for Atomic Operations","description":"Add RunInTransaction API to enable atomic multi-operation workflows.\n\nThis epic implements transaction support in the Beads storage layer, allowing clients (like VC) to execute multiple storage operations atomically. Without this, operations like plan approval that create issues, dependencies, and labels cannot guarantee all-or-nothing semantics.\n\n## Problem\n- Each storage method manages its own transaction\n- External callers cannot coordinate multiple operations atomically\n- Attempting to use BeginTx externally causes deadlocks (methods try to acquire separate connections)\n\n## Solution\nAdd `RunInTransaction(func(tx Transaction) error)` API where Transaction interface exposes key storage methods that execute within a single database transaction.\n\n## Success Criteria\n- WHEN multiple issues created in transaction THEN all succeed or all rollback\n- WHEN transaction commits THEN all changes are durable\n- WHEN transaction rolls back THEN no partial state remains\n- WHEN concurrent transactions compete THEN proper serialization via IMMEDIATE mode\n\nSee bd-9dbq for detailed design notes.","design":"Architecture:\n1. Add Transaction interface to storage/storage.go\n2. Add RunInTransaction to Storage interface \n3. Implement sqliteTxStorage in sqlite package\n4. Refactor internal helpers to accept *sql.Conn\n\nKey design decisions:\n- Use BEGIN IMMEDIATE for write lock acquisition\n- Dedicated connection per transaction (prevents pool contention)\n- Transaction interface mirrors Storage methods (subset)\n- Panic-safe with deferred rollback","status":"open","priority":1,"issue_type":"epic","created_at":"2025-11-24T11:36:18.525875-08:00","updated_at":"2025-11-24T11:36:18.525875-08:00","source_repo":".","dependencies":[{"issue_id":"bd-6pul","depends_on_id":"bd-1c4h","type":"blocks","created_at":"2025-11-24T12:30:02.978716-08:00","created_by":"daemon"}]} {"id":"bd-6sd1","content_hash":"1db772b8c6d380085b5f9b5978cf9c853723c24b5aa9245b307e473ce894d1d5","title":"Issue to close","description":"","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-11-07T19:00:16.547698-08:00","updated_at":"2025-11-07T19:00:16.570826-08:00","closed_at":"2025-11-07T19:00:16.570826-08:00","source_repo":"."} {"id":"bd-6uix","content_hash":"13189ab05a00f5291ba60c8d3331d7f0d6aacbc9d14da79ca6344214eaf5d1ba","title":"Message System Improvements","description":"Consolidate improvements to the bd message command including core functionality (message reading), reliability (timeouts), validation, and code quality refactoring","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-11-08T12:55:47.907771-08:00","updated_at":"2025-11-08T12:59:05.802367-08:00","closed_at":"2025-11-08T12:59:05.802367-08:00","source_repo":"."} {"id":"bd-6z7l","content_hash":"96ccdda5d2ef893f70cba842f813665cd3a8ae05cdc5fffef5f8f8a17425f145","title":"Auto-detect scenarios and prompt users","description":"Detect when user is in fork/contributor scenario and prompt with helpful suggestions. Check: git remote relationships, existing .beads config, repo ownership. Suggest appropriate wizard.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-05T18:04:30.070695-08:00","updated_at":"2025-11-05T19:27:33.074733-08:00","closed_at":"2025-11-05T18:57:03.315476-08:00","source_repo":".","dependencies":[{"issue_id":"bd-6z7l","depends_on_id":"bd-8rd","type":"parent-child","created_at":"2025-11-05T18:04:39.205478-08:00","created_by":"daemon"}]} diff --git a/internal/storage/sqlite/blocked_cache.go b/internal/storage/sqlite/blocked_cache.go index 98cb21e1..49a8aa70 100644 --- a/internal/storage/sqlite/blocked_cache.go +++ b/internal/storage/sqlite/blocked_cache.go @@ -100,11 +100,10 @@ type execer interface { // rebuildBlockedCache completely rebuilds the blocked_issues_cache table // This is used during cache invalidation when dependencies change -func (s *SQLiteStorage) rebuildBlockedCache(ctx context.Context, tx *sql.Tx) error { - // Use the transaction if provided, otherwise use direct db connection - var exec execer = s.db - if tx != nil { - exec = tx +func (s *SQLiteStorage) rebuildBlockedCache(ctx context.Context, exec execer) error { + // Use direct db connection if no execer provided + if exec == nil { + exec = s.db } // Clear the cache @@ -152,6 +151,6 @@ func (s *SQLiteStorage) rebuildBlockedCache(ctx context.Context, tx *sql.Tx) err // invalidateBlockedCache rebuilds the blocked issues cache // Called when dependencies change or issue status changes -func (s *SQLiteStorage) invalidateBlockedCache(ctx context.Context, tx *sql.Tx) error { - return s.rebuildBlockedCache(ctx, tx) +func (s *SQLiteStorage) invalidateBlockedCache(ctx context.Context, exec execer) error { + return s.rebuildBlockedCache(ctx, exec) } diff --git a/internal/storage/sqlite/transaction.go b/internal/storage/sqlite/transaction.go index 1e4e0558..5e2bc14d 100644 --- a/internal/storage/sqlite/transaction.go +++ b/internal/storage/sqlite/transaction.go @@ -414,6 +414,14 @@ func (t *sqliteTxStorage) UpdateIssue(ctx context.Context, id string, updates ma return fmt.Errorf("failed to mark issue dirty: %w", err) } + // Invalidate blocked issues cache if status changed (bd-1c4h) + // Status changes affect which issues are blocked (blockers must be open/in_progress/blocked) + if _, statusChanged := updates["status"]; statusChanged { + if err := t.parent.invalidateBlockedCache(ctx, t.conn); err != nil { + return fmt.Errorf("failed to invalidate blocked cache: %w", err) + } + } + return nil } @@ -499,6 +507,12 @@ func (t *sqliteTxStorage) CloseIssue(ctx context.Context, id string, reason stri return fmt.Errorf("failed to mark issue dirty: %w", err) } + // Invalidate blocked issues cache since status changed to closed (bd-1c4h) + // Closed issues don't block others, so this affects blocking calculations + if err := t.parent.invalidateBlockedCache(ctx, t.conn); err != nil { + return fmt.Errorf("failed to invalidate blocked cache: %w", err) + } + return nil } @@ -646,11 +660,30 @@ func (t *sqliteTxStorage) AddDependency(ctx context.Context, dep *types.Dependen return fmt.Errorf("failed to mark depends-on issue dirty: %w", err) } + // Invalidate blocked cache for blocking dependencies (bd-1c4h) + if dep.Type == types.DepBlocks || dep.Type == types.DepParentChild { + if err := t.parent.invalidateBlockedCache(ctx, t.conn); err != nil { + return fmt.Errorf("failed to invalidate blocked cache: %w", err) + } + } + return nil } // RemoveDependency removes a dependency within the transaction. func (t *sqliteTxStorage) RemoveDependency(ctx context.Context, issueID, dependsOnID string, actor string) error { + // First, check what type of dependency is being removed (bd-1c4h) + var depType types.DependencyType + err := t.conn.QueryRowContext(ctx, ` + SELECT type FROM dependencies WHERE issue_id = ? AND depends_on_id = ? + `, issueID, dependsOnID).Scan(&depType) + + // Store whether cache needs invalidation before deletion + needsCacheInvalidation := false + if err == nil { + needsCacheInvalidation = (depType == types.DepBlocks || depType == types.DepParentChild) + } + result, err := t.conn.ExecContext(ctx, ` DELETE FROM dependencies WHERE issue_id = ? AND depends_on_id = ? `, issueID, dependsOnID) @@ -684,6 +717,13 @@ func (t *sqliteTxStorage) RemoveDependency(ctx context.Context, issueID, depends return fmt.Errorf("failed to mark depends-on issue dirty: %w", err) } + // Invalidate blocked cache if this was a blocking dependency (bd-1c4h) + if needsCacheInvalidation { + if err := t.parent.invalidateBlockedCache(ctx, t.conn); err != nil { + return fmt.Errorf("failed to invalidate blocked cache: %w", err) + } + } + return nil }