feat(dist): conform to DECISIONS/0001 distribution channels - #122
Conversation
Specify sting's adoption of skaphos-resources DECISIONS/0001 (distribution channels by artifact shape). sting is both Shape 2 (end-user CLI) and Shape 3 (MCP server) and inherits the union of both channel sets. Covers the five unmet required obligations: version-stamped source installs, a verify-before-replace self-update subcommand, Linux .deb/.rpm packages, an MCP registry entry, and a multi-arch container image. Records winget/scoop as out of scope pending Windows Authenticode signing, per the sequencing note on issue #121. Refs #121 Signed-off-by: Shawn Stratton <shawn@skaphos.io>
Five clarifications resolved and integrated into the distribution channel conformance spec: - Release integrity is verified, not reported: a post-release step queries each channel and fails the workflow when a required one is missing or stale. - MCP registry identity is io.skaphos/sting, proven by a DNS TXT record on skaphos.io with an org-scoped publishing credential. - Self-update verifies in-process with no external tooling, pinning the signer to sting's release workflow identity and the expected certificate issuer. - Windows self-replacement is fully specified but gated on Authenticode signing; until then update reports and exits without writing. - The container image is built inside the single release invocation, keeping the MCP registry as the only fail-as-a-unit exception. Also corrects two factual claims: the channel count in the Problem section, and SC-009, which had placed the container image in the release checksum manifest. Refs #121 Signed-off-by: Shawn Stratton <shawn@skaphos.io>
Phase 0 research and Phase 1 design for the DECISIONS/0001 adoption. Research resolved five unknowns: the debug.ReadBuildInfo fallback matrix for go install vs local builds, the sigstore-go verification API and the exact signer identity to pin, dockers_v2 as the supported multi-arch path in GoReleaser v2.17, the MCP registry server.json shape and io.skaphos DNS proof, and install-ownership detection ordering. Design adds two internal packages (buildinfo, selfupdate) plus a thin Cobra command, against a larger surface of release-pipeline configuration. Four of the five obligations are config; sting update is the only real engineering. Constitution check passes. One dependency addition (sigstore-go) is justified in Complexity Tracking: verification is unskippable and must not require tooling on the user's machine, so it has to happen in-process. Also identifies a change to existing behavior: release.yml drops the Homebrew cask with only a warning when the tap credential is unusable. FR-038 makes that a hard failure. Refs #121 Signed-off-by: Shawn Stratton <shawn@skaphos.io>
sting version printed "dev" for anything GoReleaser did not build, including the go install path the README recommends. Verified against the published release: `go install github.com/skaphos/sting/cmd/sting@v1.0.0` prints "sting dev". Adds internal/buildinfo, resolving identity through three tiers: release-time ldflags, then the metadata the Go toolchain records, then an explicit "unavailable". Release output is unchanged - ldflags still win. Neither placeholder can now reach a user: "dev" falls through to build metadata, and "(devel)" is treated as not-a-version rather than surfaced. Dirty working trees are marked (modified). Corrects the research table, which assumed pre-1.24 behavior: Go 1.26 synthesizes a pseudo-version for VCS builds rather than "(devel)". Verified empirically; "(devel)" still occurs with -buildvcs=false and stays covered. Refs #121 Signed-off-by: Shawn Stratton <shawn@skaphos.io>
Implements the cross-cutting self-update requirement of DECISIONS/0001, subject to its three rules. Verify before replacing: the cosign bundle over checksums.txt is verified in-process with sigstore-go, pinned to sting's own release workflow identity and the GitHub Actions OIDC issuer. The pin is the entire control - without it any Sigstore-signed blob verifies. Negative tests assert rejection of a fork's workflow, another workflow in this repo, a branch ref, a lookalike host, and a foreign issuer, exercised against the real sigstore policy using a virtual CA rather than against a local approximation. Defer to the package manager: the running binary is resolved through symlinks before ownership is judged, then classified as Homebrew, rpm, dpkg, Go toolchain, or unmanaged. Only unmanaged binaries are replaced; the rest get that channel's correct upgrade command and a non-zero exit. The dpkg advice does not say "apt upgrade" - there is no hosted apt repository. No implicit network calls: update is the only command that contacts a release endpoint, and it sends no credential at all. Replacement is atomic via a temp file in the target directory. The Windows rename-aside path is implemented and tested but gated off pending Authenticode signing, so no user is asked to self-install an unsigned binary. Exit codes are a contract: 1 verification, 2 package-managed, 3 platform gate, 4 refused, 5 permission. Cost, recorded in ADR 0011 rather than glossed: the binary grows 11MB -> 25MB (+122%) and go.mod goes from 44 to 106 requirements. The alternatives were worse - shelling out to cosign makes the command unusable for anyone without it installed, and dropping verification is forbidden by ADR-0001. Adds Apache-2.0 to LICENSES/ and three verified license exceptions for transitive deps shipping short-form Apache headers, so notices and reuse lint both pass. Refs #121 Signed-off-by: Shawn Stratton <shawn@skaphos.io>
Adds an nfpms block producing deb and rpm for amd64 and arm64, built from the same binaries as the archives and attached to the same release. Required for Shape 2 by DECISIONS/0001. Packages inherit the checksum manifest, and therefore the build-provenance attestation, with no extra wiring - verified: a snapshot build lists all four packages alongside the six archives in checksums.txt. SBOMs are the one guarantee packages do not inherit, so the sboms block gains an artifacts: package entry. Verified by installing both formats in containers: binary on PATH, correct version reported, LICENSE/notices under /usr/share/doc/sting, package database claims the binary (which is what sting update uses to detect an owned install), and removal leaves nothing behind. Uses ConventionalFileName so each format gets its own idiomatic naming rather than one shape forced onto both. README states plainly that there is no hosted apt/yum repository, so a .deb on a release page is not read as an implied repo. Refs #121 Signed-off-by: Shawn Stratton <shawn@skaphos.io>
Adds a Dockerfile and a dockers_v2 block producing a linux/amd64 + linux/arm64 manifest on ghcr.io/skaphos/sting. Required for Shape 3 by DECISIONS/0001 so sting can be run from a docker-based MCP client configuration. The image is built inside the same GoReleaser invocation as the archives and packages, from binaries it has already compiled - there is deliberately no build stage in the Dockerfile. Compiling inside the image would produce different bytes from the ones that were signed, notarized and attested. Base is distroless/static pinned by digest: no shell, no package manager, CA certificates present. Runs as uid 65532. Verified against a snapshot build: both platform images run, report the snapshot version, and complete an MCP initialize handshake over stdio advertising get_commits and get_repo_activity, both readOnly. Also pins the build and verification toolchain in .tool-versions so it is reproducible rather than assumed: syft and cosign (which GoReleaser shells out to for SBOMs and signing), plus reuse and check-jsonschema for the compliance and schema gates. Without syft pinned, the sboms step failed locally while passing in CI, which is the kind of divergence that hides real breakage. Refs #121 Signed-off-by: Shawn Stratton <shawn@skaphos.io>
Adds a checked-in server.json declaring sting under the org-branded reverse-DNS namespace, with an OCI package entry pointing at the GHCR image over stdio. Required for Shape 3 by DECISIONS/0001. CI gains a Release Manifests job validating both server.json against the published registry schema and .goreleaser.yaml via goreleaser check. Schema validation caught a real error while writing this: the registry caps description at 100 characters. Drift tests assert the entry cannot quietly diverge from the server: the namespace matches the domain whose DNS proof authorizes publishing, the transport matches what the container actually starts, and the advertised environment variables are sting's own STING_ keys. Advertising GITHUB_TOKEN would misrepresent the credential model to every client reading the registry, so that is asserted against explicitly. The release stamps the tag into both version fields and publishes with mcp-publisher over DNS auth. This is the one channel that does not fail the release as a unit - the registry is in preview and DECISIONS/0001 calls a break there a smaller emergency than a broken cask - so it is continue-on-error but loudly reported, naming the TXT record and the secret to check. Refs #121 Signed-off-by: Shawn Stratton <shawn@skaphos.io>
Two changes closing the silent-staleness gap DECISIONS/0001 names as the cost of adding channels. First, a missing or unusable Homebrew credential now fails the release. The previous behavior appended --skip=homebrew and continued with a ::warning::, so a release could complete green with the cask unpublished - exactly how repokeeper's cask sat pinned at 0.6.0 across two releases. A release that cannot publish a required channel is a failed release, not a partial success. This modifies existing behavior rather than adding to it. Second, a post-release verify job confirms each channel actually serves the new version by querying the channel, not by trusting the publishing step. Reachability is deliberately not the test: a cask pinned to the previous release responds perfectly well, which is the failure being caught. Release assets, the cask, and the container manifest block; the MCP registry is reported but non-blocking, per its documented exemption. Retries with backoff distinguish third-party propagation latency from a channel that did not publish. Writing the asset assertions caught a naming bug worth noting: GoReleaser strips the leading v, so tag v1.2.0 produces sting_1.2.0_linux_amd64.tar.gz. Asserting on the raw tag would have failed verification on every release. Documents the release and compliance tooling in AGENTS.md, including the golangci-lint stale-cache trap that made a clean tree look broken. Refs #121 Signed-off-by: Shawn Stratton <shawn@skaphos.io>
There was a problem hiding this comment.
Pull request overview
This PR implements skaphos-resources DECISIONS/0001 distribution-channel requirements for sting (Shape 2 CLI + Shape 3 MCP server), expanding release outputs and adding cross-channel coherence checks. It introduces a verified self-update path, makes go install builds report honest version metadata, and wires release/CI configuration to publish and validate the new channels.
Changes:
- Add in-binary build metadata resolution (
internal/buildinfo) and updatesting versionto use it (fixinggo install …@latestshowingdev/(devel)). - Add
sting updatewith verify-before-replace (Sigstore identity pin + checksum-manifest verification), plus atomic self-replacement and install-channel deferral. - Expand release/CI artifacts and validation:
.deb/.rpm, multi-arch GHCR image, MCPserver.jsonentry + schema validation, and associated docs/ADR updates.
Reviewed changes
Copilot reviewed 117 out of 118 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| .dockerignore | Minimizes Docker build context for GoReleaser-built binary-only image builds. |
| .github/workflows/ci.yml | Adds CI job to validate .goreleaser.yaml and schema-validate server.json. |
| .goreleaser.yaml | Adds nfpm packages, per-package SBOMs, and multi-arch dockers_v2 image publishing. |
| .specify/feature.json | Points spec-kit feature directory to specs/002-distribution-channels. |
| .tool-versions | Pins additional release/compliance tooling versions (syft/cosign/reuse/check-jsonschema). |
| AGENTS.md | Documents local tooling expectations for release/compliance checks. |
| Dockerfile | Adds distroless, non-root container image that runs sting mcp over stdio. |
| LICENSES/Apache-2.0.txt | Adds Apache-2.0 license text to support license compliance workflows. |
| README.md | Documents Linux packages, sting update, and container-based MCP server usage. |
| docs/adr/0011-self-update-trust-model.md | Adds ADR defining the self-update trust model and constraints. |
| docs/adr/README.md | Adds ADR 0011 to the ADR index. |
| go.mod | Adds sigstore-go and its dependency tree to support in-process verification. |
| internal/buildinfo/buildinfo.go | Implements build metadata resolution across ldflags → Go build info → unknown. |
| internal/buildinfo/buildinfo_test.go | Tests buildinfo precedence and placeholder suppression (dev/(devel)). |
| internal/cli/root.go | Adds update command and introduces exitError for stable exit codes. |
| internal/cli/update.go | Implements Cobra sting update command wiring, output, and exit-code mapping. |
| internal/cli/version.go | Updates sting version output to use resolved buildinfo with “unavailable” semantics. |
| internal/cli/version_test.go | Adds tests for version rendering, “modified” marker, and placeholder suppression. |
| internal/mcpserver/serverjson_test.go | Drift tests to ensure server.json matches tool surface and credential model. |
| internal/selfupdate/extract.go | Extracts the binary from verified tar.gz/zip release artifacts with size bounds. |
| internal/selfupdate/extract_test.go | Tests extraction behavior, corrupt archives, and entry matching. |
| internal/selfupdate/ownership.go | Detects install provenance (brew/rpm/dpkg/go/unmanaged) and upgrade guidance. |
| internal/selfupdate/plan.go | Resolves update plan (target release, ownership deferral, platform gate, asset). |
| internal/selfupdate/release.go | Fetches GitHub release metadata/assets unauthenticated with rate-limit handling. |
| internal/selfupdate/replace.go | Implements atomic replacement (and a gated Windows rename-aside mechanism). |
| internal/selfupdate/replace_test.go | Tests replacement atomicity, mode preservation, cleanup, and permission behavior. |
| internal/selfupdate/sigstore_test.go | Verifies identity-pinned sigstore policy via virtual CA-minted signatures. |
| internal/selfupdate/verify.go | Implements checksum-manifest signature verification + pinned signer identity + digest checks. |
| scripts/license-exceptions.tsv | Adds license classifier exceptions for short-form Apache headers. |
| server.json | Adds MCP registry entry for io.skaphos/sting with OCI package and STING_* env vars. |
| specs/002-distribution-channels/checklists/requirements.md | Spec quality checklist for the distribution-channel feature. |
| specs/002-distribution-channels/contracts/cli-update.md | Contract for sting update behavior, output, and exit codes. |
| specs/002-distribution-channels/contracts/release-artifacts.md | Contract for required release artifact set and post-release verification. |
| specs/002-distribution-channels/contracts/server-json.md | Contract for the MCP server.json registry entry semantics. |
| third_party_licenses/runtime/github.com/asaskevich/govalidator/LICENSE | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/github.com/blang/semver/LICENSE | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/github.com/cenkalti/backoff/v5/LICENSE | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/github.com/cespare/xxhash/v2/LICENSE.txt | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/github.com/cyberphone/json-canonicalization/LICENSE | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/github.com/digitorus/pkcs7/LICENSE | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/github.com/digitorus/timestamp/LICENSE | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/github.com/go-logr/logr/LICENSE | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/github.com/go-openapi/errors/LICENSE | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/github.com/go-openapi/jsonpointer/LICENSE | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/github.com/go-openapi/jsonpointer/NOTICE | Adds runtime notice text for dependency compliance. |
| third_party_licenses/runtime/github.com/go-openapi/jsonreference/NOTICE | Adds runtime notice text for dependency compliance. |
| third_party_licenses/runtime/github.com/go-openapi/runtime/LICENSE | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/github.com/go-openapi/runtime/NOTICE | Adds runtime notice text for dependency compliance. |
| third_party_licenses/runtime/github.com/go-openapi/runtime/middleware/denco/LICENSE | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/github.com/go-openapi/spec/LICENSE | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/github.com/google/uuid/LICENSE | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/github.com/grpc-ecosystem/grpc-gateway/v2/LICENSE | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/github.com/in-toto/attestation/LICENSE | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/github.com/in-toto/in-toto-golang/LICENSE | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/github.com/opencontainers/go-digest/LICENSE | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/github.com/pkg/errors/LICENSE | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/github.com/secure-systems-lab/go-securesystemslib/LICENSE | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/github.com/theupdateframework/go-tuf/v2/metadata/NOTICE | Adds runtime notice text for dependency compliance. |
| third_party_licenses/runtime/github.com/youmark/pkcs8/LICENSE | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/golang.org/x/crypto/LICENSE | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/golang.org/x/mod/LICENSE | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/golang.org/x/net/LICENSE | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/google.golang.org/grpc/LICENSE | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/google.golang.org/grpc/NOTICE.txt | Adds runtime notice text for dependency compliance. |
| third_party_licenses/runtime/google.golang.org/protobuf/LICENSE | Adds runtime license text for dependency compliance. |
| third_party_licenses/runtime/k8s.io/klog/v2/LICENSE | Adds runtime license text for dependency compliance. |
Removes sting update, internal/selfupdate, and the sigstore-go dependency added to verify releases in-process. The command worked and met all three rules DECISIONS/0001 sets. The cost was the problem: verifying in-process is the only way to satisfy both "verification cannot be skipped" and "no tooling required on the user's machine", and the dependency that makes it possible took the binary from 11MB to 25MB (+122%), go.mod from 44 to 106 requirements, and pulled an unmaintained crypto module into the graph. Every user on every channel paid that to serve one command. It was removed rather than weakened. There is no checksum-only mode and no "verify if cosign is present" fallback: DECISIONS/0001 is right that a self-updater ignoring available signing material is worse than none, so the choice was a correct updater at +122% or no updater. go.mod and go.sum are now byte-identical to main - this branch adds zero dependencies. govulncheck reports no vulnerabilities at all again, where the sigstore tree had introduced golang.org/x/crypto/openpgp (GO-2026-5932, unmaintained, no fix available). Self-update is a REQUIRED channel for Shape 2, so dropping it is a deviation, not an omission. DECISIONS/0001 requires a record in the repo that drops a required channel; ADR 0011 is rewritten from the trust model into that deviation record, stating the cost, what users get instead, and what would reverse the decision. README documents an upgrade path per channel. Version identity (internal/buildinfo) is unaffected and still ships: it is its own required item under DECISIONS/0001, not merely a prerequisite of self-update. Spec, plan, tasks, quickstart and the update contract are annotated rather than deleted - the work happened, and ADR 0011's reasoning only makes sense against a concrete description of what was given up. Refs #121 Signed-off-by: Shawn Stratton <shawn@skaphos.io>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 27 out of 27 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
README.md:36
- The RPM filename shown here doesn’t match the actual nfpm conventional filename (release verification expects a "-1" release suffix, and RPM uses aarch64 for arm64). Updating the examples avoids pointing users at non-existent asset names.
sudo dpkg -i sting_<version>_amd64.deb # Debian, Ubuntu
sudo rpm -i sting-<version>.x86_64.rpm # Fedora, RHEL, openSUSE
Four fixes, all from the PR review, all verified rather than assumed. RPM install filename: the README showed sting-<version>.x86_64.rpm, but nfpm's conventional naming is sting-<version>-1.<arch>.rpm. A user copying the documented command would have hit a file that does not exist in the release assets. Confirmed against a snapshot build. RPM upgrade guidance: the table said `dnf upgrade sting`, which cannot work because sting publishes no yum repository - the same trap the .deb row already avoided by not promising `apt upgrade`. Verified in a Fedora container: with the package installed from a file, `dnf upgrade sting` reports "Nothing to do" and exits 0, so a user would believe they had upgraded when nothing happened. Silent success is worse than an error here. Now mirrors the .deb row: download the next release's .rpm, then `sudo rpm -U`, which is verified to work. Corrected in ADR 0011's table too, which had the same wrong advice. mcp-publisher was installed with @latest, making releases non-reproducible and liable to break when upstream changes. Pinned to v1.8.0. check-jsonschema was unpinned in CI while .tool-versions pinned 0.35.0, so a local run and CI could validate against different tools. Pinned to match. Two further comments were on internal/selfupdate/ownership.go and internal/cli/update.go, both correct at the time and both now moot: those files were removed when self-update was dropped. Refs #121 Signed-off-by: Shawn Stratton <shawn@skaphos.io>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 27 out of 27 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
internal/mcpserver/serverjson_test.go:150
- This test also assumes
s.Packages[0]exists; ifserver.jsonis malformed (e.g., missingpackages), this will panic rather than producing a readable test failure. Each test should be independently robust.
func TestServerJSONVersionsAreConsistent(t *testing.T) {
s := loadServerJSON(t)
if s.Version != s.Packages[0].Version {
t.Errorf("server version %q and package version %q disagree; the release stamps both",
s.Version, s.Packages[0].Version)
}
Two assertions indexed s.Packages[0] without a length check, so an empty or malformed packages array would panic the test binary with index out of range instead of reporting the problem. These tests exist specifically to catch a malformed server.json, so panicking on that input defeats their purpose. Adds a guarded accessor and names the package struct so it can be returned. Verified by temporarily emptying packages: both tests now report "server.json declares no package entries; MCP clients would have nothing to run" rather than panicking. Refs #121 Signed-off-by: Shawn Stratton <shawn@skaphos.io>
Addresses two review comments and moves release-please onto minor bumps. CI validated .goreleaser.yaml with a floating "~> v2" while the release runs v2.17.0, so config could pass CI against a different GoReleaser than the one that releases it. Pinned to v2.17.0, matching release.yml and .tool-versions. Post-release verification checked archives, packages and checksums but not the per-artifact SBOMs, so a release could verify green while SBOM generation had regressed. It now requires all ten: six archives plus four packages. The packages entry in the sboms block is new in this PR and is exactly the kind of thing that breaks unnoticed. Filenames confirmed against a snapshot build. Release versioning: bumped the manifest from 0.0.7 to 1.0.0 so the next release is 1.1.0, and removed bump-minor-pre-major and bump-patch-for-minor-pre-major, which only apply below 1.0.0 and are now inert. The latter is what forced feat commits to bump patch. This is not only a preference. The Go module proxy serves v1.0.0 for this module, while the newest tag and release are v0.0.7 - a v1.0.0 tag was published once and later deleted, and the proxy is immutable. So `go install github.com/skaphos/sting/cmd/sting@latest` resolves to that orphaned v1.0.0 today, which predates the version fix and prints "sting dev". Continuing to release 0.0.x would never overtake it, so the README's recommended install path would stay stuck. Releasing 1.1.0 clears it. Refs #121 Signed-off-by: Shawn Stratton <shawn@skaphos.io>
| needs: [dco, reuse, lint, test, quality, manifests] | ||
| steps: | ||
| - uses: actions/checkout@v7 | ||
| with: |
Summary
Adopts
skaphos-resourcesDECISIONS/0001 — distribution channels by artifact shape. sting is both Shape 2 (end-user CLI) and Shape 3 (MCP server), so it inherits both channel sets.Spec, plan, tasks and implementation are all on this branch. A release now publishes to six channels instead of two.
What landed
go installinternal/buildinfosting updateself-update.deb/.rpmnfpmsblockserver.json→io.skaphos/stingDockerfile+dockers_v2→ GHCRPlus the cross-cutting release-coherence work, which is the part most directly aimed at the failure mode ADR-0001 documents.
This branch adds zero dependencies —
go.modandgo.sumare byte-identical tomain.The defect, reproduced
go install github.com/skaphos/sting/cmd/sting@v1.0.0— the currently published release — printssting dev.internal/buildinforesolves through three tiers (release ldflags → Go build metadata → explicit "unavailable"), so neitherdevnor(devel)can reach a user again. Release output is unchanged.Self-update was built, measured, and dropped
A conforming implementation existed and met all three of the ADR's rules — verify before replacing (pinned to sting's own release-workflow identity), defer to the package manager, no implicit network calls. It was removed on cost:
-s -w)go.modrequirementsx/crypto/openpgp, no fix available)It was removed rather than weakened: there is no checksum-only mode and no "verify if cosign is present" fallback, because DECISIONS/0001 is right that a self-updater ignoring available signing material is worse than none. The choice was a correct updater at +122%, or no updater.
Self-update is a required channel, so this is a deviation, not an omission. DECISIONS/0001 requires a record in the repo that drops a required channel — that is ADR 0011, which states the cost, what users get instead, and what would reverse the decision. A follow-up PR against
skaphos-resourcesis warranted, since repokeeper and oiax will hit the same tradeoff.README.mdnow documents an upgrade path per channel.Two changes to existing behavior
--skip=homebrewand continued with a::warning::, so a release could complete green with the cask unpublished — exactly how repokeeper's cask sat pinned at0.6.0across two releases.verifyjob queries each channel for the version it serves. Reachability is not the test; a stale cask responds perfectly well.Verification performed
debian:stableandfedora:latest. Binary onPATH, correct version, licenses under/usr/share/doc/sting/, clean removal. All 4 packages appear inchecksums.txt, so they inherit the provenance attestation. SBOMs now cover all 10 artifacts.initializehandshake over stdio advertisingget_commitsandget_repo_activity, bothreadOnly. Non-root (65532), distroless base pinned by digest.task cigreen, coverage passes (buildinfo100%), REUSE compliant,goreleaser checkandserver.jsonschema validation both pass.govulncheckreports no vulnerabilities.Also pins
syft,cosign,reuseandcheck-jsonschemain.tool-versions— withoutsyftthe SBOM step failed locally while passing in CI, which is the kind of divergence that hides real breakage.Out of scope
winget/scoopand Windows Authenticode signing, per the sequencing note on the issue.Closes #121