Skip to content

Commit 4386681

Browse files
committed
Add EXAMPLES.md; simplify hash --style bare; docs refresh
EXAMPLES.md (new, ~800 lines): hands-on tour of every sub-command with copy-pastable examples, pipeline recipes, and scripting patterns. Structured so first-time readers are pointed at 'auth' as a prerequisite for API-touching commands, then 'config' for settings resolution, then the create → download → verify lifecycle, then each sub-command in turn. Includes: - A prerequisites section at the top listing the non-truestamp tools referenced (cat / jq / curl / sha256sum / etc.) with availability notes per platform; only 'truestamp' itself is required. - Per-command sections for auth, config, create, download, verify, hash, encode / decode, jcs, each convert sub-command, upgrade, version. - ~15 pipeline recipes (recompute protocol claims_hash intermediate, file-hash comparison against a proof, kid derivation, ID timestamp extraction, CBOR round-trip, batch verify, etc.). - A 'scripting with --json and jq' section using the real JSON schema. - A CI conventions section with the full env-var reference and the list of CI environments that auto-suppress the passive upgrade notice. - An offline / air-gapped section explaining which commands need the network and which don't. Every example was exercised end-to-end against a live Truestamp dev server (including real create → wait-past-block-boundary → download → verify → verify --remote) to catch documentation drift. Inaccuracies fixed during that pass: - 'auth status --json' — flag doesn't exist; removed, kept exit-code contract. - 'convert id' ULID sample output replaced with the real value. - 'verify --json' field name corrected from the mythical '.hashComparison.expected' to the real '.hash_comparison.found'. - The 'recompute claims_hash' recipe: the command was right but the prose incorrectly claimed the output matched 's.mh' in the proof. s.mh is the metadata_hash (prefix 0x12); the recipe computes the claims_hash intermediate (prefix 0x11), which is used internally in the item_hash derivation but is never serialized into the bundle. Reworded to reflect reality and cross-link the file-hash comparison recipe users actually want. - The 'global flags' list conflated truly-persistent flags with per-command ones (--json, --silent). Split into two explicit lists. hash --style bare simplification: Before: 'bare' still printed the filename column unless --no-filename was also passed, making 'hash --style bare file.txt' output byte-identical to 'hash --style gnu file.txt'. That accidental collision defeated the whole point of 'bare'. After: 'bare' unconditionally omits the filename column. The three styles now produce three genuinely-distinct shapes: gnu — '<hex> <filename>' sha256sum-compatible bsd — '<ALGO> (<filename>) = <hex>' shasum --tag compatible bare — '<hex>' scripting / pipe-into-another-command --no-filename is still accepted everywhere for backward compatibility (it's a no-op on bare, still useful on gnu / bsd). Updated flag help, Long description, and the golden fixture capturing the help output. README.md: new 'Documentation' section near the top linking prominently to EXAMPLES.md, plus a mid-document callout and trailing pointer so the link is discoverable at multiple scroll depths. CONTRIBUTING.md: expanded 'When to run which task' table with every Taskfile entry point, their durations, and their intended use. New 'Tests' section broken out by category (unit/integration, golden snapshots, fuzz, bench, race, coverage, lint, vuln-check) with guidance on what kind of test to add when.
1 parent 9fe9070 commit 4386681

7 files changed

Lines changed: 909 additions & 29 deletions

File tree

CONTRIBUTING.md

Lines changed: 79 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,44 @@ mise install # Installs Go, GoReleaser, cosign, shellcheck, syft, caddy from
1313
task build # Build for current platform → build/truestamp
1414
```
1515

16-
Useful tasks:
16+
Optional static-analysis and vuln-scan tools (needed by `task lint` and `task vuln-check`):
1717

1818
```sh
19-
task test # All tests with -race and coverage
20-
task precommit # fmt + vet + test + build-all
21-
task release-check # Validate .goreleaser.yaml
22-
task release-snapshot # Local GoReleaser dry-run → dist/
19+
go install honnef.co/go/tools/cmd/staticcheck@latest
20+
go install github.com/securego/gosec/v2/cmd/gosec@latest
21+
go install golang.org/x/vuln/cmd/govulncheck@latest
2322
```
2423

25-
The Go module path is `github.com/truestamp/truestamp-cli`. Minimum Go is pinned in `.tool-versions` and `go.mod`.
24+
The Go module path is `github.com/truestamp/truestamp-cli`. Minimum Go is pinned in `.tool-versions` and `go.mod`; bumping Go there should always be followed by re-running `task vuln-check` to confirm no new stdlib CVEs surface.
25+
26+
## When to run which task
27+
28+
| Task | What it runs | Typical duration | When to use it |
29+
| ---- | ------------ | ---------------- | -------------- |
30+
| `task test` | `go test ./...` — every `TestXxx` + `FuzzXxx` seed replay across 17 packages | ~2–8 s | While iterating on code |
31+
| `task precommit` | `fmt` + `lint` + `test` + `build` | <10 s hot cache | Before every commit |
32+
| `task precommit-full` | Adds `test-race` + active fuzz + `vuln-check` + `build-all` | ~3–5 min | Before opening a PR or cutting a release |
33+
| `task test-race` | Full suite under the race detector (`-race`) | ~60 s | When touching goroutines or package-level state |
34+
| `task test-coverage` | Per-package coverage summary | ~5 s | Quick "where are the gaps?" check |
35+
| `task test-coverage-full` | Coverage including CLI subprocess tests + HTML report | ~20 s | Before investing in more tests |
36+
| `task bench` | Every `BenchmarkXxx` with `-benchmem` | ~30 s | Before merging a change that may affect hot paths |
37+
| `task bench-compare` | Same, with `-count=5`, writes a baseline file for `benchstat` | ~2 min | A/B comparing performance between branches |
38+
| `task fuzz` | Smoke-runs every `FuzzXxx` with its seed corpus (no mutation) | ~5 s | Explicit fuzz-seed pass (subsumed by `task test`) |
39+
| `task fuzz-deep` | Active mutation fuzzing, default 15 s per target × 59 targets | ~15 min | Hardening pass before a release; override with `DURATION=1m task fuzz-deep` |
40+
| `task fuzz-list` | Print the fuzz-target inventory | instant | Discover what's covered |
41+
| `task lint` | `go vet` + `gofmt -l` + `staticcheck` + `gosec` | ~5–10 s | Part of `precommit`; rarely run standalone |
42+
| `task vuln-check` | `govulncheck` against `go.mod` + stdlib | ~10 s | After a `go.mod` change or Go toolchain bump |
43+
| `task release-check` | Validate `.goreleaser.yaml` | <5 s | Maintainer pre-release gate |
44+
| `task release-snapshot` | Local GoReleaser dry-run → `dist/` | ~60 s | Maintainer pre-release gate |
45+
46+
Run a single test or a focused subset:
47+
48+
```sh
49+
go test ./internal/verify/... # Every test in a package subtree
50+
go test ./internal/verify -run TestInclusionProof
51+
go test ./internal/hashing -bench=. -benchmem
52+
go test -run=^$ -fuzz=FuzzParseCBOR -fuzztime=30s ./internal/proof/
53+
```
2654

2755
## Commit conventions
2856

@@ -32,16 +60,50 @@ The Go module path is `github.com/truestamp/truestamp-cli`. Minimum Go is pinned
3260

3361
## Tests
3462

35-
- All tests must pass under `task test` (which runs `go test -race -coverprofile=coverage.out ./...`).
36-
- Prefer table-driven unit tests. Integration tests that shell out to the binary should be gated with a build tag so they can be skipped.
37-
- New `internal/*` packages should get at least one `_test.go` file alongside them.
63+
New code is expected to ship with tests. The repo has **seven categories** of tests, each with a defined purpose:
3864

39-
Run a single test file:
65+
### Unit and integration tests (`TestXxx`)
4066

41-
```sh
42-
go test ./internal/verify/...
43-
go test ./internal/verify -run TestInclusionProof
44-
```
67+
- **~650 functions across 17 packages.** Plain `go test` semantics — one test per invariant.
68+
- Table-driven tests are preferred for parser / validator / encoder code.
69+
- `cmd/` integration tests use a `TestMain` that builds the CLI binary in a tempdir once and then runs it as a subprocess for each test. This gives real exit-code + real stdout/stderr assertions without paying subprocess costs per-test. See `cmd/verify_test.go` for the pattern.
70+
- New `internal/*` packages should ship at least one `_test.go` file alongside them.
71+
72+
### Golden-output snapshot tests (`cmd/golden_test.go`)
73+
74+
- Pin every user-facing CLI output (help text, `--list`, `--json` envelopes) byte-for-byte to committed fixtures under `cmd/testdata/golden/`.
75+
- Catch silent wording / formatting / JSON-schema drift — the class of change that quietly breaks downstream scripts.
76+
- Regenerate with `UPDATE_GOLDEN=1 go test ./cmd -run Golden` after an intentional output change.
77+
- When you add a flag that affects output, add (or update) a golden test.
78+
79+
### Fuzz tests (`FuzzXxx`)
80+
81+
- **59 targets across 13 packages** covering every parser that touches attacker-controlled bytes: proof JSON + CBOR, encoding decoders, compact Merkle proofs, Bitcoin tx + txoutproof, TOML config, tar.gz extraction (path-traversal defense), ID / timestamp / URL / public-key parsers.
82+
- Go's native fuzz framework calls your target in-process (no subprocess cost). Seed corpus lives in `f.Add()` calls; `go test` replays it as regression tests on every run. Active mutation kicks in only with `-fuzz=...`.
83+
- Add a fuzz target whenever you write a parser that consumes external bytes. Assert at minimum "no panic"; add stronger invariants (round-trip, bounded output, etc.) where the semantics support it. See `internal/selfupgrade/fuzz_test.go`'s `FuzzExtractBinary` for a direct path-traversal assertion inside the fuzz callback.
84+
- Crashing inputs discovered during fuzzing are auto-saved under `<pkg>/testdata/fuzz/FuzzXxx/` and become permanent regression seeds. Commit these.
85+
86+
### Benchmarks (`BenchmarkXxx`)
87+
88+
- **20+ targets** on hot paths: hashing across all 14 algorithms, proof parse / marshal (JSON + CBOR), encoding round-trip, Merkle proof decode + verify, domain-prefixed hashing.
89+
- Run with `task bench` or `go test -bench=.`. `b.SetBytes` is used where throughput matters so `go test` reports MB/s alongside ns/op.
90+
- Before merging a change to any parser or crypto primitive, capture a baseline with `task bench-compare` and diff with [`benchstat`](https://pkg.go.dev/golang.org/x/perf/cmd/benchstat).
91+
92+
### Race detector (`task test-race`)
93+
94+
- Runs the full suite under `-race`. Currently zero-finding on `main`; keep it that way. Any new goroutine, any new package-level mutable state, any test that swaps a package-level var should stay green under this task.
95+
- Runs in `precommit-full` but not `precommit`, so PR authors should run it before opening a PR.
96+
97+
### Coverage (`task test-coverage` / `task test-coverage-full`)
98+
99+
- `task test-coverage` — fast per-package summary, no subprocess instrumentation.
100+
- `task test-coverage-full` — builds the CLI binary with `-cover` so subprocess runs in `cmd/*_test.go` are counted too; merges test-process + subprocess covdata; emits `coverage.out` and `coverage.html`. This is the honest number.
101+
- Target is 90%+ per package where reachable; packages below that threshold have structural reasons documented inline (interactive TTY, platform-specific branches, side-effect-heavy upgrade pipeline).
102+
103+
### Static analysis (`task lint`) and vulnerability scan (`task vuln-check`)
104+
105+
- `go vet` + `gofmt -l` + `staticcheck` + `gosec`. Lint exclusions (`G104`, `G115`, `G304`, etc.) are documented inline in the Taskfile with rationale — if you disagree with one, argue the case in the PR.
106+
- `govulncheck` is run by `precommit-full` and must be clean before any release. Re-run it after every Go toolchain bump.
45107

46108
## Pull requests
47109

@@ -73,8 +135,9 @@ The PR flow (introduced in 0.3.0) preserves an audit trail of every cask update;
73135
jj git fetch
74136
jj log -r 'main@origin..@' # expect 0 commits not on origin
75137

76-
# All quality gates pass.
77-
task precommit # fmt + vet + test + build-all
138+
# Full quality gate — race detector + active fuzz + vuln scan + all-platform build.
139+
# Takes ~3-5 minutes; use this at the release boundary, not for every commit.
140+
task precommit-full
78141

79142
# GoReleaser can build the full artifact set with ldflags intact.
80143
task release-check # validates .goreleaser.yaml

0 commit comments

Comments
 (0)