Skip to content

Run the Node floor the package promises, instead of naming it - #581

Merged
MongLong0214 merged 12 commits into
mainfrom
fix-residuals-081
Aug 13, 2026
Merged

Run the Node floor the package promises, instead of naming it#581
MongLong0214 merged 12 commits into
mainfrom
fix-residuals-081

Conversation

@MongLong0214

Copy link
Copy Markdown
Owner

The final review's remaining cosmetic findings, plus one that turned out not to be cosmetic.

The floor was never executed. node: [22] resolves to the newest 22.x — 22.23 at the time of writing — so the declared floor of >=22.12.0 had never been the version any CI job ran. test/install-script.test.ts asserts that the installers refuse 22.11 and accept 22.12, but a version passing a comparison is not the same as the suite passing on it, and the second is what a floor claims. It is pinned exactly now.

That renames the check run to check (22.12.0), so the release gate's REQUIRED_CHECKS and main's required contexts were updated with this change rather than after it — check (22) would otherwise be a required context that can never appear again, blocking every merge.

Three statements that had drifted out of true:

  • scripts/check-engines.mjs opened by saying the floor is Node 20. It reads engines.node and has for a while.
  • validate --help listed exit codes 0/1/2. It returns 3 for a missing shipped file — the code that tells an unattended caller the message was never examined.
  • docs/RELEASE-GATE.md listed six required checks. There are ten, they must come from the github-actions app, and lint is excluded because its job only runs on pull_request. The doc now points at REQUIRED_CHECKS instead of restating it, since restating it is how it went stale.

Verified: check-engines.mjs exits 0 reporting 22.12.0; the workflow yaml parses with node: ['22.12.0', 24]; 55/55 across both release-gate test files including the two that compare the required list against ci.yml in both directions; main's protection reads back 11 contexts under the new name.

`node: [22]` asks setup-node for the newest 22.x — 22.23 while this was
written. The declared floor is 22.12.0, so no job had ever executed it. The
installers refuse 22.11 and accept 22.12 in `test/install-script.test.ts`, but
a version being admitted by a comparison is not the same as a suite passing on
it, and the second is what the floor claims. It is pinned exactly now.

The check run is therefore named `check (22.12.0)`, which the release gate and
main's required contexts both had to learn. Both were updated with this commit
rather than after it: `check (22)` would otherwise be a required context that
can never appear again, which blocks every merge rather than gating one.

Two things that had drifted into saying something untrue:

- `scripts/check-engines.mjs` opened by stating the floor is Node 20. It has
  read `engines.node` for a while; now the comment says so instead of
  repeating a number that can go stale, which is the mistake it just made.
- `validate --help` listed exit codes 0, 1 and 2. It returns 3 for a missing
  shipped file, which is the code that tells an unattended caller the message
  was never examined — the one worth documenting most.
- `docs/RELEASE-GATE.md` listed six required checks. There are ten, they must
  come from the `github-actions` app, and `lint` is excluded for a reason worth
  writing down. It now points at `REQUIRED_CHECKS` as the source rather than
  restating it, since restating it is how it went stale.

Verified: `node scripts/check-engines.mjs` exits 0 reporting the floor as
  22.12.0; the yaml parses with `node: ['22.12.0', 24]`; 55/55 across both
  release-gate test files, including the two that compare the required list
  against `ci.yml`; main's protection reads back 11 contexts with the new name
Evidence: .github/workflows/ci.yml
Evidence: scripts/check-exact-head-ci.mjs
Evidence: docs/RELEASE-GATE.md
Follows: r-3f6d09
Blast: system
Undo: easy
Certainty: firm
Record-Id: r-1d4c72
Provenance: authored
CommitLore-Version: 2.0.0
@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown

CommitLore — record lint

Trailers: clean — 12 commits in origin/main..c670e0d3befcdd52b432fcce39cae2be3cf1e787
Active constraints: 286 limits · 587 ruled-out · 156 warnings — from 353 records over 103 changed paths

Active constraints for the paths this PR touches

Limits (286)

  • r-assertfile1 8a859c6 — this checks the report validate produced; it does not re-derive the range, so a range that silently covered nothing would pass here
  • r-answerown1 3547382warn distinguishes ours from not-ours by the command string, and does not execute anything -- a wrapper that really is a CommitLore server still reads as unverified, which is the safe direction but not a probe
  • r-pretag01 86e0153registers_commitlore reads the key, so a config that registers under a different key -- a host with its own naming -- still reads as unregistered and is wired again
  • r-dogfoodref1 f605dbb — this asserts the two checks validate performs; a third class added later is not required by name until somebody adds it here
  • r-mslquote1 5b23c44 — this is the second quoting layer in this step; a third -- a value with a double quote in it -- would need a different mechanism than more escaping
  • r-engfloor01 fe83524 — the parser covers the range shapes npm packages actually publish -- comparators like >=22 <23, and pre-release identifiers, are read by their first version and not by their bounds
  • r-readyhosts1 9db3c4d — the new jobs establish that an install runs and answers on those hosts, not that every command behaves identically there
  • r-dropfake01 06961d3 — the runtime's presence proves this installer wrote the directory, not that its contents are unmodified since
  • r-secondcopy1 01ebee5 — the budget bounds the two scans, not the command -- process startup, path resolution and rendering still sit outside it
  • r-staleclaim1 59cb5d9 — withholding uses the same pattern table as every other route, so a payload that trips nothing still passes; this closes a route that had no grading at all, not the heuristic behind it
  • r-snapshome1 e339cba — this normalises the two spellings this report produces; a third form -- a symlinked home, a UNC path -- would need its own
  • r-nodefloor1 f4c924f — this bounds the version, not the feature -- a Node that ships node:sqlite behind a flag, or removes it, is not detected here
  • r-codexunver1 980d747 — presence is read from the marketplace name, so a Codex that reports neither a source nor a listing this can parse is treated as absent and one is added under a name that may already be taken
  • r-release081 ffe702a — the capture half reaches a host that surfaces MCP instructions; one that ignores that field still needs --agents-md, and nothing detects which kind a host is
  • r-mcpproc01 db1363d — this establishes that a host which surfaces MCP instructions can capture without a skill; a host that ignores that field still needs --agents-md, and nothing here detects which kind a host is
  • r-observed01 43cfa5e — existence is not identity -- a path that resolves to something other than this tool still reads as a working registration, which is doctor reports a registered MCP command as working without establishing its identity #572
  • r-ownsemver1 fcc6e4a — the evidence is a directory this installer wrote, so an install whose data directory was deleted is now refused rather than upgraded -- a refusal naming the file, against silently destroying it
  • r-ceiling01 543453b — this bounds the scan and not the command -- process startup, path resolution and rendering are outside it, so a repository whose single cheap pass is slower than the budget still exceeds it by that much
  • r-clocktest1 6ac2b44 — the injected clock proves the loop stops and resumes correctly; it does not measure that a real budget corresponds to any particular wall-clock pause, which stays a measurement rather than a test
  • r-saywhat01 56444db — entailment is still unchecked, and this narrows the claim rather than closing the gap -- the protection remains that no drafted record is ever delivered as a directive
  • r-boundverify1 9f2c12e — the phase check makes a refused store visible; it does not let a caller re-verify a transaction that has moved on, which stays a matter of preparing a new one
  • r-bynottname1 8beaa6d — an entry whose command is launchable but wrong still counts as a registration; the check establishes that a host could start something, not that what it starts is this tool
  • r-hookbudget1 e09014c — the budget bounds the wait, not the answer -- a repository large enough to trip it keeps getting a partial view until somebody runs init, and the notice is the only thing that makes that visible
  • r-winstall1 0b4e551 — this pins what the repository says about itself, and cannot check that the tag it names has been published -- the install gate does that, after the tag exists
  • r-scanall1 62a6fbf — the scanner remains a heuristic, so this closes the exemption and not the gap behind it -- a payload that trips no pattern still reaches the agent
  • r-wrongtag1 99667f3 — PowerShell cannot be run here, so this is reasoned from the shared design and proven against the shell twin; only the windows-latest job is evidence for install.ps1
  • r-vbind001 2c88d24 — this binds the requested tag to the runtime that answers, not the tag to its content -- a tag moved after publication installs whatever it now points at, which is a signing question rather than a version-binding one
  • r-structk1 a7bee10 — this closes the exemption, not the heuristic behind it -- a payload that trips no pattern still passes, and the scanner remains a speed bump rather than a boundary
  • r-ownfail01 8de1326doctor can say the setting is unreadable and cannot say what its author meant by it, so the repository is held to the stronger mode until a person decides
  • r-expwall01 e7ddd92 — the cache cannot notice an expiry that falls between two reads inside the same day -- a record expiring at noon is still delivered until the day rolls over, which is the granularity the determinism is bought with
  • r-codexerr1 a6d0fab — the first line of Codex's output is not always the cause -- a wrapper that prints a banner before its error will have the banner reported instead, and the full output is still only visible by running the command directly
  • r-insttxn1 afb7bfb — this establishes that the installed tree is complete and its commands run on this machine at this moment -- not that the machine will still have a working node tomorrow, and not that any agent host will load what was installed
  • r-authdir01 ae2a66f — in the default mode a directive establishes that the commit's author string matched a configured one, and nothing about who produced the commit
  • r-authdir01 ae2a66f — in signature mode a verified signature establishes that a key the verifier trusts signed this commit -- not that its holder has authority over this repository, and not that the record's content is true or safe
  • r-mcpdir01 a9886b5 — neither route can tell a caller whether the trusted-author configuration reflects anyone's actual identity -- it reports what the repository decided, and the decision is a local git config value
  • r-codexreg1 5933aa4 — an entry can be correct when the installer reads it and wrong afterwards -- a later install, a moved data root or a hand edit all leave the name intact, and nothing revisits it until the installer runs again
  • r-codexplug e5fe95a — a plugin can put a skill in front of a session; it cannot make the session follow it, and nothing here reports whether one did
  • r-ociprobe1 497ed63 — the probe reports what docker said at one instant, and a daemon can die between that answer and the evaluation it authorises -- the run then fails inside the container rather than refusing before it, which is a different path with its own handling
  • r-readme001 7f82d47 — the README still cannot tell a reader whether their particular host will follow a written procedure; only the hosts with a plugin or an installer have that answered by a mechanism rather than by hope
  • r-hermesx01 2eb8176hermes skills inspect resolves remote sources only in this Hermes version, so discovery was verified through hermes skills list --source all in a fresh isolated profile rather than from inside a live conversation; that a session then follows the procedure is not something an installer can establish
  • r-codexwire 955f290 — an instruction file is guidance, not enforcement -- a host may ignore it, summarise it away, or never read it, and nothing here can tell whether any session followed the procedure
  • r-initmcp1 e601ad3 — this registers for hosts that read a repository-scoped .mcp.json; a host keeping its configuration elsewhere still needs its own installation, and this cannot tell whether any host ever loaded the file
  • r-cdeb10reg 48bd5a8 — wrong-path viability, deterministic oracle feasibility, code disclosure, bounded implementation, and unproven ordinary or benchmark authorship cannot be decided from history and remain undecided for human review
  • r-mintid01 1e5f500 — deterministic minting can reserve only identities visible in this repository; independently diverged history can still introduce a collision
  • r-cdeb09sm 74d9c62 — this exercises the attacks the design anticipated, and an adversary who reads it will look for the ones it does not attempt -- notably collusion between a candidate and an evaluator image, and any attack on the freeze itself before a run starts
  • r-notes512a ce937c9 — the observation is as old as the last doctor --fix; a mirror pushed upstream after it is not visible here, and an empty answer will read as a true empty until the next probe
  • r-autotrue2 6cc5032 — a host may be registered and never call the tool, or be configured outside the repository entirely, so this distinguishes wired from unwired and never observed from unobserved
  • r-autotrue1 70b7e06 — a host integration may still be installed or selected outside the repository, so operators must ensure it supplies the session transcript before committing; the core cannot observe or enforce that host-side action
  • r-cdeb07or 5607dd5 — the state machine cannot distinguish an agent that produced no model turn from one whose host failed to report the turn it produced, so it treats both as interrupted and neither as retryable -- a real pre-turn infrastructure failure is therefore sometimes charged as a lost run
  • r-cdeb07or 5607dd5 — atomicity rests on rename and fsync semantics of the underlying filesystem; a filesystem that reorders them can still present a partial the recovery pass reads as whole
  • r-cdeb08an 60db89f — the paired bootstrap describes resampling stability within these five frozen repositories and thirty frozen tasks, and says nothing about any other repository, task or agent population
  • r-coldpath1 0412f81 — a genuinely cold fallback still reads the whole history once, because repository-wide lifecycle folding cannot be scoped to a path without changing what the answer means
  • r-coldpath1 0412f81 — nothing outside index and init builds the index now, so a repository whose derived file was deleted stays on the scan path until one of them is run
  • r-cdeb05lg 5d347b9 — the ledger observes only bytes emitted before the agent process closes -- terminal usage never written to the stream, external billing, and work outside that stream are all unavailable to it rather than estimated
  • r-cdeb06ev e9d3c72 — a determined candidate can still spend its budget rather than forge -- the resource control bounds a spinning implementation but cannot distinguish slow honest work from deliberate exhaustion
  • r-cdeb03rt f0ad091 — enforced by code — the capability gate refuses a missing or never-probed capability, the run spec cannot express host HOME or settings inheritance, provider credentials cross the container boundary only through a two-key allowlist, the captured stream is identity-checked before anything reads it as measurement, and the pin loader rejects hand-edited enforcement shapes. Still resting on the operator — building and pulling the image the freeze records, running preflight on the machine that runs the study, keeping the container runtime daemon itself trusted, and passing provider credentials into the preflight and run calls; the gate can verify what a container does, not that the host it runs on is honest
  • r-autoswitch b8497b8 — the prompt defaults to yes and a bare Enter takes it, so a reflexive Enter costs a team-wide consent — the file is committed, and every clone captures with nobody in the loop until someone runs auto off
  • r-unattshadow b7b532a — together the two features measure how often an unattended pipeline would have written, and remove the asking from the writing -- neither half can say whether what gets written is worth a reader's attention, so shadow's number for an unattended repository is a volume, not a value
  • r-unattended511 f6679e1 — with nobody in the loop, the pipeline decides on its own what is worth recording, and every record it keeps spends a future reader's attention without asking anyone first -- the switch is a repository consenting to that cost, and nothing in this change reduces it
  • r-retireserena c1171ef — the preregistration fixes claims before numbers exist, so what it says about the calibration cannot move to match later tree state
  • r-shadow511 d093bef — shadow measures commits whose transcripts are gone, so its numbers describe the substitution of a committed message and patch for a transcript -- they say nothing about what capture would record over a live session, and no shadow output may be read as a pipeline baseline
  • r-mcpexit506 f1b1fb0 — a process killed with SIGKILL still writes nothing, so the log shows a start with no exit -- that case is inferred from the absence of a line rather than reported, and stays the way MCP tools for commitlore vanish mid-session (ToolSearch returns zero results despite server reported connected) #424's original observation had to be made
  • r-demostory505 8016424 — the demo is one scenario, so it shows supersession and not expiry, path scope, or trust grading; a reader who wants those still has to read past the image
  • r-readmeorder 383f77d — the hook leads with the headline number, so a reader who stops there has the effect without the conditions on it; the section naming those conditions is now two screens up rather than at the end, which is a shorter path than before but still a path
  • r-pindigest c5a7cfa — pinning fixes the tree, not its behaviour -- a pinned action still runs with the job's token and can read this repository, and upgrading now requires deliberately resolving a new digest rather than inheriting one
  • r-leastpriv 7a27c82 — gate jobs still run mutable @v4 action references, so a changed action can read this repository's source and its own job's token -- what it can no longer do is create a release; pinning those actions by digest is a separate decision with its own upgrade cost
  • r-canonsha499 46ab656 — the window between the binding check and gh release create is not itself covered by a check -- the ruleset is what holds it closed, and a bypass actor added to that ruleset would reopen it silently
  • r-filters471 c7572f6 — a filtered run reports honestly about what it ran and says nothing about what it skipped, so a repository whose only failure sits outside the selection reads as healthy-for-the-selection with no hint that the unexamined rows exist
  • r-envelope469 0162b73installSource is declared and derived per surface only where a test asserts that surface; an unasserted surface reports unknown rather than a guess, so the field is honest and incomplete rather than complete and unverified
  • r-effects476 43eb4aaenv and the clock are injected but process.cwd and the filesystem are still reached for directly inside some checks, so a check that reads a path can be pointed at a fixture but not at a purely synthetic tree
  • r-headline470 55b810cinit keeps the checks-only renderer, so the headline a doctor run shows is absent from the install path where a first-time user is most likely to meet a finding
  • r-budget472 8ea15f1 — 13.2x headroom over the measured baseline is sized to survive a contended shared runner, so it catches an order-of-magnitude regression and would not notice doctor becoming three times slower
  • r-dsplit467 b24e371 — the split is by responsibility, not by dependency direction -- runner, registry and renderer all still reach into the model, which is correct for a shared vocabulary and would not catch a model that grew behaviour
  • r-pubprereq 8ffb31c — the gates qualify the commit and its CI, not the tag's authorship or timing -- anyone who can push a v* tag to a qualified commit on main can still publish, and expiring or re-pointing a tag after these jobs pass is outside what any of them observe
  • r-failclosed493 3216a54 — the job runs on ubuntu-latest only, so it qualifies the POSIX install; the Windows path is still covered by install-ps1 in CI against the branch rather than against the tag
  • r-plugres483 7dab3e3 — the gate now catches the mismatch at release time, and doctor's inject-version check catches it for a user, but nothing tells a user who has never run doctor
  • r-collapse466 d24a284 — only two edges are declared -- inject-version on inject-runtime, and the §2.2 checks on the capture chain -- so a dependency nobody wrote down still surfaces as an independent finding
  • r-m5sources b910dba — the seven shards are declared individually, so a shard added later is invisible to this block until someone lists it -- which is the property the declaration was built for and the cost that comes with it
  • r-evidence465 e1a3c92 — evidence answers what was observed, not whether the observation was the right one to make -- index ingests any key: value line as a trailer; doctor reports 106 records where git has 0, and context serves commit subjects to the agent #335's wrong count would now be visible in a field rather than prevented
  • r-queryroute 4ae1f6f — the sweep covered ?? against an options field in src/commands, which is the shape that produced both defects; an option whose default is consumed some other way would not have shown up
  • r-rel071 af8e0ab — 0.7.0 stays published with its notes amended to name the defect at the top; retracting a tag people may already have installed trades a known-bad version for an unknown one
  • r-rel070 d4a4d8b — the README's behaviour claim now rests on M5 while the generated numbers block beneath it still publishes M4, which is The README's generated numbers block still publishes M4; M5 measured the thing the README leads with #480 rather than a release-time edit
  • r-numgate b770054 — the README's behaviour claim and the generated block below it now describe different studies until The README's generated numbers block still publishes M4; M5 measured the thing the README leads with #480 lands
  • r-skipreason 85aa8d6 — the union has six members because ten sites needed six, and the next check that skips will need a seventh rather than one of these stretched to fit
  • r-readmem5 6d04c0b — the README now leads its behaviour claim with a [claim]-tier number while shipping a [directive] tier nobody has measured, and that gap will widen until something measures it
  • r-snapnode 2ef8745 — normalisation is a list of known-variable things, so the next machine-specific value to appear in a detail string will fail once before it is added
  • r-registry463 ddf5592 — the registry is data but nothing filters it yet, so the ordering guarantees are tested and unused until the --only ticket
  • r-checkmodel 9cbed57 — evidence is {} on every row until the ticket that populates it, so the field exists and proves nothing yet
  • r-doctorpend 458bcec — the check reads staleness, so a capture whose base commit is still HEAD reports ok even if it has been waiting long enough that nobody remembers preparing it
  • r-v13fix f4b6859 — schemas and the verifier now require the qualification evidence, but nothing yet produces it -- CDEB-10 must build the probe and the deliverability check before a freeze can be written
  • r-cdebv13 e0bd61a — the calibration is measured on disposable tasks, so it estimates overhead on work that is not the corpus and a corpus with different injection density will sit off it
  • r-clog070 172fa3d — the entry stays under ## Unreleased and names no version, because the version bump belongs to the release commit and a changelog that pre-announces a number can be wrong about it
  • r-cdebpres e01b482 — one repository, four tasks, six usable runs per arm and a local evaluator -- no claim gate is evaluable and preregistration §8 forbids any number here reaching a public surface
  • r-readmecold 08efdff — only README.md is reordered, so the ko, ja and zh-CN readers still meet the evidence first until the follow-up lands
  • r-selfaudit cd0068f — the page is maintained by hand, so an entry can go stale against the code it describes; the closing line says so and asks for an issue when it does
  • r-trust415 a030e93 — this changes what a fresh install delivers, so M1 and M5 remain measurements of [claim]-graded delivery and their numbers do not transfer to the directive path
  • r-cdebver01 ce7b278 — the schemas freeze protocol 1.2.0 constants -- thresholds, matrix size, category names -- so a protocol change is a schema change and CI notices
  • r-mcplife424 8cd3c6d — the tool registration that was lost belongs to the client, so nothing in this repository can detect the loss from inside a session or restore it
  • r-capmode30 40818c2stage cannot check consent, so auto records what is certainly true -- no prompt was shown -- instead of asserting what it cannot know
  • r-drafted30 b126176 — a commit message is immutable, so a drafted record is never upgraded in place -- promotion is a later record that Supersedes it, and that half is not in this change
  • r-plugupd1 bd4363b — the plugin cache belongs to the client, so nothing in this repository can update it or detect the version drift except by asking the running hook
  • r-amendid430 4c450ebcommit-msg gets no argument, environment variable or ref that distinguishes an amend from an ordinary commit
  • r-hookver433 a3b92d7 — nothing here can update anything -- the plugin cache is the client's, so the only move available is to say what is true
  • r-binx428 b9d1ea8 — the allowlist accepts .mjs/.js paths, which are exactly the files most likely to carry a shebang and no execute bit
  • r-recurse422 418734c — git runs pre-push on every push including one a pre-push hook makes, so any push from inside the hook must opt out of hooks explicitly
  • r-busy420 9555569 — a full rebuild on a large repository takes longer than any timeout a hook can afford to wait, so the scan fallback stays reachable by design
  • r-sync416 deb21d2 — git neither fetches nor pushes notes by default, so a mirror only moves when something configures or invokes it
  • r-mention408 ec314cd — paraphrase space is unbounded and this table cannot see semantic rewording, so blocked remains a speed bump and the grade remains the load-bearing control
  • r-schema406 22b2c35 — an index is derived state with no migration path, so a meaning change can only be handled by discarding the file
  • r-note409 0dff3e4 — the notes ref is an ordinary ref with no signature requirement, so authorship there is a claim about who wrote the text and not proof of it
  • r-backfillclosed 00de5fa — the guard reads the mirror state at invocation, so a fetch completing mid-run is not observed
  • r-initunfetched 889d191 — it reports the state as it was before init ran, so a mirror fetched between the capture and the report would be named wrongly; that window is the four steps of one command
  • r-mirrorunread 8e4bdc5 — the caveat can only fire where notesAvailability returns unfetched, so a refspec added after cloning and never fetched through still builds silently -- the distinction config alone cannot carry, recorded on that function under r-fetchowed
  • r-claimsmatch 506ada4 — this fixes the sentences an external reviewer found; no systematic pass was made over every claim in the four files against every published measurement
  • r-m5analysis 3450656 — the script enforces the row count, not the identity of the rows; a run that produced 1,160 rows under a changed harness would satisfy it, which is what harness_commit and dist_digest on each row are for
  • r-benchscope 67f4375 — nothing checks the shape of the eight metric-row files. This gate names them and steps over them, and bench/deterministic/types.ts is the only definition that family has -- there is no JSON schema for it, so drift on that side is still invisible
  • r-benchscope 67f4375 — the pre-provenance exemption reads started_at, which is data on the row rather than a fact about the file. A row that misreported it would be held to the shorter list of requirements; that is a deliberate falsification rather than the omission this fixes, and nothing here detects it
  • r-nulsep 406dbdf — the sentinel is safe because nothing that writes into .git/hooks contains it, not because it is unrepresentable -- a hook body carrying the sentinel on its own line is the one input that could in principle collide, and the helper does not reject it
  • r-pinskew 007ccbf — the comparison reads the package.json above the recorded path rather than running it with --version, so a pin whose manifest and bundle disagree is reported by its manifest
  • r-priorart 507ae24 — the comparison is against Lore's README and its abstract; the full paper was not read, so a lifecycle described only in the PDF would have been missed
  • r-scaleproof 4c093f2 — the 100,000-commit figures come from a synthetic repository built by the deterministic harness, not from a real codebase of that size, so they describe the index's shape rather than any particular project
  • r-extbaseline 064daf6 — the band is four Python repositories chosen for having enough revert history to backfill from, so it is evidence about large long-lived Python projects rather than about repositories in general
  • r-3c9d52 dc9e769 — the sweep is two git log calls per path and the delivery phase runs git log --follow on every tracked path, so a full run over the four externals is hours rather than minutes on one machine
  • r-ledgerresult bc31c90 — both sides are byte-derived proxies under CHARS_PER_TOKEN=4 rather than a provider tokenizer, so the ratio cancels a uniform error and not a differential one between diff text and prose
  • r-ledgerresult bc31c90 — break-even in reads assumes reads land on the evaluation set the way the delivery run's per-path average describes, and real editing concentrates on a few files
  • r-surfacedeliv fae9e1e — every figure in the table is measured on this repository measuring itself, which is the weakest part of the evidence and is stated in the paragraph rather than left for a reader to discover
  • r-rel060 e999b9d — the install one-liner in all four READMEs now points at a tag that does not exist until this is tagged, so the window between merging to main and pushing v0.6.0 is one where the documented install is broken
  • r-pipesplit b4fa571 — test/dogfood.test.ts validates every record in this history, so a new violation class is only available if it rejects none of the 620 Ruled-out: values already written
  • r-gcunstageable 5cd6b8f — ADR-0021 fixes the pending format and stamps expires_at at stage only, so giving these phases an expiry earlier is a format change rather than a fix
  • r-gcunstageable 5cd6b8f — gc runs only when capture gc is invoked -- nothing schedules it, so a leaked file goes at the next run rather than at the 24-hour mark
  • r-gcunstageable 5cd6b8f — staleness is derived from base_head against HEAD; a transaction whose staged diff moved while HEAD did not is equally unstageable and is still kept, which is the conservative half of the same test
  • r-gcunstageable 5cd6b8f — a staged transaction that is never applied is still kept for ever -- the hook skips it once expires_at passes and gc protects the phase -- which is a separate leak this change deliberately does not touch
  • r-secondtie 998bf18 — committed_ts is %ct at one-second resolution and the index stores no ordinal that orders two commits inside one second, so a tie on that path can be made deterministic but never topological
  • r-dedupviol 18ad9c1 — the key includes line, so two detectors that locate one finding differently -- one with a line, one without -- would still print it twice; today both resolve the line through the same locateTrailerLines/lineForViolation path
  • r-readmesplit344 7314a03 — three checks bind content to a position in the README, so the complete record example, the protocol vocabulary table and the generated benchmark block could not move
  • r-owntmproot 6543870 — the demo still defaults to the shared tmpdir, so concurrent commitlore demo runs still create sibling directories there -- that is deliberate, and it is safe only because nothing now asserts over that namespace
  • r-diffdefault 4ac8163 — the test reads the option string out of the source rather than out of --help output, so a change to how commander renders descriptions would not be caught
  • r-shallowlast 0913821 — the spawn still happens once per validate that has a dangling ref, which is the case where the answer is actually needed
  • r-exitonemeans 89f7af8 — a shallow clone cannot tell a reference that resolves below the boundary from one that resolves nowhere, so neither verdict is available and the check can only name the question it could not answer
  • r-failopen abc54ea — with the gate installed and no CLI resolvable, commits are still refused -- that is the one hook holding a verdict back, and this change does not reach it
  • r-notereach 1e72a28 — reachability is decided against HEAD alone, so a record mirrored onto a live branch that is not checked out is not served until it is
  • r-heropolish f6144bc — README.ko.md still switches from 존댓말 to 해라체 below the hero; that split is older than this change and belongs to the restructure in README still carries the reference manual it should be linking to #344
  • r-pluginpath353 e364f3a — a plugin manifest has no way to add anything to PATH, so no plugin-side change can make the documented commands resolve
  • r-fetchowed 11f04b4 — config alone cannot separate a refspec that was fetched through from one that was only written, so the availability verdict cannot carry that distinction
  • r-guarddisclose 8a4d0c7 — a disclosure asserted by tool name covers the tool that is named, and the ADR's requirement is about every surface that exposes the behaviour
  • r-realoutput f9efea0 — a README block introduced as what the tool prints is a behavioural claim, and inventing its shape is the same defect as inventing a number
  • r-refspecfetch 936d206 — configuring a refspec is not fetching through it, and a state machine that conflates the two turns its own remedy into a way of hiding the problem
  • r-actionsleak a6fbb4b — a code path that no test and no first-party workflow exercises is the one an outside adopter takes by default, and its absence from CI is not evidence it is unused
  • r-actionsleak a6fbb4b — a trust label the caller must act on is worthless unless the value it describes is actually withheld at the point the data is built
  • r-release051 19810d2 — the hook is written at install time, so no release repairs a repository that already has one; every release touching hook behaviour has to restate what does
  • r-heroinherit 89b13ac — a headline that implies detection commits the product to guard's numbers, and guard is an advisory measured at 22% recall
  • r-convertreadme e12c816 — a README claim about the default workflow is only true if the shipped skill performs it, and the skill currently requires the user to name CommitLore first
  • r-fieldreport 753f4e7 — this section reports one engineer's day on one repository; it is evidence that the mechanism works there, not a measured effect size, and the wording has to keep those apart
  • r-readmefinal 40aeae0 — a mutation oracle anchored on a claim that can become false will silently stop testing when the claim is removed; the needle has to be asserted present
  • r-recordgate335 a83ebe3 — a denylist cannot decide whether something is a record, because the keys nobody has claimed are unbounded; that question needs the vocabulary, and the two must not be answered by one filter
  • r-recordgate335 a83ebe3Verified: in a release note is indistinguishable from Verified: in a record, and no context signal separates them without risking real records
  • r-draftfirst329 0506a5d — a usage error that names the wrong input costs an invocation and points the reader away from the fault; ordering is part of the message
  • r-release050 ad402c7 — the hook is written at install time, so a corrected release never reaches a repository that already has one; every release fixing hook behaviour has to say what repairs an existing install
  • r-uninstall1123 4ddac0d — the installers write five agent configs, not the four the ticket's measured inventory lists; the fifth is Windsurf at .codeium/windsurf/mcp_config.json
  • r-uninstall1123 4ddac0d — opencode's entry is shaped differently from the other three -- the command is an array -- so one recogniser cannot serve all of them
  • r-uninstall1123 4ddac0d — a checkout is 1366 files at this head, not the 1206 the ticket measured at 6e1d46d; any assertion bound to that count is stale
  • r-winsupported1124 6333251 — repositories that installed the hook before Windows: the commit-msg hook hangs instead of returning, and #71's containment can never match there #321 keep the old stub and must re-run commitlore hooks install; a corrected release does not reach them, and this row's claim is about a working install
  • r-winderive1124 282693b — a diagnostic that retypes the code it describes goes stale silently, because nothing fails when the two drift apart -- it has to be read out of the artifact that ships
  • r-winbound1124 156deed — an unbounded hang is not evidence; it is a job that dies at the runner timeout having printed nothing, so every hook-invoking commit here has to carry its own bound
  • r-winassert1124 616005d — an assertion whose only oracle is an absent side effect cannot distinguish "refused" from "the mechanism never worked", so each one needs a positive control that fires before the attack
  • r-winshell1124 aa68a9a — a GitHub Actions bash step is invoked as bash -eo pipefail, so set -uo pipefail inside the step does not clear -e and any bare command that fails ends the step at that line
  • r-winpath1127 bdf4ac0 — the stub is written to .git/hooks at install time, so a repository installed before this fix keeps the old text and must re-run commitlore hooks install; installing a corrected release is not enough
  • r-winpath1127 bdf4ac0${dir%/*} returns its input unchanged when no separator remains, so a loop that tests for emptiness never terminates at a drive root
  • r-winpath1127 bdf4ac0 — neither dirname nor ${var%/*} finds a parent in a backslash-separated path; both answer .
  • r-compat1122 e7d8516 — a non-empty guard does not detect deletion; each table's row keys have to be asserted as a set or the statement can silently shrink to one row
  • r-compat1122 e7d8516 — substring comparison hides a narrowing -- ./ is inside ../ and Edit|Write is inside Edit|Write|MultiEdit|NotebookEdit -- so cells are compared as their rendered form
  • r-compat1122 e7d8516 — a sentinel containing \0 makes git treat the file as binary, which costs it diff, blame and log -p permanently
  • r-compat1122 e7d8516 — the plugin path needs bash, because scripts/commitlore-run.sh carries a #!/bin/bash shebang, and no install script checks for it
  • r-muslbullet1126 04ac181 — this ticket owns four bullets and not the tests that read the section around them, so a check that breaks here means a region was taken that was not allocated
  • r-ps1scope282 72f23df — the hook-runtime probe spawns /bin/sh, so it cannot report on a Windows hook at all until T-1124 changes it
  • r-ps1stderr282 e37b4d3 — Windows PowerShell 5.1 turns a native command's stderr into a terminating error under $ErrorActionPreference = Stop, so no native call in this script may merge stderr into its output
  • r-ps1shell282 97735d6 — a step's shell key takes no expression, so a per-host matrix cannot select the interpreter
  • r-t1120nodeinst 14deeb4 — git and node are hard prerequisites now, so a host without them installs nothing and says which one is missing
  • r-t1110policy 9e7b37a — only a repository-local policy file is read -- PRD-F13 requirement 11 permits either one location or a stated precedence, and an ambiguous precedence is worse than a missing feature
  • r-gateb3rev a2e38b9 — the shipped install.sh downloads a platform asset, so no document may describe it as Node-only until the installer itself changes
  • r-rel041notes 71efe1f — 0.4.1 makes the installer honest about a verification it cannot complete rather than fixing the kill, so an upgrading user may still see the unverified message instead of a version
  • r-instverify256 3715677 — the root cause of the signal kill is unestablished; this makes the installer honest about it rather than fixing it, and Documented install exits 137 on upgrade: a killed verification turns a successful install into a failure #256 stays open for the cause
  • r-rel040pins b76c40b — the pin names a tag that does not exist until the tag is pushed; between this merge and that push the documented command refers forward
  • r-rel040notes 5d57a72 — the 26.3-point density gap quoted in the notes is measured at this head and will drift with merge volume; it is illustrative of the denominator problem rather than a stable figure
  • r-gcwiring f21f28e — the guard against this class is four CLI-level tests; nothing structurally prevents a future subcommand from colliding with a parent option again
  • r-flake221fix 2b21ed9 — checkInjectRuntime ENOENT does not block init
  • r-lb0xl89a 236229e — the static contract uses explicit placeholder text for TRANSCRIPT and DIFF rather than omitting those sections, because the prompt text references them by name
  • r-c44a1edb 71f5197 — src/core/pending-gc.ts -- gc must never remove a staged or applied file regardless of expiry; T-1018 post-commit may still finalise them
  • r-0ll5sxk0 2853a22 — consumption happens after commit succeeds, exactly once; consuming earlier loses the record on failed commits, consuming twice lets one record attach to two commits
  • r-t1009stage b5fcf4e — the nonce pattern check bounds what a caller can send, but a caller holding a valid nonce for its own repository can stage repeatedly until the record is consumed
  • r-t1005gates 15421c0 — policy identity is compared as a hash, so a policy edit that produces the same hash is indistinguishable from no edit
  • r-t1016svg 321c6f1 — byte-exactness is verified on this platform; a different platform's Node could in principle render differently, and nothing here proves it does not
  • r-t1006cli d22580b — the command composes the phases in one process, so a crash between verify and stage leaves a verified pending record that only garbage collection will clean up
  • r-t1008mcp ab00b54 — src/mcp/server.ts: readOnlyHint must be false for verify_capture — the tool writes verification results to the pending transaction
  • r-t1007mcp b6ef112 — commitlore_prepare_capture uses readOnlyHint: false because it writes a pending transaction
  • r-t1003verify7b 5e9f96b — Verification failure must never block a commit | the verify phase returns empty on any failure rather than throwing, because blocking a commit on an optional enrichment step is the feature people disable (ADR-0006)
  • r-t1013verbose205 294ec82 — --verbose only selects the formatter; it does not change runInit logic, step order, exit codes, or --json output
  • r-t1022sig e0c641d — the first pushed attempt asserted one header string in the test and built another in the formatter; CI caught the mismatch and the formatter was aligned to the asserted string, which is the one that states the measured figures on the output surface
  • r-t1022sig e0c641d — focused-test evidence for this change is CI's, not local; test/guard.test.ts reports zero tests and stalls on this machine at dev with no changes applied
  • r-t1024bc 023f6d9 — response shape is exactly five fields per CEO amendments and ADR-0020 confidence-separation constraint | adding a sixth field or letting context inherit guard_confidence violates the acceptance criteria
  • r-t1021known 8dfffc1 — the figures are measured against one archived 417-decision corpus, which is deliberately hard and is not deployment prevalence
  • r-t1011demo 1c0fc0c — the scene is one fixed pair of decisions, so it demonstrates the mechanism rather than measuring how often it matters
  • r-t1001pend 0f5ef32 — the test proves monotonic transitions but cannot prove absence of TOCTOU between read and rename on a loaded filesystem; atomic rename is the kernel-level guarantee
  • r-t1001pend 0f5ef32 — the first attempt's Record-Id used hyphens, which the r-[a-z0-9]{6,} format rejects; both the lint action and the dogfood test caught it
  • r-t1020desc dd12b42 — the test asserts on the exact precision and recall figures; a future re-measurement changes both the description and the test
  • r-t1020desc dd12b42 — the first attempt's Record-Id used hyphens, which the r-[a-z0-9]{6,} format rejects; both the lint action and the dogfood test caught it
  • r-initresult204 ea4a08e — --verbose flag not wired yet (T-1013)
  • r-t1030diag 344ada0 — the heuristic uses a regex on the first line of stderr; an error that prints no stack frame and no "not found" string will be reported as cause unclear even if a human could classify it
  • r-pin030readme 504b54e — install.sh must already support tag-based download for the one-liner to work; verified that the URL resolves to a tagged tree
  • r-fix191amb cb94448 — the same-message test still passes by accident of collectRecords returning one record per commit; the divergent-notes test is what exercises the actual suppression path
  • r-fix187val 40f2436 — the tip-scan adds one full-history git-log call per range invocation; acceptable for a lint-time check but visible in benchmarks at scale
  • r-notes030 a289ca5 — the density denominator is named here and in the handoff, not in the harness that emits it, so the next run reproduces the same ambiguity
  • r-hero172a bc0d971 — Stale-exposure benchmark is one corpus, one query, and one pinned embedding model at a fixed two-record budget
  • r-dupsucceed 6f77fcf — supersession is resolved within one repository's history, so a record superseded in a fork that was never merged still grades as current here
  • r-dupsuccorder f46c02d — a successor before a later duplicate cannot resolve that later collision
  • r-dupsucc729 5a6b238 — published dev history cannot be rewritten
  • r-valdup145 bcb9563 — the same-message check sees only the message, so two commits each declaring the same id separately are still caught by the reference check rather than here
  • r-convtrail150 57e89d2 — the denylist answers a different question from isRecordKey's allowlist, so a conventional trailer this protocol later claims would need removing from one and adding to the other
  • r-epipe2026 d9ee9ff — spawnSync may report EPIPE after git exits while its input pipe is being written
  • r-doctorepipe 0420f5c — the new deterministic tests exercise evaluateInjectRun with a synthetic spawnSync result rather than forcing the live race, because no payload this check sends is large enough to make the write block deterministically the way an artificially large one does in the reproduction above
  • r-init107 f485f07 — the generated dist artifacts are rebuilt from TypeScript source
  • r-survsplit e73aed5 — path-reachability is measured against git's rename detection, so the figure moves with git's similarity threshold rather than with anything here
  • r-be140cost 8c01bd5 — no per-turn provider token ledger or observed avoided-work cost exists yet
  • r-probepath 51f6446 — the probe still only runs a command it recognises, so a hand-edited but equivalent hook reports not-checked rather than a verdict
  • r-readme129 ab5f210 — the break-even rests on tokens estimated from bytes at the product's own four-characters-per-token constant, so it moves with that assumption
  • r-doctorprobe ed94491 — the probe runs only a command it recognises, so a user who hand-edits the hook into an equivalent but different form gets not-checked rather than a verdict
  • r-m4basis 5e2d2cb — the guard question stays unanswered until the exposure instrument is verified and M4 is rerun on it
  • r-m4withdraw e5f9b73 — the guard question is now unanswered rather than answered null
  • r-instpath119 9e1fce7 — a user who ignores the printed line still gets "not found" on the next command
  • r-readmeux1 b664205 — interactive record building does not exist, so the honest answer is still "an agent writes it or you do"
  • r-rel021a a79e350 — v0.2.0 remains on the remote with no release attached
  • r-expreadme1 9e69abe — bench/VERDICT-M4.md still cites the Fisher figure; the two disagree until the verdict records why the number was withdrawn from the README
  • r-expomerge1 d6ad014 — M4's existing rows have no exposure field and must read as unknown, not as not-exposed — backfilling by inference would erase the finding
  • r-f61a2c 9114cf0 — the matcher remains deterministic and lexical; no embedding or semantic service is available to distinguish paraphrases
  • r-rdme96a 9c9371c — scripts/check-readme-numbers.mjs's withdrawal-notice and stray-statistic checks constrain what can appear outside the (absent, here) generated benchmark block — re-checked after every edit, not just at the end
  • r-init96a 913c7e3 — doctor's own exit-code contract treats warn as non-fatal by design (SPEC §10, commitlore-setup skill) — init deliberately diverges from it for its own summary, and that divergence is the one thing most likely to look like a bug on a future read of this diff
  • r-fix92dupid 7f41a6e — cross-references between two blocks declared by the same commit (a Follows:/Supersedes: naming a sibling block's id) are still reported as dangling rather than resolved against the sibling -- unchanged from before this fix, and called out in validate.ts's own comment as future work
  • r-fix93pkg 9c4a396 — package.json remains a development artifact (build, typecheck, dependency floor) -- it is not read as a distribution manifest by anything in this repository
  • r-relinstall c6e1d04 — never tested against the real GitHub release infrastructure (no release exists yet — that is the owner's action) — verified against a locally built SEA binary, a hand-made SHA256SUMS, and a local HTTP server standing in for GitHub's release-asset redirects, which is everything this repository lets a change verify before a tag exists.
  • r-distrace88 d118a73 — the fix insulates bench-ablation.test.ts from the race; it does not remove the underlying design (four test files independently, redundantly rebuilding one shared dist/ in their own beforeAll). A fifth file doing the same thing, or a future check elsewhere that also depends on dist/'s mid-run stability, can still race the same way.
  • r-parsemulti 6d39d25parse has no git-commit context (no sha, no notes mirror) — its identityCollision check is local to the one message being parsed and cannot detect a Record-Id that collides with something already committed elsewhere in history the way context's fold does.
  • r-multirec01 92aeb24 — parseRecordBlocks only recognizes a non-final block by its declared Record-Id, so an unidentified inherited record beyond the first stays recoverable in the plan that computed it but not in a later re-parse of stored text; squash-preserve orders unidentified blocks last so the common case (at most one) is unaffected.
  • r-multirec01 92aeb24 — multi-block reference checking (Follows:/Supersedes:) does not resolve one block's reference against a sibling block declared by the same commit; each block is still checked against every earlier commit in history.
  • r-exit065 e545dee — any new command's exit codes must be drawn from SPEC §10, not invented locally
  • r-fix70a1 d707fc7 — one encoding layer and explicit lexical forms in the four published languages; semantic paraphrases, nested encodings, and split payloads remain outside coverage
  • r-shwt66 5efa206 — git rev-parse --git-path may return a repository-relative path, so resolve it against cwd
  • r-merge66 40e7987 — Generated dist files were resolved only by npm run build and npm run bundle
  • r-fix760 fb8ba45 — Git remains the authority on trailer recognition; diagnostics must not loosen the parser
  • r-refint74 572f573 — validate cannot perform conservation checks because it has no before state
  • r-warn75 24c7cc8 — exit-code semantics remain owned by guard's exit 2 means blocked; everywhere else in the same CLI exit 2 means bad usage #65
  • r-shallow66 60a8659 — a depth-1 clone can only inspect its reachable commit history
  • r-doctor72 996bcde — generated dist artifacts must come from npm run build and npm run bundle, not a hand merge
  • r-fix067 a915af0 — PreToolUse hook failures must always exit 0 and never change stdout's hookSpecificOutput contract
  • r-fix063 0b8c496 — doctor performs remote probes; an unreachable remote reports could not verify instead of ok
  • r-det058 695cdf6 — the suite must need no model, agent, network or uncommitted benchmark input
  • r-fix053 ecc4b90 — QueryResult.notes remains repository-level availability and is independent from whether one record was mirrored
  • r-fix055 43b40f8 — harvest-verify makes no model call, so semantic entailment is outside its contract
  • r-fix054 664d4e2 — notes-only metadata must survive folding; a mirror is one record, not two
  • r-fix056 55cb8bc — blocked output may retain only validated structural values that cannot carry prose
  • r-7a3e91 cf859e4 — better-sqlite3 stays external because it is native — the bundle degrades to --no-index without it, which only works because r-6f2a08 made that load lazy first
  • r-6f2a08 4c2d432 — esbuild cannot follow createRequire(import.meta.url)('x'), so any dependency reached that way stays external no matter what the bundle config says
  • r-9c07e2 9c4d25a — the plugin still needs Node for the CLI — the protocol does not, but guard, the index and the MCP server do (T-706 · Bundle the CLI as a single file — run from a clone alone #38)
  • r-5a29f7 7727944 — guard's false-alarm rate on real agent output is 5 in 25 on diffs; an earlier pass reported 0 in 10 on curated proposals, which was the optimistic case rather than a regression since
  • r-3b8f52 1f8b4be — the figure is one machine, one run — it establishes the order of magnitude, not a regression baseline anyone should tune against
  • r-9c2f74 d653153 — the ablation arms cannot discriminate on these fixtures -- no-grade and no-lifecycle are byte-identical to the treatment in 9 of 10 tasks, because the seeds carry one reconstructed record and one task with a lifecycle trailer between them
  • r-9c2f74 d653153 — the harness assembles its own projection rather than calling the shipped injector, so what is measured is the harness's rendering of the records, not src/core/inject.ts (issue B-08 · Replace the benchmark harness injector with the actual src/core/inject.ts #36)
  • r-4a8e15 49e12c7 — git's grammar requires a subject before a trailer block, so a serialized block is not by itself a parseable message
  • r-1e7b40 c3ecbb6 — the merge is the only moment the branch's commits are still reachable, so this has to be right the first time or the records are gone
  • r-6e1a72 5e09846npx commitlore is the first thing a reader will try, and it fails until the package is published
  • r-0c5d38 aeb54a6 — the suite runs against the source tree, so no test in it can observe what packaging drops
  • r-2b8f45 0adcaf5 — a matcher that flags real work gets uninstalled, so the false positive rate is the binding constraint, not detection
  • r-4e9c72 a7a7e26 — the index is derived, so nothing about its state can make the tool give a wrong answer -- only a slower one
  • r-6f2e58 ea9ae6d — a library test and a binary test cover different failures, and the packaging layer between them is exactly where a working module becomes a broken install
  • r-8c4a17 f2ab0c2 — Record-Id is single-valued, so a merge that inherits several records has no well-formed way to declare them in the message
  • r-8c4a17 f2ab0c2 — a verifier that accepts near-miss citations verifies nothing, so normalisation cannot grow past whitespace
  • r-1b7d94 736ef92 — git reads ambient configuration, so a suite that does not neutralise it is testing the developer's machine as much as the code
  • r-7e5f02 e5f5e00 — npm installs through an engine mismatch, so the ecosystem's own signal cannot be relied on to stop anything
  • r-3a9d68 6a3fc3b — a test runner reports what ran, and nothing in its summary distinguishes "did not run" from "does not exist"
  • r-9a5e17 6d68703 — five workers on one repository share npm test and tsc, so file ownership alone does not prevent one worker from "fixing" another's half-written code -- verification scope had to be split too
  • r-8e2d51 ef93c0e — git is the parser, so a git version that folds or bounds trailers differently is a correctness problem for us, not a compatibility footnote
  • r-7f0e39 76f3f2d — literal substitution only catches the exact strings you list, so the same term written with a different separator survives
  • r-5a8c04 c46a577 — git owns the definition of a trailer block, so any behavior we cannot get from interpret-trailers is behavior we must not invent
  • r-9d31b7 4ac6e30 — the example lives in four translated files, so any fix that is not mechanically enforced will drift again on the next edit
  • r-c0f4e2 3d249cd — npm gitlore is held by an active same-domain CLI, so the owner's first-choice name was not available
  • r-b2e7f1 00d348d — Parsing must delegate to git interpret-trailers -- reimplementing the block rules would drift from the rest of the git ecosystem
  • r-a8f3c1 ef48843 — Rename must land before any code exists -- after 27 tickets it would touch spec, fixtures, index, hooks and every doc

Ruled out (587)

  • r-past585 f88193f — add higher than and caused to REJECTION_MARKERS | they match a proposal that names a threshold and an investigation that asks what caused the spike, which is the bar this ticket said not to move
  • r-engfloor01 fe83524 — adding semver as a dependency to parse this | one regex over a handful of published shapes does not justify a runtime dependency in a check that runs before install
  • r-mcpproc01 db1363d — keeping AGENTS.md as the default carrier | it reaches only repositories that adopt the convention, and it puts a hundred lines of protocol into a file the repository owns and commits
  • r-ownsemver1 fcc6e4a — keeping the semver rule and warning instead | the failure is destructive and silent, and a warning printed after the file is gone is not a warning
  • r-hookbudget1 e09014c — building the index from the hook | that is the unbounded rebuild the recorded design gives to index and init, and doing it on the edit path would trade a bounded pause for an unbounded one
  • r-scanall1 62a6fbf — keeping validateRecord and bundling ajv into the hook path | the hot path should not grow a dependency to answer a question a regex already answers
  • r-vbind001 2c88d24 — deleting a checkout that fails the version check | a directory the installer cannot identify may not be its own, and refusing costs an operator one command while destroying it may cost them something unrecoverable
  • r-structk1 a7bee10 — validating whole records at read time and discarding those that fail | history is not editable, and a reader that drops malformed records would silently lose real decisions instead of grading them cautiously
  • r-ownfail01 8de1326 — treating a malformed value as an error that stops the command | grading runs on the edit path, and refusing to answer there costs more than answering under the stricter rule and saying so
  • r-expwall01 e7ddd92 — documenting that expiry follows repository time | it is honest and it abandons the property, and the property is the reason the field exists
  • r-insttxn1 afb7bfb — treating "verification could not run" as fatal | it fails a good install over a missing optional tool, which is the failure the non-fatal policy was introduced to stop
  • r-authdir01 ae2a66f — requiring signatures by default | it would demote every existing repository's records for a risk this project is not currently exposed to, and a silent capability removal on upgrade is its own kind of dishonesty
  • r-authdir01 ae2a66f — deleting the directive tier to make the claims true | the tier is how a record says it is a constraint, and removing it would resolve the wording by removing the feature
  • r-mcpdir01 a9886b5 — having runQuery read the trusted authors itself | it would fix these two call sites and silently change every other one, including tests that mean to grade without trust
  • r-relfix01 96102b4 — keeping "floor, measured" and footnoting it | the sentence is the claim a reader takes away, and a footnote that contradicts it is worse than either alone
  • r-codexreg1 5933aa4 — replacing any entry named commitlore | a user may run their own server under that name, and taking it because the name matched is the failure this fix is about, pointed the other way
  • r-ociprobe1 497ed63 — retrying the probe until it agrees with itself | it converts a disagreement the code should not be able to have into a delay, and leaves the second answer just as unowned as the first
  • r-readme001 7f82d47 — deleting the evidence and audit material to shorten the page | the willingness to publish unflattering results is the asset, and shortening by removing it would trade the strongest thing here for a faster read
  • r-hermesx01 2eb8176 — writing the bundle into the profile directory and regenerating its manifest | it would be undone by the next sync and disagree with the manifest until then
  • r-codexwire 955f290 — a Codex-specific integration | the instruction surface is shared, and writing one integration per host would leave the same gap open for the next five
  • r-initmcp1 e601ad3 — writing into a user's host configuration from init | a repository may describe itself, but reaching into the machine that opened it is not the same act and not one an init should take unasked
  • r-mintid01 1e5f500 — random or clock-based identities | a retried capture would give one decision a different identity
  • r-cdeb09sm 74d9c62 — relaxing the analyzer to accept rows discovered on disk | it would have closed the wiring gap by removing the guarantee that closes bench:m5 reads every jsonl in bench/results, so the registered analysis would run over M1, M4 and files marked non-citable #441
  • r-notes512a ce937c9 — probing the remote from the query path | context runs before every edit and an edit must not wait on a network round trip
  • r-notes512a ce937c9 — treating a covering refspec as evidence the remote was consulted | the refspec says what this clone would fetch, never what a remote has
  • r-autotrue1 70b7e06 — initiating capture from a Git hook with the staged diff | a diff cannot supply the host transcript or establish that a decision was made
  • r-cdeb07or 5607dd5 — retrying an agent after a post-turn infrastructure failure | keeping whichever attempt survived selects on the outcome, and the surviving attempt is not a random draw
  • r-cdeb07or 5607dd5 — re-deriving the candidate tree for an evaluator retry | it converts an evaluator flake into a second candidate, which is a different experiment
  • r-cdeb08an 60db89f — discovering row files under the result directory | an unregistered file contaminates the matrix while leaving every stopping rule looking satisfied
  • r-cdeb08an 60db89f — filling or dropping unavailable usage | both change a token aggregate without evidence, one by inventing a number and one by redefining the population
  • r-coldpath1 0412f81 — applying --limit before the lifecycle fold | it would bound the cold work and silently change which records survive supersession
  • r-coldpath1 0412f81 — serving a stale index when catching it up is not possible | a fast wrong answer is worse here than a slow right one; the fallback stays fail-closed and git remains the authority
  • r-cdeb05lg 5d347b9 — inferring terminal usage from the turns that did arrive | it would be a plausible number carrying no evidence, and plausible is exactly what makes it survive review
  • r-cdeb05lg 5d347b9 — excluding an unavailable run from the denominator | that silently redefines the population the token claim describes
  • r-cdeb06ev e9d3c72 — reading the candidate's own test command from its package manifest | that is the manifest the candidate wrote, and honouring it hands the verdict back to the author it is meant to judge
  • r-cdeb06ev e9d3c72 — treating a passing candidate-owned suite as corroborating evidence | a forgery that passes its own tests is the expected shape of the attack, so agreement there carries no information
  • r-cdeb03rt f0ad091 — degrading with a warning when the CLI lacks an isolation flag, as the legacy driver does | that is exactly the silent degradation this ticket exists to remove — a run that inherited the operator's settings measures one laptop, not the study
  • r-cdeb03rt f0ad091 — container networking set to none with a unix-socket egress proxy | the agent's HTTP stack cannot be assumed to speak a socket proxy, so provider reachability would fail closed on every machine; an internal network plus allowlist proxy is enforceable and probe-verified instead
  • r-cdeb03rt f0ad091 — filling the pin manifest's digest fields with values read from this machine | the digests must come from the freeze ceremony that builds the image; a hand-filled manifest is an unfrozen pin pretending, and the gate is built to refuse it
  • r-autoswitch b8497b8 — enabling by default where no terminal can answer | the file is committed, so a CI run would hand itself a team-wide flip nobody ever saw asked
  • r-autoswitch b8497b8 — rewriting a policy file whose effective setting already matches | the bytes would change while the policy did not, and the identity hash would report a policy change that never happened — the false positive the hash exists to avoid
  • r-autoswitch b8497b8 — asking the question when a policy file already exists | the answer cannot change anything, and a yes that does nothing reads as consent being taken rather than given
  • r-unattshadow b7b532a — keeping the unattended branch's inline prepare body | the shadow refactor exists so both entry points share one side-effect-free half, and two copies of the same hashing and policy logic would drift the first time either changed
  • r-unattshadow b7b532a — checking unattended consent in the live path only | the refusal for mode "off" already lives in the shared half of prepare, and a consent check that guards one door but not the other is no guard for the next entry point added
  • r-unattended511 f6679e1 — putting unattended into the default policy identity hash | the default is a fixed false, and hashing it would refuse every capture in flight across the upgrade in every repository that never opted in -- a policy change that never happened, the exact false positive the hash exists to avoid
  • r-unattended511 f6679e1 — checking consent at stage instead of prepare | stage receives a nonce and nothing else by design, and cannot observe whether a declaration was made; consent checked nowhere it can be observed is checked nowhere
  • r-unattended511 f6679e1 — ignoring "unattended": true outside auto mode | a consent the mode cannot honour would become a silent no-op, and a user who believes a setting applied is worse than one told it did not
  • r-retireserena c1171ef — rewriting the evidence documents to match the retirement | the transcripts and the preregistration report what the recorded runs saw, and a published claim the evidence does not support is the defect class docs/SELF-AUDIT.md exists to catalogue
  • r-retireserena c1171ef — keeping the ignore entry and the test guard as cheap insurance | the record that justified them said the directory comes back while the tool runs; it no longer runs, and a guard for a tool nobody uses guards nothing
  • r-retireserena c1171ef — editing r-strayserena in place | a record lives in the commit that declared it; retirement is a new record that names the old one

Truncated: 704 lines omitted — the comment hit GitHub's 65000 character limit.

Trailer violations fail this check. Active constraints are informational — they are what the repository already decided, not a verdict on this PR.

The two cases added to stop the release gate drifting from CI did not do that.
They asked whether each ci.yml job had *some* entry in `REQUIRED_CHECKS`, by
prefix — `check.startsWith(`${job} (`)`. The parenthesised value was never
compared to anything. So one entry satisfied a job however many legs it had:

    revert `check (22.12.0)` to `check (22)`   -> both cases passed
    replace it with `check (banana)`           -> both cases passed
    delete three required entries entirely     -> both cases passed

Three of ten legs could drop out of the gate with the guard green. That is the
same defect one layer up from the one these cases exist to catch: a name
standing in for the thing it names, which is exactly what #571 was about.

Worse, I cited them as evidence. The `Verified:` trailer on r-1d4c72 says the
rename was checked by "the two that compare the required list against ci.yml".
Those two pass identically with the rename reverted. They verified nothing, and
`docs/RELEASE-GATE.md` repeats the claim.

The expansion is now computed — job name when there is no matrix, `job (value)`
per value when there is — and compared as a set. A multi-axis matrix would be
named `job (a, b)` by GitHub, so rather than guess at that composition the
helper refuses; there is none today, and a wrong guess would be this bug again.

Verified: each of the three mutations above now fails 3, 3 and 2 cases
  respectively, and all three pass with them restored — the negative control I
  should have run before claiming the first version worked
Evidence: test/release-publish-prerequisites.test.ts
Follows: r-1d4c72
Blast: module
Undo: easy
Certainty: firm
Record-Id: r-6e2a94
Provenance: authored
CommitLore-Version: 2.0.0
`admits` was fixed once for `>=`: reading only the major said `>=22.12.0`
admitted 22.5.0. The caret, tilde and bare-version branch of the same function
still did exactly that — it returned true whenever the majors matched:

    ^22.13.0  admits 22.12.0 -> true
    ~22.12.5  admits 22.12.0 -> true
    22.13.0   admits 22.12.0 -> true

So a dependency declaring `^22.13.0` passed `check-engines.mjs` against a
22.12.0 floor, which is the check whose entire job is to refuse that. One of
two branches got the fix; the docstring above it described the defect while the
code below kept it.

Each operator denotes a window and both ends are compared now. `^` allows the
rest of the major, `~` the rest of the minor, and a bare version is bounded by
whatever it leaves unstated — `22` is all of 22, `22.13` is all of 22.13,
`22.13.0` is only itself.

The existing table could not have caught this. Every entry was a `>=` form, a
`*`, or `^18.0.0 || >=20.0.0` whose second clause returns first, so nothing
reached the branch. Fifteen cases now do, covering each operator on both sides
of its boundary.

Also corrected `docs/RELEASE-GATE.md`, which cited the drift tests as proof the
required list cannot drift. It said "two tests ... in both directions"; those
matched by prefix and accepted `check (banana)`. It now describes what they
compare and why prefix matching was wrong.

Verified: restoring the major-only comparison fails 6 of the new cases and none
  of the old ones; 32/32 with it in place; `check-engines.mjs` still reports all
  10 direct dependencies supporting the floor
Evidence: scripts/engine-floor.mjs
Evidence: test/engine-floor.test.ts
Follows: r-6e2a94
Blast: module
Undo: easy
Certainty: firm
Record-Id: r-0b7f31
Provenance: authored
CommitLore-Version: 2.0.0
The dogfooding gate passes on an empty range. `validate --range HEAD..HEAD
--json` returns `shape: ok`, `reference: ok`, no violations — byte-identical to
a clean run over the whole history, because the report carried no count of what
it examined. Reproduced: the assertion exits 0 on it.

This matters because the range is derived, not pinned.
`scripts/adoption-range.mjs` finds the oldest commit carrying
`CommitLore-Version:`, so a history rewrite, a squash, or a filter that moves
that boundary to HEAD yields `<HEAD>..HEAD` — and the gate that proves this
repository keeps its own protocol reports clean having read nothing. #542 was
the same shape one step along: a check that did not run, reported as one that
passed. Closing it by refusing `not-checked` left this door open.

`validate --json` now reports `examined`, and the gate requires it to be a
number greater than zero. A report without the field is refused rather than
assumed fine — an older `validate` on a newer gate is precisely the case where
guessing would restore the silence.

Verified: the assertion exits 1 on `HEAD..HEAD` ("the range was empty") and on
  a report with no `examined` field, and exits 0 printing `examined: 3
  message(s)` on `HEAD~3..HEAD`; removing the requirement fails the three new
  cases and none of the nine older ones
Evidence: src/commands/validate.ts
Evidence: scripts/assert-dogfood.mjs
Follows: r-0b7f31
Blast: module
Undo: easy
Certainty: firm
Record-Id: r-4f8d13
Provenance: authored
CommitLore-Version: 2.0.0
node:sqlite was added in 22.5.0 but stayed behind --experimental-sqlite
until 22.13.0. The 22.12.0 floor let init die at the Index step:
require failed with "No such built-in module: node:sqlite". CI proved
it — pinning the floor job to 22.12.0 failed 134 tests with 85 of that
error, while Node 24 passed everything.

    node 22.12.0: require('node:sqlite') -> "No such built-in module: node:sqlite"
    node 22.13.0: require('node:sqlite') -> OK
    node 22.13.0: zlib.zstdCompressSync  -> not a function
    node 22.15.0: zlib.zstdCompressSync  -> OK

zstdCompressSync is used only in bench/cdeb, never in src/ or dist/, so
it does not raise the package floor. Those tests skip when zstd is
missing and name 22.15.0 as the version they need.

check-engines.mjs read declared dependency ranges and could not see a
bare node: builtin. That is why 22.12.0 shipped. It now compares every
node: specifier under src/ to the version that provides it unflagged,
and fails when the floor is below what an imported builtin needs.
Reverting engines.node to >=22.12.0 fails that case and the script;
restoring 22.13.0 passes both.

ADR-0010 said a later raise needs a superseding ADR and forbade a
package.json-only change. That instruction was ignored at 22.12.0.
ADR-0033 is the superseding document.

Closes #586.

Blast: system
Undo: easy
Certainty: firm
Record-Id: r-floor213
Provenance: authored
CommitLore-Version: 2.0.0
Verified: npx tsc --noEmit and bench tsc exit 0; npm run build updated dist/core/index-db.js; 201/201 across manifest, readme, install-script, engine-floor, compatibility-matrix and release-publish-prerequisites, including install.sh refusing 22.12.0 and accepting 22.13.0; check-engines names the sqlite gap on a reverted 22.12.0 floor and passes at 22.13.0; engine-floor's declared-floor case fails then passes under the same revert; 27/27 cdeb-evaluator and provider-ledger tests still pass on this Node (zstd present)
Evidence: docs/adr/ADR-0033-node-floor-22-13.md
Evidence: scripts/check-engines.mjs
Evidence: test/engine-floor.test.ts
Evidence: package.json
Adding `examined` to `validate --json` broke three tests that assert the whole
envelope by deep equality. I ran the gate's own suite and not the ones that pin
the shape I had just changed, so CI found it instead of me — on both Node legs,
which is what says it was the change and not the runtime.

The count is added to each expectation rather than loosened out of them. These
three are where the envelope's shape is documented, and `examined` is part of
that shape now: a report that does not say how much it read cannot be told from
one that read nothing.

Verified: 146/146 across test/validate.test.ts and test/hooks.test.ts
Evidence: test/validate.test.ts
Evidence: test/hooks.test.ts
Follows: r-4f8d13
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-7c1e58
Provenance: authored
CommitLore-Version: 2.0.0
validate already exits 3 when spec/schema/record.schema.json is
absent: "this installation is missing ... — the commit message was
not examined. Reinstall CommitLore to restore it: ...". doctor is
the command a user runs to learn whether the installation works, and
the release workflow's install-gate treats its exit 0 as evidence
that the tagged tree is intact. On that same tree doctor listed the
usual runtime and history rows, mentioned the missing file nowhere,
and exited 0.

A new non-optional installation-integrity check now reads the files
this installation actually loads through readInstalledFile —
package.json from packageVersion(), spec/schema/record.schema.json
from schema.ts, and spec/SPEC.md from harvest.ts. A missing file is
fail, not warn: every commit against this install is refused with a
message the user cannot act on by editing, and a warning would let
install-gate keep passing. The detail is readInstalledFile's own
error, so validate and doctor cannot drift into two repairs.

Doctor's exit code already becomes 1 when a non-optional check
fails, so no separate exit-path change was required. The check-set
snapshot is updated because a row was added.

Verified: a copied install with spec/ removed now fails
  installation-integrity and exits 1; the same case with the check
  reverted exits 0 ("expected +0 not to be +0"); restoring it
  passes again. An intact checkout still reports the row as ok.
  npx tsc --noEmit, npm run build, 163/163 across the doctor
  test files.
Evidence: src/commands/doctor/checks/runtime-installation-integrity.ts
Evidence: src/commands/doctor/registry.ts
Evidence: test/doctor.test.ts
Blast: module
Undo: easy
Certainty: firm
Record-Id: r-instint1
Provenance: authored
CommitLore-Version: 2.0.0
The detector asked the neighbourhood of a Ruled-out quote for a
refusal phrase — "does not work", "ruled out", "not viable". A
measured rejection often never uses those words. It reports what
happened when the alternative was tried. During a real capture the
transcript said exponential backoff "re-synchronised the in-flight
clients and pushed the 429 rate higher than it was without retries
at all"; the verifier discarded the trailer twice with
ruled-out-no-rejection, and the strongest constraint in the change
survived only as a Warn. Closes #585.

A second outcome layer now matches past-tense concluded harm —
"did not work", "rolled back", "regressed", "made … worse",
"higher/slower/worse than it was/without", "caused … to spike" —
without putting "caused" or "higher than" into the marker table.
Those tokens alone would accept "the rate is already higher than
10, we should try X". The quote must still be in the transcript,
verbatim, and a mention, question, or proposal is still refused.

Ruled-out: add `higher than` and `caused` to REJECTION_MARKERS | they match a proposal that names a threshold and an investigation that asks what caused the spike, which is the bar this ticket said not to move
Verified: reverting the outcome layer fails all 10 past-tense
  accept cases with ruled-out-no-rejection and leaves the 5 refuse
  cases passing; restoring it is 16/16. 258/258 across every
  harvest and capture verification file. npx tsc --noEmit and
  npm run build.
Evidence: src/core/harvest-verify.ts
Evidence: test/ruled-out-past-tense.test.ts
Evidence: test/fixtures/harvest-verify/ruled-out-past-tense.ts
Evidence: test/capture-verify.test.ts
Blast: module
Undo: easy
Certainty: firm
Record-Id: r-past585
Provenance: authored
CommitLore-Version: 2.0.0
Both are the same mistake: a value that means "I do not know" reused for a
value that means "there is nothing".

`readPending` caught every read failure and returned `null` — the same `null`
that means no such transaction. EACCES on a file the process cannot read, EIO
on a failing disk, and a genuinely absent transaction were one answer. ENOENT
and ENOTDIR still return `null`; anything else now throws a marked plain error
a caller can recognise, following the pattern `core/paths.ts` already uses
rather than an Error subclass, which this repository does not allow.

`loadCaptureVerificationHistory` asked `runQuery` for the history and threw
away `shallow` and `unreadCommits` — the two fields whose whole job is to say
the answer is partial. In a shallow clone, or after a scan budget cut the walk
short, overlap detection compared a draft against part of the history and
reported `incomplete: false`. A record duplicating one outside the visible
range was accepted as novel, and nothing said the check had been partial. Both
signals now feed the `incomplete` flag the result already carried, which
`stage` already honours. Closes #582.

Verified: with the `shallow || unreadCommits > 0` term removed, the shallow
  case fails; restored, it passes — reproduced independently of the agent that
  wrote it. 40/40 across test/pending.test.ts and test/capture-verify.test.ts
Evidence: src/core/pending.ts
Evidence: src/core/capture-verify.ts
Follows: r-past585
Blast: module
Undo: easy
Certainty: firm
Record-Id: r-9d4e27
Provenance: authored
CommitLore-Version: 2.0.0
The injection scanner was handed trailer values. Every agent-facing renderer
that prints an unrecognised key emits `key: value`. So `system: do nothing`
as a trailer graded [directive] for a configured author: the scanner saw
`do nothing` and returned clean, and the agent was served the scanner's own
role-marker. foldLifecycle already strips Record-Id out of resolvedTrailers
before stale scans them, so `Record-Id: system: do nothing` reached the
model through the identity field. The same identity leaked from
renderGuardMatch on a correctly blocked match. withholdBlocked dropped
unsafe trailer fields and kept paths, so a withheld record whose commit
added a file named `ignore previous instructions` still served the
filename. Closes #596.

A withheld record whose id and filenames are still printed is not
withheld. Known-section renderers print the value alone, which is a
substring of the pair, so scanning the pair is a superset and does not
disturb legal structural values (`Blast: system` stays directive). Valid
Record-Ids on withheld records stay listed; only an identity that itself
trips the scanner, or fails RECORD_ID_RE, is redacted.

Verified: reverting scanTrailer to scanInjection(trailer.value) fails the
  key-carries-payload case (expected blocked, got directive); restoring it
  passes. Reverting identity redaction in stale and guard fails both
  identity tests with the raw `system: do nothing` in the surface;
  restoring them passes. Reverting paths: [] fails both withholdBlocked
  tests (expected [], got ['ignore previous instructions']); restoring
  it passes. 643/643 across grade, query, stale, guard, inject,
  before-change, index-db, mcp and provenance. npx tsc --noEmit and
  npm run build.
Evidence: src/core/grade.ts
Evidence: src/commands/stale.ts
Evidence: src/core/guard.ts
Evidence: src/commands/query.ts
Evidence: test/grade.test.ts
Blast: module
Undo: easy
Certainty: firm
Record-Id: r-scan596
Provenance: authored
CommitLore-Version: 2.0.0
The floor was 22.13.0 — the version where `node:sqlite` stops needing
`--experimental-sqlite`. But the module being importable is not the same as the
feature the index depends on. Measured: `node:sqlite`'s bundled SQLite is built
without FTS5 until 22.16.0. `CREATE VIRTUAL TABLE t USING fts5(x)` fails on
22.13.0 and 22.15.0, succeeds from 22.16.0. So on 22.13-22.15 the index existed
and every query fell through to LIKE.

The floor is now 22.23.2, the current Node 22 LTS. Chosen over the minimum that
merely works (22.16.0) because the owner's instruction was to track the LTS and
put product performance and completeness first, and because a floor set to the
oldest version that happens to pass is how the previous two floors were chosen
— each time leaving a capability the code assumed and nothing checked.

With FTS5 guaranteed, `test/index-db.test.ts` gets its assertion back. It had
been weakened to `expect(typeof handle.fts).toBe('boolean')` to survive a floor
where the answer could be either; `expect(handle.fts).toBe(true)` is a real
claim again. Closes #593.

`scripts/engine-floor.mjs` carried `node:sqlite` at 22.13.0 in its built-in
table. That is right for the module and wrong for what the code needs, which is
the whole mistake one layer down; the table now records the version the feature
requires. ADR-0034 supersedes ADR-0033 with these measurements, per the
convention that a floor moves by ADR rather than by editing package.json.

Verified: `check-engines.mjs` reports all 10 dependencies and all imported
  builtins clear at 22.23.2; 185/185 across index-db, engine-floor, manifest,
  readme and compatibility-matrix; reverting the FTS5 metadata to 22.13.0 fails
  2 engine-floor cases, and lowering the installer patch floor to 22.23.1 fails
  the rejection case
Evidence: docs/adr/ADR-0034-node-floor-22-23.md
Evidence: scripts/engine-floor.mjs
Follows: r-9d4e27
Blast: system
Undo: easy
Certainty: firm
Record-Id: r-3b8c04
Provenance: authored
CommitLore-Version: 2.0.0
…est follow it

Raising the floor to 22.23.2 turned four jobs red, and both causes were the
same mistake wearing different clothes.

`install-macos` and `install-script` ask setup-node for `22`, which resolves to
whatever the runner's mirror has newest — 22.23.1 today. So the installer
refused the runtime CI handed it, correctly: it enforces the declared floor and
CI was executing something else. That is exactly the defect this whole line of
work started from, `node: [22]` never running the 22.12.0 it promised. Every
`node-version:` in every workflow is now the floor, so a job cannot silently
run a version the package does not claim. Fourteen references across ci,
release, demo-lint and demo-preserve; none floating.

`test/install-ps1.test.ts` pinned the phrase `Node.js $NodeMajorMin or newer is
required`. The floor stopped being expressible as a major alone when it grew a
minor and a patch, so both installers interpolate `$NodeFloor` / `$NODE_FLOOR`
now and the assertion was holding them to a name that no longer exists. The
test asserts the current constant, and the comment says why it changed.

Verified: 106/106 across install-ps1, install-script and engine-floor; every
  workflow still parses; `grep -rn "node-version: 22$" .github/workflows/`
  returns nothing
Evidence: .github/workflows/ci.yml
Evidence: .github/workflows/release.yml
Evidence: test/install-ps1.test.ts
Follows: r-3b8c04
Blast: system
Undo: easy
Certainty: firm
Record-Id: r-5e0a91
Provenance: authored
CommitLore-Version: 2.0.0
@MongLong0214
MongLong0214 merged commit 0771936 into main Aug 13, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant