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/disableis 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:410documents anupdate.check_intervalthat 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-docs — docs 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(→rustlssafe defaults),pkg/osinfo(→os_info/env::consts; RTB already derives OS/arch inrtb-update/src/asset.rs:55),pkg/workspace(→Path::ancestors()),internal/circuitbreaker/internal/ratelimit(→tower/governorif a server ever exists — RTB won't hand-roll them either way). - Build-time changelog generation:
pkg/changelog→ release-plz owns it. (The runtimechangelogcommand 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 /
servetransport story:pkg/httpserver +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 loopbackdocs servetoday — but why-rtb.md:44 already gestures at integratingaxumfor a toolservesubcommand. If RTB commits to thatservestory, this whole cluster becomes in-scope at once (and pulls in the C1 resilience bits viatower/governor). Deferred until that product call. (Open: does RTB want a first-classserve? — resolve before declaring done.) - Author-side signing / PGP trust:
pkg/signing(KMS/PEM signers),pkg/openpgpkey(WKD),internal/trustkeys,sign/keyscommands. RTB today verifies (Ed25519/minisign in rtb-update) and delegates release-artefact signing to release-plz + cargo-dist; the trust anchor isToolMetadata::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/forms→rtb-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.
scaffolder-custom-template-overlays— SEQUENCING DEPENDENCY. The spec namesgitlab-ci(.gitlab-ci.yml) as a suppressible component (§6:96) and a protected path (§7:107) — but RTB ships no.gitlab-ci.ymltoday (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.scaffolder-feature-toggle— ADJUST. Mechanically compatible (RTB's genericmerge_marker_regionshandles the new// rtb:features-*region with zero changes; manifest extension is additive). ButFeature::Changelogis toggleable with no backing command (§B) — toggling it "on" yields a dead flag. The spec needs a feature-has-backing-command invariant (implementchangelog, or mark it backing-less).mcp-command-exposure-gating— MINOR ADJUST. Same root cause: the resolver must not assume everyFeature/command pair has a registered command (the changelog flag is the counterexample). Add an explicit rule for flag-without-command.rtb-update-policy-v0.1— SOUND AS WRITTEN. The sweep validated it:Enabledmode callsUpdater::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 inrtb-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/
servestory + 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_boundedbrowser::open_urlare 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-policyremains the cleanest first implementation (no changes needed); butcompile_bounded/browser::open_urlarguably outrank it as they are security contracts already promised.
Recommended new specs (Phase-2 output, priority order)¶
Drafting status as of 2026-06-26 (✅ = drafted APPROVED).
- ✅
2026-06-26-rtb-app-regex-util-compile-bounded(S) — documented security contract. - ✅
2026-06-26-rtb-cli-browser-open-url(S) — scheme allowlist; unblocks rtb-docs. - ✅
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. - ✅
2026-06-26-rtb-cli-changelog-subcommand(M) — backsFeature::Changelog. - ⬜
rtb-ai-v0.4-usage-accumulation-and-toolloop(L; can split) + model-default refresh — pending user decisions (tool-loop scope, fallback shape). - ✅
2026-06-26-scaffolder-generate-project-git-push(M). - ✅
2026-06-26-scaffolder-command-authoring-parity(S) — command metadata (--short/--aliases/--parent/--args) + Rust-keyword rejection (combined). - ✅
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. - ✅
2026-06-26-doc-corrections-parity— soften why-rtb.md:44serve; reconcile CLAUDE.md:244 HTTP-middleware claim. - ⬜ (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-gating → rtb-cli-changelog-subcommand → scaffolder-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 climain.rs.j2still usesrun()with an adoption note — it pins a publishedrust-tool-base, so it switches torun_and_exitonly once the release that ships the API is out (nextrelease-plzrun after Wave 0). Tracked in2026-06-26-rtb-error-exit-code-attachment.md. - update-policy: full
Feature::Updategating (App doesn't carry the runtimeFeatures) + the mock-release-server assert_cmd e2e + scaffolder threading + runtime config override. Tracked in2026-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-binlinks have hit ENOSPC. Mitigations: cleartarget/{debug/incremental,doc}, capCARGO_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.