Skip to content

Releases: sebastienrousseau/stratos

Stratos v0.0.21

15 Jun 23:13
v0.0.21
5bbb993

Choose a tag to compare

Fixed — root help and macOS symlink-path entry guard (post-v0.0.20 verification)

Two latent issues surfaced during the v0.0.20 post-release verification pass. Neither affects any supported install path; both are tightenings to keep the surface honest.

  • Root stratos help now lists cost, carbon, and rules validate. The v0.0.20 release added the commands and shipped per-command help (stratos help cost, etc.), stratos schema, and MCP tools/list entries — but the top-level help index was not updated. Discoverability for the FinOps differentiator was broken at exactly the surface a first-time user reads. Adds a dedicated Cost & sustainability section to HELP_ROOT and slots rules validate next to its get/set/diff siblings.
  • Entry guard now realpath-canonicalizes process.argv[1] before comparing against fileURLToPath(import.meta.url). On macOS, /tmp is a symlink to /private/tmp; the ESM loader canonicalizes import.meta.url through realpath but argv[1] retains the raw path the user typed, so a direct node /tmp/.../stratos.mjs version silently exited 0 with no output. Same class as the v0.0.18 Bun $bunfs mismatch (v0.0.19 changelog). Supported install paths (npm bin shim, install.sh, brew, docker, scoop, winget) are all canonical end-to-end and were never affected; this hardens the rarely-used direct-Node-invocation path so it can't regress silently again.

Engineering notes

  • New regression test exercises both fixes end-to-end through a symlinked directory so the symlink-path bug stays caught regardless of platform (/tmp is a regular directory on Linux runners, so the v0.0.20 test matrix never had a chance of catching it).
  • realpathSync is imported from node:fs — same module already imported for createReadStream. No new dependency, no new bundle weight.

Stratos v0.0.20

15 Jun 22:27
v0.0.20
f6ca34f

Choose a tag to compare

Added — Feature Breadth tier-7 + tier-1 (the FinOps differentiator + a DX win)

Implementation plan (~/Drop/stratos-ip.md) Phase 3.1 + 3.2 + a Tier-1 entry.

  • stratos cost [--days N] [--zone Z] [--projected] — spend breakdown by region. Reads /api/billing/usage when CloudCDN exposes it (some deployments don't yet); falls back to projecting from /api/core/statistics × the published rate card (COST_RATES). --projected forces the projection mode explicitly. Output columns: region, requests, bandwidth_gb, cost_usd. Surfaces as MCP tool cloudcdn_cost.
  • stratos carbon [--days N] [--region X] [--intensity-below N] — energy + CO2e attribution for cached traffic. Computes compute_kwh + transfer_kwh per region from documented coefficients (CARBON_DEFAULTS), then multiplies by grid intensity. Live intensity comes from the Electricity Maps public API (free tier — set ELECTRICITY_MAPS_TOKEN for the keyed tier); on any failure (network glitch, missing region, rate limit), falls back to documented per-region defaults so the command never hard-fails on a network blip. --intensity-below N is the carbon-aware deploy gate: exits 0 if at least one region's grid intensity is below the threshold, 69 otherwise. Use as: stratos carbon --intensity-below 250 && stratos deploy. Surfaces as MCP tool cloudcdn_carbon.
  • stratos rules validate <_headers|_redirects> -f <file> — offline structural validator for the two config-as-code formats. Catches the common typos (bad URL pattern, missing colon in header, malformed redirect status, wrong token count) at PR-review time instead of at deploy time. Exits 0 on clean, 65 (EX_DATAERR) with a line-by-line issue list otherwise. No API call.

Documentation

  • ROADMAP.md — new file mapping the 8 tiers of Feature Breadth work (existing-API completion → deployments → edge compute → edge data → security → identity → FinOps → compliance) against CloudCDN's platform-side API rollout. Tiers 2, 3, 4 are CloudCDN-confirmed roadmapped; the file tracks status transitions (plannednextshippingdone) over time. Per the rating math in the implementation plan, completing all tiers lifts Feature Breadth from v0.0.20's ~6/10 to ~9.5/10 over 12–18 months.

Background

The implementation plan's Phase 3 called stratos cost + stratos carbon "the talkable feature that earns Console.dev / TLDR placement" — nobody else in the CDN-CLI space has shipped sustainability primitives at the terminal. The FinOps Foundation formalised Cloud Sustainability as a framework capability in 2026; Greenpixie / Opencost surface this for IaC but no CDN CLI does. v0.0.20 ships both. stratos carbon --intensity-below is the carbon-aware deploy gate that the byteiota — FinOps + GreenOps 2026 writeup specifically called out as "talked about, almost no CLI implements it."

The rules validate addition is a Tier-1 DX gap that didn't need a platform change — it caught me out twice in the v013-branch-push tests when typo'd _headers lines silently passed rules set then exploded at edge-time. Now they fail at validate time, before the file ever reaches the API.

Engineering notes

  • 19 new tests (test/v020-cost-carbon.test.mjs) covering happy path + auth-error + threshold-gate + region-fallback + zone-flag forwarding + MCP dispatch for both new tools.
  • stratos schema now reports 36 commands (was 34). Both new commands carry mcp_tool entries; cost and carbon are also registered in MCP_TOOLS with proper input schemas so MCP hosts (Claude Code, Cursor) get them automatically.
  • rules command's exits[] now includes EX.DATAERR (65); the manifest regression suite caught this drift on first run.
  • 603 / 603 tests pass; 100 / 100 / 100 / 100 coverage held (some defensive nullish-coalesce arms wear c8 ignore comments with explanations).

Stratos v0.0.19

14 Jun 19:42
v0.0.19
4e0218b

Choose a tag to compare

Fixed

  • Windows binary entrypoint guard — second attempt. v0.0.18 tried to detect the Bun-compiled binary context via Bun.embeddedFiles.length > 0, but that array is for --embed FILE extras (additional files passed alongside the entry script), not the main entry itself. So the check stayed false on Windows, main() never ran, and the binary remained silent. The v0.0.18 release.yml's strengthened smoke step (assert stdout matches ^stratos v[0-9]) caught it cleanly: the binaries (windows-latest, …, smoke: true) step failed with ::error::empty or malformed output: '', every downstream job (manifests / SLSA / tap-bump / scoop-bump / winget-submit / smoke-verify) was skipped via the needs: dependency chain, and no broken Windows binary was attached to the v0.0.18 release. npm @cloudcdn/stratos@0.0.18 and the Docker image v0.0.18 did publish (their paths don't go through the Bun binary).
  • v0.0.19's fix uses the simpler invariant: typeof Bun !== 'undefined'. Stratos is a Node-only package (engines.node >= 20; NON-GOALS.md documents the no-Bun-as-library stance), so any time the Bun global exists at runtime, we're inside a bun build --compile artefact. Library importers under Bun aren't a supported configuration; in the unlikely case someone tries, main() runs eagerly — a minor surprise rather than a silent-zero-exit, which is the right side of the trade-off. The Node-entrypoint check is unchanged.
  • The bug class that originally produced this — smoke step asserts only exit code, not output — was already fixed in v0.0.18. The strengthened assertion did its job during the v0.0.18 release: identified the silent binary, aborted the release before downstream artefacts shipped. That gate is what makes v0.0.19 a low-stakes iteration rather than a v0.0.16-style "ship and pray, fix later" cycle.

Partial v0.0.18 state to be aware of

  • @cloudcdn/stratos@0.0.18 published to npm (the fix code is there; works for npm users — Bun binary isn't involved on that path).
  • ✓ Docker image ghcr.io/sebastienrousseau/stratos:0.0.18 published (Docker runs Node).
  • ✓ GitHub Release v0.0.18 exists with canonical artefacts: stratos.mjs, installers, SBOM, VEX, man page, four of the five binaries (linux-x64, linux-arm64, darwin-x64, darwin-arm64) and their Cosign signatures.
  • ⚠️ No stratos-win-x64.exe attached to the v0.0.18 release (the silent binary was rejected at smoke).
  • ⚠️ Homebrew tap, Scoop bucket, winget still pointing at v0.0.17 (the manifests / tap-bump / scoop-bump / winget-submit jobs were needs:-blocked by the failed binaries cell).
  • ⚠️ No SLSA L3 attestation bundle for v0.0.18 (same skip).

v0.0.19 will land all of the above end-to-end — assuming the new entrypoint check works (and the strengthened smoke is now the proof).

Stratos v0.0.18

14 Jun 19:05
v0.0.18
bc81e86

Choose a tag to compare

⚠️ Superseded by v0.0.19

v0.0.18 ships only a partial set of artefacts. The Windows binary smoke
(intentionally) blocked the release at the binaries step when the
entrypoint-guard fix didn't work, so:

  • @cloudcdn/stratos@0.0.18 did publish to npm (the fix code is
    there; npm path doesn't involve the Bun binary)
  • ghcr.io/sebastienrousseau/stratos:0.0.18 did publish (Docker
    runs Node, not the Bun binary)
  • ⚠️ No stratos-win-x64.exe attached here
  • ⚠️ Homebrew tap, Scoop bucket, winget all still point at v0.0.17
  • ⚠️ No SLSA L3 attestation

v0.0.19 lands the correct fix end-to-end across every channel. Use
v0.0.19 or later.


Fixed

  • Windows binary (stratos-win-x64.exe) was silent on every invocation. v0.0.15 through v0.0.17 shipped a bun --compile-built Windows binary that exited 0 while producing zero bytes of stdout for every command. Discovered by reproducing microsoft/winget-pkgs#382755 — the reviewer @stephengillie's manual validation of stratos --version returned empty output, and we narrowed the cause through two rounds of CI diagnostic (diag-windows-binary.yml, diag-windows-binary-2.yml).
    • Root cause: the script-entrypoint guard at the bottom of stratos.mjs used the classic Node check fileURLToPath(import.meta.url) === process.argv[1]. In a Bun-compiled standalone binary on Windows, process.argv[1] is the .exe path on disk (C:\…\stratos.exe) while import.meta.url resolves into Bun's virtual $bunfs filesystem (file:///$bunfs/root/…). These never match → guard false → main() never runs → silent zero-exit. Linux/macOS binaries happened to work because their argv[1]/url comparison coincidentally passes, but the codepath was just as fragile.
    • Fix: detect compiled-binary context explicitly via typeof Bun !== 'undefined' && Array.isArray(Bun.embeddedFiles) && Bun.embeddedFiles.length > 0 and run main() if either that OR the Node entrypoint check is true. Bun.embeddedFiles is non-empty only inside a bun build --compile artefact, so importers of stratos.mjs as a library (Node or Bun, from source) still see main() not auto-run — the programmatic-API contract is preserved.
  • The release-pipeline binary smoke was a no-op gate. shell: bash, ./<binary> version with set -e saw exit 0 from the silent binary and passed. Strengthened to assert the stdout matches ^stratos v[0-9] explicitly — any future silent-zero-exit fails the release matrix at build time.
  • smoke-verify matrix now includes binary-win-x64 alongside the existing binary-linux-x64 and binary-darwin-arm64 cells. Mirrors what the winget reviewer's manual test did: curl the .exe from the release and verify the version banner. Closes the post-release verification gap that let v0.0.15..v0.0.17 ship a broken Windows binary unnoticed.

Background

PR microsoft/winget-pkgs#382755 was closed unmerged on 2026-06-13 by the Microsoft reviewer after manual validation. CloudCDN.Stratos couldn't be auto-resubmitted because (a) it's not yet in microsoft/winget-pkgs (so the auto-bump action skips by design) and (b) the underlying silent-output bug would have produced an identical failure for any new submission. v0.0.18 fixes the binary; a manual first-version PR for manifests/c/CloudCDN/Stratos/0.0.18/ (already pre-built and attached to the GitHub release as winget-manifests.tar.gz) is the next step to land CloudCDN.Stratos on winget.

Diagnostic workflows retained

  • diag-windows-binary.yml — round 1, walks the failure surface across shells and invocations
  • diag-windows-binary-2.yml — round 2, discriminates Bun-build vs stratos.mjs vs stdio

Both are workflow_dispatch only — no auto-fire. Kept as record of the investigation and as ready-to-trigger probes if the silent-output class ever recurs.

Stratos v0.0.17

14 Jun 15:07
v0.0.17
bf2b26c

Choose a tag to compare

Added — schema-driven regression suite (Phase 0 follow-up)

  • 50 new regression tests across three new files that pin the public surface as a contract:
    • test/v017-regression-manifest.test.mjs (15 tests) — schema/MCP_TOOLS/EX/error_types/exports cross-reference. Catches drift where a new command is added without updating one of the four parallel registries. Every EX.* code must be referenced somewhere; every MCP_TOOLS entry must map to a known command verb; every error_types.retryable bit is the documented contract.
    • test/v017-regression-cli-smoke.test.mjs (20 tests) — stratos help <cmd> and stratos <cmd> --help exit cleanly for every command in KNOWN_COMMANDS. Subcommand routers reject unknown subcommands with EX.USAGE. stratos completion <shell> renders for bash/zsh/fish/powershell. stratos explain works for every EX value, the documented symbolic aliases, and common HTTP statuses. stratos halth produces a Levenshtein "did you mean health" suggestion.
    • test/v017-regression-output-matrix.test.mjs (15 tests) — every documented --output format (json, ndjson, jsonl alias, yaml, csv, table) renders against a mock server for both single-object and list bodies. --json is verified as a shorthand for --output json. --filter jq pipeline works on single-line expressions.

Fixed

  • stratos help <cmd> now works for every command in KNOWN_COMMANDS, not just the ~24 with curated entries in HELP_BY_COMMAND. Falls back to a synthesised stratos <cmd>\n <summary>\n Exit codes: <list> from COMMAND_META when no hand-tuned entry exists. Closes the "stratos help stats / audit / analytics / ask / search / upgrade / logout / stream / pipeline / passkey returns EX.USAGE" surface that the regression suite caught on first run.
  • --filter now handles all jq output shapes. applyFilter() parses each line of jq's stdout as an independent JSON value, so filters that produced pretty-printed multi-line single documents (e.g. {status, edge}) used to fail with "jq produced non-JSON output". Fixed by passing -c (compact) to jq — every line is now a complete JSON document, so single values, stream-style outputs (.[].id), and object projections ({a, b}) all work uniformly. Single-line expressions and per-line streams were already working; this just closes the multi-line gap. Discovered by the v017 regression-output-matrix suite on first run.
  • Bumps EXPECTED_SHA in install/install.{sh,ps1} to match the v0.0.17 bytes.

Stratos v0.0.14

02 Jun 18:22
v0.0.14
64e53ba

Choose a tag to compare

Fixed

  • winget-submit no longer fails the release on a package that doesn't exist yet in microsoft/winget-pkgs. vedantmgoyal9/winget-releaser@v2 is strictly a bump action — it errors out on the first version. v0.0.13's run failed for exactly this reason. v0.0.14 adds a pre-check that probes api.github.com/repos/microsoft/winget-pkgs/contents/manifests/c/CloudCDN/Stratos: if the package isn't there yet, the step skips with a titled ::warning:: + step-summary entry pointing at the one-time manual PR workflow. Once that PR merges, subsequent releases auto-bump as designed.
  • Test isolation race between test/v006.test.mjs and test/v007.test.mjs. Both called scripts/make-winget.mjs and scripts/make-scoop.mjs writing to dist/winget/ and dist/stratos.scoop.json in the repo root. node --test runs files in parallel; the second write would clobber the first, and the first's assertions would fail on slower runners (Windows / Node 22 was the most frequent victim). Both scripts now accept a --dist-dir <dir> flag; both test files use per-test mkdtemp to isolate. No more flake.

Changed

  • scripts/make-winget.mjs and scripts/make-scoop.mjs gain a --dist-dir <dir> option. Default behaviour (write to dist/) unchanged; the flag is for callers that want to isolate.

Stratos v0.0.13

02 Jun 17:25
v0.0.13
34cc00e

Choose a tag to compare

Verified

  • First release where tap-bump, scoop-bump, and winget-submit actually drive the publish-side instead of skipping. No code changes from v0.0.12 — this is a version-bump-only release whose purpose is to exercise the now-configured secrets (HOMEBREW_TAP_TOKEN, SCOOP_BUCKET_TOKEN, WINGET_PAT) end-to-end. Confirmation criteria: the v0.0.13 GH Release run shows actual git pushes to sebastienrousseau/homebrew-tap and sebastienrousseau/scoop-bucket and an actual PR opened against microsoft/winget-pkgs (instead of three ::warning:: skips).

Stratos v0.0.12

02 Jun 15:24
v0.0.12
8d0f303

Choose a tag to compare

Fixed

  • smoke-verify (npm) was the last false-failure cell. v0.0.11's $(npm config get prefix)/bin/stratos returned a path that didn't actually contain the symlink on Ubuntu runners — got: came back empty. Rewritten to run the just-installed stratos.mjs directly via node "$(npm root -g)/@cloudcdn/stratos/stratos.mjs" version. Points at the file npm wrote to disk; no PATH / symlink dependency.
  • install.sh and install.ps1 now default to the GitHub Release URL (https://github.com/sebastienrousseau/stratos/releases/download/v<version>/stratos.mjs) instead of https://cloudcdn.pro/dist/stratos/stratos.mjs. The CDN is now opt-in via the CLOUDCDN_URL env var. The GH release is canonical (signed by the release workflow, immutable per-tag, SLSA L3 attested) and removes the dependency on a separate cdn-sync step having credentials configured. The smoke-verify (install-sh) fallback that worked around the stale CDN can be removed in v0.0.13.

Stratos v0.0.11

02 Jun 14:40
v0.0.11
2860388

Choose a tag to compare

Added — validation hardening

  • actionlint CI gate (new lint-workflows job in ci.yml). Every PR that touches .github/workflows/*.yml is now parsed by actionlint + its embedded shellcheck. This would have caught the v0.0.10 / v0.0.11 ghost-tag bug (multi-line git commit -m "…" strings breaking YAML block-scalar indentation) at PR-review time, before any tag could fire a broken workflow.
  • scripts/check-versions.mjs — single source of truth that verifies every version-bearing file agrees: stratos.mjs VERSION, package.json version, package-lock.json version, install.sh VERSION, install.ps1 $Version, test/router.test.mjs assertions, top CHANGELOG.md entry. Also asserts install.sh EXPECTED_SHA (and the PowerShell equivalent) matches the actual SHA-256 of stratos.mjs. Wired into a new check-versions CI job. This would have caught the v0.0.6 EXPECTED_SHA-drift bug and the v0.0.11 branched-off-stale-main bug.
  • scripts/preflight-release.sh — mandatory pre-git tag checklist that runs locally: workflow YAML parses, actionlint clean, check-versions clean, working tree clean, local main is up to date with origin/main, new version is strictly greater than the latest tag, CHANGELOG has an entry for the new version, npm test + coverage:check + docs:check all green. Run it before every tag.

Fixed

  • smoke-verify matrix bugs that caused 3/6 channels to fail on v0.0.10:
    • npm smoke now uses $(npm config get prefix)/bin/stratos instead of bare stratos — bypasses the Ubuntu-runner PATH issue that returned empty got:.
    • install-sh smoke no longer depends on cloudcdn.pro being fresh. The smoke step downloads stratos.mjs from the GH release directly when the CDN's content doesn't match, so a skipped cdn-sync no longer breaks the smoke.
    • homebrew smoke first reads the tap's currently-published version and short-circuits with a ::warning:: (not a failure) if it's behind — distinguishes "tap-bump was skipped because HOMEBREW_TAP_TOKEN is unset" from "we shipped something broken".
  • Gate-skip semantics for tap-bump / scoop-bump / winget-submit / cdn-sync. Each gated step now emits a titled ::warning:: annotation and a ### ⚠️ SKIPPED section in the workflow's Job Summary. This makes the difference between "step succeeded by skipping" and "step succeeded by actually doing the work" visible at-a-glance in the GitHub Actions UI.
  • shellcheck warnings in release.ymlsha256sum *sha256sum ./* (guards against --prefixed filenames), and 4 × SC2129 (grouped redirects to $GITHUB_STEP_SUMMARY). actionlint now exits 0.

Stratos v0.0.10

02 Jun 13:52
v0.0.10
69ddb3e

Choose a tag to compare

Added

  • Five new distribution channels wired into the release pipeline. The release.yml workflow now auto-bumps sebastienrousseau/homebrew-tap (gated on HOMEBREW_TAP_TOKEN), pushes to sebastienrousseau/scoop-bucket (gated on SCOOP_BUCKET_TOKEN), opens a PR against microsoft/winget-pkgs via vedantmgoyal9/winget-releaser@v2 (gated on WINGET_PAT), and uploads stratos.mjs + installers to cloudcdn.pro (gated on CDN_UPLOAD_URL + CDN_UPLOAD_TOKEN). Every gate is graceful — missing secrets emit a ::warning:: and skip the step, leaving the core release green.
  • Post-release smoke verification job. New smoke-verify matrix runs after every release, installs Stratos from npm + Homebrew + Docker + linux-x64 binary + darwin-arm64 binary + the GH-release-hosted install.sh, runs stratos version, and fails the workflow if any channel prints anything other than the expected tag. Catches "shipped but doesn't install" regressions (the class of bug that bit v0.0.7's install.sh on BSD-style sha256sum).
  • docs/release-pipeline.md — full job-graph diagram, secret-by-secret setup instructions, and the bootstrapping recipe for adding new distribution channels (Arch AUR, Snap, Nix flake, etc.).

Fixed

  • README install table fully expanded and corrected. Eight distribution channels (npm, Homebrew, winget, Scoop, single binary, install.sh, install.ps1, from-source) instead of five. The Homebrew command uses the fully-qualified brew install sebastienrousseau/tap/stratos — bare brew install stratos does not find tap-only formulas, which we confirmed by smoke-testing the v0.0.9 tap end-to-end during scoping.