Bootstrap sigillum: a standalone signing & verification CLI over go/signing¶
- Authors
- Matt Cockayne, Claude Fable 5 (AI drafting assistant)
- Date
- 2026-07-28
- Status
- DRAFT. Phase 0 (repo bootstrap) is done and verified; the remaining phases and the open decisions below need review before implementation starts.
- Related
- go-tool-base spec
2026-07-28-ed25519-kms-signing.md(the #5 design spike — KMS-custody-preserving signing; carries decisions D-2..D-6 this project consumes; currently on gtb branchdocs/ed25519-kms-signing-spec, not yet merged),gitlab.com/phpboyscout/go/signingandgo/signing-aws-kms(the logic these commands drive)
1. Purpose & context¶
The phpboyscout ecosystem signs and verifies release artefacts. The signing
logic is already an extracted, reusable module family —
gitlab.com/phpboyscout/go/signing (v0.2.2) with backends such as
go/signing-aws-kms (v0.2.5) and helpers go/signing/openpgpkey,
go/signing/verify. What is not shared is the command surface: the cobra
commands that expose signing to operators live inside go-tool-base as
internal/cmd/sign and internal/cmd/keys, private to gtb and wired to gtb's
Props DI container.
gtb is deliberately both a library and a CLI, so hosting these commands
internally made sense while gtb was the only consumer. sigillum changes that:
it is a pure CLI whose entire reason to exist is signing/verification, and
it should own the top-level sign / keys commands as first-class citizens
with all real work delegated to go/signing. Copying gtb's internal commands
into sigillum would duplicate them; importing gtb wholesale would pull the
entire framework and risk a module cycle if gtb ever needs a sigillum-side
capability. The resolution is a small shared command module (§3).
2. Phase 0 — repo bootstrap (DONE)¶
Completed and verified on 2026-07-28:
- Generated with the current gtb generator (post the generator-template
version-tracking fix), so every pin is at head — no manual patching:
gtb generate project --name sigillum --repo phpboyscout/sigillum --host gitlab.com --git-backend gitlab --features init,update,docs,doctor,changelog,keychain,config. - Pins at head: phpboyscout/cicd components v0.33.0, releaser-pleaser
v0.9.0, pre-commit golangci-lint v2.12.2 / pre-commit-hooks v6.0.0, Go
toolchain from the build environment,
go-tool-basev0.33.0. - Dependencies refreshed (
go get -u ./... && go mod tidy) to latest. - Builds and runs:
go build ./...clean; thesigillumbinary lists the scaffolded commands (init,update,docs,doctor,changelog,config,version). Repo git-initialised; scaffold committed.
The sign / keys commands are not present yet — they arrive in Phase 2.
3. Target architecture¶
gitlab.com/phpboyscout/go/signing (logic: sign, verify,
▲ ▲ openpgpkey, backends)
│ │
gitlab.com/phpboyscout/go/signing-aws-kms (KMS backend)
▲
│ (real work)
gitlab.com/phpboyscout/go/signing-cli ← NEW tiny module
(cobra command builders: sign, keys {generate,mint,wkd};
props-decoupled — depends only on go/signing + cobra)
▲ ▲
│ attach │ re-attach (drops internal copies)
sigillum (pure CLI) go-tool-base (library + CLI)
go/signing-cli is a new, dedicated, tiny module (decision below). It holds
only the cobra command constructors and their flag wiring. It must not
import gtb's props package. Because it depends only on go/signing (+ cobra),
there is no path back to gtb or sigillum, so no module cycle is possible.
The Deps seam — resolved (from the current gtb code). The coupling turns
out to be tiny: the command logic functions already take only
props.LoggerProvider, and the sole logger methods called across sign and
all of keys are Info and Warn. The only other gtb type in play is the
*setup.Command return type (gtb's feature-middleware wrapper). So:
go/signing-clideclares its own narrow logger interface — a slog-shaped subset,Logger interface { Debug/Info/Warn/Error(msg string, args ...any) }. gtb'slogger.Loggerand sigillum's logger both satisfy it structurally; no adapter, no gtb import.- Constructors take that
Loggerand return plain*cobra.Command, not*setup.Command. The gtb-specificsetup.Wrapstays caller-side: - gtb:
setup.Wrap("", signingcli.NewCmdSign(p.GetLogger())); - sigillum: attaches the returned
*cobra.Commanddirectly to its root. go/signing-cliimports only cobra, pflag,go/signing,go/signing-aws-kms.
This makes the extraction a near-mechanical move plus a return-type/param change, not a redesign.
Command surface to share (as it exists in gtb today)¶
| Command | Purpose |
|---|---|
sign <input-file> |
Produce an ASCII-armored OpenPGP detached signature via a configured backend |
keys |
Manage OpenPGP keys for release-binary signing (parent) |
keys generate |
Generate a fresh keypair locally (Ed25519 or RSA), emit both halves |
keys mint |
Mint an ASCII-armored OpenPGP public key from an existing signer |
keys wkd <public-key.asc>... |
Generate a Web Key Directory tree from public keys |
All five already call into go/signing / go/signing/openpgpkey /
go/signing/verify; the extraction moves the cobra wrappers, not the logic.
4. Roadmap¶
- Phase 0 — bootstrap (DONE, §2).
- Phase 1 —
go/signing-climodule. Create the repo (per the module extraction playbook: public,<name>.go.phpboyscout.ukdocs, cicd at head). Move the command builders out of gtb'sinternal/cmd/{sign,keys}, decoupling them via the resolvedDepsseam above (narrowLoggerinterface; constructors return*cobra.Command;setup.Wrapmoves caller-side). Port the existing tests. The KMS-Ed25519 → prehashed-minisign sink from #5 lands here (or ingo/signing) as the follow-on; it is gated on the rekey. - Phase 2 — sigillum attaches. Depend on
go/signing-cli; attachsignandkeysto sigillum's root; provide sigillum's adapter for theDepsseam; add E2E/Gherkin coverage for the signing workflows. - Phase 3 — gtb re-attaches. Replace gtb's
internal/cmd/{sign,keys}withgo/signing-cliattachments through a Props→Deps adapter; delete the internal command packages; keep gtb's behaviour byte-compatible for its users. (Extraction cut-over ⇒feat, notrefactor, so releaser-pleaser cuts a release — see the ecosystem convention.)
5. Decisions (all resolved 2026-07-28)¶
- Name —
sigillum(wax-seal theme, market-checked). [#5 D-4] sign/keyscommand home — a dedicated tinygo/signing-climodule (not gtbpkg/, not duplicated), props-decoupled.- D-2 — minisign format: prehashed "ED". Producers emit the prehashed
"ED" form (
ed25519(BLAKE2b-512(<file>))— a 64-byte digest, within KMS's 4096-byte RAW Sign limit, so HSM custody is preserved) and never pure "Ed". "ED" is also the form cargo-binstall requires. Two KMS Sign calls per artefact (main signature over the digest + the minisign global signature). - D-3 — rtb-update: "ED"-only, clean break, drop the manifest.
verify.rsaccepts only prehashed "ED" (legacy pure-"Ed" and raw-64-byte acceptance removed); the separate manifest goes away, so a single self-describing.sigserves both rtb-update and cargo-binstall. Sequencing: an "ED"-only producer switch means already-deployed "Ed"-only binaries cannot self-update across the cutover. This is coordinated with the Friday rekey, which is itself a hard trust-root cutover (deployed binaries pin the old public keys and cannot verify new-key signatures regardless of Ed/ED). Doing Ed→ED in the same rotation therefore adds no incremental breakage — one clean break, not two. The ED-capable rtb-update ships as part of the rekeyed release set; anyone on a pre-rekey binary re-establishes trust and reinstalls once, as the rekey already requires. - D-5 — key rotation. Held; Matt completes the prod rebuild/rekey himself (Friday window). Now also the coordination point for the D-3 cutover.
- D-6 — key-generation algorithm: no implicit default.
keys generaterequires an explicit--algorithm ed25519|rsa(or config equivalent) and errors, listing valid choices, when unset — no silent default, an informed-choice posture appropriate to a signing tool. (Signing with an existing key needs no choice; the algorithm is fixed by the key.)
6. Acceptance criteria¶
- sigillum generated from the current generator, all pins/deps at head, builds and runs.
-
go/signing-cliexists, props-decoupled, with the five commands and their ported tests green; docs microsite live. - sigillum exposes
signandkeysat the top level, delegating entirely togo/signing, with E2E coverage. - gtb re-attaches the shared commands and deletes
internal/cmd/{sign,keys}with no user-visible behaviour change. - No module cycle:
go mod graphshowsgo/signing-clidepending ongo/signingonly (never gtb or sigillum).