feat: v0.1.1 -- UX ship-blockers, hardening, and refactor

Summary

v0.1.1 patch addressing the four CRITICAL UX ship-blockers found after v0.1.0 shipped, plus adjacent hardening (internal-CA support, restricted- environment token file, tightened file modes) and a pre-existing CLI-setup duplication between run and collect.

Every commit is a coherent unit; the branch is squash-ready but also bisect-friendly as-is.

Change groups

Ship-blocker fixes

  • fix: preserve health reports on partial collector runs -- report.Generate runs even when collector.Run returns an error; errors.Join preserves the collector error for errors.Is callers.
  • fix: flag unknown counts on partial runs -- "Active users on instance" and "MR authors in window" render (unknown) when their stage did not complete; new UNKNOWN zone value keeps partial-run reports from reading as "catastrophically unhealthy".
  • fix: fast-fail preflight on unreachable GitLab instance -- new --preflight-timeout (default 5s); probe also returns the instance version so the orchestrator skips a duplicate /api/v4/version.
  • fix: Dockerfile bind-mount ergonomics -- both variants now mkdir + chown /tach-data before USER 1001, set WORKDIR, and CMD ["run", "--output", "/tach-data"] so bare docker run plus a bind-mount works on first run. VOLUME directive removed (anonymous- volume accumulation without a bind-mount is worse than the benefit).
  • fix: tighten output file modes with Windows handling -- 0600 on collection.json, health-report.{txt,json}, monthly.csv; --token-file permission check gates on runtime.GOOS != "windows" since Windows synthesises mode bits.

Hardening

  • feat: support --ca-bundle and SSL_CERT_FILE -- PEM file appended to the system trust store for instances fronted by an internal CA.
  • feat: support --token-file for restricted environments -- file-based PAT sourcing for hosts that forbid secrets in env vars; mutually exclusive with GITLAB_TOKEN; trim rule enforced; internal whitespace rejected; Unix permission check gated on 0600-or-stricter.

Refactor

  • refactor: extract shared CLI setup for run and collect -- the flag block, env resolution, lockfile apply, and client construction collapse into cmd/tach/setup.go. --help output is byte-identical before/after for each command; the two commands now diverge only in what they do with the resolved config (report render vs collect-only).

Cleanup

  • chore: remove unused checkpoint.json write path -- nothing read it.
  • chore: drop residual context-leak wording in SECURITY.md and hero.svg.
  • docs: changelog and gitlab-docs conformance -- new CHANGELOG.md in Keep-a-Changelog format; vale sweep (British -> American English, via -> through, filesystem -> file system, useful scrubbed).

Tests

  • test: bound CheckAdmin transport-error retry ladder -- drops a 34s test to sub-second by short-circuiting the retry with a bounded context.
  • test: assert --token-file rejects every group-or-other bit -- table- driven truth table (0640/0620/0610/0604/0602/0601/0660/0606).

Not in scope

  • Full e2e against a live GitLab instance (deferred; unit coverage is tight and prior audits cleared the silent-wrong-answer paths).
  • collection.json schema rework (compact mr_authors, derived pipeline/SAST/per-user-throughput fields). Tracked for v0.2.0 as a coordinated bump with the sibling consumer.

Test plan

  • go vet ./... clean
  • go build ./... clean
  • go test -count=1 ./... clean (full suite ~3s; collector package ~1s, down from ~36s before the CheckAdmin retry-bound)
  • go run ./cmd/tach {run,collect,report,verify} --help prints cleanly; flag lists match README
  • Four independent audits (simplify, adversarial context-leak, UX regression, vale) in two rounds against the diff -- zero blockers at the final sweep
  • CI pipeline green
  • Release pipeline produces clean artifacts (binaries + SLSA + cosign + container images)

Merge request reports

Loading