Skip to content

Commit 4ab6270

Browse files
kab0rnclaude
andcommitted
fix(test): keep CI on bd v0.57.0; add SDK-based assertConvoyExists; rewrite cross-rig dep targets to external: form
Reverts bd CLI version bump to v1.0.0 — bd v1.0.0 dropped the cross-DB literal-ID lookup that several scheduler integration tests rely on (addBeadDependencyOfType across rigs). v0.57.0 keeps that lookup working with SDK v1.0.0; the only place the v0.57.0 schema mismatch surfaces is \`bd show\` against a convoy in HQ, which now goes through the SDK (assertConvoyExists) instead of the CLI. Also makes addBeadDependencyOfType resolve cross-rig targets to the external:<rig>:<id> form so the same helper still works under bd v1.0.0 when we eventually bump again. Switches getBeadDescription/beadHasLabel to MaybePrependAllowStale so they tolerate either bd version. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent b71fc27 commit 4ab6270

4 files changed

Lines changed: 98 additions & 35 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ jobs:
175175

176176
- name: Install beads (bd)
177177
if: steps.cache-beads-int.outputs.cache-hit != 'true'
178-
run: go install github.com/steveyegge/beads/cmd/bd@v1.0.0
178+
run: go install github.com/steveyegge/beads/cmd/bd@v0.57.0
179179

180180
- name: Install gotestsum
181181
run: go install gotest.tools/gotestsum@latest

.github/workflows/nightly-integration.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333

3434
- name: Install beads (bd)
3535
if: steps.cache-beads.outputs.cache-hit != 'true'
36-
run: go install github.com/steveyegge/beads/cmd/bd@v1.0.0
36+
run: go install github.com/steveyegge/beads/cmd/bd@v0.57.0
3737

3838
- name: Install Dolt
3939
run: |

internal/cmd/scheduler_integration_test.go

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -310,33 +310,10 @@ func TestSchedulerAutoConvoyCreation(t *testing.T) {
310310
t.Fatalf("convoy ID not stored in sling context")
311311
}
312312

313-
// Verify: convoy is resolvable via bd show from hq.
314-
// --allow-stale is a global flag: must come before the subcommand.
315-
// Use Output() so stderr (permission/deprecation warnings) doesn't pollute JSON.
316-
showArgs := beads.MaybePrependAllowStale([]string{"show", fields.Convoy, "--json"})
317-
cmd := exec.Command("bd", showArgs...)
318-
cmd.Dir = hqPath
319-
out, err := cmd.Output()
320-
if err != nil {
321-
stderr := ""
322-
if ee, ok := err.(*exec.ExitError); ok {
323-
stderr = string(ee.Stderr)
324-
}
325-
t.Fatalf("bd show convoy %s failed: %v\nstdout: %s\nstderr: %s", fields.Convoy, err, out, stderr)
326-
}
327-
var convoys []struct {
328-
ID string `json:"id"`
329-
IssueType string `json:"issue_type"`
330-
}
331-
if err := json.Unmarshal(out, &convoys); err != nil {
332-
t.Fatalf("parse convoy show (output=%q): %v", out, err)
333-
}
334-
if len(convoys) == 0 {
335-
t.Fatalf("convoy %s not found via bd show", fields.Convoy)
336-
}
337-
if convoys[0].IssueType != "convoy" {
338-
t.Errorf("convoy issue_type = %q, want %q", convoys[0].IssueType, "convoy")
339-
}
313+
// Verify: convoy is resolvable via the SDK store (bd CLI's `bd show` runs a
314+
// schema-dependent SQL path that breaks across CLI/SDK version skews; querying
315+
// through the SDK is what production code does anyway).
316+
assertConvoyExists(t, hqPath, fields.Convoy)
340317

341318
// Verify: convoy has a "tracks" dependency pointing to the rig bead.
342319
// This is the core cross-rig link: convoy lives in HQ DB, bead in rig DB.
@@ -354,6 +331,46 @@ func TestSchedulerAutoConvoyCreation(t *testing.T) {
354331
assertConvoyTracksBead(t, hqPath, fields.Convoy, beadID)
355332
}
356333

334+
// assertConvoyExists verifies a convoy issue exists in the HQ DB and is of type
335+
// "convoy". Uses the SDK store directly to avoid bd CLI/SDK schema skew.
336+
func assertConvoyExists(t *testing.T, hqPath, convoyID string) {
337+
t.Helper()
338+
339+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
340+
defer cancel()
341+
342+
b := beads.NewWithBeadsDir(hqPath, filepath.Join(hqPath, ".beads"))
343+
store, cleanup, err := b.OpenStore(ctx)
344+
if err != nil {
345+
t.Fatalf("open hq store: %v", err)
346+
}
347+
defer cleanup()
348+
349+
var issueType string
350+
var found bool
351+
txErr := store.RunInTransaction(ctx, "", func(tx beadsdk.Transaction) error {
352+
issue, err := tx.GetIssue(ctx, convoyID)
353+
if err != nil {
354+
return err
355+
}
356+
if issue == nil {
357+
return nil
358+
}
359+
found = true
360+
issueType = string(issue.IssueType)
361+
return nil
362+
})
363+
if txErr != nil {
364+
t.Fatalf("get convoy %s: %v", convoyID, txErr)
365+
}
366+
if !found {
367+
t.Fatalf("convoy %s not found in hq store", convoyID)
368+
}
369+
if issueType != "convoy" {
370+
t.Errorf("convoy issue_type = %q, want %q", issueType, "convoy")
371+
}
372+
}
373+
357374
// assertConvoyTracksBead verifies the HQ convoy has a "tracks" dependency
358375
// referencing the rig bead. The stored depends_on_id is either the bead ID
359376
// (same-rig) or `external:<rig>:<beadID>` (cross-rig).

internal/cmd/scheduler_test_helpers_test.go

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ import (
1010
"os"
1111
"os/exec"
1212
"path/filepath"
13+
"strings"
1314
"testing"
1415

16+
"github.com/steveyegge/gastown/internal/beads"
1517
"github.com/steveyegge/gastown/internal/config"
1618
"github.com/steveyegge/gastown/internal/scheduler/capacity"
1719
"github.com/steveyegge/gastown/internal/testutil"
@@ -145,7 +147,8 @@ func createTestBead(t *testing.T, dir, title string) string {
145147
// Runs bd show --json from dir and inspects the labels array.
146148
func beadHasLabel(t *testing.T, beadID, label, dir string) bool {
147149
t.Helper()
148-
cmd := exec.Command("bd", "show", beadID, "--json", "--allow-stale")
150+
args := beads.MaybePrependAllowStale([]string{"show", beadID, "--json"})
151+
cmd := exec.Command("bd", args...)
149152
cmd.Dir = dir
150153
out, err := cmd.Output()
151154
if err != nil {
@@ -171,7 +174,8 @@ func beadHasLabel(t *testing.T, beadID, label, dir string) bool {
171174
// getBeadDescription returns the description of a bead via bd show --json.
172175
func getBeadDescription(t *testing.T, beadID, dir string) string {
173176
t.Helper()
174-
cmd := exec.Command("bd", "show", beadID, "--json", "--allow-stale")
177+
args := beads.MaybePrependAllowStale([]string{"show", beadID, "--json"})
178+
cmd := exec.Command("bd", args...)
175179
cmd.Dir = dir
176180
out, err := cmd.Output()
177181
if err != nil {
@@ -220,14 +224,56 @@ func addBeadDependency(t *testing.T, blocked, blocker, dir string) {
220224
}
221225

222226
// addBeadDependencyOfType adds a dependency with a specific type (e.g., "tracks",
223-
// "depends_on"). The from bead must exist in the local DB at dir; the to bead can
224-
// be in a different DB if routes.jsonl is present in dir's .beads/.
227+
// "depends_on"). The from bead must exist in the local DB at dir; if the to bead
228+
// lives in a different rig DB (per the town's routes.jsonl), it is rewritten as
229+
// an external:<rig>:<id> reference, which is bd v1.0.0's required cross-DB form.
225230
func addBeadDependencyOfType(t *testing.T, from, to, depType, dir string) {
226231
t.Helper()
227-
cmd := exec.Command("bd", "dep", "add", from, to, "--type="+depType)
232+
target := resolveCrossRigTarget(t, dir, from, to)
233+
cmd := exec.Command("bd", "dep", "add", from, target, "--type="+depType)
228234
cmd.Dir = dir
229235
if out, err := cmd.CombinedOutput(); err != nil {
230-
t.Fatalf("bd dep add %s %s --type=%s failed: %v\n%s", from, to, depType, err, out)
236+
t.Fatalf("bd dep add %s %s --type=%s failed: %v\n%s", from, target, depType, err, out)
237+
}
238+
}
239+
240+
// resolveCrossRigTarget returns `to` unchanged when `from` and `to` share a DB,
241+
// otherwise returns external:<rig>:<to>. Walks up from dir to find the town
242+
// .beads/routes.jsonl that maps prefixes → rig paths.
243+
func resolveCrossRigTarget(t *testing.T, dir, from, to string) string {
244+
t.Helper()
245+
if strings.HasPrefix(to, "external:") {
246+
return to
247+
}
248+
fromPrefix := beads.ExtractPrefix(from)
249+
toPrefix := beads.ExtractPrefix(to)
250+
if fromPrefix == "" || toPrefix == "" || fromPrefix == toPrefix {
251+
return to
252+
}
253+
townRoot := findTownRootFromDir(dir)
254+
if townRoot == "" {
255+
return to
256+
}
257+
rigName := beads.GetRigNameForPrefix(townRoot, toPrefix)
258+
if rigName == "" {
259+
return to
260+
}
261+
return "external:" + rigName + ":" + to
262+
}
263+
264+
// findTownRootFromDir walks up from start until it finds a .beads/routes.jsonl, then
265+
// returns that directory. Returns "" if no routes file is found.
266+
func findTownRootFromDir(start string) string {
267+
cur := start
268+
for {
269+
if _, err := os.Stat(filepath.Join(cur, ".beads", "routes.jsonl")); err == nil {
270+
return cur
271+
}
272+
parent := filepath.Dir(cur)
273+
if parent == cur {
274+
return ""
275+
}
276+
cur = parent
231277
}
232278
}
233279

0 commit comments

Comments
 (0)