Skip to main content

ABOUT THE EXTENSION PUBLISHING LIFECYCLE

Publishing an extension to the swamp registry is not a single upload. It is a sequence of gates, each of which must pass before the next is attempted, and the final push is refused unless every earlier gate has already passed. This page is about why the pipeline is shaped that way, what each gate is actually protecting, and how the work of writing an extension connects to the work of releasing it.

Two state machines, one path

There are really two lifecycles involved, and they meet in the middle.

The first is authoring: searching the registry to confirm nothing already covers the need, writing the model, confirming swamp loads it, running it, testing it. This lifecycle is exploratory. You move back and forth — write a method, run it, find a bug, fix it, run it again. Nothing here is gated, because nothing here is irreversible. The cost of a mistake is a re-run.

The second is publishing: taking a finished extension and releasing it under a collective so other people can install it. This lifecycle is linear and gated, because publishing is irreversible in a way authoring is not. A version pushed to the registry can be deprecated or yanked, but the fact that it existed, and anything anyone pulled from it, cannot be recalled. The gates exist to make the irreversible step boring — by the time you reach the push, every question that could have made it go wrong has already been answered.

The handoff between the two is the manifest. Authoring produces source files; the manifest is the first artifact that belongs to publishing, because it declares what a release is: its name, its version, the files it ships. Once the manifest exists, the publishing gates have something to check.

What each gate protects

The gates are ordered so that the cheapest, most fundamental checks come first and the expensive, registry-touching checks come last. Each one rules out a specific class of failure.

The repository gate confirms you are inside an initialized swamp repository at all. It comes first because every later command needs the repository marker to resolve paths and configuration. Without it, nothing else can run.

The authentication and collective gates confirm who you are and that you are allowed to publish under the name in the manifest. A scoped name like @acme/widget is a claim of ownership, and the registry will reject a push whose collective does not match the authenticated account. Checking this locally, early, means you discover a naming mistake before building an archive rather than after uploading one. Reserved collectives such as @swamp and @si are refused outright.

The version gate asks the registry what already exists and computes the next CalVer version. Versions are immutable once published — you cannot overwrite 2026.05.31.1 with new code — so the pipeline computes the next free version rather than trusting a number you typed. This is what prevents two different builds from claiming the same identity.

The formatting gate runs deno fmt and deno lint over the extension's TypeScript. This is not about taste. Published source is read by the people who install it, and the scorer parses it with deno doc; consistent, lint-clean source is a precondition for both. The push enforces formatting automatically, so an unformatted extension cannot reach the registry by accident.

The safety gate scans every file for patterns that have no place in a published extension: dynamic code execution, symlinks, hidden files, disallowed file types, and archives that are too large or contain too many files. Some findings are hard errors that block the push; others — spawning a subprocess, very long lines, embedded base64 — are warnings that ask for confirmation. The distinction reflects intent: eval() is almost never legitimate in this context, but shelling out to a command sometimes is, so one is refused and the other is merely flagged.

The dry run is the whole pipeline rehearsed without the upload. It builds the archive, runs every safety and quality check the real push runs, and stops short of touching the registry. It exists so that "will this push succeed?" has a definite answer that costs nothing to obtain.

Only after all of this does the push happen, and by then it is almost anticlimactic: the archive is built, the checks are green, the version is known to be free. The upload itself is a three-phase exchange — initiate, upload, confirm — but the interesting work already happened in the gates.

Where the quality score fits

The quality score is not a gate. An extension that scores 4 out of 14 publishes exactly as readily as one that scores 14 out of 14. The score is a signal for the people deciding whether to install your extension, and a checklist for you while you still have the source in front of you — does it have a README, is the type surface documented, is there a license, does the repository link resolve.

It sits between formatting and the dry run in the pipeline for a practical reason: by that point the archive has been packaged and cached, so scoring it is nearly free, and seeing the score before you push is more useful than seeing it afterward. But skipping it never blocks anything. The scorecard explanation covers what the score measures and why verification is a separate badge; the rubric reference lists the exact factors.

Supply-chain trust is the exception

One part of the quality system does behave like a gate, and it is worth calling out because it is easy to conflate with the score. The dependency-trust factor audits every npm: package the extension imports against OSV.dev for known vulnerabilities and against the npm registry for trust signals — deprecation, license, maintainer count, download volume, recency. Most of these only affect the score: a package with few downloads or an unrecognized license costs points but does not stop a publish. But a deprecated package, or one with a HIGH, CRITICAL, or unknown-severity vulnerability, is a hard error that blocks the push the same way a safety violation does. You cannot publish an extension that pulls in a known-vulnerable dependency, regardless of how every other factor scores.

Two details surprise people. The first is that jsr: dependencies are not audited at all — the jsr registry already enforces SPDX licensing, provenance, and a prohibition on install scripts, so the npm-style trust gates would be redundant. The second is that zod, despite being an npm: import, is not counted as a dependency: it is shared with swamp's own runtime rather than bundled into the extension, so there is nothing to audit. An extension whose only import is npm:zod@4 reports no dependencies to audit and earns the factor automatically.

This audit is about the code your extension ships. It is unrelated to the collective trust managed by swamp extension trust, which governs whether swamp will auto-resolve types from a collective when someone installs an extension. One is about the safety of what you publish; the other is about who a consumer is willing to pull from. They share the word "trust" and nothing else.

After publication

Publishing is not the end of an extension's life. A version that should no longer be used can be deprecated — a soft signal that leaves it pullable but flags it across search, info, and pull — or yanked, which removes it from normal resolution. Both are reversible. Deprecation is the gentler tool, meant for "there is a better option now"; yanking is for "do not use this version." Neither deletes anything, because the registry's promise is that a version, once published, keeps meaning what it meant.