Skip to content

release(prep): v1.2.1 for all 129 free plugins (70 were unpublished; monotonic versions) - #81

Merged
acamarata merged 20 commits into
mainfrom
p6/release-v1.0.1-repackaged-free-plugins
Sep 11, 2026
Merged

acamarata merged 20 commits into
mainfrom
p6/release-v1.0.1-repackaged-free-plugins

Conversation

@acamarata

@acamarata acamarata commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

What / Why — re-cut from 1.0.1 to 1.2.1

This PR previously set all 129 free-registry entries to 1.0.1. That was
wrong: measured against main, registry.json had 41 entries at 1.1.2,
2 at 1.1.0, 1 at 1.2.0 (entitlements), and 1 at 1.1.1 (ollama) —
the 1.0.1 target would have downgraded all 45 of them. A downgrade
breaks inter-plugin requires semver ranges (internal/plugin/compat.go)
and any installed-version comparison, and the release tag must equal the
registry version because the Cloudflare Worker resolves
releases/download/v<version>/<name>-<version>.tar.gz straight from the
version field.

Every one of the 129 entries now moves to 1.2.1 instead — strictly
greater than the highest version any entry held on main — so nothing
goes backward.
ollama moves to 1.2.1 with everyone else; the earlier
plan to leave it at 1.1.1 is void. shared-utils gets only its version
field bumped (installable:false, no tarball, no releaseTag, no
checksum — unchanged otherwise).

Original scope-expansion rationale (still applies, unchanged by the
re-cut): plugins#84 found 70 of the 129 free plugins had no published
tarball for the version registry.json stated on main — full defect
list there. Full re-cut writeup: .github/RELEASE-v1.2.1.md in this PR.

What changed in this re-cut

  • registry.jsonversion1.2.1 for all 129 entries, releaseTag
    v1.2.1 for every entry that carries one, stale tarball URLs (which
    had the old version/tag baked in) refreshed to match.
  • Every free/<name>/plugin.jsonversion1.2.1 to match (CI's
    version-consistency gate requires the two to agree).
  • scripts/deterministic-tar.sh — added --mode='go-w,a+rX'. Measured
    today: identical git content checked out on two machines produced tar
    streams that hashed differently by file mode alone (644 vs 664) even
    with mtime/uid/gid/order already pinned — the checkout environment's
    umask was leaking into the artifact. The mode flag fixes it; verified
    locally (same content at 644 and 664 now hashes identically).
  • .github/workflows/tarball-checksum-gate.yml — both jobs (source
    tarball, and the checksums.platforms job from commit 0eae661) no
    longer rebuild tarballs in CI and diff against registry.json. That
    comparison can never reliably hold: with byte-identical tar streams as
    input, gzip -n -9 produces different output depending on the
    gzip/zlib build — measured today, Apple gzip 479 vs GNU gzip 1.12
    diverged on real plugin data (6e9d3966 vs 0283c3b3); macOS zlib
    1.2.12 vs Linux zlib 1.3 diverges the same way. DEFLATE output is
    implementation/version-dependent by design — no tar flag fixes this,
    the divergence is downstream of tar. The gate now downloads the real
    published release asset for each plugin's registry.json
    releaseTag and diffs that. With no release for a tag yet (the normal
    state of this PR right now — no v1.2.1 release exists), it skips the
    compare with an explicit ::notice:: and passes; it never fails
    merely because the release hasn't been cut, and never silently passes
    without saying which branch it took. Once a release exists, a mismatch
    fails it — proven locally end-to-end with the literal script this
    workflow runs: registry checksum vs. a real rebuilt asset passes, a
    corrupted asset fails (exit 1), restoring it passes again. Separately,
    running the no-release path against the real 129-entry registry.json
    skips all 128 checksummed entries with a notice and exits 0.
    scripts/deterministic-tar.sh/build-tarballs.sh are unchanged in
    spirit — they still make a local rebuild reproducible across
    machines, which is what a contributor needs before uploading; they were
    never going to make two gzip implementations agree byte-for-byte.
  • scripts/verify-published-checksums.sh — rewritten. The version merged
    in from main (plugins#83) required an explicit list of plugin names and
    never wrote registry.json (report-only). This version enumerates every
    registry.json entry on a given releaseTag itself (source tarball
    plus, for binaryName plugins, the five per-platform assets), and
    either reports mismatches against registry.json (default, exit
    non-zero if any) or, with --write, writes the published hashes into
    checksum / checksums.sha256 / checksums.platforms.<platform>.
    Verified locally against a scratch registry + a mocked gh CLI serving
    real built tarballs: report mode flags a deliberately wrong checksum and
    exits 1, --write overwrites it with the correct hash and a follow-up
    report run then passes, and a partial per-platform asset set writes what
    exists while still exiting 1 for what's missing (nothing silently left
    unflagged). Not run against a real release — no v1.2.1 release
    exists yet.

Checksums in this PR are provisional — not final

This re-cut did not rebuild any tarball or recompute any checksum. The
checksum / checksums.sha256 / checksums.platforms values currently in
registry.json are carried over unchanged from the previous version's
content — stale the moment the version field next to them changed, and
not to be read as validated for 1.2.1. Per the release flow this PR
depends on: the owner creates a draft v1.2.1 release with the built
tarballs attached, scripts/verify-published-checksums.sh v1.2.1 --write
reads the checksums from those published bytes and writes them into
registry.json, and only that state gets merged.

event-bus asset naming — confirmed correct

Checked: scripts/build-and-upload-tarballs.sh names tarballs
"${plugin_name}-${TAG#v}.tar.gz" (strips v before building the
filename) and scripts/build-tarballs.sh uses the already-v-stripped
${version} the same way — so a 1.2.1 build of event-bus names its
asset event-bus-1.2.1.tar.gz, matching the .../v1.2.1/event-bus-1.2.1.tar.gz
URL now in registry.json. No occurrence of the historical stray-v slip
(event-bus-v1.0.0.tar.gz) remains in either script.

This PR does NOT merge, tag, or release anything

The owner's sequence, once this PR is approved:

# (a) Owner creates a DRAFT release with the built 1.2.1 tarballs attached,
#     targeting this PR's head commit:
gh release create v1.2.1 -R nself-org/plugins --draft \
  --target <this-PR-head-sha> \
  -F .github/RELEASE-v1.2.1.md \
  upload-all/*.tar.gz upload-all/*.sha256

# (b) Run verify-published-checksums.sh against the draft's published
#     bytes and push the resulting registry.json to this PR branch:
./scripts/verify-published-checksums.sh v1.2.1 --write
git add registry.json && git commit -m "chore(registry): checksums from published v1.2.1 draft assets"
git push

# (c) Owner merges this PR (this is the version bump landing on main).

# (d) Publish the draft (tag is created on publish, pointing at the merge
#     commit — re-target with --target if needed):
gh release edit v1.2.1 -R nself-org/plugins --draft=false

# (e) Purge the Worker's KV cache and spot-verify:
curl -X POST https://plugins.nself.org/api/sync
curl -sI https://plugins.nself.org/plugins/storage/tarball
# expect: HTTP/2 302, location: .../releases/download/v1.2.1/storage-1.2.1.tar.gz
curl -sI https://plugins.nself.org/plugins/access-controls/tarball
nself plugin install notifications

Do not merge until the owner has reviewed this PR and given the go-ahead.

Note for owner — plugins-pro/bundles republish (not this repo)

Carried over from the prior re-cut brief, unresolved and out of this
repo's scope: nself-audit is in source but not served; nself-csam has
no checksum anywhere.

Local gate

  • jq -r '.plugins[].version' registry.json | sort -u → exactly 1.2.1
    (129/129, including ollama and shared-utils).
  • git grep -n "1\.0\.1" -- registry.json .github/RELEASE-v1.2.1.md
    empty.
  • Downgrade check across all 129 entries vs. main → zero entries where
    the main version is greater than 1.2.1.
  • bash shared/validate-registry.sh0 errors, 1 pre-existing
    unrelated warning (alphabetical sort order — same before and after this
    change, present since before PR release(prep): v1.2.1 for all 129 free plugins (70 were unpublished; monotonic versions) #81).
  • pnpm run ci:local — passes (no tsconfig in this repo, gate no-ops by
    design).
  • No tarballs were built and no checksums were recomputed this session
    (see "Checksums in this PR are provisional" above).

This is a PUBLIC repo (nself-org/plugins) — this account cannot
self-approve, so this PR stays open pending owner review.

Not done here (out of hard limits)

No tag, no gh release create, no merge, no local tarball rebuild
presented as final checksums — those are the owner's steps above.

Bumps content-progress, cron, donorbox, maintenance, notifications, notify,
search, storage from 1.0.0 to 1.0.1 in registry.json and each plugin's own
plugin.json (CI's version-consistency gate requires the two to match). PRs
routes on the 7 with an HTTP server, stale license gates removed) but the
registry still pointed at the v1.0.0 GitHub Release tarballs, so
nself plugin install still served the pre-repackage code.

Also records the SHA-256 checksum on each of the 8 registry entries: the
flat checksum field (what internal/plugin/registry_parse.go actually
deserializes and installer_locked.go's verifyChecksum checks), plus
checksums.sha256 + releaseTag (what registry-schema.json documents and
build-and-upload-tarballs.sh writes, currently unread by the CLI — see
.github/RELEASE-v1.0.1.md Known gap).

Tarballs are NOT included in this commit (dist/ stays gitignored); they are
attached to the v1.0.1 GitHub Release by the owner per the PR body.

No merge, tag, or release performed here — see .github/RELEASE-v1.0.1.md
for the three owner commands this PR sets up.
acamarata added a commit that referenced this pull request Sep 4, 2026
…83)

The CLI installer's registry parser (nself-org/cli
internal/plugin/registry_parse.go pluginEntry.Checksum, json:"checksum")
and installer_locked.go's verifyChecksum() only ever read the FLAT
`checksum` field on a plugin entry. build-and-upload-tarballs.sh and
registry-schema.json only ever wrote/documented the nested
`checksums.sha256` field, which no CLI code path consumes (confirmed via
`git grep checksums origin/main -- internal` on nself-org/cli). Result:
every plugin without a hand-written flat checksum installs with checksum
verification silently skipped by the CLI (a stderr warning only) for
any plugin not explicitly status=stable.

- build-and-upload-tarballs.sh: write the flat `checksum` field alongside
  the nested `checksums.sha256` (both raw lowercase hex, no "sha256:"
  prefix — the previous nested write had the prefix, which would have
  broken any future checksum == checksums.sha256 consistency check).
- registry-schema.json: document `checksum` as the field the CLI reads;
  require it (non-empty) when a plugin's `status` is the literal string
  "stable"; document `checksums` as legacy/unread.
- shared/validate-registry.sh: new CHECK-15 — errors when a status=stable
  plugin lacks a valid flat checksum, when a present checksum is not
  64 lowercase hex chars, or when checksum and checksums.sha256 disagree.
- scripts/verify-published-checksums.sh: new read-only spot-check that
  downloads a release tag's real tarball assets and hashes them, since
  the other 121 plugins' checksums were never computed against what the
  CLI would actually download and recomputing them from the source tree
  would not verify that. Does not write registry.json.

Does not touch the 8 plugin entries PR #81 already populated (both
fields, no "sha256:" prefix) for v1.0.1 — rebase-safe with that PR.

No plugin in the current registry has an explicit status=stable, so
this changes no install-time behavior yet; it fixes the field the
installer reads once entries are marked stable.
…shed)

plugins#84 found that 70 of the 129 free plugins have no published tarball
for the version registry.json currently states (11 have no release tag for
that version at all, 59 have the tag but no matching *-<version>.tar.gz
asset — every *@1.1.2 entry). nself plugin install 404s for all 70 today,
independent of the original 8-plugin scope of this PR.

Aligns every free-registry entry's version to 1.0.1 (128 of 129 — ollama
stays at its already-published, working 1.1.1; forcing it back would be a
regression) and rewrites each tarball URL + plugin.json version to match
(CI's version-consistency gate requires the two to agree).

Writes checksum + checksums.sha256 + releaseTag for 128 of 129 entries from
the tarballs scripts/build-tarballs.sh built this session (pipeline-
generated, not TOFU) — checksum coverage 1/129 -> 128/129. shared-utils
(installable: false, no tarball/download_url field, an internal library
other plugins import at build time) has nothing to checksum against.
ollama's checksum was still populated, from its real published v1.1.1
asset downloaded and hashed this session (matches the existing .sha256
sidecar byte for byte).

plugins#84's 51 newly-populated TOFU baseline checksums are superseded for
every entry whose version moved here — they were computed against each
entry's prior version, which no longer matches.

Tarballs are NOT included in this commit (dist/ stays gitignored); all 129
are attached to the v1.0.1 GitHub Release by the owner per the PR body.

No merge, tag, or release performed here — see .github/RELEASE-v1.0.1.md
for the three owner commands this PR sets up.
@acamarata acamarata changed the title release(prep): v1.0.1 for the 8 repackaged free plugins release(prep): v1.0.1 for all 129 free plugins (70 were unpublished) Sep 4, 2026
… recipe

scripts/build-tarballs.sh and .github/workflows/release-tarballs.yml each
called plain `tar -czf` independently, so the bytes they produced for the
same plugin could differ: one walked `free/*/` via a bash glob, the other
via `find free -maxdepth 1 -type d`, and both let tar preserve real
mtimes and gzip stamp the wall-clock build time. Two builds of identical
source never produced identical bytes, so a sha256 recorded in
registry.json could never reliably hold against what either pipeline
actually published later.

deterministic-tar.sh fixes mtime (pinned to epoch, matching the
normalization already shipped for paid plugins in
nself-org/web@51518c38), owner/group (zeroed), pax atime/ctime headers
(stripped), and member order (--sort=name) so identical input always
produces identical output regardless of which of the two build sites ran
it. Requires GNU tar; macOS ships bsdtar which cannot reproduce the same
byte layout, so the script fails loudly with an install hint (`brew
install gnu-tar`) rather than silently diverging from CI.

Verified on this machine: two independent full rebuilds of all 129 free
plugin tarballs (via build-tarballs.sh, which now calls this script)
produced byte-identical sha256 for every one, including after bumping a
source file's mtime between runs.
… recipe

Both build-tarballs.sh and release-tarballs.yml's build_plugin() now call
scripts/deterministic-tar.sh instead of invoking `tar -czf` directly, for
both the per-plugin source tarball and the per-platform binary tarball
(the `-C "$stage" "$plugin_name"` step) — the binary archives had the
identical mtime/ordering non-determinism as the source ones, from
compiled-binary mtimes and per-run staging-directory enumeration order.

build-tarballs.sh also now walks free/ via `find ... | sort` instead of a
bash glob, matching the workflow's traversal, so both scripts iterate
plugin directories in the same fixed order. The directory argument passed
to tar no longer carries a trailing slash in either place.
…hecksums

Rebuilds every free plugin's source tarball from the checked-out tree with
scripts/deterministic-tar.sh and fails the PR if the freshly computed
sha256 disagrees with registry.json's checksum or checksums.sha256 for
that plugin. nself plugin install hard-refuses on a checksum mismatch
(internal/plugin/security.go verifyChecksum), so a stale or wrong
checksum in registry.json is a shipped defect — this makes it unmergeable
instead of discoverable only after a release breaks installs.

ollama is excluded: its checksum is verified against its own external,
already-published v1.1.1 release rather than this repo's build pipeline,
so rebuilding free/ollama here has no matching artifact to compare
against by design. Entries with no checksum recorded at all (shared-utils)
are skipped, not failed.

Verified locally by extracting the workflow's run: script and executing it
directly: fails with 128 mismatches against the pre-fix registry.json (one
per plugin, matching the tar non-determinism), passes cleanly against the
regenerated one, and fails again — only on the one plugin touched — when a
single checksum is deliberately corrupted.
…c recipe

The checksum and checksums.sha256 values this PR previously wrote for all
128 free entries were computed with the old, non-reproducible `tar -czf`
(real mtimes, gzip timestamp) — bytes that .github/workflows/release-tarballs.yml
would never actually reproduce when it built the real release assets,
since it used a different traversal of the same non-deterministic
primitive. Every one of those checksums would have failed
nself plugin install's verification the moment the real release tarball
was built.

Regenerated all 127 non-ollama entries' checksum and checksums.sha256
from a fresh scripts/deterministic-tar.sh build of the checked-out
free/<name> tree, confirmed byte-for-byte identical across two independent
rebuilds. ollama's checksum is untouched — kept exactly as PR81 verified
it, against its own real, already-published v1.1.1 release asset, which
this pipeline does not rebuild. shared-utils keeps having no checksum
field, unchanged (installable:false, not independently distributed).

jq empty registry.json and shared/validate-registry.sh both pass with the
same 0 errors / 1 pre-existing warning as before this change. The new
tarball-checksum-gate.yml workflow passes cleanly against this file.
…alls

Every binaryName plugin ships a per-platform tarball alongside its source
one, but this script only ever built the source tarball — there was no
local way to reproduce the bytes the release pipeline's per-platform build
step (release-tarballs.yml's build_plugin()) actually publishes, which is
exactly what registry.json's checksums need to be verified against before a
release ships.

Ported build_platform_tarballs() from that workflow's proven, already-running
Go-compile step, kept command/package resolution byte-for-byte equivalent
(cliCommands vs. single binaryName, cmd/<command>/ vs. cmd/) so a local build
and a CI build can never disagree about what gets compiled. Uses the same
scripts/deterministic-tar.sh recipe as the source tarball — one recipe for
both, not two.

The file is now sourceable as well as executable: main() only runs when
invoked directly (./build-tarballs.sh), guarded so
.github/workflows/tarball-checksum-gate.yml can `source` it and call
build_platform_tarballs() per plugin without a second copy of this
orchestration logic. NSELF_SKIP_PLATFORM_TARBALLS=1 skips the platform build
for faster local iteration when only the source tarball is needed.

Verified: two independent rebuilds of all 28 binaryName free plugins (all 5
platforms each) produced byte-identical sha256 for every one, including the
existing source tarball checksums already in registry.json.
registry.json carried exactly one checksum per plugin (the source tarball's,
checksum / checksums.sha256), but a plugin with a binaryName ships five more
release assets — one per platform — and nself plugin install downloads one
of those instead of the source tarball whenever binaryName is set
(internal/plugin/download.go). Every such install was hashing the
downloaded platform tarball and comparing it against the source tarball's
checksum, which can never match (plugins#83).

checksums.platforms is a map keyed by exactly the platform strings
internal/plugin/arch.go's PlatformArch() returns (darwin-arm64,
darwin-amd64, linux-amd64, linux-arm64, windows-amd64) — a mismatch here is
an install failure there, so the schema pins the key set with
additionalProperties: false. Values are plain lowercase hex sha256, no
"sha256:" prefix, unlike the sibling sha256 field (which some writers
prefix) — the CLI's checksum comparison is a raw hex equality check.
Extends the gate added in b9403fa with a second job,
platform-checksum-gate: for every free plugin with a binaryName, it
cross-compiles all 5 platform tarballs from the checked-out tree (sourcing
scripts/build-tarballs.sh's build_platform_tarballs() rather than
re-implementing the Go-build orchestration a third time) and fails the PR
if the freshly built sha256 disagrees with registry.json's
checksums.platforms.<platform> for that plugin.

A MISSING checksums.platforms entry for a plugin that does ship a binary for
that platform is refused exactly like a mismatch, not skipped — unlike the
existing source-checksum job's leniency for a plugin with no checksum at all
(shared-utils, etc.), install-time verification for a platform artifact
(internal/plugin/installer_locked.go's resolveArtifactChecksum) refuses
unconditionally on a missing entry, with no env var to bypass it, so this
gate holds registry.json to the same standard before it ships.

ollama is excluded for the same reason as the existing job (external
pre-existing release, not built by this pipeline).

Verified locally by extracting this job's script and running it against a
scoped registry.json (ai-cli + sentry-cli, a single- and multi-command
plugin): passes cleanly against the real, freshly-populated checksums;
fails with MISMATCH when one platform checksum is deliberately corrupted;
fails with MISSING (not silently skipped) when that same entry is removed
outright; passes again once restored.
Backfills the new checksums.platforms field (previous commit) for every
free plugin that has a binaryName — 28 of them as of this registry, not the
32 originally estimated when this defect was scoped; recount with:
  jq '[.plugins[] | select(.binaryName or .implementation.binaryName)] | length' registry.json

Each entry's 5 platform checksums were computed by building that plugin's
actual per-platform tarball with scripts/build-tarballs.sh (the same
deterministic-tar.sh + Go cross-compile recipe release-tarballs.yml uses),
confirmed byte-identical across two independent rebuilds, including the
28 pre-existing source checksums this run reproduced unchanged.

ollama (checksum verified against its own external v1.1.1 release, no
binaryName) and shared-utils (installable:false, no checksum at all) are
untouched, per the existing exceptions this registry already carries.
PR #81 set every entry to 1.0.1, which downgrades 45 entries measured
against main (41 at 1.1.2, 2 at 1.1.0, 1 at 1.2.0, 1 at 1.1.1 for
ollama). A downgrade breaks inter-plugin requires semver ranges
(internal/plugin/compat.go) and installed-version comparisons, and the
release tag must equal the registry version since the Worker resolves
releases/download/v<version>/<name>-<version>.tar.gz directly from it.

1.2.1 is strictly greater than every version any entry held on main, so
none of the 129 entries goes backward. ollama moves to 1.2.1 with
everyone else (the earlier plan to leave it at 1.1.1 is void, per the
builder brief); shared-utils gets only its version field bumped
(installable:false, no tarball, no releaseTag, no checksum).

Also refreshes the stale tarball URL field (baked-in old version/tag)
for every entry that has one, and renames the release notes doc from
RELEASE-v1.0.1.md to RELEASE-v1.2.1.md with the re-cut explained.

Checksums are NOT recomputed here — this is a version-only re-cut. The
checksum/checksums.sha256/checksums.platforms values already in
registry.json are carried over unchanged and are provisional until
scripts/verify-published-checksums.sh runs against the v1.2.1 draft
release's published bytes.
deterministic-tar.sh already pinned mtime, uid/gid and member order but
not file modes, and tar records mode bits per member. Measured today
(P6 re-cut): identical git content checked out on two machines produced
tar streams that hashed differently by mode alone (644 vs 664) — the
checkout environment's umask was leaking into the artifact the same
way mtime/uid/gid did before those were pinned.

Adding --mode='go-w,a+rX' makes both machines produce the identical
tar stream (verified locally: same content at 644 and 664 now hashes
identically).
…a CI rebuild

The checksum gate rebuilt every tarball in CI and diffed the fresh
sha256 against registry.json — a comparison that can never reliably
hold. Measured today: with byte-identical tar streams as input,
gzip -n -9 produces different output depending on the gzip/zlib build
doing the compressing (Apple gzip 479 vs GNU gzip 1.12: 6e9d3966 vs
0283c3b3 for the same real plugin data; macOS zlib 1.2.12 vs Linux
1.3 diverges the same way). DEFLATE output is implementation- and
version-dependent by design — no tar flag fixes this, because the
divergence happens downstream of tar entirely.

Both jobs (the source-tarball gate and the checksums.platforms gate
added in 0eae661) now download the real release asset for each
plugin's registry.json releaseTag and diff that instead — the exact
bytes nself plugin install downloads and internal/plugin/security.go's
verifyChecksum checks. When no release exists yet for a tag (the
normal state of an open version-bump PR, including this one right
now), the gate skips the compare for that plugin with an explicit
::notice:: and passes — it never fails merely because the release
hasn't been cut, and it never silently passes without saying which
branch it took. Verified locally end-to-end using the literal script
this file runs: a registry checksum matching a real rebuilt asset
passes, a corrupted asset fails (exit 1), restoring it passes again;
separately, running the no-release path against the actual 129-entry
registry.json skips all 128 checksummed entries with a notice and
exits 0.

scripts/deterministic-tar.sh and scripts/build-tarballs.sh are
unchanged in spirit — they still make a local rebuild reproducible
across machines, which is what they were for. They were never going
to make two different gzip implementations agree byte-for-byte, and
this gate no longer asks them to.
…istry.json and support --write

The version merged in from main required an explicit list of plugin
names and never wrote registry.json — informational only. The release
flow this repo depends on (tarball-checksum-gate.yml, RELEASE-v1.2.1.md)
needs a script that: takes a tag, enumerates every registry.json entry
on that releaseTag itself (source tarball plus, for binaryName plugins,
the five per-platform assets), computes sha256 against the real
published release asset, and either reports mismatches (default, exit
non-zero if any) or writes the published hashes into registry.json's
flat checksum, nested checksums.sha256, and checksums.platforms.<platform>
fields (--write).

Verified locally against a scratch registry.json + a mocked gh CLI
serving real built tarballs: report mode flags a deliberately wrong
checksum and exits 1; --write mode overwrites it with the correct
published hash and a follow-up report run then passes; a partial
per-platform asset set writes the platforms that exist and still
exits 1 for the ones missing from the release, rather than silently
leaving stale/absent entries unflagged.

Not run against a real release in this PR — no v1.2.1 release exists
yet; registry.json's checksums remain provisional until the owner cuts
the draft release and this script runs against it for real.
@acamarata acamarata changed the title release(prep): v1.0.1 for all 129 free plugins (70 were unpublished) release(prep): v1.2.1 for all 129 free plugins (70 were unpublished; monotonic versions) Sep 11, 2026
acamarata and others added 4 commits September 11, 2026 10:49
The upload step used --clobber and called it idempotent. Clobber is the
opposite: it overwrites, and overwriting is destructive here.

registry.json's checksums are written from the bytes published on the release
(scripts/verify-published-checksums.sh --write). The release flow uploads
tarballs to a DRAFT first, writes their hashes into registry.json, merges, then
publishes. Publishing creates the tag, and the tag triggers this workflow. With
--clobber it would rebuild all 129 tarballs in CI and overwrite the published
ones, so the bytes on the release would no longer be the bytes those checksums
attest to. Every free plugin would fail checksum verification at the exact
moment of release.

CI cannot reproduce the uploaded bytes. Identical tar streams still gzip
differently across machines: Apple gzip 479 vs GNU gzip 1.12, and across zlib
versions, measured 2026-09-11 on free/access-controls (6e9d3966 vs 0283c3b3).
Reproducibility flags on tar do not help, because the divergence is in DEFLATE.

Upload is now additive: assets already present are left untouched and only
missing ones are uploaded, with both counts logged. That is what the file
header has claimed since it was written.
…lished bytes

Three related fixes. The automated backfill in the previous commit corrupted
registry.json, and the checksums it wrote were not the published ones anyway.

1. registry.json — restored to 129 entries, checksums written from the bytes
   actually served.

   The backfill step derived the plugin name as "${tarball_name%-<version>.tar.gz}".
   That suffix does not match a platform tarball ending "-linux-arm64.tar.gz",
   so the substitution was a no-op, the name came out as the whole filename, and
   jq's assignment CREATED it. registry.json went 129 -> 289 entries, with 160
   junk keys such as "ai-cli-1.2.1-darwin-amd64.tar.gz", each carrying a
   checksum and no version. The CLI resolves installs from this file.

   Separately, every checksum already on this branch disagreed with the release:
   all 128 source tarballs mismatched when compared against the published
   assets. They had been computed from an earlier local build. Identical tar
   streams do not imply identical .tar.gz bytes — gzip's DEFLATE output is
   implementation- and version-dependent — which is exactly why the checksum of
   record must come from the published artifact.

   Now written by scripts/verify-published-checksums.sh --write v1.2.1: 288
   fields from the release's own bytes. Re-verified in report mode afterwards:
   288 assets checked, 0 mismatches, 0 missing. 32 plugins carry per-platform
   checksums (4 more than before, which had never been populated).

2. release-tarballs.yml — the backfill now splits at the first "-<version>",
   classifies source vs platform, writes platform hashes to
   checksums.platforms.<platform>, refuses to write a key that does not already
   exist, and fails if the entry count changes. Any of those four guards alone
   would have caught this.

3. verify-published-checksums.sh — it issued one `gh release download --pattern`
   per asset (578 API calls for this release) with stderr discarded, so a
   throttled download was reported as a missing asset. It produced a false
   MISSING for flags/linux-arm64, which is present on the release. It now lists
   assets once, downloads once, and separates "absent from the release" (a real
   defect) from "published but not downloaded" (transient), because those call
   for opposite responses.
Publishing the draft creates the tag, which re-triggers this workflow. Every
asset is already on the release by then, so the upload step correctly uploads
nothing — but the backfill step would still rebuild dist/ and recompute every
checksum from those fresh local bytes, overwriting values that were verified
against the published artifact.

gzip's DEFLATE output is implementation- and version-dependent, so a rebuild is
not guaranteed to reproduce the published bytes. That is the whole reason the
checksum of record comes from the release rather than from a build. Letting the
tag-push run recompute them re-introduces the bug by the back door.

The upload step now reports how many assets it actually published, and backfill
and its commit are skipped when that is zero. Writing checksums for a release
that is already fully populated is
scripts/verify-published-checksums.sh --write's job; it reads published bytes.
@acamarata
acamarata merged commit 216b7bf into main Sep 11, 2026
31 checks passed
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.

1 participant