Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/dep-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ on:
TOOL is a semver artifact, so a behaviour change is a version bump somebody reviewed.
The pin lives here rather than in eleven callers.
type: string
default: '0.11.0'
default: '0.11.1'
run-floor-check:
description: |
Also run the suite against the BOTTOM of every declared sibling range, not only
Expand Down
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]


## [0.11.1] - 2026-09-07

### Fixed

- **A `workspace:` range no longer reads as a moved contract (usetheokit/theokit#659).** 0.11.0
compared the workspace manifest against the published one key by key, and `pnpm pack` rewrites
`workspace:^` to the version it resolves to — so the two differ on that key in EVERY package that
depends on a sibling. `contractMoved` was therefore true almost everywhere, handing a local tarball
to nearly every sibling and undoing the narrowness the comparison exists to keep. Measured on the
`usetheokit/theokit` workspace, where three packages were substituted with no contract having moved.

A local protocol (`workspace:`, `link:`, `file:`, `portal:`) is a placeholder rather than a
contract, and whether the version it resolves to is published is already the question the
unpublished branch answers. Those keys are skipped.

0.11.0 is published and should not be used: its substitution is broad where it was meant to be
narrow. The failure it causes is a weaker check, never a false block.
## [0.11.0] - 2026-09-07

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion packages/dep-check/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@theokit/dep-check",
"version": "0.11.0",
"version": "0.11.1",
"description": "The ecosystem dependency gate: does a package's declared range still describe the sibling it ships against? Four checks, kept apart by what they need to answer and therefore by whether they may fail a build.",
"type": "module",
"engines": {
Expand Down
11 changes: 10 additions & 1 deletion packages/dep-check/src/checks.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,16 @@ function contractMoved(local, publishedManifest) {

function sameRanges(a = {}, b = {}) {
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
for (const k of keys) if (a[k] !== b[k]) return false;
for (const k of keys) {
// A local protocol is a PLACEHOLDER, not a contract. `pnpm pack` rewrites `workspace:^` to the
// version it resolves to, so the workspace manifest and the published one differ on that key in
// every package that depends on a sibling. Comparing them raw made `contractMoved` true almost
// everywhere, which hands a local tarball to almost everything and undoes the narrowness this
// comparison exists to keep. Whether the resolved version is published is already the question
// the branch above answers.
if (LOCAL_PROTOCOL.test(a[k] ?? "") || LOCAL_PROTOCOL.test(b[k] ?? "")) continue;
if (a[k] !== b[k]) return false;
}
return true;
}

Expand Down
21 changes: 21 additions & 0 deletions packages/dep-check/test/checks.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,27 @@ describe("unpublishedSiblings — what check D cannot get from the registry yet"
expect(out[0].reason).toBe("contract-moved");
});

it("test_a_workspace_protocol_range_is_not_a_moved_contract", () => {
// The workspace manifest says `workspace:^` and the published one says the version pnpm
// rewrote it to at pack time. Comparing those two raw strings finds a difference in EVERY
// package that depends on a sibling, which would hand a local tarball to almost everything and
// undo the narrowness the test above protects. A local protocol is a placeholder, not a
// contract — the version it resolves to is already the `unpublished` branch's job.
const ws = [
{
name: "@theokit/agents",
version: "13.0.0",
dir: "/w/packages/agents",
manifest: { dependencies: { "@theokit/presenter": "workspace:^", "@theokit/sdk": "^5.0.0" } },
},
];
const pub = { "@theokit/agents": ["13.0.0"] };
const manifests = {
"@theokit/agents": { "13.0.0": { dependencies: { "@theokit/presenter": "0.9.0", "@theokit/sdk": "^5.0.0" } } },
};
expect(unpublishedSiblings({ references: [{ dep: "@theokit/agents" }], workspace: ws, published: pub, manifests })).toEqual([]);
});

it("test_leaves_a_published_version_alone_when_the_local_contract_is_identical", () => {
// The narrowness is the point (#659). Substituting whenever a package is in the workspace would
// stop testing what a consumer resolves — the reason the skip exists at all. Only a contract
Expand Down