Repository navigation
📦 Let a first release publish on both registries - #473
Merged
Merged
Conversation
@executablemd/workflow was the first package in this scope to be published, and neither registry's path survived it. JSR refused it for having no license: the vendored Cloudflare LICENSE satisfies Deno's license-file search, so the workspace-root LICENSE was never pulled into the tarball and JSR, which reads only the root, saw none. workflow and cli now declare the license in deno.json. npm's half is components/BootstrapNpmPackage.md, which could not get past its own pre-flight: `npm trust list` needs a one-time password even to answer, and the document read it before asking for one. The EOTP that came back was classified `unreadable`, which shared a branch with `conflicting`, so every run reported a trusted publisher that was not there. The read now happens after the code, replacing the post-prompt re-read it made redundant, and `unreadable` reports what npm said. A run that published then reported failure, because npm accepts a publish before its package reads report it and the confirming read called that 404 `absent`. The confirming reads now wait for the registry to carry the package, and reads still behind after the wait say so. Both writes moved from `silent exec` to `exec as="…"` with failure branches, so a rejected publish is reported in npm's own words instead of a bare exit code.
PR #473: 📦 Let a first release publish on both registries5 files, +422 / -159 Scope🟡 581 lines changed. PRs under 400 receive more thorough review. 🟡 Large PR with no linked issue. Structural✅ No structural bloat detected. Slop✅ Slop indicators look low. Static Analysis✅ Oxlint found no issues. CorrectnessNo extraneous code patterns detected. |
No read decides whether to publish any more. A read is a claim about a moment that has passed, and npm accepts a write before its package reads report it — which is how a completed run reported itself failed, and how the re-run that followed planned a publish for a version that already existed. The placeholder is now always offered and npm's answer settles it. npm 11.17 asks the registry for the package's versions with preferOnline before uploading and refuses over one it already carries, with an uncoded error carrying "You cannot publish over the previously published versions: <version>" (lib/commands/publish.js:175); a registry staler than that check refuses the upload instead, as EPUBLISHCONFLICT. Either is the answer "already reserved", from the party that decides it. That removes the readiness wait, the fetch it probed with, the confirming package reads, and the branch that chose between publishing and skipping. The two reads that remain are guards: a package carrying real versions is not one to offer a placeholder to, and neither read selects what happens next. The trust half stays read-first. npm's trust command adds no mapping for an existing configuration, so its failure shape could not be established without attempting one against a live package — and the read is also the only thing that can tell your configuration from someone else's, which is the refusal this document exists for. Also corrects the OIDC paragraph: it is the package that cannot be created from CI, not the repository.
taras
enabled auto-merge (squash)
August 18, 2026 03:16
4 tasks done
taras
added a commit
that referenced
this pull request
Aug 18, 2026
0.8.0's JSR half cannot be recovered by re-running its jobs: tag v0.8.0 points at f3d777d, which predates the license fix in #473, and the jsr job checks out the tag's own commit. npm carries all ten packages at 0.8.0; JSR is missing @executablemd/workflow and @executablemd/cli. Stamps every @executablemd manifest and restamps the matching workspace entries in bun.lock, which the bump task does not touch (release spec §2). packages/test-support stays at 0.0.0 — it is private and outside the lockstep. Co-authored-by: Taras Mankovski <74687+taras@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The 0.8.0 release failed to publish
@executablemd/workflowon both registriesand took
@executablemd/clidown with it(run 32087568416).
workflowwas the first package in this scope ever published, and neitherregistry's path survived a first publish.
npm was then bootstrapped by hand, which surfaced three more defects in
components/BootstrapNpmPackage.md— one that made it unusable for any package,and two that made a run that had worked report itself as failed.
What changes
JSR refused
@executablemd/workflowfor having no license.No package declares
license; they rely ondeno publishpulling theworkspace-root
LICENSEinto each member's tarball, which it does —/LICENSEis in the published manifests for
core,runtimeandweb.workflowis theexception: the vendored
vendor/cloudflare-computer-dofs/LICENSEalreadysatisfies Deno's license-file search, so the root file is never pulled in. The
tarball carried a LICENSE at a nested path, JSR reads only the root, and the
publish was rejected — aborting the workspace publish and skipping
cli.Before:
deno publishfails the whole workspace onworkflow.After:
workflowandclideclare"license": "MIT", which does not depend onfile discovery.
The document asked npm for the trusted publisher before it had the credential
to read it.
Before:
npm trust listran in the pre-flight, above the<Elicit>. npm needs aone-time password for that read — it makes the trusted publisher readable only to
someone who could change it — so the read exited
EOTP,classifyTrustcouldnot match
E404, andunreadableshared an<If>branch withconflicting.Every run, for every package, ended in a false "already trusts a publisher this
document did not set up" with an empty summary and an
npm trust revoke … --idcarrying no id.
After: the version reads still refuse a live package before any code is asked
for; the trust read moved below the
<Elicit>and carries the code, replacingthe post-prompt re-read it made redundant.
unreadablehas its own branch andrenders npm's own diagnostic.
A read decided whether to publish, and disagreed with reality in both
directions.
npm accepts a write before its package reads report it. So the confirming
npm viewstill answered 404 for the name just created,classifyReservationcorrectly called that
absent, and the guard turned it intois not reserved at 0.0.0-bootstrap.0 … : absent— after both writes had landed.The re-run then hit the same stale read, planned a publish for a version that
already existed, and npm's rejection was invisible because both writes were
silent exec, which routes stdout and stderr to hidden: bareCommand failed (exit 1).After: no read decides whether to publish. The placeholder is always offered and
npm's answer settles it — npm 11.17 asks the registry for the package's versions
with
preferOnlinebefore uploading and refuses over one it already carries(
lib/commands/publish.js:175), which is the same answer a read would havegiven, from the party that decides it. Both writes are
exec as="…"withexplicit failure branches, so a refusal is reported in npm's words.
How it works
The publish-conflict signal is npm's uncoded
ErrorcarryingYou cannot publish over the previously published versions: <version>, withEPUBLISHCONFLICTas the registry-side fallback when npm's own check was stale.Both are handled and the reasoning is recorded next to
classifyPublish.Review guide
Start with:
components/BootstrapNpmPackage.md,## Reserving the nameThen review:
specs/release-process-spec.md§6 — the ordering guarantees that changedclassifyPublish, and the three branches it selects## Reading the trusted publisher— the moved read and theunreadablebranchscripts/tests/bootstrap-npm-package.test.tsLook carefully at:
What must stay true
A refusal writes nothing— the trust read is sequenced beforenpm publish,checked by the three
refuses a trusted publisher with …cases.A placeholder is never offered to a live package— enforced by the versionreads before and after the prompt, checked by
refuses a package already published at another versionandrefuses a package that gained a foreign version.The code reaches the authenticated commands and nowhere else— checked bycarries one code to the authenticated commands and nowhere else, which alsoasserts the code never reaches rendered output.
npm's own words survive a failure— checked byreports npm's own error when it will not say what the package trustsandreports a publish npm refused for some other reason.How to verify it
takes npm's refusal over an existing version as the reservationproves astale read no longer produces a false failure, and fails if the conflict is
read as an error.
confirms a publish it made without reading the package backrigsnpm viewto never report the package and asserts no
viewcall occurs after thepublish — it fails if any read-back is reintroduced.
reports a publish npm refused for some other reasonproves a non-conflictrefusal is still a failure, and fails if
classifyPublishover-matches.fails 10 of the 43 steps.
Scope
Included
licenseonpackages/workflowandpackages/clispecs/release-process-spec.md§6Intentionally unchanged
npm's trust command adds no mapping foran existing configuration (
lib/commands/trust/,lib/trust-cmd.js), so itsfailure shape could not be established without attempting a second
configuration against a live package. The read is also the only thing that
distinguishes our configuration from a foreign one, which is the refusal the
document exists for.
licenseand still rely on the rootLICENSEreaching their tarballs, which works — only a package vendoring itsown LICENSE is affected.
0.0.0-bootstrap.0, thebootstrapdist-tag, and §4's trusted-publisher values.Generated or mechanical changes
packages/workflow/deno.jsonandpackages/cli/deno.jsonadd one field each.Risks and limitations
idempotent, so a code expiring partway is recovered by re-running.
in a window this document cannot see would be offered a placeholder. npm's own
preferOnlinecheck catches the exact version but not that case; the guardsnarrow it, they do not close it.
is finished by re-running.
Scope confirmation