Skip to content

Commit 21d1e62

Browse files
committed
Release v0.6.0
1 parent 6302a82 commit 21d1e62

3 files changed

Lines changed: 173 additions & 2 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ coverage/
1515

1616
# Task runner cache
1717
.task/
18+
19+
# Claude Code local settings
20+
.claude/settings.local.json

CHANGELOG.md

Lines changed: 169 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,173 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.6.0] — 2026-04-23
11+
12+
### Added
13+
- **Proof format restructure.** The subject-kind discriminator moves
14+
from the string `s.src` to a top-level integer `t` that identifies
15+
both the subject and every external-commitment chain. The byte is
16+
emitted as a 2-byte big-endian u16 right after the version byte in
17+
the Ed25519 signing pre-image, so flipping `t` on a bundle without
18+
re-signing breaks signature verification — real cryptographic
19+
domain separation between subject kinds. Frozen type-code registry
20+
(single source of truth at `internal/proof/ptype/ptype.go`):
21+
- `10` block · `20` item · `30` entropy_nist · `31` entropy_stellar
22+
· `32` entropy_bitcoin · `40` commitment_stellar · `41` commitment_bitcoin.
23+
The wire field `v` remains `1`; this is a structure-only refresh,
24+
not a new bundle version. Parser tightened: unknown `t` rejected,
25+
`cx` required non-empty, block proofs must not carry `s` / `ip`,
26+
non-block proofs must, every hash field decodes to the exact byte
27+
count declared by the CDDL schema (32 for hashes, 4 for kid, 32
28+
for pk, 64 for sig). Opaque failures three steps into the pipeline
29+
now surface as "pk must be 32 bytes, got 31" at parse time.
30+
- **Beacons are first-class proof subjects (`t = 11`).** Beacon and
31+
block (`t = 10`) share the same wire shape (no `s`, no `ip`,
32+
`subject_hash == block_hash`) but are cryptographically distinct
33+
because `t` is in the signed payload — a block bundle and a beacon
34+
bundle over the same underlying block have different signatures.
35+
Verify dispatches both through the same pipeline via the new
36+
`ptype.IsBlockLikeSubject` / `ProofBundle.IsBlockLike()` predicates;
37+
the report's Type row reads "Beacon" for `t=11`, "Block" for `t=10`.
38+
- **`truestamp beacon {latest,list,get,by-hash}`** — read-only client
39+
for the Truestamp Beacons JSON:API (`GET /api/json/beacons/*`).
40+
Shared `--json` / `--hash-only` / `--silent` flags, client-side
41+
UUIDv7 and 64-hex validation before the network round-trip, and
42+
typed sentinel errors (`Unauthorized`, `NotFound`, `BadRequest`,
43+
`RateLimited`, `Server`) that preserve the server's JSON:API error
44+
envelope. `truestamp beacon` alone defaults to `latest`.
45+
- **`truestamp download --type`** extended to the full six-value
46+
enum matching the server's strict `/proof/generate` contract:
47+
`item | entropy_nist | entropy_stellar | entropy_bitcoin | block | beacon`.
48+
No `auto`, no bare `entropy`. Smart default from id shape: a ULID
49+
id defaults to `--type item` (the only unambiguous case); a UUIDv7
50+
id without `--type` fails fast listing the five valid choices,
51+
since the server cannot infer the subtype from id alone. Downloaded
52+
filenames use the `truestamp-<stem>-<id>.<ext>` convention with
53+
hyphenated stems (`truestamp-entropy-nist-<id>.json`) while the
54+
wire value stays underscored to match the server enum.
55+
- **`truestamp verify --type`** — assert the expected subject type
56+
locally and remotely, defending against swapped-file confused-deputy
57+
scenarios (block and beacon are wire-identical apart from the `t`
58+
byte, so both verify cleanly on their own). Local mode surfaces a
59+
mismatch as a "Subject Type" failure step in the report. Remote
60+
mode forwards the value to the server's `/proof/verify type` arg;
61+
the server rejects with HTTP 4xx + `meta.code=subject_type_mismatch`
62+
if the posted bundle's `t` disagrees. When `--type` is omitted the
63+
CLI infers it from the input filename / URL basename using the
64+
download convention (`truestamp-beacon-019d…json` → beacon), prints
65+
a faint stderr hint so the inference is traceable in transcripts,
66+
and lets an explicit `--type` override. Inference goes through a
67+
dedicated fuzz target (`FuzzInferTypeFromFilename`) with a 20-seed
68+
corpus.
69+
- **Live entropy-source consistency checks.** The Entropy Source
70+
verification step now contacts the canonical upstream source for
71+
each subject type and byte-compares the bundle's stored value:
72+
- NIST Beacon (`t=30`) — `beacon.nist.gov/beacon/2.0/chain/{c}/pulse/{p}`,
73+
compare `outputValue` and `timeStamp`.
74+
- Stellar ledger (`t=31`) — Horizon `/ledgers/{seq}` on the network
75+
derived from the bundle's Stellar commitment; compare `hash` and
76+
`closed_at`.
77+
- Bitcoin block (`t=32`) — `blockstream.info/api/block/{hash}` pinned
78+
to mainnet (the authoritative public-randomness source, even in
79+
dev deployments that commit to regtest/testnet); compare `height`
80+
and `time`.
81+
Skipped under `--skip-external`.
82+
- **Dual Details + Verify URLs on every post-action card** (beacon,
83+
download, create, and the verify report's Proof section). URL shape
84+
comes from the shared `ui.SubjectDetailURL` / `ui.SubjectVerifyURL`
85+
/ `ui.BeaconDetailURL` / `ui.BeaconVerifyURL` helpers, routed
86+
through one `publicWebBase` that strips a trailing `/api/json`.
87+
URLs render unconditionally — localhost, 127.0.0.1, and plain-http
88+
hosts too — so the links are visible when developing against a
89+
local server.
90+
- `internal/beacons` — HTTP client for the Beacons JSON:API (Latest /
91+
List / Get / ByHash) with typed errors, 429 `Retry-After` surfacing,
92+
and a fuzz suite over the parser + UUIDv7 / 64-hex validators.
93+
94+
### Changed
95+
- **Homegrown semver parser replaced with `golang.org/x/mod/semver`**
96+
(the same package `go mod` uses). ~100 lines of parser + comparator
97+
deleted; pre-release identifier comparison (numeric vs alphanumeric,
98+
shortest-prefix rules from SemVer §11) now comes from battle-tested
99+
upstream code. Public API (`Semver`, `ParseSemver`, `Compare`,
100+
`Display`, `IsPreRelease`) is preserved. `go.mod` gains a direct
101+
`golang.org/x/mod` dependency — already transitive, so effectively
102+
zero footprint growth in real-world installs.
103+
- **Upgrade-check no longer mis-ranks git-describe dev builds.** A
104+
locally-built `0.5.0-4-g356ee75-dirty` binary is conceptually
105+
4 commits AHEAD of v0.5.0, but SemVer §11 ranks it as a pre-release
106+
BELOW the tag. Two concrete fixes: new
107+
`selfupgrade.IsGitDescribeDev` predicate regex-detects the
108+
`<N>-g<SHA>[-dirty]` shape (accepts bare post-tag and post-release
109+
suffixes, rejects plain pre-releases like `rc.1`); new
110+
`selfupgrade.UpgradeAvailable` delegates to the normal comparator
111+
but compares MAJOR.MINOR.PATCH cores only for dev builds, so
112+
`0.5.0-4-g...-dirty → v0.5.0` reports no upgrade (would downgrade)
113+
while → v0.5.1 / v0.6.0 / v1.0.0 still does. Both `truestamp
114+
upgrade --check` and the passive upgrade-check nag now use this
115+
predicate.
116+
- **Narrow-TTY layout fix.** `lipgloss.JoinVertical(Left, …)` pads
117+
every line to the widest line across all inputs; one over-wide
118+
section pushed every other row past the terminal width, which the
119+
terminal then hard-wrapped, producing phantom blank lines after
120+
every table row. Switched to `strings.Join(…, "\n")` at six call
121+
sites (`internal/verify/presenter.go`, `cmd/config.go`,
122+
`cmd/beacon.go`, `cmd/beacon_list.go`, `cmd/create.go`,
123+
`cmd/download.go`). Each site carries a comment pointing back to
124+
the root-cause note.
125+
- **Tight vertical spacing across every card.** `lipgloss.HiddenBorder()`
126+
emits invisible top/bottom border rows that stack with explicit
127+
`""` separators in `strings.Join`, doubling the apparent gap
128+
between a section header and its first row. New
129+
`ui.CompactTable()` helper returns a table with `HiddenBorder`
130+
plus `BorderTop/Bottom/Left/Right(false)` — content is flush to
131+
whatever comes before and after, and callers control inter-section
132+
spacing explicitly. Applied at 16 call sites across `cmd/` and
133+
`internal/verify/presenter.go`.
134+
- **Full-hash display everywhere.** Removed `truncateHash()` and its
135+
seven call sites in `internal/verify/presenter.go`; diagnostic
136+
contexts (hash-mismatch diffs, entropy-source mismatch, Bitcoin
137+
fetch/height errors) now emit the full 64-char hex. Two different
138+
hashes sharing prefix + suffix used to read as visually identical
139+
— no longer. Commitments section already did, so this is the
140+
convergence pass. `cmd/beacon_list.go` also drops its local
141+
`truncateHashShort` in favour of TTY-aware width logic.
142+
- **Centralized timestamp truncation.** Promoted the package-local
143+
`verify.truncateToSecond()` to exported `ui.TruncateToSecond()`,
144+
applied at every human-display site (beacon list TIMESTAMP column,
145+
beacon card, verify report Timeline section). `--json` output and
146+
timestamp-extraction sites (`convert time`, `convert id`) keep full
147+
microsecond precision; `verify` Stellar `closed_at` / Bitcoin `time`
148+
mismatch errors keep full precision so a sub-second diff isn't
149+
masked.
150+
- **Stellar `net` strict.** Only `"testnet"` and `"public"` accepted
151+
— the previous tolerance of other values is gone.
152+
- **Legacy `s.kid == b.kid` equality check removed.** Legitimate key
153+
rotation can produce divergent kids; subject-kid tampering is
154+
still detected because `kid` is an input to the 0x13 / 0x23
155+
composite hash.
156+
- Verify report groups renamed to match the authoritative spec:
157+
Subject Data, Block Hash, Epoch Proof, Temporal Window,
158+
Entropy Source.
159+
- Subject-card URL rows now render as rows of the SAME table as
160+
labels (Details / Verify inherit the right-aligned-label column),
161+
and the `` arrow between label and URL was dropped as redundant
162+
visual noise.
163+
- CBOR parser now accepts `t ∈ {10, 11}` for no-`s` / no-`ip` proofs
164+
matching the JSON parser. Marshaller emits no `s` / `ip` for both.
165+
166+
### Removed
167+
- `--type auto` and bare `--type entropy` on `download` — rejected
168+
client-side before any I/O. The server's strict six-value enum is
169+
the contract.
170+
- Old `s.src` string discriminator. All callers branch on the `t`
171+
integer exclusively.
172+
173+
### Security
174+
- Build pulls `golang.org/x/mod` direct; `task vuln-check` clean.
175+
- Dependabot: `github-actions` group updates ([#4](https://github.com/truestamp/truestamp-cli/pull/4)).
176+
10177
## [0.5.0] — 2026-04-21
11178

12179
### Added
@@ -473,7 +640,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
473640
v0.1.0 is the first release of a standalone Go codebase; the two share
474641
nothing beyond the repository name.
475642

476-
[Unreleased]: https://github.com/truestamp/truestamp-cli/compare/v0.5.0...HEAD
643+
[Unreleased]: https://github.com/truestamp/truestamp-cli/compare/v0.6.0...HEAD
644+
[0.6.0]: https://github.com/truestamp/truestamp-cli/releases/tag/v0.6.0
477645
[0.5.0]: https://github.com/truestamp/truestamp-cli/releases/tag/v0.5.0
478646
[0.4.0]: https://github.com/truestamp/truestamp-cli/releases/tag/v0.4.0
479647
[0.3.3]: https://github.com/truestamp/truestamp-cli/releases/tag/v0.3.3

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ require (
2222
github.com/spf13/cobra v1.10.2
2323
github.com/spf13/pflag v1.0.10
2424
golang.org/x/crypto v0.50.0
25+
golang.org/x/mod v0.35.0
2526
)
2627

2728
require (
@@ -57,7 +58,6 @@ require (
5758
github.com/rivo/uniseg v0.4.7 // indirect
5859
github.com/x448/float16 v0.8.4 // indirect
5960
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
60-
golang.org/x/mod v0.35.0 // indirect
6161
golang.org/x/sync v0.19.0 // indirect
6262
golang.org/x/sys v0.43.0 // indirect
6363
)

0 commit comments

Comments
 (0)