bun run release:publish -- --dry-run is the last step of the local release simulation in docs/RELEASING.md. As of #103 it fails, and it will fail on every lockstep release from now on.
npm notice name: @adrkit/spec-kit
npm notice version: 0.1.2
npm error You cannot publish over the previously published versions: 0.1.2.
error: Publishing @adrkit/spec-kit@0.1.2 failed with exit 1
Why
publishRelease gates the registry idempotency check behind !dryRun (scripts/release-publish.ts:134-141):
for (const artifact of manifest.artifacts) {
if (!dryRun) {
const integrity = await existingIntegrity(artifact);
if (!shouldPublishArtifact(artifact, integrity)) {
console.log(`release-publish: ${artifact.name}@${artifact.version} already matches; skipping`);
continue;
}
}
await npmPublish(artifact, dryRun);
}
Independently versioned adapters ride along in a lockstep pack at their own versions, so a v* manifest carries @adrkit/spec-kit@0.1.2 alongside the four packages at 0.4.0. The real run consults the registry, finds the version already published with matching integrity, and skips it. The dry run never asks, so it hands npm a tarball for a version that exists.
This is not a defect in the release itself. The packed tarball is byte-identical to the published one:
packed: sha512-63jDIb5zYogVOb9Z4nDU+jgyL28UBUs0GPvHhz1Ec1eCvGClXhxSAz2La6hzBprccnHISeHRqbVFR9N+yH0LVA==
registry: sha512-63jDIb5zYogVOb9Z4nDU+jgyL28UBUs0GPvHhz1Ec1eCvGClXhxSAz2La6hzBprccnHISeHRqbVFR9N+yH0LVA==
It went unnoticed because v0.3.0 (2026-07-30) predates spec-kit-v0.1.0 (2026-08-02). v0.4.0 is the first lockstep release since an independently versioned adapter existed.
The cost of leaving it
The simulation exists so a maintainer can see the publish path before tagging. Right now it aborts on the one artifact that will not be published, and a maintainer who reads that as a real failure either stops a good release or learns to ignore a red step. The four lockstep dry-runs do pass first, but only because spec-kit sorts last in the manifest; an adapter added earlier in release-pack.ts's definitions would abort before any of them ran, and the simulation would cover nothing at all while still looking like it ran.
Fix
Move the idempotency check outside the !dryRun guard so the dry run takes the same branch the real run takes, logging already matches; skipping instead of attempting a publish. existingIntegrity is a plain registry fetch, so a dry run gains no privilege from it.
Per ADR-0016, scripts/release-publish.test.ts needs a case that fails against today's gating before the fix counts as covered: a dry run over a manifest containing an already-published artifact should skip it, and that assertion should be observed failing on the current if (!dryRun).
Deliberately not fixed inside #103 — a release cut is the wrong commit to change release tooling in.
bun run release:publish -- --dry-runis the last step of the local release simulation indocs/RELEASING.md. As of #103 it fails, and it will fail on every lockstep release from now on.Why
publishReleasegates the registry idempotency check behind!dryRun(scripts/release-publish.ts:134-141):Independently versioned adapters ride along in a lockstep pack at their own versions, so a
v*manifest carries@adrkit/spec-kit@0.1.2alongside the four packages at 0.4.0. The real run consults the registry, finds the version already published with matching integrity, and skips it. The dry run never asks, so it hands npm a tarball for a version that exists.This is not a defect in the release itself. The packed tarball is byte-identical to the published one:
It went unnoticed because
v0.3.0(2026-07-30) predatesspec-kit-v0.1.0(2026-08-02). v0.4.0 is the first lockstep release since an independently versioned adapter existed.The cost of leaving it
The simulation exists so a maintainer can see the publish path before tagging. Right now it aborts on the one artifact that will not be published, and a maintainer who reads that as a real failure either stops a good release or learns to ignore a red step. The four lockstep dry-runs do pass first, but only because spec-kit sorts last in the manifest; an adapter added earlier in
release-pack.ts's definitions would abort before any of them ran, and the simulation would cover nothing at all while still looking like it ran.Fix
Move the idempotency check outside the
!dryRunguard so the dry run takes the same branch the real run takes, loggingalready matches; skippinginstead of attempting a publish.existingIntegrityis a plain registryfetch, so a dry run gains no privilege from it.Per ADR-0016,
scripts/release-publish.test.tsneeds a case that fails against today's gating before the fix counts as covered: a dry run over a manifest containing an already-published artifact should skip it, and that assertion should be observed failing on the currentif (!dryRun).Deliberately not fixed inside #103 — a release cut is the wrong commit to change release tooling in.