Skip to content

ci: publish every release on Zapstore - #906

Merged
grunch merged 1 commit into
mainfrom
ci/zapstore-publish
Oct 28, 2026
Merged

grunch merged 1 commit into
mainfrom
ci/zapstore-publish

Conversation

@grunch

@grunch grunch commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Linked issue

None: maintainer pull request. Every release has been put on Zapstore by hand with zsp; MostroP2P/bosstro#50 automates the same for Bosstro.

Type

ci

What changes and why

From the next release on, pushing the tag also lists it on Zapstore. A new zapstore job in release.yml runs zsp publish --quiet zapstore.yaml once the GitHub release is out. It signs as Mostro's Zapstore publisher (npub1m0str0…), the same key that published every version so far by hand.

Affected flow

No app flow: only the release workflow (.github/workflows/release.yml, new job zapstore after publish) and zapstore.yaml.

  • Key: ZAPSTORE_SIGN_WITH, a secret of the release environment. It reaches zsp through the SIGN_WITH environment variable, never through argv.
  • Binary: zsp 0.4.17, pinned and checked against the asset's sha256.
  • Wrong key: zapstore.yaml now names the publisher (pubkey), so zsp refuses another key ("pubkey mismatch") instead of publishing a second Mostro.
  • Re-runs: zsp always takes the latest GitHub release. The job only publishes while its tag is the latest one. A re-run of an older tag leaves Zapstore as it is, shows a warning and does not need the secret. A release that is already listed is a no-op.
  • Not automated: the certificate proof (kind 30509, expires 2027-10-21) is still renewed by hand. --quiet skips certificate linking, and the runner has no keystore.

Blast radius

  • publish and changelog are unchanged, and nothing waits for zapstore. If it fails, the GitHub release and the changelog PR are unaffected, and the job can be re-run on its own.
  • release-dry-run.yml and release-builds.yml are untouched and still never see a secret.
  • With required reviewers on the release environment, this job asks for its own approval after android's.
  • pubkey in zapstore.yaml is read by zsp only, to refuse another signer. The events it publishes do not change.

Before merging: add the secret ZAPSTORE_SIGN_WITH (the nsec1… of npub1m0str0…) under Settings → Environments → release. Without it the job fails at "Check Zapstore secret", after the GitHub release is already out.

Worth knowing: docs/RELEASING.md asks for three protections, but today the release environment only has the v*.*.* tag rule. There is no tag ruleset and no required reviewers. Only grunch and Catrya can push, and both are admins, so in practice only admins can release. But a new writer could push a tag on a commit outside main with a modified workflow, and that run would reach the APK signing key, and with this PR the Zapstore key too. A tag ruleset limited to admins, as MostroP2P/bosstro has, closes that.

Platforms tested

None: no app code changes. The workflow was exercised locally with the pinned zsp binary, as below.

Manual testing

Setup

The zsp 0.4.17 linux-amd64 binary, downloaded and checked by the job's own install step. GITHUB_TOKEN is set from gh auth token.

Steps

  1. Run the install step: the download passes sha256sum --check --strict.
  2. SIGN_WITH=npub1m0str0… zsp publish --quiet zapstore.yaml (npub mode signs nothing and publishes nothing): exit 0 with no output, because v2.1.1 is already on Zapstore.
  3. Repeat with --overwrite-release: it builds kind 32267, 30063 foundation.mostro.app@2.1.1 and 3063 with the arm64-v8a APK and the release certificate (abfbaa99…).
  4. Repeat with a random npub: exit 1, "pubkey mismatch: zapstore.yaml has pubkey npub1m0str0… but SIGN_WITH resolves to …".
  5. zsp publish --check zapstore.yaml → {"package_id":"foundation.mostro.app"}.

Not covered

A real run in Actions with the secret. The next release is the first one.

Screenshots

No visible change.

Automated tests

test/ci/release_workflow_test.dart, group release zapstore job:

  • it runs only after publish succeeded;
  • it publishes only while its tag is the latest release (the comparison and both outputs, and three guarded steps);
  • a skipped re-run does not need the secret (step order);
  • zapstore.yaml names the publisher;
  • it uses the release environment with no write token, and only this job reads ZAPSTORE_SIGN_WITH;
  • zsp is pinned and checked against its checksum;
  • the key goes through the environment and never argv;
  • the changelog PR does not wait for it.

flutter test test/ci/ test/licensing: +167: All tests passed!

Checklist

  • cd rust && cargo fmt --check && cargo clippy -- -D warnings && cargo test pass locally (n/a: no Rust change)
  • dart format on the changed test, flutter analyze and flutter test test/ci/ test/licensing pass locally
  • Changed rust/src/api/ or an .arb: regenerated code committed in the same commit (n/a)
  • New or changed user-facing text: every locale updated, no hardcoded strings (n/a)
  • Changed an automation identifier: coordinated in the linked issue (n/a)
  • Protocol or transport change: impact on the daemon and other clients stated (n/a)
  • Behaviour or contract change: docs/RELEASING.md and CLAUDE.md updated
  • Commits are signed
  • I ran the Manual testing steps myself and can explain every line of this diff (for the maintainer)

AI assistance

Written with Claude Code: the workflow job, the tests, the docs, and the local zsp runs above. It is the same job as MostroP2P/bosstro#50, including the fixes from that PR's reviews. Look hardest at the secret's exposure in the release environment (see Worth knowing).

🤖 Generated with Claude Code

https://claude.ai/code/session_016wLvM1dvKHtf1wYBWAz5Ys

Summary by CodeRabbit

  • New Features
    • Latest releases are now published to Zapstore alongside GitHub releases.
    • Zapstore listings are signed by the configured publisher.
  • Documentation
    • Updated release guidance with Zapstore publishing and troubleshooting details.
  • Bug Fixes
    • A Zapstore publishing failure does not prevent the GitHub release or changelog update from completing.

The release workflow now ends with a zapstore job: once the GitHub
release is out, it runs zsp publish --quiet zapstore.yaml, which takes
the arm64-v8a APK of the latest GitHub release and the listing from
the tagged zapstore.yaml, and signs it as Mostro's Zapstore publisher
(npub1m0str0…), as every release so far was by hand. MostroP2P/bosstro
does the same (MostroP2P/bosstro#50).

- The key is ZAPSTORE_SIGN_WITH, a secret of the release environment;
  it reaches zsp through SIGN_WITH, never argv.
- zsp is pinned (0.4.17) and checked against its sha256.
- zapstore.yaml names the publisher (pubkey), so zsp refuses any
  other key instead of publishing a second Mostro.
- zsp always takes the latest GitHub release, so the job publishes
  only while its tag is the latest one: a re-run of an older tag
  leaves Zapstore as it is, with a warning, and does not need the
  secret.
- Nothing waits for it: a failed zapstore job leaves the GitHub release
  and the changelog PR as they are, and can be re-run on its own (a
  release already listed is a no-op).

docs/RELEASING.md documents the secret, the certificate proof, which
the job does not renew (it expires 2027-10-21), upgrading zsp and
recovering from a failed run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wLvM1dvKHtf1wYBWAz5Ys
@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between a01a060 and c2e09e0.

Files selected for processing: 5
  • .github/workflows/release.yml
  • CLAUDE.md
  • docs/RELEASING.md
  • test/ci/release_workflow_test.dart
  • zapstore.yaml

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Walkthrough

The release workflow adds a Zapstore job that publishes only when the run tag is the latest GitHub release. The job uses a pinned, checksum-verified zsp binary and a configured signing secret. Tests and documentation cover the workflow and publisher setup.

Changes

Zapstore Release Publication

Layer / File(s) Summary
Publisher identity and signing requirements
zapstore.yaml, docs/RELEASING.md, test/ci/release_workflow_test.dart
The Zapstore listing specifies its publisher key. Documentation and tests cover the signing secret, publisher key, certificate proof, and pinned zsp version and digest.
Gated release publication
.github/workflows/release.yml, test/ci/release_workflow_test.dart, CLAUDE.md, docs/RELEASING.md
The workflow checks whether the run tag is latest before publishing. Tests and release guidance cover job ordering, changelog independence, and retry behavior.

Priority: Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant GitHubActions
  participant GitHubReleases
  participant zsp
  participant Zapstore
  GitHubActions->>GitHubReleases: Check whether the run tag is latest
  GitHubActions->>zsp: Download and verify the pinned binary
  GitHubActions->>zsp: Run publish with zapstore.yaml and signing key
  zsp->>Zapstore: Publish the release listing
Loading
Merge Risk: Minimal · up to c2e09

The Zapstore job is ready to merge after normal checks. In an unusual release-selection case, its listing could refer to a different APK; the GitHub release remains unaffected.

Pre-merge checks: Passed 5
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: automating Zapstore publication for every release.
Description check Passed The description follows the repository template and explains the workflow changes, affected files, security considerations, testing, limitations, and checklist status. It also clearly states that a re…
Docstring Coverage Passed No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description follows the repository template and explains the workflow changes, affected files, security considerations, testing, limitations, and checklist status. It also clearly states that a real Actions run with the secret has not yet occurred.

Full details: Docstring Coverage

Explanation

No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 0 functions across 0 files. (5 skipped: 5 unsupported.)

Finishing Touches
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

I’m a rabbit by the release gate,
I check the tag before I wait.
A signed listing hops along,
With verified tools to keep it strong.
Zapstore gets the latest hare,
And older tags stay where they were.

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-28T00:22:32.224110Z c2e09e0 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@grunch

grunch commented Oct 28, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@grunch
grunch merged commit c8d3396 into main Oct 28, 2026
10 checks passed
@grunch
grunch deleted the ci/zapstore-publish branch October 28, 2026 04:18
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