Shunt Fork — Interface Abstraction Spec
Shunt Fork — Interface Abstraction Spec
Section titled “Shunt Fork — Interface Abstraction Spec”Target: git.rbtr.dev/laputacloudco/shunt (our fork of rbtr/shunt)
Purpose: Abstract forge and metrics dependencies so the engine can run
in Cloudflare Workers (which has no net/http TCP transport, no filesystem,
and no process spawning).
The engine’s core logic stays unchanged. We swap concrete types for interfaces that both the standard Go implementation and a Workers-compatible implementation can satisfy.
Changes Required
Section titled “Changes Required”1. internal/forge/client.go — Add ForgeClient interface
Section titled “1. internal/forge/client.go — Add ForgeClient interface”Add this interface after the Client type definition. The Client struct
already implements all these methods, so no changes to the existing client.
// ForgeClient is the interface that *Client implements.// Both the standard *Client and a Workers-compatible implementation satisfy this.type ForgeClient interface { ListOpenPRs(ctx context.Context, owner, repo, base string) ([]PullRequest, error) GetPR(ctx context.Context, owner, repo string, index int) (PullRequest, error) AutomergeScheduled(ctx context.Context, owner, repo string, index int) (bool, error) LatestCommitStatus(ctx context.Context, owner, repo, sha, statusContext string) (CommitStatus, bool, error) RunStatus(ctx context.Context, owner, repo, sha, branch string) (string, error) RunTargetURL(ctx context.Context, owner, repo, sha, branch string) (string, error) SetCommitStatus(ctx context.Context, owner, repo, sha, statusContext, state, desc, targetURL string) error ScheduleAutomerge(ctx context.Context, owner, repo string, index int, style, headSHA string) (ScheduleAutomergeResult, error) CancelAutomerge(ctx context.Context, owner, repo string, index int) (bool, error) DeleteBranch(ctx context.Context, owner, repo, branch string) error Comment(ctx context.Context, owner, repo string, index int, body string) error UpsertComment(ctx context.Context, owner, repo string, index int, marker, botUser, body string) error ReadFile(ctx context.Context, owner, repo, ref, path string) ([]byte, error)}No other changes needed to internal/forge/client.go. The existing *Client
satisfies this interface by virtue of implementing all the methods.
2. internal/engine/engine.go — Swap *forge.Client for interface
Section titled “2. internal/engine/engine.go — Swap *forge.Client for interface”Change the Config struct:
// Before:type Config struct { Forge *forge.Client // concrete type ...}
// After:type Config struct { Forge forge.ForgeClient // interface ...}That’s the only change needed in the engine. All callers that create an
engine.Engine need to pass a forge.ForgeClient instead of *forge.Client.
Since *forge.Client implements ForgeClient, existing production code
continues to work without changes.
3. internal/engine/engine.go — Make metrics optional
Section titled “3. internal/engine/engine.go — Make metrics optional”Some engine methods call e.cfg.Metrics without checking for nil. Add nil
guards:
// Before:e.cfg.Metrics.Observe(...)
// After:if e.cfg.Metrics != nil { e.cfg.Metrics.Observe(...)}Search for all occurrences of e.cfg.Metrics. in engine.go and wrap them in
nil checks. There are about 3-4 such call sites.
4. internal/metrics/metrics.go — Make HTTP handler nil-safe
Section titled “4. internal/metrics/metrics.go — Make HTTP handler nil-safe”The Handler() method may panic or misbehave if no registry is configured.
Make it return nil instead:
func (c *Collector) Handler() http.Handler { if c.registry == nil { return nil } // ... existing logic}Same for StatusHandler() and StatusPageHandler().
5. No changes needed to internal/gitops/
Section titled “5. No changes needed to internal/gitops/”The Stager interface is already an interface. No changes needed.
6. No changes needed to internal/checkpoint/
Section titled “6. No changes needed to internal/checkpoint/”The Store interface is already an interface. No changes needed.
Summary of changes
Section titled “Summary of changes”| File | Change | Lines affected |
|---|---|---|
internal/forge/client.go |
Add ForgeClient interface |
~20 new lines |
internal/engine/engine.go |
*forge.Client → forge.ForgeClient in Config |
1 line |
internal/engine/engine.go |
Nil guards around e.cfg.Metrics.* calls |
~5 lines |
internal/metrics/metrics.go |
Nil-safe Handler(), StatusHandler(), StatusPageHandler() |
~6 lines |
Total: ~30 lines of changes, all additive. No behavior changes for existing shunt deployments.
After merge
Section titled “After merge”gondolier imports the fork:
go get git.rbtr.dev/laputacloudco/shunt@latestgondolier provides:
internal/forge/— Workers-compatibleForgeClientimplementationinternal/gitops/— Workers-compatibleStagerimplementationinternal/io/— I/O adapter that wires engine → Workers