Skip to content

feat: tracker hardening, graph integrity, and ready --explain#2814

Open
harry-miller-trimble wants to merge 18 commits intosteveyegge:mainfrom
harry-miller-trimble:feat/tracker-hardening-and-core
Open

feat: tracker hardening, graph integrity, and ready --explain#2814
harry-miller-trimble wants to merge 18 commits intosteveyegge:mainfrom
harry-miller-trimble:feat/tracker-hardening-and-core

Conversation

@harry-miller-trimble
Copy link
Contributor

Summary

Runtime hardening for all tracker integrations, dependency graph integrity checks, and a new bd ready --explain command for AI-supervised workflows. Addresses 11 issues filed from a comprehensive codebase audit.

Changes

Runtime Hardening (tracker clients)

  • Jira: Full retry loop with exponential backoff, Retry-After header support, MaxPages=1000 pagination guard, MaxResponseSize=50MB response limit
  • GitHub/GitLab/ADO: Add jitter to exponential backoff; fix jitter not applied to server-mandated Retry-After delays (respects rate limit contracts)
  • Linear: MaxPages=1000 pagination guard, io.LimitReader response size limit, ctx.Done() checks in pagination loops
  • Terminal safety: SanitizeForTerminal strips ANSI escapes and control characters from external tracker content at display time (raw data preserved in Dolt)

Graph Integrity

  • Cycle detection: Extended from blocks to include conditional-blocks dependency type
  • Self-dependency: Explicit guard with clear error message (previously caught as trivial cycle)
  • bd graph check: New subcommand for forensic cycle detection

New Feature: bd ready --explain

  • Shows dependency-aware reasoning: why each issue is ready or blocked
  • Displays resolved blockers, blocker details, dependency counts, cycle warnings
  • Supports --json for programmatic consumption by AI agents
  • Core logic extracted into types.BuildReadyExplanation for testability

Sync Engine

  • SyncResult.Warnings now populated (was always empty despite field existing)
  • createDependencies returns error count; failures surfaced via warnings
  • PullStats.Errors tracks external_ref update failures

Security & Permissions

  • .beads/ directory enforced at 0700 (warn-only, non-fatal on existing dirs)
  • SECURITY.md expanded with trust model, credential guidance, AI content safety
  • docs/INTEGRATION_CHARTER.md: formal scope boundary for tracker integrations

Documentation & Tests

  • docs/QUICKSTART.md: "Why Beads?" section, --explain examples
  • Compaction dry-run/analyze improved with per-issue size details
  • ADO test env var leakage fixed (t.Setenv)
  • 19 new test cases: sanitization (16), permissions (6), retry logic (4), explain builder (8), cycle detection (7), engine warnings (4)

Backward Compatibility

  • .beads/ permissions: Warning only — existing dirs with 0755 get a log warning, not a failure. Windows is a no-op.
  • Cycle detection: Write-path only — existing databases with historical cycles won't crash on read. New violations are prevented on write.
  • SyncResult.Warnings: New field in JSON output. Consumers ignoring unknown fields are unaffected.
  • bd graph check: New command, no impact on existing commands.

Test Coverage

Component Coverage
SanitizeForTerminal 88.5%
BuildReadyExplanation 100%
config/permissions 100%
Jira retry + pagination 76-81%
ADO package 92.5%
Types package overall 91.0%

Checklist

  • go build ./... passes
  • go test -short ./... passes (all non-Dolt packages)
  • make test-full-cgo passes (full suite with Docker)
  • No new lint warnings
  • 16 atomic commits, each independently buildable

harry-miller-trimble and others added 18 commits March 24, 2026 15:14
- Add retry with exponential backoff to Jira doRequest (was single-attempt)
- Add MaxRetries, RetryDelay, MaxResponseSize constants to jira/types.go
- Add response body size limit via io.LimitReader to Jira client
- Handle permanent errors (400/401/403/404) without retry
- Parse Retry-After header for rate-limited responses
- Add jitter (0-50% of delay) to backoff in all 5 tracker clients:
  GitHub, GitLab, Linear, ADO, and Jira
- Guard rand.Int64N against zero delay to prevent panics
- Add retry tests: 429, 503, no-retry-on-400, max-retries-exceeded

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Linear FetchIssues/FetchIssuesSince: add MaxPages=1000 guard to prevent
  infinite loops from malformed API responses with hasNextPage=true
- Linear: add ctx.Done() checks at start of each pagination iteration
- Jira SearchIssues: add ctx.Done() check at start of each pagination iteration
- Add MaxPages constant to internal/linear/types.go

Fixes: bd-6fy

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…se size limits

- Add ui.SanitizeForTerminal() to strip ANSI escape sequences, OSC sequences,
  and C0/C1 control characters from external content before terminal display.
  Preserves printable UTF-8, newlines, and tabs.
- Apply sanitization in tracker engine dry-run output (3 call sites)
- Add MaxResponseSize (50MB) and io.LimitReader to Linear client to prevent
  OOM from malformed API responses (was the only tracker without this guard)
- Add comprehensive test suite for sanitization (17 test cases including
  ANSI injection, screen clearing, OSC title change, control characters)

Fixes: bd-u9u

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Formally documents what beads tracker integrations will and will not build.
Establishes that integrations are an adoption bridge, not a product.

Explicit out-of-scope: webhooks, cross-tracker orchestration, attachment
sync, comment mirroring, credential vaults, UI parity features.

Includes design guidelines for new integrations and a decision log.

Fixes: bd-9go

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Collect warnings in Engine.warnings during Sync() and populate
  result.Warnings (field existed but was never used)
- Fix createDependencies: return error count, stop silently discarding
  GetIssueByExternalRef errors (previously used _ discard)
- Fix external_ref update failure: now increments stats.Errors when
  the local link-back fails after creating an external issue
- Add Errors field to PullStats and propagate to SyncResult
- Dependency creation failures now counted as Skipped in pull stats

Fixes: bd-5cx

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
bd ready --explain provides a comprehensive view of the dependency graph:
- Ready items with reasoning (resolved blockers, dependency counts)
- Blocked items with detailed blocker info (ID, title, status, priority)
- Cycle detection results
- Aggregate summary stats

Works in both JSON (--json) and human-readable text modes. JSON output
gives agents structured data for graph-aware work prioritization.

No storage layer changes — all data comes from existing GetReadyWork,
GetBlockedIssues, GetDependencyRecordsForIssues, and DetectCycles APIs.

New types: ReadyExplanation, ReadyItem, BlockedItem, BlockerInfo,
ExplainSummary (all in internal/types/types.go).

Fixes: bd-7zt

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add documentation for:
- Trust boundary: all external tracker data treated as untrusted
- Content sanitization (ANSI stripping, response size limits)
- Credential handling: recommend platform-native auth over plaintext tokens
- Sync security model: user-initiated only, no daemons or webhooks
- AI agent content safety: prompt injection awareness
- Dependency pinning via go.sum

Fixes: bd-68o

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Extend write-path CTE and DFS cycle detection to include conditional-blocks
  (also creates blocking relationships, same as blocks)
- Add explicit self-dependency check in AddDependencyInTx
- Add bd graph check subcommand: runs DetectCycles, reports results in
  JSON or human-readable format, exits with code 1 if issues found

waits-for is intentionally excluded from cycle detection — it uses gate
semantics (fan-in) rather than direct blocking, so A waits-for B does
not mean B blocks A in the traditional sense.

Fixes: bd-2qr

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add 'Why Beads?' section contrasting flat trackers vs dependency-aware ready queue
- Add bd ready --explain example showing full graph reasoning output
- Document bd ready vs bd list --status open distinction
- Add graph check and explain commands to Next Steps section

Fixes: bd-47m

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
TestTracker_InitMissingPAT, InitMissingOrg, and InitMissingProject were
not isolating from environment variables. When AZURE_DEVOPS_PAT is set
in the environment (common in CI and dev), getConfig falls through to
os.Getenv and the PAT check passes, causing the test to hit the org
validation instead and fail.

Fix: Use t.Setenv to clear all ADO env vars in tests that assert
specific missing-config errors. t.Setenv automatically restores the
original values after the test.

Fixes: bd-czk

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
All three compact paths (single, all, analyze) now show consistent
structured output:
- Per-candidate details: ID, title, closed date, age in days, content size
- Summary footer: total candidates and aggregate content bytes
- JSON output uses {candidates: [...], summary: {...}} structure

Text output uses a tabular format for easy scanning. No changes to
actual compaction behavior.

Fixes: bd-oxs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…f-deps

- Enhance mockTracker with createFailAfter/createCount for partial
  failure simulation (previously only supported all-or-nothing errors)
- TestEnginePushPartialFailure: 3 issues, fail after 2 creates,
  asserts Created=2, Errors=1, warnings collected
- TestEngineSyncCollectsWarnings: verifies result.Warnings is populated
  from push failures (tests the bd-5cx warning collection fix)
- TestAddDependency_SelfDependencyRejected: verifies self-dependency
  check returns error (tests the bd-2qr self-dep guard)

Fixes: bd-3jd

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add config.BeadsDirPerm (0700) and config.BeadsFilePerm (0600) constants
- Add config.CheckBeadsDirPermissions() for startup warning if permissions
  are too permissive (non-fatal, checks group/world bits)
- Add config.EnsureBeadsDir() helper for consistent directory creation
- Tighten all MkdirAll calls for .beads/ paths from 0750/0755 to 0700:
  init.go, doltserver.go, credentials.go, audit.go, recipes.go,
  embeddeddolt store.go and flock.go
- Add startup check in loadEnvironment() (cmd/bd/main.go)
- Windows build tag with no-op implementation (ACL-based permissions)
- 6 tests: constants, dir creation perms, warning on permissive, no
  warning on secure, no error on nonexistent

Fixes: bd-kl6

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add MaxPages=1000 pagination guard to SearchIssues() to match the pattern
already implemented in Linear, GitHub, and GitLab clients. Without this,
a malicious or buggy Jira instance returning inflated Total values could
cause an infinite pagination loop.

Also fix jitter application: only add jitter to our own exponential
backoff delays, not to server-mandated Retry-After values. Adding jitter
to Retry-After could violate rate limit contracts.

Additionally guard against zero-length pages which could also cause
infinite loops even within the MaxPages limit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Apply the same fix as the Jira client: only add jitter to our own
exponential backoff delays, not to server-mandated Retry-After values.
Adding jitter to Retry-After could violate rate limit contracts by
waiting less than the server-specified duration.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rnings

Extract BuildReadyExplanation into types package as a pure function,
enabling unit tests without Dolt dependency. Add 8 test cases covering:
empty input, ready with/without deps, resolved blockers, parent links,
blocked issues with missing blockers, cycles, and full scenario.

Add 7 Dolt integration tests for cycle detection:
- conditional-blocks cycles (prevented by AddDependency)
- mixed blocks + conditional-blocks cycles
- waits-for intentionally excluded from cycle detection
- self-dependency rejected for all dependency types
- ready work respects conditional-blocks
- blocked issues include blocker details

Add 4 engine tests:
- createDependencies with valid deps
- createDependencies with unresolvable external refs (warns)
- createDependencies with empty input
- warn() collects messages and triggers OnWarning callback

Types package coverage: 89.6% → 91.0%

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…koff)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant