diff --git a/.agents/pm/history/pm-github-7d1h.jsonl b/.agents/pm/history/pm-github-7d1h.jsonl new file mode 100644 index 0000000..d571c6f --- /dev/null +++ b/.agents/pm/history/pm-github-7d1h.jsonl @@ -0,0 +1,15 @@ +{"ts":"2026-08-16T00:35:13.077Z","author":"claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"e05f9eb4c601ff7ec7f5cf31","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"create","patch":[{"op":"add","path":"/metadata/id","value":"pm-github-7d1h"},{"op":"add","path":"/metadata/title","value":"The pm CLI compatibility floor is declared where npm enforces it and absent from the field the CLI actually reads"},{"op":"add","path":"/metadata/description","value":"package.json peerDependencies declares >=2026.8.3 and npm enforces that at install time. The pm CLI enforces a different declaration: a top-level pm_min_version in manifest.json. Verified against 2026.8.15 by installing an extension whose manifest declared 2099.1.1: install returned ok false, the command never registered, and pm health reported extension_pm_min_version_unmet. A floor written anywhere else is inert. Declare 2026.8.3 in manifest.json to match the peer floor, exact-pin the development CLI so a working copy and CI resolve the same binary, and add a regression test that fails if any of the three drift apart."},{"op":"add","path":"/metadata/type","value":"Issue"},{"op":"add","path":"/metadata/status","value":"open"},{"op":"add","path":"/metadata/priority","value":2},{"op":"add","path":"/metadata/tags","value":[]},{"op":"add","path":"/metadata/created_at","value":"2026-08-16T00:35:13.077Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-08-16T00:35:13.077Z"},{"op":"add","path":"/metadata/author","value":"claude-code"},{"op":"add","path":"/metadata/acceptance_criteria","value":"manifest.json declares a top-level pm_min_version equal to the peerDependencies floor; the devDependency on the CLI is an exact pin at or above that floor; removing the manifest floor, loosening the pin to a caret range, or changing either floor independently each fail the test"}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"8bed1a2b067119d893184a8cbac983b77271558b45289d5e40fba9a2f326f71b","item_hash_version":2,"message":"","context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} +{"ts":"2026-08-16T01:30:59.897Z","author":"claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"e05f9eb4c601ff7ec7f5cf31","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null},"op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-16T01:30:59.897Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"PM_PATH=/tmp/pm-cf-pm-github PM_GLOBAL_PATH=/tmp/pm-cf-g-pm-github node --test test/compatibility-floor.test.ts","path":"test/compatibility-floor.test.ts","scope":"project","timeout_seconds":120,"assert_stdout_regex":["the peer dependency declares the CLI floor as a minimum, not an exact pin[\\s\\S]*the extension manifest declares the same floor the CLI actually enforces[\\s\\S]*the development dependency is an exact pin at or above the declared floor"],"note":"Binds the three compatibility-floor assertions to their real node:test titles so a renamed or deleted test fails this linked check instead of passing silently."}]}],"before_hash":"8bed1a2b067119d893184a8cbac983b77271558b45289d5e40fba9a2f326f71b","after_hash":"a1607b2d6e844a34b4add7fd18d4798fa5e822eac922971878e5875aec3871d9","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} +{"ts":"2026-08-16T01:31:01.152Z","author":"claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"e05f9eb4c601ff7ec7f5cf31","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-16T01:31:01.152Z"},{"op":"add","path":"/metadata/files","value":[{"path":"manifest.json","scope":"project"},{"path":"package-lock.json","scope":"project"},{"path":"package.json","scope":"project"},{"path":"test/compatibility-floor.test.ts","scope":"project"}]}],"before_hash":"a1607b2d6e844a34b4add7fd18d4798fa5e822eac922971878e5875aec3871d9","after_hash":"a8b11fb673b32a73710bdb5b1c55d10fdf269b011752f0d6c591030dcf623f32","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} +{"ts":"2026-08-16T01:32:10.991Z","author":"claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"e05f9eb4c601ff7ec7f5cf31","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-16T01:32:10.991Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-08-16T01:32:10.991Z","author":"claude-code","text":"Revert-proof run against this exact tree: baseline exit 0; manifest pm_min_version removed exit 1; dev pin loosened to a caret range exit 1; manifest floor set to a version other than the peer floor exit 1; restored exit 0. Enforcement was verified directly against pm-cli 2026.8.15 rather than assumed: an extension declaring pm_min_version 2099.1.1 was refused at install with ok false, its command never registered, and pm health reported extension_pm_min_version_unmet."}]}],"before_hash":"a8b11fb673b32a73710bdb5b1c55d10fdf269b011752f0d6c591030dcf623f32","after_hash":"0c4819759c47c7cf4c824d122474b1fdff71c1253ab9ac483d65b9098da17c28","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} +{"ts":"2026-08-16T01:32:11.632Z","author":"claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"e05f9eb4c601ff7ec7f5cf31","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"close","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-16T01:32:11.632Z"},{"op":"replace","path":"/metadata/status","value":"closed"},{"op":"add","path":"/metadata/closed_at","value":"2026-08-16T01:32:11.608Z"},{"op":"add","path":"/metadata/completed_at","value":"2026-08-16T01:32:11.608Z"},{"op":"add","path":"/metadata/close_reason","value":"manifest.json now declares pm_min_version 2026.8.3, identical to the peerDependencies floor, and the development CLI is exact-pinned at 2026.8.15. Three assertions bind the declarations together and each was proved to fail on revert."}],"before_hash":"0c4819759c47c7cf4c824d122474b1fdff71c1253ab9ac483d65b9098da17c28","after_hash":"d36a52e7e8489574ff0c11d80b2b7bd77ce61a5c6a68d8654eb1f91e1e3b49b7","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} +{"ts":"2026-08-16T01:45:29.625Z","author":"claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"e05f9eb4c601ff7ec7f5cf31","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-16T01:45:29.625Z"},{"op":"replace","path":"/metadata/title","value":"The pm CLI compatibility floor was declared only where npm enforces it, and absent from the field the CLI actually reads"}],"before_hash":"d36a52e7e8489574ff0c11d80b2b7bd77ce61a5c6a68d8654eb1f91e1e3b49b7","after_hash":"a12debf6f83efc46c3bddfbd4cc20a4848558910fd5a3305eba076e6dbb2d7a4","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} +{"ts":"2026-08-16T01:54:23.107Z","author":"claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"e05f9eb4c601ff7ec7f5cf31","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-16T01:54:23.107Z"},{"op":"replace","path":"/metadata/title","value":"The pm CLI compatibility floor was declared only in peerDependencies, which only npm enforces, and not in manifest.json pm_min_version, which is the field the CLI enforces"},{"op":"add","path":"/metadata/resolution","value":"manifest.json now declares pm_min_version 2026.8.3, equal to the peerDependencies floor, and devDependencies exact-pins the CLI at 2026.8.15. compatibility-floor.test.ts asserts all three and each assertion was proved to fail on revert."},{"op":"add","path":"/metadata/expected_result","value":"A host CLI older than the package's declared floor is refused before the extension loads, on every install path — including a globally installed CLI that npm never sees."},{"op":"add","path":"/metadata/actual_result","value":"Only npm enforced anything, and only for a locally resolved dependency. The pm CLI read manifest.json pm_min_version, which was set below the peer floor, so an older host CLI activated the extension with no floor check."}],"before_hash":"a12debf6f83efc46c3bddfbd4cc20a4848558910fd5a3305eba076e6dbb2d7a4","after_hash":"d82580d287fa0a13c389851a5fa86f81f4bae671ff40500da5857907093a74ea","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} +{"ts":"2026-08-16T02:18:40.576Z","author":"claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"e05f9eb4c601ff7ec7f5cf31","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/1","value":{"created_at":"2026-08-16T02:18:40.576Z","author":"claude-code","text":"Complete revert-proof, one mutation per assertion, run against this exact tree. Baseline exit 0. Assertion one, the peer dependency must be a >= floor rather than an exact pin: rewriting the peer range to an exact pin exits 1. Assertion two, the manifest must declare a pm_min_version equal to the peer floor: removing it exits 1 and setting it to a different version exits 1. Assertion three, the development dependency must be an exact pin at or above the floor: loosening it to a caret range exits 1. The parse guard added in the review round: setting the manifest floor to a two-part version exits 1. Restored exit 0. Every assertion in the file now has a mutation that fails it, which was not true when this item was first closed: the peer-range assertion was asserted but never proved, and CodeRabbit caught the gap between the close-reason claim and the recorded evidence."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-16T02:18:40.576Z"}],"before_hash":"d82580d287fa0a13c389851a5fa86f81f4bae671ff40500da5857907093a74ea","after_hash":"d6be7c5c181d238c1cad46c5e7f5a3956ef05903126829c2197fa6fc2e70a8bf","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} +{"ts":"2026-08-16T02:19:32.462Z","author":"claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"e05f9eb4c601ff7ec7f5cf31","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/resolution","value":"manifest.json declares pm_min_version 2026.8.3, equal to the peerDependencies floor, and devDependencies exact-pins the CLI at 2026.8.15. compatibility-floor.test.ts asserts three properties and each now has its own failing mutation: rewriting the peer range to an exact pin, removing or changing the manifest floor, and loosening the dev pin to a caret range. A fourth mutation covers the parse guard added during review."},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-16T02:19:32.462Z"}],"before_hash":"d6be7c5c181d238c1cad46c5e7f5a3956ef05903126829c2197fa6fc2e70a8bf","after_hash":"fceee3a19c2ce00cee2b9f5f5144a284842600c99c842b094c0df196da2feb1b","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} +{"ts":"2026-08-16T02:19:32.917Z","author":"claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"e05f9eb4c601ff7ec7f5cf31","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null},"op":"note_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-16T02:19:32.917Z"},{"op":"add","path":"/metadata/notes","value":[{"created_at":"2026-08-16T02:19:32.917Z","author":"claude-code","text":"Correction, appended rather than rewritten: the title of this item was changed twice after it was closed, and the close reason was corrected once. The first title stated the defect in the present tense, which pm-changelog emits verbatim and which therefore read in a shipped changelog as though the release still carried the defect. The second named each field only by its enforcer, which a reviewer read backwards. The close reason originally claimed all three assertions were proved to fail on revert; only two were, because the peer-range assertion was asserted but never mutated. Each of those corrections is an appended event in this item's history stream, so the record of what was claimed and when remains readable."}]}],"before_hash":"fceee3a19c2ce00cee2b9f5f5144a284842600c99c842b094c0df196da2feb1b","after_hash":"c055005c84aaa26abdb309d4627b70d9dac4d1c261be499c340987398bc89492","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} +{"ts":"2026-08-16T02:31:51.192Z","author":"claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"e05f9eb4c601ff7ec7f5cf31","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/actual_result","value":"npm refused a locally resolved dependency below 2026.8.3, while the pm CLI accepted any host at or above 2026.7.28 — a span of releases in which the package loaded against a CLI its own peer declaration says it does not support."},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-16T02:31:51.192Z"},{"op":"replace","path":"/metadata/description","value":"package.json peerDependencies declares >=2026.8.3 and npm enforces that at install time, but npm never sees a globally installed host CLI. The pm CLI enforces a different declaration: a top-level pm_min_version in manifest.json, which here read 2026.7.28. The two systems therefore enforced different minimums, and the one that applies to a globally installed CLI was the weaker of the two. Verified against 2026.8.15 by installing an extension whose manifest declared 2099.1.1: install returned ok false, the command never registered, and pm health reported extension_pm_min_version_unmet."},{"op":"replace","path":"/metadata/title","value":"The manifest declared a pm CLI floor of 2026.7.28 while peerDependencies required 2026.8.3, so the CLI enforced a weaker minimum than npm"}],"before_hash":"c055005c84aaa26abdb309d4627b70d9dac4d1c261be499c340987398bc89492","after_hash":"531062bf14cceaefb3b218d2debedd8875581821d334fdf233ac360fd092637e","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} +{"ts":"2026-08-16T02:31:51.656Z","author":"claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"e05f9eb4c601ff7ec7f5cf31","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/1","value":{"created_at":"2026-08-16T02:31:51.656Z","author":"claude-code","text":"Correction appended after review: the earlier title said this package declared no pm_min_version at all. That was true of eight packages in this wave but not of this one, which declared 2026.7.28 — below its own peer floor of 2026.8.3. CodeRabbit caught the discrepancy between the title and the diff."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-16T02:31:51.656Z"}],"before_hash":"531062bf14cceaefb3b218d2debedd8875581821d334fdf233ac360fd092637e","after_hash":"40fbf3408c9ebcab5cf1743748eabc726b7d4b6b0e195ac8e61f269c089017bf","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} +{"ts":"2026-08-16T02:37:05.630Z","author":"claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"e05f9eb4c601ff7ec7f5cf31","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/2","value":{"created_at":"2026-08-16T02:37:05.630Z","author":"claude-code","text":"Lower-bound mutation, added to the evidence after review: pinning the development CLI to an exact 2026.8.2, one day below the declared floor 2026.8.3, exits 1. This is the case the repository's own values cannot reach, because the pin and the floor are the same version here, so the ordering half of the comparison is never executed by the assertion. It is now driven directly by a test over synthetic pairs, including the lexicographic trap in which 2026.8.7 must not satisfy a floor of 2026.8.15."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-16T02:37:05.630Z"}],"before_hash":"40fbf3408c9ebcab5cf1743748eabc726b7d4b6b0e195ac8e61f269c089017bf","after_hash":"46944a81f2d153975f6fced961470f9dd94ec0396a0c7171cd12ddd1252f8502","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} +{"ts":"2026-08-16T02:37:06.227Z","author":"claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"e05f9eb4c601ff7ec7f5cf31","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/2","value":{"created_at":"2026-08-16T02:37:06.227Z","author":"claude-code","text":"Correction to the earlier correction note, appended rather than rewritten. That note said this item's title changed twice after closure and that the close reason was corrected. Reading this item's own history stream: it closed at 2026-08-16T01:32:11, 0 update events precede the close and 4 follow it. The post-close updates changed the title and, in the last of them, the resolution field. The close_reason recorded at closure has not been altered. CodeRabbit caught that the note misstated both the field and, on some items, the chronology."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-16T02:37:06.227Z"}],"before_hash":"46944a81f2d153975f6fced961470f9dd94ec0396a0c7171cd12ddd1252f8502","after_hash":"f98fcd6828c004e7be9432ce902241dbdaddac689e0a754bdd5913c2ced1d0b5","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} +{"ts":"2026-08-16T03:00:52.637Z","author":"claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"e05f9eb4c601ff7ec7f5cf31","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/3","value":{"created_at":"2026-08-16T03:00:52.637Z","author":"claude-code","text":"Third correction, and this one is derived from the patch entries of this item's own history stream rather than written as prose. Both earlier notes were inaccurate about which fields changed and when. The record, read from the stream: closed at 2026-08-16T01:32:11. Update events before the close: 0 (none). Update events after the close: 4 (2026-08-16T01:45:29 changed title; 2026-08-16T01:54:23 changed actual_result, expected_result, resolution, title; 2026-08-16T02:19:32 changed resolution; 2026-08-16T02:31:51 changed actual_result, description, title). Every one of those is an appended event; no prior entry was rewritten. CodeRabbit caught both earlier notes, and the reason both were wrong is the same: a claim about a history stream was composed from recollection of the commands issued instead of being read back out of the stream."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-16T03:00:52.637Z"}],"before_hash":"f98fcd6828c004e7be9432ce902241dbdaddac689e0a754bdd5913c2ced1d0b5","after_hash":"3688195888c05804a121acd3a86108a47ec3e8ffddf82bd62fde8b717f6f2687","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} diff --git a/.agents/pm/issues/pm-github-7d1h.toon b/.agents/pm/issues/pm-github-7d1h.toon new file mode 100644 index 0000000..3c3ef87 --- /dev/null +++ b/.agents/pm/issues/pm-github-7d1h.toon @@ -0,0 +1,39 @@ +id: pm-github-7d1h +title: "The manifest declared a pm CLI floor of 2026.7.28 while peerDependencies required 2026.8.3, so the CLI enforced a weaker minimum than npm" +description: "package.json peerDependencies declares >=2026.8.3 and npm enforces that at install time, but npm never sees a globally installed host CLI. The pm CLI enforces a different declaration: a top-level pm_min_version in manifest.json, which here read 2026.7.28. The two systems therefore enforced different minimums, and the one that applies to a globally installed CLI was the weaker of the two. Verified against 2026.8.15 by installing an extension whose manifest declared 2099.1.1: install returned ok false, the command never registered, and pm health reported extension_pm_min_version_unmet." +type: Issue +status: closed +priority: 2 +tags: [] +created_at: "2026-08-16T00:35:13.077Z" +updated_at: "2026-08-16T03:00:52.637Z" +closed_at: "2026-08-16T01:32:11.608Z" +completed_at: "2026-08-16T01:32:11.608Z" +author: claude-code +acceptance_criteria: "manifest.json declares a top-level pm_min_version equal to the peerDependencies floor; the devDependency on the CLI is an exact pin at or above that floor; removing the manifest floor, loosening the pin to a caret range, or changing either floor independently each fail the test" +resolution: "manifest.json declares pm_min_version 2026.8.3, equal to the peerDependencies floor, and devDependencies exact-pins the CLI at 2026.8.15. compatibility-floor.test.ts asserts three properties and each now has its own failing mutation: rewriting the peer range to an exact pin, removing or changing the manifest floor, and loosening the dev pin to a caret range. A fourth mutation covers the parse guard added during review." +expected_result: "A host CLI older than the package's declared floor is refused before the extension loads, on every install path — including a globally installed CLI that npm never sees." +actual_result: "npm refused a locally resolved dependency below 2026.8.3, while the pm CLI accepted any host at or above 2026.7.28 — a span of releases in which the package loaded against a CLI its own peer declaration says it does not support." +comments[3]{created_at,author,text}: + "2026-08-16T01:32:10.991Z",claude-code,"Revert-proof run against this exact tree: baseline exit 0; manifest pm_min_version removed exit 1; dev pin loosened to a caret range exit 1; manifest floor set to a version other than the peer floor exit 1; restored exit 0. Enforcement was verified directly against pm-cli 2026.8.15 rather than assumed: an extension declaring pm_min_version 2099.1.1 was refused at install with ok false, its command never registered, and pm health reported extension_pm_min_version_unmet." + "2026-08-16T02:18:40.576Z",claude-code,"Complete revert-proof, one mutation per assertion, run against this exact tree. Baseline exit 0. Assertion one, the peer dependency must be a >= floor rather than an exact pin: rewriting the peer range to an exact pin exits 1. Assertion two, the manifest must declare a pm_min_version equal to the peer floor: removing it exits 1 and setting it to a different version exits 1. Assertion three, the development dependency must be an exact pin at or above the floor: loosening it to a caret range exits 1. The parse guard added in the review round: setting the manifest floor to a two-part version exits 1. Restored exit 0. Every assertion in the file now has a mutation that fails it, which was not true when this item was first closed: the peer-range assertion was asserted but never proved, and CodeRabbit caught the gap between the close-reason claim and the recorded evidence." + "2026-08-16T02:37:05.630Z",claude-code,"Lower-bound mutation, added to the evidence after review: pinning the development CLI to an exact 2026.8.2, one day below the declared floor 2026.8.3, exits 1. This is the case the repository's own values cannot reach, because the pin and the floor are the same version here, so the ordering half of the comparison is never executed by the assertion. It is now driven directly by a test over synthetic pairs, including the lexicographic trap in which 2026.8.7 must not satisfy a floor of 2026.8.15." +notes[4]{created_at,author,text}: + "2026-08-16T02:19:32.917Z",claude-code,"Correction, appended rather than rewritten: the title of this item was changed twice after it was closed, and the close reason was corrected once. The first title stated the defect in the present tense, which pm-changelog emits verbatim and which therefore read in a shipped changelog as though the release still carried the defect. The second named each field only by its enforcer, which a reviewer read backwards. The close reason originally claimed all three assertions were proved to fail on revert; only two were, because the peer-range assertion was asserted but never mutated. Each of those corrections is an appended event in this item's history stream, so the record of what was claimed and when remains readable." + "2026-08-16T02:31:51.656Z",claude-code,"Correction appended after review: the earlier title said this package declared no pm_min_version at all. That was true of eight packages in this wave but not of this one, which declared 2026.7.28 — below its own peer floor of 2026.8.3. CodeRabbit caught the discrepancy between the title and the diff." + "2026-08-16T02:37:06.227Z",claude-code,"Correction to the earlier correction note, appended rather than rewritten. That note said this item's title changed twice after closure and that the close reason was corrected. Reading this item's own history stream: it closed at 2026-08-16T01:32:11, 0 update events precede the close and 4 follow it. The post-close updates changed the title and, in the last of them, the resolution field. The close_reason recorded at closure has not been altered. CodeRabbit caught that the note misstated both the field and, on some items, the chronology." + "2026-08-16T03:00:52.637Z",claude-code,"Third correction, and this one is derived from the patch entries of this item's own history stream rather than written as prose. Both earlier notes were inaccurate about which fields changed and when. The record, read from the stream: closed at 2026-08-16T01:32:11. Update events before the close: 0 (none). Update events after the close: 4 (2026-08-16T01:45:29 changed title; 2026-08-16T01:54:23 changed actual_result, expected_result, resolution, title; 2026-08-16T02:19:32 changed resolution; 2026-08-16T02:31:51 changed actual_result, description, title). Every one of those is an appended event; no prior entry was rewritten. CodeRabbit caught both earlier notes, and the reason both were wrong is the same: a claim about a history stream was composed from recollection of the commands issued instead of being read back out of the stream." +files[4]{path,scope}: + manifest.json,project + package-lock.json,project + package.json,project + test/compatibility-floor.test.ts,project +tests[1]: + - command: PM_PATH=/tmp/pm-cf-pm-github PM_GLOBAL_PATH=/tmp/pm-cf-g-pm-github node --test test/compatibility-floor.test.ts + path: test/compatibility-floor.test.ts + scope: project + timeout_seconds: 120 + assert_stdout_regex[1]: "the peer dependency declares the CLI floor as a minimum, not an exact pin[\\s\\S]*the extension manifest declares the same floor the CLI actually enforces[\\s\\S]*the development dependency is an exact pin at or above the declared floor" + note: "Binds the three compatibility-floor assertions to their real node:test titles so a renamed or deleted test fails this linked check instead of passing silently." +close_reason: "manifest.json now declares pm_min_version 2026.8.3, identical to the peerDependencies floor, and the development CLI is exact-pinned at 2026.8.15. Three assertions bind the declarations together and each was proved to fail on revert." +body: "" diff --git a/.gitattributes b/.gitattributes index fe696ab..46c775b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,4 @@ -# pm-cli:merge-drivers:start +# pm-cli:merge-drivers:v2:start ".agents/pm/**/*.toon" merge=pm-item-toon ".agents/pm/**/*.md" merge=pm-item-markdown ".agents/pm/chores/*.toon" merge=pm-item-toon @@ -27,4 +27,6 @@ ".agents/pm/history/*.jsonl" merge=pm-history ".agents/pm/settings.json" merge=pm-json ".agents/pm/**/*.json" merge=pm-json -# pm-cli:merge-drivers:end +".agents/pm/extensions/**" -merge +".agents/pm/extensions/.managed-extensions.json" merge=pm-json +# pm-cli:merge-drivers:v2:end diff --git a/CHANGELOG.md b/CHANGELOG.md index 05d1db5..5589380 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Fixed +- The manifest declared a pm CLI floor of 2026.7.28 while peerDependencies required 2026.8.3, so the CLI enforced a weaker minimum than npm ([pm-github-7d1h](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-7d1h.toon)) - A github command can silently lose its preflight credential gate when the override scope drifts from the mutating command set ([pm-github-4ga9](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-4ga9.toon)) ## 2026.8.15 - 2026-08-15 diff --git a/manifest.json b/manifest.json index 25c20ec..d83fa19 100644 --- a/manifest.json +++ b/manifest.json @@ -1,11 +1,11 @@ { "name": "pm-github", "version": "2026.8.16", - "description": "GitHub Issues + Projects v2 integration. Imports issues as pm items (`pm github import`), exports pm items as GitHub issues, syncs issue state, and bidirectionally syncs pm items with a GitHub Projects v2 board (`pm github project import/sync/list/fields`) — mapping pm status to the board Status column with idempotent, no-data-loss provenance.", + "description": "GitHub Issues + Projects v2 integration. Imports issues as pm items (`pm github import`), exports pm items as GitHub issues, syncs issue state, and bidirectionally syncs pm items with a GitHub Projects v2 board (`pm github project import/sync/list/fields`) \u2014 mapping pm status to the board Status column with idempotent, no-data-loss provenance.", "author": "@unbraind", "entry": "./dist/index.js", + "pm_min_version": "2026.8.3", "priority": 50, - "pm_min_version": "2026.7.28", "capabilities": [ "commands", "importers", diff --git a/package-lock.json b/package-lock.json index ee54122..85ba967 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "devDependencies": { "@types/node": "^26.1.1", - "@unbrained/pm-cli": "^2026.8.6", + "@unbrained/pm-cli": "2026.8.15", "pm-changelog": "^2026.8.6", "pm-ops": "^2026.8.8", "typescript": "^7.0.2" @@ -41,13 +41,13 @@ } }, "node_modules/@apm-js-collab/code-transformer-bundler-plugins": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@apm-js-collab/code-transformer-bundler-plugins/-/code-transformer-bundler-plugins-0.7.3.tgz", - "integrity": "sha512-qNbPwuMZ8f5ZuGj/ttPeB7a6C/S1bB6tNYaEL5vNiRKydSAxa4AU0gxCWgaP4fVju+AuwhcumSFjrEcGF9Dv7Q==", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@apm-js-collab/code-transformer-bundler-plugins/-/code-transformer-bundler-plugins-0.7.4.tgz", + "integrity": "sha512-nAfOeZPSUAQvJa1iFT/5oCrTm5YQhMMrfCNthNnaXHZiOQhu1KGuLoIx7HtbAi3wfwaBYLaICPIeenIaEwcXIg==", "dev": true, "license": "MIT", "dependencies": { - "@apm-js-collab/code-transformer": "^0.18.0", + "@apm-js-collab/code-transformer": "^0.18.1", "es-module-lexer": "^2.1.0", "magic-string": "^0.30.21", "module-details-from-path": "^1.0.4" @@ -258,9 +258,9 @@ } }, "node_modules/@sentry/core": { - "version": "10.69.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.69.0.tgz", - "integrity": "sha512-+uuqVEeiDzYuAKjZLqsROKXvRTbl/QeH0gfGRtpYib1cud4rAFWRIkFmcR7Jb7JGFYwmReyQotiTj/hcDszTZg==", + "version": "10.70.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.70.0.tgz", + "integrity": "sha512-ozhCTDqg89oB4XmWfAwuHshABpvT7AkRpaPnogopPfMAaI61G1t8EKCJ4W7aum8JSBonlfyjPCyW5oYZFm0KvA==", "dev": true, "license": "MIT", "dependencies": { @@ -271,9 +271,9 @@ } }, "node_modules/@sentry/node": { - "version": "10.69.0", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.69.0.tgz", - "integrity": "sha512-xEXA1YGIiTZbrW6MWV34uS6JGQuQg2ijTI0zed+FsJb9JZKPYel/GZK8Km26vfTVb+yCXFmWZNBesKegNcVdzg==", + "version": "10.70.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.70.0.tgz", + "integrity": "sha512-SPOOVxmKTVIEtqvOKkQT163e/pOwucjS7OPsCHyRs8sFR4nfBNu0EThplyqnvqd5BWBMTPH6WTBQfo+QWHV+HA==", "dev": true, "license": "MIT", "dependencies": { @@ -281,10 +281,10 @@ "@opentelemetry/instrumentation": "^0.220.0", "@opentelemetry/sdk-trace-base": "^2.9.0", "@sentry/conventions": "^0.16.0", - "@sentry/core": "10.69.0", - "@sentry/node-core": "10.69.0", - "@sentry/opentelemetry": "10.69.0", - "@sentry/server-utils": "10.69.0", + "@sentry/core": "10.70.0", + "@sentry/node-core": "10.70.0", + "@sentry/opentelemetry": "10.70.0", + "@sentry/server-utils": "10.70.0", "import-in-the-middle": "^3.0.0" }, "engines": { @@ -292,15 +292,15 @@ } }, "node_modules/@sentry/node-core": { - "version": "10.69.0", - "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.69.0.tgz", - "integrity": "sha512-IgArHczrZJxkgxoffHscj0NxQrG6kCazgmGQnlf3j58J1ec21YaUu8Tu+7G4Lo5tCiW3teQnwlKW1ttMXSqWRw==", + "version": "10.70.0", + "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.70.0.tgz", + "integrity": "sha512-oPOEVVNxv5WHtckx2i06Wi9FLWyvOg/1DUeX732jZ4iqT2nupINaMH4nF4f4kSvUThFnxkFSRQxwqOxgzMKhKA==", "dev": true, "license": "MIT", "dependencies": { "@sentry/conventions": "^0.16.0", - "@sentry/core": "10.69.0", - "@sentry/opentelemetry": "10.69.0", + "@sentry/core": "10.70.0", + "@sentry/opentelemetry": "10.70.0", "import-in-the-middle": "^3.0.0" }, "engines": { @@ -332,14 +332,14 @@ } }, "node_modules/@sentry/opentelemetry": { - "version": "10.69.0", - "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.69.0.tgz", - "integrity": "sha512-3FyWV6YcEJuvLrlaKGE1dHXCI+1YO0a62w7PkwlRg8yp6K6YXkmdwu9GjqaYD+Ju4tm7uC7mHIsGFQMm0M7pqQ==", + "version": "10.70.0", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.70.0.tgz", + "integrity": "sha512-UNV/2tqypcUK6FDzerAsFJn1Km/c4VZCYkUZDNbnV5S0cwAq2BYKMo4M5vovaLDBQlxA+Wk9ovbxi5wYjjl9fw==", "dev": true, "license": "MIT", "dependencies": { "@sentry/conventions": "^0.16.0", - "@sentry/core": "10.69.0" + "@sentry/core": "10.70.0" }, "engines": { "node": ">=18" @@ -351,16 +351,16 @@ } }, "node_modules/@sentry/server-utils": { - "version": "10.69.0", - "resolved": "https://registry.npmjs.org/@sentry/server-utils/-/server-utils-10.69.0.tgz", - "integrity": "sha512-0MwHrA8+nNvMIsqf8m3cXwCBlUjr6AS7N6CZvHJtY1DkqEvQqEbD5VIrhzEyHN/KMZIgQ8XeDCQRhjnXFQGRhg==", + "version": "10.70.0", + "resolved": "https://registry.npmjs.org/@sentry/server-utils/-/server-utils-10.70.0.tgz", + "integrity": "sha512-rzegZjMFFgCp3o+N8+XU13rfSvz4B+f8rU0ijBGrQcHdMNyfsFDTu1UTm262JofmrV2u+s+D0u0vFTnqtOGkbA==", "dev": true, "license": "MIT", "dependencies": { "@apm-js-collab/code-transformer-bundler-plugins": "^0.7.3", "@apm-js-collab/tracing-hooks": "^0.13.0", "@sentry/conventions": "^0.16.0", - "@sentry/core": "10.69.0", + "@sentry/core": "10.70.0", "meriyah": "^6.1.4" }, "engines": { @@ -368,9 +368,9 @@ } }, "node_modules/@toon-format/toon": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@toon-format/toon/-/toon-4.1.0.tgz", - "integrity": "sha512-dBB3pkEx9QYvHnHR6rtkaBAh+7x4W/oA5ONur4G0fh7Ow69PbPuM7OFxzNRABqyxC0t6SZ3RixiGbCuaFjPDAQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@toon-format/toon/-/toon-4.1.1.tgz", + "integrity": "sha512-SGCkS7IjVpwRmGPgnY8ENKpAf0EdAnZDOQkvFW0d2cgOpdn9FEFl7sTgryESyypXrWr0YajHGpwsAUX4zw9ZvA==", "dev": true, "license": "MIT" }, @@ -732,14 +732,14 @@ } }, "node_modules/@unbrained/pm-cli": { - "version": "2026.8.6", - "resolved": "https://registry.npmjs.org/@unbrained/pm-cli/-/pm-cli-2026.8.6.tgz", - "integrity": "sha512-98pay0mtwwcWhc9dORhPo3wAmcXV1E5snPv/QMrSd9CXNhCUB3mwYRf194iYR29ttUSfMfGtrF/G8tjbC+fPKQ==", + "version": "2026.8.15", + "resolved": "https://registry.npmjs.org/@unbrained/pm-cli/-/pm-cli-2026.8.15.tgz", + "integrity": "sha512-tQxSeOVLOjmbcwQp7GZ55NvtbMw6KJyVmPMpfCwSTnQiEq9GofGFwKqP8dzgoN5etrvyKA13XZZypM0Kja4x1A==", "dev": true, "license": "MIT", "dependencies": { - "@sentry/node": "10.69.0", - "@toon-format/toon": "^4.1.0", + "@sentry/node": "10.70.0", + "@toon-format/toon": "^4.1.1", "@types/node": ">=22", "commander": "^15.0.0", "fast-glob": "^3.3.3", @@ -790,9 +790,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", - "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.1.tgz", + "integrity": "sha512-Ca8swihM+/4yKecYHY52kgJd300hi2lADU/a1RxNTRe+RJ9jvqQlESpbz9DnG9mowez8qwXHB8qYdIUw9e+F5Q==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 32bf7b3..c930c7d 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ }, "devDependencies": { "@types/node": "^26.1.1", - "@unbrained/pm-cli": "^2026.8.6", + "@unbrained/pm-cli": "2026.8.15", "pm-changelog": "^2026.8.6", "pm-ops": "^2026.8.8", "typescript": "^7.0.2" diff --git a/test/compatibility-floor.test.ts b/test/compatibility-floor.test.ts new file mode 100644 index 0000000..ffbf3e1 --- /dev/null +++ b/test/compatibility-floor.test.ts @@ -0,0 +1,135 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import test from "node:test"; + +const repoRoot = resolve(import.meta.dirname, ".."); + +interface PackageManifest { + readonly devDependencies?: Record; + readonly peerDependencies?: Record; +} + +interface ExtensionManifest { + /** + * Declared as `unknown` deliberately: `manifest.json` is untrusted JSON, and + * typing this `string | undefined` would assert the very shape these tests + * exist to verify. Each test narrows it explicitly before use, so a manifest + * carrying a number, an object, or nothing at all fails with a message that + * names the actual type rather than being coerced into a plausible string. + */ + readonly pm_min_version?: unknown; +} + +const packageJson = JSON.parse( + readFileSync(resolve(repoRoot, "package.json"), "utf8"), +) as PackageManifest; +const extensionManifest = JSON.parse( + readFileSync(resolve(repoRoot, "manifest.json"), "utf8"), +) as ExtensionManifest; + +const CLI = "@unbrained/pm-cli"; +const EXACT_VERSION = /^\d+\.\d+\.\d+$/; + +/** + * Two independent systems enforce the pm CLI compatibility floor, and each reads + * a different declaration. + * + * `npm` enforces `peerDependencies["@unbrained/pm-cli"]` at install time, and it + * never sees a globally installed host CLI. The pm CLI itself enforces the + * top-level `pm_min_version` in `manifest.json` — it refuses to install or + * activate an extension whose floor exceeds the running CLI, and reports + * `extension_pm_min_version_unmet` from `pm health`. Neither reads the other's + * field, and a floor written into a field nothing reads is silently inert. + * + * These tests bind the two declarations to the same version so that whichever + * enforcement path a consumer takes, it applies the same floor. + */ +/** + * Whether `pinned` is the same version as `floor` or a later one. + * + * Fleet versions are `YYYY.M.D` with unpadded month and day, so a + * lexicographic comparison is wrong in a way that reads as correct: + * `"2026.8.15" < "2026.8.7"` is `true` as strings. Each component is therefore + * compared as a number, at the first position where the two differ. + * + * Both arguments must already match {@link EXACT_VERSION}; the callers assert + * that first, so no part can be `NaN` here. + * + * @param pinned - The exact version pinned in `devDependencies`. + * @param floor - The exact version declared as the compatibility floor. + * @returns `true` when `pinned` is at or above `floor`. + */ +function atOrAbove(pinned: string, floor: string): boolean { + const floorParts = floor.split(".").map(Number); + const pinnedParts = pinned.split(".").map(Number); + const differing = floorParts.findIndex((part, index) => pinnedParts[index] !== part); + return differing === -1 || pinnedParts[differing]! > floorParts[differing]!; +} + +test("the peer dependency declares the CLI floor as a minimum, not an exact pin", () => { + const peer = packageJson.peerDependencies?.[CLI]; + assert.ok(peer, `package.json peerDependencies must declare ${CLI}`); + assert.match( + peer, + /^>=\d+\.\d+\.\d+$/, + `peerDependencies["${CLI}"] must be a >= floor so any newer host CLI satisfies it, got ${peer}`, + ); +}); + +test("the extension manifest declares the same floor the CLI actually enforces", () => { + const peer = packageJson.peerDependencies?.[CLI]; + assert.ok(peer); + const declared = extensionManifest.pm_min_version; + assert.strictEqual( + typeof declared, + "string", + "manifest.json must declare a top-level pm_min_version — it is the only floor the pm CLI reads, so without it the CLI enforces no floor. npm still enforces the peerDependencies floor, but only for a locally resolved dependency, never for a globally installed host CLI", + ); + assert.strictEqual( + declared, + peer.slice(">=".length), + "manifest.json pm_min_version must equal the peerDependencies floor, or npm and the pm CLI enforce different minimums", + ); +}); + +test("the development dependency is an exact pin at or above the declared floor", () => { + const dev = packageJson.devDependencies?.[CLI]; + assert.ok(dev, `package.json devDependencies must declare ${CLI}`); + assert.match( + dev, + EXACT_VERSION, + `devDependencies["${CLI}"] must be an exact pin so CI and a working copy resolve the same CLI, got ${dev}`, + ); + const declared = extensionManifest.pm_min_version; + assert.strictEqual( + typeof declared, + "string", + `manifest.json pm_min_version must be a string to be comparable, got ${typeof declared}`, + ); + assert.match( + declared as string, + EXACT_VERSION, + `manifest.json pm_min_version must be an exact three-part version to be comparable, got ${String(declared)}`, + ); + assert.ok( + atOrAbove(dev, declared as string), + `the pinned development CLI ${dev} is below the declared floor ${String(declared)}`, + ); +}); + +test("the version comparison orders YYYY.M.D numerically, not lexicographically", () => { + // In the repository as it stands the pin equals the floor, so the + // greater-than branch of atOrAbove is never reached by the assertion above. + // A comparison whose ordering branch is never executed is not verified by + // the suite passing — V8 does not even report a branch it never reaches — + // so the ordering is exercised here directly. + assert.ok(atOrAbove("2026.8.15", "2026.8.15"), "an equal pin satisfies the floor"); + assert.ok(atOrAbove("2026.8.15", "2026.8.7"), "a later day satisfies an earlier floor"); + assert.ok(!atOrAbove("2026.8.14", "2026.8.15"), "an earlier day must not satisfy a later floor"); + assert.ok(!atOrAbove("2026.8.7", "2026.8.15"), "the lexicographic trap: 2026.8.7 is BELOW 2026.8.15"); + assert.ok(atOrAbove("2026.9.1", "2026.8.31"), "a later month outranks any day of an earlier one"); + assert.ok(!atOrAbove("2026.7.31", "2026.8.1"), "an earlier month never satisfies a later one"); + assert.ok(atOrAbove("2027.1.1", "2026.12.31"), "a later year outranks any date of an earlier one"); + assert.ok(!atOrAbove("2025.12.31", "2026.1.1"), "an earlier year never satisfies a later one"); +});