Skip to content

go-tool-base → rust-tool-base parity audit

Go Tool Base (GTB) has matured substantially since RTB's baseline — now v0.22.0 (~29 library packages, ~23 commands). This is the material record of a deliberate parity check: review GTB's library + CLI, identify drift and feature gaps in RTB, and decide which become RTB specs.

The audit is paradigm-aware: RTB is not a Go→Rust port (see docs/about/why-rtb.md). Some GTB surface is an intentional non-goal for RTB; the audit distinguishes those from real gaps. It runs in small phases (quota-bounded). Each confirmed gap becomes an APPROVED spec in specs/, with open questions resolved at draft time.

GTB repo (read-only reference): gitlab.com/phpboyscout/go-tool-base.

Status

Phase Scope State
1 Top-3 big-ticket drift + feature-toggle ✅ Analysed; 4 specs APPROVED
2 Signing/trust/authn, generator drift, resilience/plumbing, service/API surface, shared-subsystem drift ✅ Analysed (2026-06-26, 5-agent sweep); 8 specs + 2 amendments drafted APPROVED
Impl Spec implementation, in dependency-ordered waves (see below) 🟢 In progress — Waves 0–1 merged; Wave 2 next

Phase 1 — findings (2026-06-23)

Subsystem Verdict RTB today Gap / drift Decision Spec
Custom/external templates 🔴 Real gap (L) built-in minimal/cli presets only --template + template add/update/remove/list, git fetch + SHA-keyed cache, overlay layering, *-template.yaml descriptor, overlay security model GOAL; phased (local engine → git fetch) 2026-06-23-scaffolder-custom-template-overlays
MCP exposure gating 🔴 Real gap (M) flat per-command mcp_exposed bool, default hidden hierarchical tri-state (Inherit/Exposed/Excluded), ancestor-walk resolver, subtree exclude + re-expose, scaffolder threading, enable/disable mcp <path> keep opt-in/hidden default; full scope 2026-06-23-mcp-command-exposure-gating
Self-update policy 🔴 Real gap (M) user-initiated updater only 3-state policy (disabled/prompt/enabled), check-interval + throttle, pre-run enforcement, scaffolded policy default Disabled; baseline-only v0.1 2026-06-23-rtb-update-policy-v0.1
Feature toggle 🔴 Real gap (M) — reversed from non-goal runtime Features builder; no toggle tool author can't turn built-ins on/off post-scaffold without hand-editing main.rs GOAL (GTB's maturity proves it's needed for generated code); author-side enable/disable verb 2026-06-23-scaffolder-feature-toggle

Cross-cutting decisions

  • MCP default polarity stays RTB's safer opt-in/hidden (a command is not an AI-callable tool unless the author chose it) — not GTB's default-exposed.
  • enable/disable is one unified command family — feature toggling and per-command MCP exposure (enable/disable mcp <path>) share the surface, mirroring GTB.
  • Custom templates are an explicit goal (why-rtb.md had been silent); to be delivered in phases.
  • Feature toggling was briefly assessed an intentional non-goal, then reversed to a goal — implemented the RTB way (scaffolder verb rewriting the .features(...) builder marker region + manifest, not a runtime/config toggle, which would clash with the typed-config paradigm).

Orphaned drift closed

  • rust-tool-base.md:410 documents an update.check_interval that was never implemented — the update-policy spec closes it.

Phase 2 — findings (2026-06-26)

Five-agent paradigm-aware sweep across the un-audited surface. Verdicts classify each item REAL GAP (RTB needs it), DRIFT (RTB's equivalent has fallen behind), ALREADY-PRESENT (parity, different shape), or NON-GOAL (intentional — Rust ecosystem or paradigm supersedes). The headline result: most of GTB's growth is server/RPC/PGP surface that is set aside for now — partly a permanent paradigm non-goal, partly deferred pending parity (§C splits the two) — and the real gaps are concentrated in the scaffolder, rtb-ai, and a cluster of RTB's own documented-but-unimplemented contracts that the sweep surfaced incidentally.

A. Real gaps (paradigm-aligned — RTB should close these)

Item Sev RTB today Gap Sources
CI-pipeline scaffolding L presets ship no .gitlab-ci.yml GTB scaffolds a full GitLab pipeline from cicd-components (--ci-component-source). Unblocks the custom-template overlay spec (which assumes a gitlab-ci file to suppress/protect). GTB internal/generator/skeleton.go:33; RTB presets have none
rtb-ai drift cluster L chat client only (a) token-usage accumulator + UsageObserver (per-response Usage exists, no lifetime total); (b) tool-use / ReAct loop (spec deferred it); © provider fallback/failover; (d) stale default models (spec says Opus 4.7, should be 4.8) GTB pkg/chat/{usage,client,fallback_policy}.go; RTB rtb-ai/src/message.rs:74
generate project git push M init + commit, gated by --no-git no --push, no remote derivation, no existing-repo detection, no --git-branch, no host-git author resolution GTB internal/generator/gitinit.go:44; RTB commands/generate/project.rs:254
internal/agent agentic loop L none autonomous build/test/lint sandboxed tool loop for AI-assisted scaffolding (output truncation+redaction, query_user). Only a gap if AI-assisted scaffold/verify parity is a product goal. GTB internal/agent/; RTB none
output format breadth M --output text\|json + spinner no yaml/csv/tsv/markdown formatters, no progress-bar/multi-step status. Backed by serde_yaml/indicatif if wanted. GTB pkg/output/output.go:18; RTB rtb-cli/src/render.rs
generate command metadata S name/desc only no command-level --short/--aliases/--parent/--args (RTB's own doc-comment defers them) GTB internal/cmd/generate/command.go:62; RTB commands/generate/command.rs:14
Rust-keyword command rejection S reserved-name list mirror GTB's token.IsKeyword guard for Rust keywords GTB internal/generator/validate.go:117; RTB validate.rs:102
rtb-error exit-code attachment S every error exits 1 GTB threads custom process exit codes (WithExitCode, 128+signum). Non-funnel, paradigm-safe. GTB pkg/errorhandling/exitcode.go:23; RTB none
rtb-telemetry spill + deletion M OTLP/http/file sinks no offline spill-to-disk buffer on sink failure; no upstream data-deletion request (RTB only deletes local consent). PostHog/Datadog backends remain a non-goal (OTLP supersedes). GTB pkg/telemetry/{spill,deletion}.go; RTB rtb-telemetry/src/sink.rs:19

B. RTB's own documented-but-unimplemented contracts (surfaced by the sweep)

These are not GTB-parity items — they are places where an RTB spec or CLAUDE.md asserts an API that does not exist in the code. Several are security-relevant and one blocks an already-approved spec. Verified by direct grep (2026-06-26).

Contract Status Impact
rtb_app::regex_util::compile_bounded Missing (documented CLAUDE.md §Regex; referenced validate.rs:69) Security — external regex (config/CLI/TUI/HTTP) has no bounded compiler to route through, yet the standard says it must.
rtb_cli::browser::open_url (scheme allowlist) Missing (referenced 5× by 2026-04-23-rtb-docs-v0.1.md; CLAUDE.md §URL Opening) Security + blocks rtb-docsdocs serve --open and external-link routing have no opener; no scheme allowlist enforcement exists.
changelog subcommand Missing (Feature::Changelog flag exists, no command registered) Dead feature flag — cross-impacts feature-toggle + MCP-gating specs (see below).
rtb-cli HTTP middleware redaction Missing (CLAUDE.md:244 claims SENSITIVE_HEADERS is wired into reqwest) Doc overstates reality — SENSITIVE_HEADERS is defined in rtb-redact but not wired into any client.

C. Not-now surface — two tiers

GTB surface RTB does not pursue today splits into two materially different categories (decision 2026-06-26, MC). The distinction matters: one tier is permanent, the other is parked and revisited.

C1. Paradigm non-goals (permanent — nothing to "reach parity" on)

Rust's ecosystem/idiom already covers these differently or for free; RTB will never grow its own version, so they are not "deferred work."

  • Plumbing Rust gives for free: pkg/logger (→ tracing), pkg/utils (→ which/IsTerminal), pkg/tls (→ rustls safe defaults), pkg/osinfo (→ os_info/env::consts; RTB already derives OS/arch in rtb-update/src/asset.rs:55), pkg/workspace (→ Path::ancestors()), internal/circuitbreaker / internal/ratelimit (→ tower/governor if a server ever exists — RTB won't hand-roll them either way).
  • Build-time changelog generation: pkg/changelog → release-plz owns it. (The runtime changelog command is a separate real gap — §B.)
  • Telemetry vendor backends: PostHog/Datadog → OTLP is the universal path.
  • man pages: rtb-docs spec line 430 declares this a non-goal.

C2. Deferred pending parity (parked — revisit once core parity lands)

These are real capabilities, not rejected — they hinge on a product decision that is premature until the core gaps (§A) are closed and the feature shape is understood. Treat as intentionally deferred goals, not non-goals. Reassess at the end of the parity programme.

  • Server / serve transport story: pkg/http server + SecurityHeadersMiddleware, pkg/grpc, pkg/gateway, pkg/openapi, pkg/authn (server-side API-key / JWT-OIDC-discovery / mTLS verification), pkg/controls (lifecycle + liveness/readiness health). All exist to serve GTB's HTTP/gRPC servers. RTB ships only loopback docs serve today — but why-rtb.md:44 already gestures at integrating axum for a tool serve subcommand. If RTB commits to that serve story, this whole cluster becomes in-scope at once (and pulls in the C1 resilience bits via tower/governor). Deferred until that product call. (Open: does RTB want a first-class serve? — resolve before declaring done.)
  • Author-side signing / PGP trust: pkg/signing (KMS/PEM signers), pkg/openpgpkey (WKD), internal/trustkeys, sign/keys commands. RTB today verifies (Ed25519/minisign in rtb-update) and delegates release-artefact signing to release-plz + cargo-dist; the trust anchor is ToolMetadata::update_public_keys (compile-time-embedded), not WKD. But if downstream tools need to sign their own artefacts (not RTB releases), an author-side signing surface could become a goal. Deferred until the downstream need is concrete. (Open: what would a downstream tool actually need to sign, and is cargo-dist insufficient?)

D. ALREADY-PRESENT (parity confirmed, no action)

pkg/formsrtb-tui Wizard; pkg/docs→rtb-docs (modulo the open_url gap); pkg/config→rtb-config (hot-reload at parity); pkg/credentials→ rtb-credentials (RTB has more backends); pkg/vcs→rtb-vcs (RTB's git/ module exceeds GTB — adds blame/walk; GitLab nested groups + all 6 release providers present; GitHub-App auth absent in both — not a gap); pkg/version→VersionInfo; --shorthand for flags; regenerate non-destructive marker-merge (RTB's is arguably stronger).


Cross-impact on the Phase-1 specs (the reason for Phase 2)

The user's premise — finish the audit before implementing, because findings may reshape the specs — was correct. Net: no Phase-1 spec is invalidated, but three need adjustment and one needs a sequencing dependency.

  1. scaffolder-custom-template-overlays — SEQUENCING DEPENDENCY. The spec names gitlab-ci (.gitlab-ci.yml) as a suppressible component (§6:96) and a protected path (§7:107) — but RTB ships no .gitlab-ci.yml today (gap A1). The CI-scaffolding gap should land before or with this spec, else the suppressible component suppresses nothing. Also: Phase-B git fetch of third-party templates trusts a commit SHA-pin only (no signature/allowlist) — acceptable (cargo's git-dep model), flagged for a future template-provenance decision.
  2. scaffolder-feature-toggle — ADJUST. Mechanically compatible (RTB's generic merge_marker_regions handles the new // rtb:features-* region with zero changes; manifest extension is additive). But Feature::Changelog is toggleable with no backing command (§B) — toggling it "on" yields a dead flag. The spec needs a feature-has-backing-command invariant (implement changelog, or mark it backing-less).
  3. mcp-command-exposure-gating — MINOR ADJUST. Same root cause: the resolver must not assume every Feature/command pair has a registered command (the changelog flag is the counterexample). Add an explicit rule for flag-without-command.
  4. rtb-update-policy-v0.1 — SOUND AS WRITTEN. The sweep validated it: Enabled mode calls Updater::run(), which unconditionally enforces Ed25519 verification (no --no-verify), so auto-update inherits mandatory signature verification — no signing work is gated by the policy. Throttle uses a persisted last-check timestamp, not ratelimit/circuitbreaker (confirmed non-goals). OS/arch asset selection already lives in rtb-update/src/asset.rs:55 (not blocked by osinfo). No change needed.

Manifest-schema collision check: the additive blocks (features:, templates:, a future git:/CI block) are independently keyed — safe so long as each uses #[serde(default, skip_serializing_if)] to preserve deny_unknown_fields round-tripping.


Impact assessment — what "achieving parity" actually costs

  • The not-now share is large and reassuring — but split it (see §C). Roughly half of GTB's package growth is set aside, in two tiers: a permanent paradigm non-goal tier (§C1 — Rust idiom supersedes; nothing to port) and a deferred-pending-parity tier (§C2 — the server/serve story + author-side signing; parked, not rejected, revisit once §A is closed). Parity does not mean porting either tier now — but the §C2 cluster is a deliberate deferral with open product questions, not a closed door. why-rtb.md's paradigm holds up under audit.
  • The real work clusters into three buckets, in recommended order:
  • Close RTB's own unimplemented contracts (§B) first — these are security/correctness debt independent of GTB, cheap, and one (browser::open_url) already blocks a shipped spec. compile_bounded
    • browser::open_url are the priorities.
  • Scaffolder parity — CI-scaffolding (unblocks the overlay spec) → git-push → command-metadata → keyword rejection. This is where downstream tool authors feel the gap most.
  • rtb-ai modernisation — token usage, tool-use loop, fallback, model refresh. Largest single subsystem; can be staged.
  • Phase-1 specs are safe to implement after the three adjustments above are folded in. rtb-update-policy remains the cleanest first implementation (no changes needed); but compile_bounded / browser::open_url arguably outrank it as they are security contracts already promised.

Drafting status as of 2026-06-26 (✅ = drafted APPROVED).

  1. 2026-06-26-rtb-app-regex-util-compile-bounded (S) — documented security contract.
  2. 2026-06-26-rtb-cli-browser-open-url (S) — scheme allowlist; unblocks rtb-docs.
  3. 2026-06-26-scaffolder-ci-pipeline-scaffolding (L) — unblocks the overlay spec; GitLab (Phase A) + GitHub Actions (Phase B); default-on/cli-preset/full-pipeline.
  4. 2026-06-26-rtb-cli-changelog-subcommand (M) — backs Feature::Changelog.
  5. rtb-ai-v0.4-usage-accumulation-and-toolloop (L; can split) + model-default refresh — pending user decisions (tool-loop scope, fallback shape).
  6. 2026-06-26-scaffolder-generate-project-git-push (M).
  7. 2026-06-26-scaffolder-command-authoring-parity (S) — command metadata (--short/--aliases/--parent/--args) + Rust-keyword rejection (combined).
  8. 2026-06-26-rtb-error-exit-code-attachment (S). ⬜ rtb-telemetry-spill-and-deletion (M); ⬜ rtb-cli-output-format-breadth (M) — lower priority, not yet drafted.
  9. 2026-06-26-doc-corrections-parity — soften why-rtb.md:44 serve; reconcile CLAUDE.md:244 HTTP-middleware claim.
  10. (deferred, product call) rtb-ai-agentic-scaffold-verify-loop (L) — only if AI-assisted scaffolding parity is wanted.

Spec-amendments (applied 2026-06-26): ✅ feature-toggle §9 + MCP-gating §10 "feature-has-backing-command" invariant; update-policy needs none.


Implementation progress (dependency-ordered waves)

Specs are implemented in waves ordered by dependency, not by drafting order (rationale + full dependency graph: the cross-impact analysis above). rtb-ai is intentionally last — it is an independent island (blocks nothing, blocked by nothing) and the largest single effort.

Wave Scope State
0 — Foundations compile_bounded (rtb-app) · browser::open_url (rtb-cli) · exit-code attachment (rtb-error, wired) · doc-corrections Merged (MR !69)
1 — Update policy UpdatePolicy baseline + policy::evaluate engine + new BUILTIN_PRERUN_HOOKS extension point + activation Merged (MR !70); spec IMPLEMENTED
2 — Scaffolder polish scaffolder-command-authoring-parity (--short/--aliases/--parent/--args + Rust-keyword rejection) · scaffolder-generate-project-git-push ⬜ Next — independent, no decisions needed
3 — MCP/feature cluster mcp-command-exposure-gatingrtb-cli-changelog-subcommandscaffolder-feature-toggle (ordered: CommandSpec change first; changelog kills the dead-flag edge; toggle last)
4 — Template chain scaffolder-ci-pipeline-scaffolding (Phase A GitLab) → scaffolder-custom-template-overlays (Phase A local) → then the Phase-B halves ⬜ Hard sequencing dep
5 — rtb-ai (island) rtb-ai v0.4 (usage + tool-loop + fallback + model refresh) — needs decisions + a drafted spec first; then telemetry-spill, output-breadth ⬜ Deferred; do when ready

Carried follow-ups (future-dated / cross-wave)

  • cli-preset template → run_and_exit: the cli main.rs.j2 still uses run() with an adoption note — it pins a published rust-tool-base, so it switches to run_and_exit only once the release that ships the API is out (next release-plz run after Wave 0). Tracked in 2026-06-26-rtb-error-exit-code-attachment.md.
  • update-policy: full Feature::Update gating (App doesn't carry the runtime Features) + the mock-release-server assert_cmd e2e + scaffolder threading + runtime config override. Tracked in 2026-06-23-rtb-update-policy-v0.1.md.
  • Local disk pressure: the dev box (and likely the homelab CI runner) sits at ~91% disk; heavy rtb-cli-bin links have hit ENOSPC. Mitigations: clear target/{debug/incremental,doc}, cap CARGO_BUILD_JOBS.

How to resume

Analysis is complete; specs are drafted. Implementation is mid-flight — Waves 0–1 are merged to main. Next is Wave 2 (scaffolder polish): it is independent of the rest and needs no design decisions, so it can run straight through (command-authoring-parity + generate-project git push, TDD). After that, follow the wave order above. The three Phase-1 spec adjustments (custom-template CI sequencing, feature-toggle + MCP backing-command invariant) are folded into Waves 3–4. rtb-ai (Wave 5) still needs its open questions resolved before drafting.