Skip to content

feat(dist): conform to DECISIONS/0001 distribution channels - #122

Merged
mfacenet merged 13 commits into
mainfrom
feature/distribution-channels
Jul 26, 2026
Merged

feat(dist): conform to DECISIONS/0001 distribution channels#122
mfacenet merged 13 commits into
mainfrom
feature/distribution-channels

Conversation

@mfacenet

@mfacenet mfacenet commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Adopts skaphos-resources DECISIONS/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

# Obligation Status
1 Version-stamped go install internal/buildinfo
2 sting update self-update dropped — recorded deviation, ADR 0011
3 Linux .deb / .rpm nfpms block
4 MCP registry entry server.jsonio.skaphos/sting
5 Multi-arch container image Dockerfile + dockers_v2 → GHCR

Plus the cross-cutting release-coherence work, which is the part most directly aimed at the failure mode ADR-0001 documents.

This branch adds zero dependenciesgo.mod and go.sum are byte-identical to main.

The defect, reproduced

go install github.com/skaphos/sting/cmd/sting@v1.0.0 — the currently published release — prints sting dev. internal/buildinfo resolves through three tiers (release ldflags → Go build metadata → explicit "unavailable"), so neither dev nor (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:

Without With self-update
Binary (-s -w) 11 MB 25 MB (+122%)
go.mod requirements 44 106
Unmaintained modules in graph 0 1 (x/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-resources is warranted, since repokeeper and oiax will hit the same tradeoff.

README.md now documents an upgrade path per channel.

Two changes to existing behavior

  1. A missing Homebrew credential now fails the release. It previously 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.
  2. A post-release verify job queries each channel for the version it serves. Reachability is not the test; a stale cask responds perfectly well.

Verification performed

  • Packages: installed and removed both formats in debian:stable and fedora:latest. Binary on PATH, correct version, licenses under /usr/share/doc/sting/, clean removal. All 4 packages appear in checksums.txt, so they inherit the provenance attestation. SBOMs now cover all 10 artifacts.
  • Image: both platform images run; completed an MCP initialize handshake over stdio advertising get_commits and get_repo_activity, both readOnly. Non-root (65532), distroless base pinned by digest.
  • Gates: task ci green, coverage passes (buildinfo 100%), REUSE compliant, goreleaser check and server.json schema validation both pass. govulncheck reports no vulnerabilities.

Also pins syft, cosign, reuse and check-jsonschema in .tool-versions — without syft the SBOM step failed locally while passing in CI, which is the kind of divergence that hides real breakage.

Out of scope

winget/scoop and Windows Authenticode signing, per the sequencing note on the issue.

Closes #121

mfacenet added 9 commits July 25, 2026 16:07
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>
@mfacenet
mfacenet marked this pull request as ready for review July 25, 2026 23:40
Copilot AI review requested due to automatic review settings July 25, 2026 23:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 update sting version to use it (fixing go install …@latest showing dev/(devel)).
  • Add sting update with 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, MCP server.json entry + 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.

Comment thread internal/selfupdate/ownership.go Outdated
Comment thread internal/cli/update.go Outdated
Comment thread README.md
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>
Copilot AI review requested due to automatic review settings July 26, 2026 00:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread README.md Outdated
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/ci.yml Outdated
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>
Copilot AI review requested due to automatic review settings July 26, 2026 00:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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; if server.json is malformed (e.g., missing packages), 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)
	}

Comment thread internal/mcpserver/serverjson_test.go
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>
Copilot AI review requested due to automatic review settings July 26, 2026 00:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated 2 comments.

Comment thread .github/workflows/ci.yml
Comment thread .github/workflows/release.yml
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>
Copilot AI review requested due to automatic review settings July 26, 2026 00:44
@mfacenet
mfacenet merged commit cfb6dad into main Jul 26, 2026
12 checks passed
@mfacenet
mfacenet deleted the feature/distribution-channels branch July 26, 2026 00:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated 1 comment.

Comment thread .github/workflows/ci.yml
Comment on lines +144 to 147
needs: [dco, reuse, lint, test, quality, manifests]
steps:
- uses: actions/checkout@v7
with:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Conform to DECISIONS/0001: distribution channels by artifact shape

2 participants