expand openspec for extension delivery architecture - #452
Conversation
peter-smith-phd
left a comment
There was a problem hiding this comment.
Thanks for doing this! As I've mentioned several times, the update process is quite sensitive and we need to test it very carefully. I'd suggest your next step is to generate a detailed test plan for the DevX team to review.
| - binary archive: `lstk-*` binaries and `lstk-extensions.toml` as siblings of `lstk` at the archive root; | ||
| - Homebrew: automatic via the cask's Caskroom staging of the whole archive (lstk ships as a **cask**, not a formula — no libexec involved); the cask's post-install quarantine hook is widened from the single `lstk` binary to the whole staged directory; | ||
| - npm: bundled files are copied into each **platform package** (`@localstack/lstk-<os>-<arch>`), where the real binary lives — not the wrapper package — via a post-processing step in the release job. | ||
| - **Pull the prebuilt closed-source bundled binaries from the private extensions repository's releases** into the release build context, **version-pinned via a pin file in this repo** (`bundled/extensions.version`), checksum-verified against the private release's manifest, authenticated with a dedicated read-only token, without exposing source in the public repository. |
There was a problem hiding this comment.
I'm not clear on the the bundled/extensions.version concept. I had been thinking that each lstk package would take the latest version of the bundled extensions. There would be no need for any "pinning". Was this a conscious change? Does it introduce a maintenance burden we don't currently have?
There was a problem hiding this comment.
This was one of my points of discussion with Claude. The reasoning is to be able to track versions bundled together and have control when we are doing (hope not soon) braking changes. Blindly taking latest release could potentially produce unexpected results. In addition, it's not obvious to identify the version from the binaries. Pinning version is a fairly easy way to control versions in the bundle. I consider it easy enough to not be a real burden. Extensions developers should update this file when they want to release a new version.
If we want to remove this friction and just bundle latest version is fine, but we must make sure everyone is aware of this (merging anything to main will generate a new version in less than a week).
There was a problem hiding this comment.
If we want to remove this friction and just bundle latest version is fine, but we must make sure everyone is aware of this (merging anything to main will generate a new version in less than a week).
Yes, this is normal behaviour in a CI/CD environment. The rule is that whenever you hit merge, it could be in the customer's hands within a few hours. So basically we should feel confident enough that our testing is solid, and that we're not pushing out bad code. If we don't have that confidence, then we need to fix our processes/culture 😄
Having said that, if we could set the default version to latest, then that would alleviate the need update it manually, but give us the ability to set it to <explicit version> if we did want to lock it down to an older version. I think that's a reasonable trade-off.
|
|
||
| The private extensions repository — the same source of truth that builds the closed-source binaries and hand-authors the descriptions file — publishes **tagged releases** whose assets are the per-platform binaries (`lstk-<name>_<os>_<arch>[.exe]`), `lstk-extensions.toml`, and a `checksums.txt` manifest covering them. | ||
|
|
||
| This repo carries a **pin file**, `bundled/extensions.version` — a single line naming the private release tag. Each lstk release therefore maps deterministically and reproducibly to one extensions bundle; bumping the pin is an ordinary reviewable PR (automatable later from the private repo's release workflow). `scripts/fetch-bundled-extensions.sh` reads the pin, downloads the assets (`gh release download`), **verifies each against the bundle's `checksums.txt`**, and stages them under `bundled/` with canonical names. It hard-fails when any lstk target platform has no matching asset (subject to an explicit not-supported allowlist), so platform gaps surface at pull time, not as an empty-glob failure inside GoReleaser. |
There was a problem hiding this comment.
The "reviewable PR" seems like an extra burden for the lstk maintainers. Is the expectation that somebody will review the extensions release and make a go/no-go decision on whether to start using them? I was thinking that we always release the latest extensions alongside each lstk release.
There was a problem hiding this comment.
Issues (I can think of) that could come of no pinning:
An extension can ship before lstk is ready for it.
A broken extensions build breaks the lstk release.
The same lstk version can contain different extensions (if version is repackaged for any reason)
Undoing a bad extension needs a new extensions release.
No record of what shipped. If a customer reports a bug in lstk-deploy, you can't tell from their lstk version which extension build they have. You'd be guessing from dates.
Merging to the extensions repo means shipping to everyone.
If we're comfortable with "if extensions break, we cut a new release," always-latest is a reasonable call and it is genuinely simpler. If everyone is aligned in this, I'm fine with always-latest approach.
There was a problem hiding this comment.
An extension can ship before lstk is ready for it.
This is a general problem when coordinating software, and it just requires planning. In this case, we update lstk to have the new code, before we merge code in the extensions. It's also mandatory that our pipeline tests against the latest lstk, rather than something in a private branch.
A broken extensions build breaks the lstk release.
Then we have a problem with our testing, and we need to improve. It should not be able to break all of lstk though, but just the extension's sub-command itself.
The same lstk version can contain different extensions (if version is repackaged for any reason)
I'm not sure I understand this one. Could you give an example?
Undoing a bad extension needs a new extensions release.
You mean, a new lstk release? Yes, that's true, but I don't think we want to start deploying extensions separately, since that's more of a headache for customers.
No record of what shipped.
We can fix this by having the lstk build process clearly state (in the logs) which Git hash from lstk-bundled-extensions was included.
If a customer reports a bug in lstk-deploy, you can't tell from their lstk version which extension build they have. You'd be guessing from dates.
Yes you can, because each version of lstk is shipped with exactly one version of the bundled extensions, and we should log the Git hash in the lstk build logs. In most cases it'll be the latest lstk (because of auto-update), so it would be rare to go back and fix bugs in older versions.
Merging to the extensions repo means shipping to everyone.
Yes, that's how CI/CD works 😄
| 1. **Discover** the set at the extracted archive root: the lstk binary, every executable `lstk-*`, and `lstk-extensions.toml`. An archive with no extensions yields a set of size one — today's behavior, byte for byte. | ||
| 2. **Clean orphans**: remove any `*.lstk-new` siblings left by a previously crashed update. | ||
| 3. **Stage**: copy each member into the destination dir (the running executable's directory) as a `<name>.lstk-new` sibling — same directory ⇒ same filesystem ⇒ each upcoming rename is atomic — and set 0755 on binaries. Any failure here removes the staged files and leaves the installation untouched. | ||
| 4. **Commit**: rename each `.lstk-new` over its final name — extensions and the descriptions file first, `lstk` itself **last**, so the load-bearing swap is the final act and "update reported success" implies the whole set committed. Windows keeps the existing rename-running-exe-to-`.old` dance for `lstk.exe` only; extensions are not running during `lstk update` and rename directly. |
There was a problem hiding this comment.
Migrating the lstk binary last seems to only matter if lstk refers to specific version of the extension. Given that it solely relies on the name, such as lstk-doctor, it doesn't really matter if lstk is first or last. The update of multiple files is simply going to be non-atomic, but since renames rarely fail (I've never seen it happen, when within the same directory), I'm not worried about this.
There was a problem hiding this comment.
IMHO it doesn't seem to be a complex thing and it's a safety net. Happy to remove this requirement if everyone agree is not needed.
There was a problem hiding this comment.
Actually, this might be a requirement if we make extensions mandatory for the update and must fail if extensions fail. 🤔
pending: decide what to do with symlinks pending: remove pinned version
|
I documented a pending decision about symlink distribution and how to fix it with some proposals |
This PR includes only openspec docs for the extension bundling mechanism. It expands previous work (Thanks Peter!) and defines how the bunlding would work and the tasks to make it real.
This is intended to be the first draft to move forward the project. Any feedback, change, correction, pun or non pejorative expression will be very welcome!