Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .fulmen/app.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ app:
# Config file name (without extension); resolves to ~/.config/3leaps/decernor.yaml.
config_name: decernor
description: "Local key-material hygiene and readiness checks"
version: "0.1.2"
version: "0.1.3"
metadata:
repository_category: cli
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

## [0.1.3] - 2026-08-18

First **signed** private cut. Fingerprint pins are generated by
`decernor fingerprint` and staged into the checksum set before signing
([PDR-0001](docs/decisions/PDR-0001-committed-signing-anchors.md)).

### Added

- Waitprims-style release DAG: download, notes, **stage-anchors**,
checksums, sign, export-keys, verify, upload.
- `make release-insert-anchors` writes `keys/expected-fingerprints.*`
from env-only bindings (no key paths in the tree).

## [0.1.2] - 2026-08-18

First tagged snapshot of the Decernor CLI. Private repository; unsigned draft
Expand Down Expand Up @@ -46,4 +59,5 @@ release is the intended publish shape for this cut.
- Upstream baseline changelog and release-note history inherited from the
template.

[0.1.3]: https://github.com/3leaps/decernor/releases/tag/v0.1.3
[0.1.2]: https://github.com/3leaps/decernor/releases/tag/v0.1.2
80 changes: 80 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
.PHONY: all help bootstrap bootstrap-force hooks-ensure tools sync dependencies verify-dependencies version version-set version-bump-major version-bump-minor version-bump-patch
.PHONY: lint test build install build-all package package-sign verify-release-key clean fmt fmt-check check-all precommit prepush pr-final license-audit
.PHONY: release-check release-prepare release-build release-preflight release-notes-check doctor validate-app-identity
.PHONY: release-clean release-download release-notes release-stage-anchors release-insert-anchors
.PHONY: release-checksums release-sign release-export-keys release-verify-checksums
.PHONY: release-verify-signatures release-verify-keys release-verify release-upload release
.PHONY: release-guard-tag-version
.PHONY: sync-embedded-identity verify-embedded-identity test-standalone-binary cdrl-verify

# Binary and version information
Expand All @@ -10,6 +14,13 @@ ifeq ($(OS),Windows_NT)
BINARY_EXT := .exe
endif
VERSION := $(shell cat VERSION 2>/dev/null || echo "dev")
DECERNOR_RELEASE_TAG ?= v$(VERSION)
export DECERNOR_RELEASE_TAG
DECERNOR_MINISIGN_KEY ?=
DECERNOR_MINISIGN_PUB ?=
DECERNOR_PGP_KEY_ID ?=
DECERNOR_GPG_HOMEDIR ?=
DIST_RELEASE := dist/release
COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
BUILD_DATE := $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
LDFLAGS := -X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.buildDate=$(BUILD_DATE)
Expand Down Expand Up @@ -207,12 +218,80 @@ release-notes-check: ## Verify VERSION, identity yaml, and notes files for this
echo "✅ Release notes check passed ($$V)"

release-preflight: release-notes-check verify-embedded-identity fmt-check lint test ## Non-mutating tag gate
@if [ ! -f keys/expected-fingerprints.txt ] || [ ! -f keys/expected-fingerprints.ndjson ]; then \
echo "❌ missing keys/expected-fingerprints.{txt,ndjson} — run make release-insert-anchors"; exit 1; \
fi
@echo "✅ Release preflight passed"

release-check: release-preflight ## Alias for release-preflight

release-prepare: release-preflight ## Alias for release-preflight

release-guard-tag-version: ## Verify DECERNOR_RELEASE_TAG matches VERSION
@./scripts/release-guard-tag-version.sh

release-clean: ## Remove dist/release
rm -rf $(DIST_RELEASE)
@echo "[ok] $(DIST_RELEASE) cleaned"

release-download: ## Download unsigned archives from GitHub
@if [ -z "$(DECERNOR_RELEASE_TAG)" ] || [ "$(DECERNOR_RELEASE_TAG)" = "v" ]; then \
echo "error: set DECERNOR_RELEASE_TAG=vX.Y.Z" >&2; exit 2; \
fi
@./scripts/download-release-assets.sh $(DECERNOR_RELEASE_TAG) $(DIST_RELEASE)

release-notes: ## Copy docs/releases/vX.Y.Z.md into dist before checksums
@src="docs/releases/$(DECERNOR_RELEASE_TAG).md"; \
if [ ! -f "$$src" ]; then echo "❌ missing $$src" >&2; exit 1; fi; \
mkdir -p "$(DIST_RELEASE)"; \
cp "$$src" "$(DIST_RELEASE)/release-notes-$(DECERNOR_RELEASE_TAG).md"; \
echo "[ok] copied $$src into the checksum set"

release-insert-anchors: ## Generate keys/ pins from DECERNOR_* env + decernor fingerprint
@./scripts/insert-expected-fingerprints.sh

release-stage-anchors: ## Copy committed pins into dist before checksums (net-new)
@./scripts/stage-release-anchors.sh $(DIST_RELEASE)

release-checksums: ## Generate SHA256SUMS and SHA512SUMS (archives + notes + pins)
@./scripts/generate-checksums.sh $(DIST_RELEASE) $(DECERNOR_RELEASE_TAG)

release-sign: ## Sign checksum manifests (requires DECERNOR_MINISIGN_KEY)
@if [ -z "$(DECERNOR_MINISIGN_KEY)" ]; then \
echo "error: DECERNOR_MINISIGN_KEY is not set" >&2; exit 2; \
fi
@./scripts/sign-release-assets.sh $(DECERNOR_RELEASE_TAG) $(DIST_RELEASE)

release-export-keys: ## Export public signing keys (DECERNOR_MINISIGN_PUB + GPG env)
@./scripts/export-release-keys.sh $(DIST_RELEASE)

release-verify-checksums: ## Verify SHA256SUMS against staged files
@cd $(DIST_RELEASE) && shasum -a 256 -c SHA256SUMS

release-verify-signatures: ## Verify minisign/PGP signatures
@./scripts/verify-signatures.sh $(DIST_RELEASE)

release-verify-keys: ## Public-only + pin match via decernor
@./scripts/verify-public-keys.sh $(DIST_RELEASE)

release-verify: release-verify-checksums release-verify-signatures release-verify-keys
@echo "[ok] All release verifications passed"

release-upload: release-verify ## Upload signed provenance (draft unchanged)
@./scripts/upload-release-assets.sh $(DECERNOR_RELEASE_TAG) $(DIST_RELEASE)

# Serialized walk. Leaves stay independent. Stage anchors before checksums.
release: release-guard-tag-version ## Full signing workflow (after CI draft)
$(MAKE) release-clean
$(MAKE) release-download
$(MAKE) release-notes
$(MAKE) release-stage-anchors
$(MAKE) release-checksums
$(MAKE) release-sign
$(MAKE) release-export-keys
$(MAKE) release-upload
@echo "[ok] Release $(DECERNOR_RELEASE_TAG) complete"

release-build: build-all ## Build release artifacts (binaries + checksums)
@echo "✅ Release build complete"

Expand Down Expand Up @@ -267,6 +346,7 @@ verify-release-key: ## Verify exported public key contains no private material
test: verify-embedded-identity ## Run all tests
@echo "Running test suite..."
$(GOTEST) ./... -v -cover
@bash tests/release/ceremony_test.sh

lint: ## Run lint checks with goneat
@if [ -z "$(GONEAT_BIN)" ]; then echo "❌ goneat not found. Run 'make bootstrap' first."; exit 1; fi
Expand Down
75 changes: 27 additions & 48 deletions RELEASE_CHECKLIST.md
Original file line number Diff line number Diff line change
@@ -1,74 +1,53 @@
# Release Checklist

Maintainer walk for each `vX.Y.Z` tag. Decernor is a Go CLI. There is no
crate publish, no bindings tag, and no FFI tarball.
Maintainer walk for each `vX.Y.Z` tag. Decernor is a Go CLI.

This repository is **private**. A successful first cut is a private annotated
tag plus a **draft** GitHub release. Signing is optional until org public keys
are on disk and fingerprints are inserted from `decernor fingerprint` (not
hand-typed).
Bindings are **environment variables only** (`DECERNOR_MINISIGN_KEY`,
`DECERNOR_MINISIGN_PUB`, `DECERNOR_GPG_HOMEDIR`, `DECERNOR_PGP_KEY_ID`).
No key paths in this tree. Load them from a host-local profile.

## Prerequisites

- `gh` authenticated with push access to `3leaps/decernor`
- Local `main` matches `origin/main`
- `gpg` / `minisign` only if this cut will sign
See [PDR-0001](docs/decisions/PDR-0001-committed-signing-anchors.md).

## 1. Write / prep

- [ ] `VERSION` is the tag without the `v` prefix
- [ ] `.fulmen/app.yaml` `app.version` matches `VERSION`
- [ ] Embedded identity is in sync: `make sync-embedded-identity` then
`make verify-embedded-identity`
- [ ] `CHANGELOG.md` has a `## [X.Y.Z]` section and an empty `[Unreleased]`
- [ ] `RELEASE_NOTES.md` is the landing page for this cut
- [ ] `docs/releases/vX.Y.Z.md` is **that cut only**
- [ ] `make sync-embedded-identity && make verify-embedded-identity`
- [ ] Pins exist (`make release-insert-anchors` after env is loaded)
- [ ] `CHANGELOG.md` has `## [X.Y.Z]`; `RELEASE_NOTES.md` has `## vX.Y.Z`
- [ ] `docs/releases/vX.Y.Z.md` is that cut only
- [ ] `make release-preflight` passes
- [ ] PR merge; CI green on `main`

Commit the prep on a `chore/release-vX.Y.Z` branch. Conventional subject only
(no private planning ids). Open a PR; merge after CI is green.

## 2. Tag (after the notes PR is on `main`)
## 2. Tag

```bash
git switch main
git pull --ff-only origin main
git switch main && git pull --ff-only origin main
test "$(cat VERSION)" = "X.Y.Z"
make release-preflight
git tag -a "vX.Y.Z" -m "vX.Y.Z"
git push origin "vX.Y.Z"
```

Do not tag until CI on that `main` commit is green.

## 3. Draft GitHub release

`release.yml` builds on `v*` and opens a **draft** with `dist/release/*`.
Wait for the Release workflow to draft unsigned archives.

- [ ] Confirm the workflow finished
- [ ] Confirm `VERSION` matched the tag (workflow fails closed if not)
- [ ] Leave the release **draft** unless this cut is explicitly a publish
## 3. Sign / upload (MFA host)

## 4. Signing (optional, later)

Only when both org public files exist:

```text
decernor fingerprint <gpg.asc> --class public --kind gpg --format json \
--path-mode none --gpg-role primary
decernor fingerprint <minisign.pub> --class public --kind minisign \
--format json --path-mode none
```bash
# env already loaded: DECERNOR_*
export DECERNOR_RELEASE_TAG=vX.Y.Z
make release
```

Write `keys/expected-fingerprints.txt` from those records (verbatim hex).
Do not wrap `gpg --show-keys` or `xxd | head`. Then `make package-sign` and
`make verify-release-key`. Undraft only after verify is green.
That walk is: clean → download → **notes** → **stage-anchors** →
checksums → sign → export-keys → verify → upload.

`release-stage-anchors` is **net-new**. Checksum scripts will not pick
up the pin files unless this runs first.

If publics are missing, **stop after step 3**. Unsigned private draft is
success for that cut.
The GitHub release stays **draft**. Undraft is a separate maintainer step.

## 5. Out of scope for a private 0.1.x tag
## 4. Rekey

- Flipping the GitHub repository public
- crates.io / Homebrew / Scoop
- Hand-typed fingerprints
New export + `make release-insert-anchors` + new cut. Do not edit hex
by hand.
35 changes: 13 additions & 22 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,21 @@
# Release Notes

Landing page for the latest Decernor cut. Per-cut payload for GitHub:
[`docs/releases/v0.1.2.md`](docs/releases/v0.1.2.md).

## v0.1.2 — 2026-08-18
Landing page for the latest Decernor cut. Per-cut payload:
[`docs/releases/v0.1.3.md`](docs/releases/v0.1.3.md).

First tagged snapshot. **Private** repository. Draft GitHub release;
unsigned unless org public keys are inserted from `decernor fingerprint`.
## v0.1.3 — 2026-08-18

**Shipped verbs:** `scan`, `guardread`, `fingerprint`/`fp`, `validate`
(contract-base + classification gate), `readiness validate-config`,
`version`, `envinfo`, `doctor`.
First **signed** private release. Pins in `keys/expected-fingerprints.txt`
are produced by `decernor fingerprint` (not hand-typed) and copied into
`dist/release/` **before** checksums, then signed with the rest of the
payload.

**Fingerprint contract:** successful GPG records carry `key_role` and
`key_id`. `--gpg-role primary` is the unique-primary selector (exit 3, no
stdout, if that named file has 0 or more than one primary). Minisign
trust-anchor field is lowercase 64-hex `minisign-public-blob-sha256-v1`.
Signing uses `DECERNOR_*` environment variables only. No key paths live
in this repository.

**Not in this cut:** static readiness evaluation, proof checks,
`derive-public-key`, `migrate`, embedded ceremony docs, public GitHub
visibility.
Still private. Draft GitHub release until a maintainer undrafts.

### Verify
## v0.1.2 — 2026-08-18

```sh
make check-all
make build
decernor version # 0.1.2
```
First tagged snapshot (unsigned draft). See
[`docs/releases/v0.1.2.md`](docs/releases/v0.1.2.md).
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.1.2
0.1.3
Loading
Loading