Skip to content

feat(release): application release command family - #336

Open
JulianRuiseco wants to merge 2 commits into
mainfrom
jrr/release-commands
Open

feat(release): application release command family#336
JulianRuiseco wants to merge 2 commits into
mainfrom
jrr/release-commands

Conversation

@JulianRuiseco

@JulianRuiseco JulianRuiseco commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Objective

fslabscli is the de facto location for CI logic. This PR gives it the
application release pipeline: the logic behind fsl_libs' release workflows
(fslabs/fsl_libs#4230 rewrites them as thin, pinned invocations of these
commands), which previously lived as bash under that repo's
.github/scripts/.

It also makes fslabscli the single owner of the library-vs-application
release boundary: release classify shares the publish- tag-prefix
constant with the publish path, so a library release created by
fslabscli publish is skipped by the app pipeline by construction.

Solution

This PR is the pipeline CORE - everything a release cannot ship without.
The operational periphery (resolve, record, healthcheck,
cleanup-drafts) stacks on top in a follow-up PR so each unit stays
reviewable.

A release subcommand family (cargo fslabscli release <cmd>):

Command Purpose
classify Library skip vs validated <app>-<version> app release; app discovery from [package.metadata.fslabs.release] across every git-tracked manifest, including excluded workspaces
verify-production Tag-resolved revision (never target_commitish), ancestry of main, workspace version binding, green check runs
probe-store Conditional-write go/no-go gate against a deployed store
publish Atomic publication: signature re-verification, monotonicity from committed manifests, read-back before the manifest commit point, index CAS
promote The only writer of channels.json; backward gate re-runs inside the CAS retry loop; rollback = acknowledged backward move with provenance
bundle-linux cargo-deb + bundle-nothing AppImage with a glibc-floor assertion
sign-linux Detached OpenPGP signatures in a throwaway GNUPGHOME

The manifest / index / channels.json serde definitions in
release/types.rs are the single source of truth for the published object
shapes; storage semantics live in release/store.rs on the existing
opendal dependency (put-if-absent via if_not_exists, ETag If-Match CAS
with the transform re-run on every retry). New deps: semver,
thiserror. All commands run on Linux runners, so the existing
linux-musl release asset remains the only distributed binary.

This family supersedes the legacy publish.binary installer/channel model
in check_workspace/binary.rs (nightly/alpha blob channels); deprecating
and removing that is follow-up work after the fsl_libs cutover.

Size

One squashed commit, ~3.4k lines: seven subcommands, their 45 unit tests
(the ported specification of the bash this replaces), and the shared
contract types / storage / HTTP / keyring modules. The doc comments carry
the published contract (why the manifest is written last, why
monotonicity ignores index.json, why a backward move re-validates inside
the CAS loop) and are load-bearing. Review path: types.rs + store.rs
first (the contract), then classify/publish/promote (the decisions), then
bundle/sign/probe (tool wrappers). The stacked periphery PR is ~1.3k.

Testing

  • cargo test: 324 passed (58 in the release module: classification
    grammar and tag-boundary scenarios, conditional-write semantics, the
    backward-move gate and provenance cap, manifest completeness both
    directions, glibc symbol parsing, api-url normalisation).
  • End to end with the built binary against a throwaway MinIO:
    sign-linux -> publish 1.0.0 and 1.1.0 (six objects each, manifest last;
    re-publishing 1.0.0 refused with the version-spent error) -> promote
    latest -> unacknowledged backward move refused, acknowledged one records
    backward: true + reason -> resolve --download digest- and
    GPG-verifies against the published key -> healthcheck reports the
    surface healthy, and names both digests when an object is tampered.
  • Against the real fsl_libs tree: release classify discovers
    spatial_engine from its metadata in the excluded fdk_apps workspace,
    routes production/preview correctly, and skips publish-* releases
    including null-name ones.
  • Three integration defects were found by those live runs and fixed (all
    invisible to unit tests): toml 0.9 document-vs-value parsing, clap's
    propagated auto --version flag colliding with --version arguments,
    and opendal expressing S3 If-None-Match:* as if_not_exists.

Known wart: main.rs requires a git root before dispatch, so
release resolve panics outside a checkout; relaxing that touches shared
code and is left for a follow-up.

Release

After merge, tag a release so fslabs/fsl_libs#4230 can set its pin
(version + binary sha256) in .github/actions/install-fslabscli.

@fslabs-bot

fslabs-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please ask for approval from julianruiseco. For more information see the Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@fslabs-bot fslabs-bot Bot added the size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. label Aug 18, 2026
The release contract logic moves here from fsl_libs' .github/scripts
(bash), per the doctrine that fslabscli owns CI logic; the fsl_libs
workflows become thin invocations of a pinned fslabscli
(fslabs/fsl_libs#4230). This also makes fslabscli the single owner of the
library-vs-application release boundary: `release classify` shares the
publish- tag-prefix constant with the publish path, so a library release
this tool creates is skipped by the app pipeline by construction.

This PR is the pipeline CORE - everything a release cannot ship without;
the operational periphery (resolve, record, healthcheck, cleanup-drafts)
stacks on top in a follow-up PR.

- classify: library skip vs validated <app>-<version> app release, keyed
  on the TAG (this tool leaves library release names null); apps are
  discovered from [package.metadata.fslabs.release] across every
  git-tracked manifest, INCLUDING excluded workspaces (fdk_apps holds the
  only shipped app).
- verify-production: revision resolved from the tag itself (never
  target_commitish, which is a branch name), ancestry of main, workspace
  version binding, green check runs.
- probe-store: the conditional-write go/no-go gate against a deployed
  store. Object lock plus versioning stops deletion but NOT overwrites;
  only conditional writes make published objects immutable.
- publish: completeness against configured targets (missing OR extra
  artifacts fail; every linux artifact needs its .asc), signature
  re-verification against the bytes (osslsigncode per MSI, gpg against
  the PUBLISHED key for linux; DMG is stapler-validated on the mac host),
  digesting, monotonicity derived from COMMITTED MANIFESTS (never
  index.json), artifact writes read back and digest-compared, the
  manifest written LAST as the atomic commit point, index
  compare-and-swap. A refused overwrite propagates as-is: the version is
  spent.
- promote: the only writer of channels.json. Production-manifest gate
  ("previews never"), per-target artifact presence, selected-artifact
  digest re-verification, and a pure apply_move whose backward gate
  re-runs INSIDE the CAS retry loop; acknowledged backward moves (the
  rollback procedure) record backward:true plus the reason in a capped
  provenance log.
- bundle-linux: cargo-deb plus a bundle-nothing AppImage (pinned
  appimagetool by sha256) with the glibc floor asserted from the binary's
  versioned symbols; no versioned glibc symbols at all is an error, not a
  pass.
- sign-linux: temp GNUPGHOME, detach-sign, self-verify, fingerprint out.

types.rs is the single source of truth for the manifest / index /
channels.json shapes; store.rs holds the storage semantics on the
existing opendal dependency; http.rs and keyring.rs are the one HTTP
client and one gpg keyring every command shares. New deps: semver,
thiserror. All commands run on Linux runners, so the existing linux-musl
release asset remains the only distributed binary.

Proven end to end with the built binary against a throwaway MinIO
(publish 4 artifacts + manifest-last; re-publish refused; backward move
refused then acknowledged with provenance) and against the real fsl_libs
tree (classify discovers spatial_engine from metadata in the excluded
workspace). Integration defects found by those live runs, invisible to
the unit tests: toml 0.9 document-vs-value parsing, clap's propagated
auto --version flag colliding with --version arguments, and opendal
expressing S3 If-None-Match:* as if_not_exists.

Known wart: main.rs requires a git root before dispatch; a follow-up.
The legacy publish.binary installer/channel model in
check_workspace/binary.rs is superseded by this family; deprecation is
follow-up after the fsl_libs cutover.

cargo test: 311 passed. clippy: zero diagnostics.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant