// Package coding orchestrates ONE autonomous coding run: open a live agent
// session, dispatch the job to the bot-gateway sandbox runtime, mirror the
// sandbox's progress into the session, verify the pushed branch landed in native
// git, open a native "PR" work item, and return a Result the caller renders.
//
// It is a LIBRARY, not an app: no route, no plugin, no manifest row. Its one
// caller is apps/integrations (the Slack `code:` trigger). It touches its
// collaborators only through interface seams (Sessions, Tracker, Runner) plus two
// git functions (CloneURL, VerifyRef), so the whole orchestration is unit-testable
// with fakes and — critically — coding does NOT import apps/git: git imports
// apps/integrations, integrations calls coding, so coding->git would cycle.
// The composition root assembles the real Dispatcher (adapters.go) and injects
// it into the trigger surface.
//
// ISOLATION: org is the ONLY tenant key and is threaded to every seam call
// (session, tracker, git, and the bot-gateway X-Org-Id). A run for org A can only
// ever open A's session, read/verify A's repo, and file A's PR. The clone URL is
// built from (org, repo) so the sandbox is pointed only at this org's namespace,
// and the credential (write-only) is scoped by IAM to this org at the edge.
package coding
import (
"context"
"encoding/json"
"regexp"
"strings"
)
// RepoRE is the repo-name rule every door validates against, mirroring the git
// subsystem's own name check. It is here and exported because a door that
// invented its own rule would eventually accept a token carrying a path
// separator, and a repo name that can carry a path can address another org's
// namespace.
var RepoRE = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$`)
// OrgRE is the tenant-name rule, mirroring the git subsystem's own (git.go
// orgRE). A run's org is interpolated into a git namespace, so it is
// shape-checked rather than merely required: a tenant name that can carry a
// separator or a `..` can address another tenant's repositories.
var OrgRE = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$`)
// BaseRE is the branch-name rule, mirroring the git subsystem's own (push.go
// branchRE) — nested names allowed, so `release/2.1` works.
//
// The load-bearing property is the FIRST character class: a branch is alnum-led,
// so a base can never begin with '-'. The base reaches a `git clone -b `
// argument on a machine we do not own, where a leading dash makes it a flag
// rather than a branch, and `--upload-pack=` / `--config=core.fsmonitor=` are
// each arbitrary command execution on that machine. Length and the absence of a
// space matter too, but the dash is the one that turns data into a program.
var BaseRE = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._/-]{0,127}$`)
// BranchFor is the ONE branch a run is permitted to write, derived from the
// session that owns it. It is a pure function of the session id — not a name the
// caller supplies and not a name the model chooses — which is what lets the
// forge's ref policy state the rule structurally: a coding run writes
// refs/heads/agent/, and nothing else, ever.
func BranchFor(sessionID string) string { return "agent/" + shortID(sessionID) }
// Event kinds mirrored into the agent session. These are the agents session
// vocabulary (a stable wire contract): a phase is a tool-call, a free line is a
// log, a lifecycle transition is a status. Kept as local constants so coding does
// not import the agents package for three strings.
const (
kindToolCall = "tool-call"
kindLog = "log"
kindStatus = "status"
)
const (
statusDone = "done"
statusError = "error"
)
// Sessions is the live agent-session registry seam (clients/agents in-process).
type Sessions interface {
Open(ctx context.Context, org, actor, agent, title string) (string, error)
// OpenOn opens a session tagged with the run's dispatch TARGET, so a routed
// run shows in mission-control on the machine it was sent to. An empty target
// behaves exactly like Open.
OpenOn(ctx context.Context, org, actor, agent, title, target string) (string, error)
Log(ctx context.Context, org, sessionID, kind, actor string, payload []byte) error
Close(ctx context.Context, org, sessionID, status string) error
}
// Tracker is the work-item seam (clients/tracker in-process).
type Tracker interface {
CreatePR(ctx context.Context, in PRInput) (PRRef, error)
}
// Runner is the bot-gateway coding-task seam (clients/bot in-process client).
type Runner interface {
Run(ctx context.Context, org, userID string, req RunRequest, onStep func(Step)) (RunResult, error)
}
// PRInput / PRRef mirror tracker's agent-PR shape without leaking its types into
// the seam (the adapter bridges).
type PRInput struct {
Org string
Project string
Repo string
Base string
Head string
Title string
Body string
Assignee string
}
type PRRef struct {
Identifier string
ProjectKey string
Number int
}
// RunRequest / Step / RunResult mirror the bot coding contract.
type RunRequest struct {
CloneURL string
BaseBranch string
Branch string
Prompt string
SessionID string
RunTimeoutSeconds int
CredUser string
CredToken string // write-only secret — never logged
}
type Step struct {
Type string
Step string
Message string
Status string
}
type RunResult struct {
Branch string
CommitSha string
Diffstat string
Changed bool
OK bool
LogTail string
Error string
}
// Req is one coding request the trigger surface dispatches. Credential is the
// per-org agent git secret the caller resolved from KMS fail-closed; it is
// relayed to the sandbox and NEVER logged or placed in a session event.
//
// TargetID, when set, ROUTES the run to a registered machine (#48): the run is
// enqueued as a durable task addressed to that target instead of executing in the
// cloud-side sandbox, and the credential is NOT used (the machine authenticates
// with its own). When empty, the local sandbox path runs unchanged.
type Req struct {
Org string
UserID string // linked Hanzo subject — session attribution + X-User-Id
AgentRef string // agent label (e.g. "hanzo")
Repo string
Project string // IAM project slug (tracker + git scope); "" = org default
Base string // base branch; "" = repo default
Prompt string
CredUser string
CredToken string
TimeoutSeconds int
TargetID string // when set, route to this registered machine instead of the sandbox
// SessionID adopts an ALREADY-OPEN session instead of opening one. The door
// (Start) opens it so it can answer with a real handle the moment the run is
// admitted, rather than an empty promise the caller cannot watch. Empty keeps
// the original behaviour — Run opens its own — which is what the unit tests
// exercise and what a direct Dispatcher caller still gets.
SessionID string
}
// RoutedRun is the NON-SECRET spec coding hands the Route seam to enqueue on the
// durable engine. It mirrors agents.RoutedRun so coding.go stays pure (no agents
// import); the adapter bridges the two, exactly as PRInput/RunRequest mirror
// their downstream types. It carries no credential by design — the executing
// machine authenticates git + model routing with its own already-held creds.
type RoutedRun struct {
Org string
TargetID string
SessionID string
Repo string
Project string
Base string
Branch string
Prompt string
CloneURL string
TimeoutSeconds int
// Actor + AgentRef are the dispatching user + agent label, carried so the durable
// completion path can attribute the session close and file the PR with the same
// assignee the local path uses. Neither is a secret and neither crosses to the
// machine (the durable view the machine claims omits them).
Actor string
AgentRef string
}
// Result is the terminal outcome the trigger surface renders.
type Result struct {
SessionID string
Repo string
Branch string
CommitSha string
Diffstat string
Changed bool
OK bool
Verified bool // pushed branch confirmed present in native git
PR PRRef
LogTail string
Error string
// Routed reports that the run was ENQUEUED to a target machine rather than run
// in the cloud sandbox. When true, OK means "accepted + queued" (not
// "completed"): the terminal outcome flows through the session stream as the
// machine executes. TargetID is the machine it was routed to.
Routed bool
TargetID string
}
// Runner-facing behavioral defaults.
const (
defaultTimeoutSeconds = 1200 // 20 min sandbox budget when the caller sets none
maxTitlePrompt = 120
maxPromptLen = 32 << 10 // 32 KiB — a task prompt, not a document
)
// Dispatcher wires the seams. The two git functions are injected (not an
// interface) because they are pure reads with no cloud-side state. Both take a
// ctx: git is another PROCESS, so both are calls that can be slow, refused, or
// cancelled with the run.
type Dispatcher struct {
Sessions Sessions
Tracker Tracker
Runner Runner
CloneURL func(ctx context.Context, org, repo string) string
VerifyRef func(ctx context.Context, org, repo, branch string) (string, bool)
// Log is an optional structured log seam for best-effort mirror failures; nil
// is fine (mirror failures are non-fatal and simply dropped).
Log func(msg string, kv ...any)
// Route enqueues a routed run on the durable engine (the tasks-engine binding
// in routed.go). Nil disables routing — a run with a TargetID then fails
// closed rather than silently running in the sandbox.
Route func(ctx context.Context, run RoutedRun) error
// TargetGate is the fail-closed liveness+existence check for a routed run's
// target (agents.TargetDispatchable): the target exists in this org, is
// online, and has a live runner. Nil disables routing.
TargetGate func(ctx context.Context, org, targetID string) error
// Watch observes every event the run mirrors into its session — the SAME
// events, at the same moment, from the one place a run narrates itself.
//
// It exists because a run is watched from more than one place and must not
// grow a second narration to serve each. The session stream is the primary
// feed; a chat thread is a second reader of the same story. Adding a hook
// here rather than a Slack call inside the orchestration is what keeps
// coding.go ignorant of chat: it emits events, and who listens is a
// composition decision made where the run is started.
//
// Nil is the ordinary case and costs nothing. It is best-effort by
// construction — it runs beside a mirror that is itself best-effort, and a
// watcher that fails must never fail the run whose work already happened.
Watch func(ctx context.Context, kind string, payload []byte)
}
// Run executes one coding job end to end and returns its Result. It never
// panics on a seam failure (each is turned into a recorded error); the caller
// runs it under a bounded, recovered goroutine with a deadline ctx.
func (d Dispatcher) Run(ctx context.Context, req Req) Result {
org := strings.TrimSpace(req.Org)
repo := strings.TrimSpace(req.Repo)
prompt := strings.TrimSpace(req.Prompt)
res := Result{Repo: repo}
// Boundary validation (fail closed): a coding run needs a tenant, a repo, a
// task, and a credential to reach native git.
if org == "" || repo == "" {
res.Error = "org and repo are required"
return res
}
if prompt == "" {
res.Error = "empty task"
return res
}
if len(prompt) > maxPromptLen {
prompt = prompt[:maxPromptLen]
}
// ROUTED PATH (#48): a run with a chosen target is ENQUEUED as a durable task
// addressed to that machine and executed THERE, never in the cloud sandbox. It
// carries no credential (the machine uses its own), so it branches BEFORE the
// credential gate; everything below — the local sandbox path — is unchanged.
if strings.TrimSpace(req.TargetID) != "" {
return d.routed(ctx, req, org, repo, prompt, res)
}
if strings.TrimSpace(req.CredToken) == "" {
res.Error = "no agent credential for this org"
return res
}
cloneURL := ""
if d.CloneURL != nil {
cloneURL = d.CloneURL(ctx, org, repo)
}
if cloneURL == "" {
res.Error = "git is not available"
return res
}
agentRef := strings.TrimSpace(req.AgentRef)
if agentRef == "" {
agentRef = "hanzo"
}
actor := strings.TrimSpace(req.UserID)
// 1. Register the live session (the durable record + live stream root), or
// adopt the one the door already opened to answer its caller with.
sessionID := strings.TrimSpace(req.SessionID)
if sessionID == "" {
var err error
sessionID, err = d.Sessions.Open(ctx, org, actor, agentRef, codingTitle(repo, prompt))
if err != nil {
res.Error = "could not start a session: " + err.Error()
return res
}
}
res.SessionID = sessionID
branch := BranchFor(sessionID)
res.Branch = branch
// Terminal bookkeeping (final status mirror, session close, PR row) runs on a
// cancel-immune context: a run that hit its deadline still transitions the
// session out of "running" and files its PR, instead of leaving a zombie
// (same discipline receive-pack uses for its post-push side effects).
term := context.WithoutCancel(ctx)
d.mirror(ctx, org, sessionID, actor, kindStatus, map[string]any{
"status": "started", "repo": repo, "branch": branch, "base": baseOr(req.Base),
})
// 2. Dispatch to the sandbox runtime, mirroring every progress line live.
runReq := RunRequest{
CloneURL: cloneURL, BaseBranch: strings.TrimSpace(req.Base), Branch: branch,
Prompt: prompt, SessionID: sessionID, RunTimeoutSeconds: timeoutOr(req.TimeoutSeconds),
CredUser: req.CredUser, CredToken: req.CredToken,
}
onStep := func(s Step) {
kind := kindLog
if s.Type == "step" {
kind = kindToolCall
}
d.mirror(ctx, org, sessionID, actor, kind, map[string]any{
"step": s.Step, "message": s.Message, "status": s.Status,
})
}
runRes, rerr := d.Runner.Run(ctx, org, req.UserID, runReq, onStep)
if rerr != nil {
return d.fail(term, org, sessionID, actor, res, "coding run failed: "+rerr.Error(), "")
}
res.Diffstat = runRes.Diffstat
res.Changed = runRes.Changed
res.LogTail = runRes.LogTail
// runRes.Branch is NOT read. The branch is BranchFor(sessionID) — issued by
// cloud, named after a session the sandbox did not choose — and adopting the
// sandbox's self-report let a compromised one answer `main`, which then flowed
// into VerifyRef (which only asks whether a ref EXISTS, and main does) and out
// as CreatePR{Head: "main"}. A PR headed at the trunk, filed by us, from a run
// that never had permission to write there.
//
// The sandbox has nothing to report here: it was TOLD which branch to push,
// and its grant admits that one ref and no other, so a disagreement between
// what it claims and what we issued is not new information — it is the
// signal that something is wrong.
res.CommitSha = runRes.CommitSha
if !runRes.OK {
return d.fail(term, org, sessionID, actor, res, nonEmpty(runRes.Error, "coding run reported failure"), runRes.LogTail)
}
// 3. No changes is a legitimate, non-error outcome — nothing to PR.
if !runRes.Changed {
d.mirror(term, org, sessionID, actor, kindStatus, map[string]any{"status": "done", "changed": false})
_ = d.Sessions.Close(term, org, sessionID, statusDone)
res.OK = true
return res
}
// 4+5. Verify the branch landed and file the PR — the SAME completion a routed
// run's terminal report runs (completeChanged), so cloud-side integrity + the PR
// row are identical whether the run executed in the sandbox or on a machine.
return d.completeChanged(term, completion{
org: org, repo: repo, project: strings.TrimSpace(req.Project), base: req.Base,
prompt: prompt, sessionID: sessionID, branch: branch, actor: actor, agentRef: agentRef,
diffstat: runRes.Diffstat, logTail: runRes.LogTail,
}, res)
}
// completion bundles the run context the shared changed-run completion needs, so the
// local sandbox path and the routed path hand it the same values.
type completion struct {
org, repo, project, base string
prompt, sessionID, branch string
actor, agentRef string
diffstat, logTail string
}
// completeChanged is the shared terminal for a run that reported CHANGES: confirm the
// pushed branch LANDED in native git (integrity — trust the tips we can read, not a
// self-report), open the native PR work item, mirror the done status, and close the
// session done. Fail-closed: when the verify seam is wired and the ref is absent, the
// session closes ERROR and NO PR is filed. A tracker failure is recorded but does not
// fail the run (the branch is pushed + verified). ctx is the cancel-immune terminal
// context. Used by the local path (Run) and the routed completion (finalizeRouted).
func (d Dispatcher) completeChanged(ctx context.Context, c completion, res Result) Result {
if d.VerifyRef != nil {
sha, ok := d.VerifyRef(ctx, c.org, c.repo, c.branch)
if !ok {
return d.fail(ctx, c.org, c.sessionID, c.actor, res,
"pushed branch "+c.branch+" was not found in native git", c.logTail)
}
res.Verified = true
if sha != "" {
res.CommitSha = sha // authoritative tip from our own storage
}
}
pr, perr := d.Tracker.CreatePR(ctx, PRInput{
Org: c.org, Project: strings.TrimSpace(c.project), Repo: c.repo,
Base: baseOr(c.base), Head: c.branch, Title: codingTitle(c.repo, c.prompt),
Body: prBody(c.prompt, c.base, c.branch, res.CommitSha, c.diffstat, c.sessionID), Assignee: c.agentRef,
})
if perr != nil {
d.logf("coding: tracker PR create failed", "org", c.org, "repo", c.repo, "err", perr)
d.mirror(ctx, c.org, c.sessionID, c.actor, kindLog, map[string]any{"message": "tracker PR not created: " + perr.Error()})
} else {
res.PR = pr
}
d.mirror(ctx, c.org, c.sessionID, c.actor, kindStatus, map[string]any{
"status": "done", "changed": true, "branch": c.branch, "commit": res.CommitSha, "pr": pr.Identifier,
})
_ = d.Sessions.Close(ctx, c.org, c.sessionID, statusDone)
res.OK = true
return res
}
// finalizeRouted is the CLOUD-SIDE completion for a routed run whose machine reported
// a terminal result. The machine pushed with its OWN credential and streamed into the
// session; cloud still owns the integrity gate + the PR row + the session's terminal
// state (the machine never closes the session), exactly as the local keystone path
// does after a sandbox push. No secret crosses — cloud only reads the ref it can see.
//
// - reported failure -> session closed ERROR (no PR).
// - reported no changes -> session closed DONE (no PR).
// - reported a changed push -> completeChanged: VerifyRef the branch LANDED (fail
// closed to a session ERROR + no PR if absent), file
// the native PR, close DONE — the shared path.
//
// Best-effort + cancel-immune: it runs on its own terminal context so a run near its
// deadline still transitions out of "running". It is invoked from the durable delivery
// activity once, after the report is in hand, so it never re-executes the run.
func (d Dispatcher) finalizeRouted(ctx context.Context, in RoutedRun, res RoutedResult) {
if d.Sessions == nil {
return
}
out := Result{SessionID: in.SessionID, Repo: in.Repo, Routed: true, TargetID: in.TargetID}
if !res.OK {
d.fail(ctx, in.Org, in.SessionID, in.Actor, out, nonEmpty(res.Error, "the routed run reported failure"), "")
return
}
if !res.Changed {
d.mirror(ctx, in.Org, in.SessionID, in.Actor, kindStatus, map[string]any{"status": "done", "changed": false})
_ = d.Sessions.Close(ctx, in.Org, in.SessionID, statusDone)
return
}
// res.Branch is NOT read, for the reason stated in Run: the branch is the one
// cloud issued and put in the RoutedRun, and a machine that reports a
// different one is reporting something it was never asked.
branch := in.Branch
out.Branch = branch
out.CommitSha = res.CommitSha
out.Changed = true
agentRef := strings.TrimSpace(in.AgentRef)
if agentRef == "" {
agentRef = "hanzo"
}
_ = d.completeChanged(ctx, completion{
org: in.Org, repo: in.Repo, project: in.Project, base: in.Base,
prompt: in.Prompt, sessionID: in.SessionID, branch: branch, actor: in.Actor, agentRef: agentRef,
diffstat: res.Diffstat, logTail: "",
}, out)
}
// RoutedResult mirrors agents.RoutedResult so coding.go stays free of an agents import
// on the completion path (the adapter bridges). It is the terminal a machine reports.
type RoutedResult struct {
OK bool
Changed bool
Branch string
CommitSha string
Diffstat string
Error string
}
// routed dispatches one run to a chosen target machine (#48). It opens the live
// session tagged with the target (so mission-control shows it on that machine),
// enqueues a DURABLE task addressed to the target on the tasks engine, and
// returns immediately — the machine claims and executes it, streaming the
// terminal outcome into the SAME session. It never runs locally: an unavailable
// target, a disabled router, or a failed enqueue FAILS CLOSED with an honest
// error.
func (d Dispatcher) routed(ctx context.Context, req Req, org, repo, prompt string, res Result) Result {
target := strings.TrimSpace(req.TargetID)
res.Routed = true
res.TargetID = target
// Routing must be wired (composition root binds both). A half-wired dispatcher
// must not fall through to the sandbox with a target set.
if d.Route == nil || d.TargetGate == nil {
res.Error = "routing is not available"
return res
}
// The machine clones the org's repo with its OWN credential; we still need the
// clone URL (non-secret) to hand it.
cloneURL := ""
if d.CloneURL != nil {
cloneURL = d.CloneURL(ctx, org, repo)
}
if cloneURL == "" {
res.Error = "git is not available"
return res
}
// Liveness + existence gate: only dispatch to a target that exists in this
// org, is online, and has a live runner. Fail closed — never elsewhere.
if err := d.TargetGate(ctx, org, target); err != nil {
res.Error = "target " + target + " is not available: " + err.Error()
return res
}
agentRef := strings.TrimSpace(req.AgentRef)
if agentRef == "" {
agentRef = "hanzo"
}
actor := strings.TrimSpace(req.UserID)
sessionID := strings.TrimSpace(req.SessionID)
if sessionID == "" {
var err error
sessionID, err = d.Sessions.OpenOn(ctx, org, actor, agentRef, codingTitle(repo, prompt), target)
if err != nil {
res.Error = "could not start a session: " + err.Error()
return res
}
}
res.SessionID = sessionID
branch := BranchFor(sessionID)
res.Branch = branch
d.mirror(ctx, org, sessionID, actor, kindStatus, map[string]any{
"status": "routed", "repo": repo, "branch": branch, "base": baseOr(req.Base), "target": target,
})
run := RoutedRun{
Org: org, TargetID: target, SessionID: sessionID,
Repo: repo, Project: strings.TrimSpace(req.Project), Base: strings.TrimSpace(req.Base),
Branch: branch, Prompt: prompt, CloneURL: cloneURL, TimeoutSeconds: timeoutOr(req.TimeoutSeconds),
Actor: actor, AgentRef: agentRef,
}
// Enqueue on the durable engine. A failure fails the run closed (session
// error) rather than leaving a zombie "running" session or running locally.
if err := d.Route(ctx, run); err != nil {
term := context.WithoutCancel(ctx)
return d.fail(term, org, sessionID, actor, res, "could not queue the routed run: "+err.Error(), "")
}
res.OK = true // accepted + queued; the machine drives it to terminal from here
return res
}
// fail records the error into the session, closes it error, and stamps the Result.
func (d Dispatcher) fail(ctx context.Context, org, sessionID, actor string, res Result, msg, logTail string) Result {
d.mirror(ctx, org, sessionID, actor, kindStatus, map[string]any{"status": "error", "error": msg})
_ = d.Sessions.Close(ctx, org, sessionID, statusError)
res.OK = false
res.Error = msg
if logTail != "" {
res.LogTail = logTail
}
return res
}
// mirror appends one event to the session, best-effort (a live-fan-out failure
// must never fail the run — the run + its side effects already happened). The
// credential is never a field of any payload passed here.
func (d Dispatcher) mirror(ctx context.Context, org, sessionID, actor, kind string, payload map[string]any) {
if d.Sessions == nil {
return
}
b, err := json.Marshal(payload)
if err != nil {
return
}
if err := d.Sessions.Log(ctx, org, sessionID, kind, actor, b); err != nil {
d.logf("coding: session event mirror failed", "org", org, "session", sessionID, "err", err)
}
if d.Watch != nil {
d.Watch(ctx, kind, b)
}
}
func (d Dispatcher) logf(msg string, kv ...any) {
if d.Log != nil {
d.Log(msg, kv...)
}
}
// ---- pure helpers ----
func codingTitle(repo, prompt string) string {
p := firstLine(prompt)
if len(p) > maxTitlePrompt {
p = strings.TrimSpace(p[:maxTitlePrompt]) + "…"
}
if p == "" {
return "Agent changes to " + repo
}
return repo + ": " + p
}
// prBody renders the PR description: the task, the base..head, the tip, a
// diffstat, and the session link so a reviewer can open the live run.
func prBody(prompt, base, branch, commit, diffstat, sessionID string) string {
var b strings.Builder
b.WriteString("Opened by the @hanzo coding agent.\n\n")
b.WriteString("**Task:** " + firstLine(prompt) + "\n\n")
b.WriteString("**Branch:** `" + branch + "` → base `" + baseOr(base) + "`\n")
if commit != "" {
b.WriteString("**Commit:** `" + commit + "`\n")
}
if strings.TrimSpace(diffstat) != "" {
b.WriteString("\n```\n" + strings.TrimSpace(diffstat) + "\n```\n")
}
b.WriteString("\nSession: `/v1/agents/sessions/" + sessionID + "`\n")
return b.String()
}
func firstLine(s string) string {
s = strings.TrimSpace(s)
if i := strings.IndexByte(s, '\n'); i >= 0 {
return strings.TrimSpace(s[:i])
}
return s
}
// shortID returns the last 12 hex chars of a "sess_" id for a stable, unique
// branch suffix. Falls back to the whole id when it is short/unprefixed.
func shortID(id string) string {
if i := strings.LastIndexByte(id, '_'); i >= 0 && i+1 < len(id) {
id = id[i+1:]
}
if len(id) > 12 {
return id[len(id)-12:]
}
return id
}
func baseOr(base string) string {
if b := strings.TrimSpace(base); b != "" {
return b
}
return "main"
}
func timeoutOr(s int) int {
if s > 0 {
return s
}
return defaultTimeoutSeconds
}
func nonEmpty(a, b string) string {
if strings.TrimSpace(a) != "" {
return a
}
return b
}