Skip to content

Commit cd11b49

Browse files
Merge pull request #431 from StepenkoAnatoli/fix/review-2026-10-19
review: fix what the 2026-10-19 review and break-test pass 9 found (16 commits) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 parents 3c085ad + 1a83372 commit cd11b49

33 files changed

Lines changed: 1909 additions & 126 deletions

‎CHANGELOG.md‎

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,123 @@ Each release of Research-Kit, newest first. The version is `KIT_VERSION` in
55

66
## Unreleased
77

8+
- Moving the top level's project below it - `git mv research packages/x/research`, turning a
9+
one-project repository into the nested layout - commits again. The gate counted the top level as
10+
always staying, so it called the move a deletion: "research/raw/.fetches.jsonl is in HEAD and is
11+
being deleted ... restore it", a fix that undoes the move. When one other folder's staged ledger
12+
holds every entry of HEAD's ledger, every capture HEAD holds sits beside it unchanged, and no
13+
part of the top level's `research/` stays tracked, the top level is not judged for it, and the
14+
project is judged where it went. A ledger copied with a capture dropped or edited, or a
15+
`research/` left partly tracked at the top, still blocks. A move that leaves the old `research/`
16+
on disk holding only untracked files - the gate's own log after a copy and `git rm -r research` -
17+
blocks, naming them, with `git clean -f -d -x -- research` as the fix; with it gone, the project
18+
is judged where it went (check (c) of the 2026-10-19 review, F2, and check (e), E1 and E2;
19+
ADR-0184 amended).
20+
- `researcher-release constructor --root x` prints the usage and exits 2, as any unknown
21+
subcommand does, and so do `toString`, `__proto__`, `hasOwnProperty` and the other
22+
`Object.prototype` names. The per-subcommand flag lists that refuse an unknown flag were
23+
looked up by plain index, so such a word found an `Object.prototype` member and crashed with
24+
a stack and exit 1, the code for FAIL; they are now read by own member only (review of the
25+
2026-10-19 sweep's fixes, F4).
26+
- A file hidden with `git update-index --skip-worktree` and present on disk is in the commit's
27+
scratch again, as the commit holds it. The sparse-checkout fix below marked every entry
28+
`ls-files -t` tags `S` as skipped in the scratch, but the bit has a second writer: the user,
29+
keeping local edits out of `git status` in a full clone. Such a file was left out of the tree
30+
the suite ran on, so a test that read it failed and the commit blocked on a file it holds.
31+
Only the entries absent from the working tree, a sparse checkout's, stay skipped (check (c)
32+
of the 2026-10-19 review fixes, ADR-0183 amended).
33+
- A spaced quote in Chinese or Japanese - `[quote: 每月 20 美元]`, `月額 1,980 円`,
34+
`API 料金 limits` - is checked against its capture again. The weight that let an elided quote
35+
of full sentences be checked replaced the count on spaces, and such a fragment weighs one
36+
word and a few characters, so it warned `quote-too-short` and was never compared: a quote
37+
with an altered figure passed with that warning instead of failing `quote-not-found`. A
38+
fragment is now short only when it is short by both counts, so every quote checked before
39+
the weight is checked still (second review of the 2026-10-19 sweep, ADR-0162 amended).
40+
- Two `collect-remote` tests no longer fail for a contributor whose temp folder holds the word
41+
"token" (`C:\...\gittoken\...`). They proved that a refusal came before the token check by
42+
the first line's lack of "token", and the `--out` refusal names the folder; they now assert
43+
that the first line is the refusal itself. README's long-path row names `git clone` and
44+
`checkout` too, where a deep folder first meets `MAX_PATH`, and the one-clone
45+
`git clone -c core.longpaths=true` (break-test pass 9, Windows).
46+
- A browser render no longer leaves its profile or its net log in the temp folder. Chromium
47+
ran without `--user-data-dir`, so each render left a `HeadlessChrome*` profile there - killed
48+
or not, on the operator's PC - and a guard child the transport killed at its allowance never
49+
removed its `rk-netlog-*` folder: 1841 profiles (656 MB) and 72 net-log folders had built up
50+
in `%TEMP%` there. Each render now has one `rk-render-` folder, made by the transport,
51+
holding the profile (`--user-data-dir`) and the net log; the guard child removes it, and the
52+
transport removes it again once the child is gone, however it ended. A removal Windows holds
53+
is retried for about 1.6 s, and a folder that will not go is left without changing the
54+
render's result (ADR-0185, break-test of the 2026-10-19 review, B1). Folders already in the
55+
temp folder stay; remove them while no headless Chrome is running, e.g.
56+
`rm -rf "$LOCALAPPDATA"/Temp/HeadlessChrome* "$LOCALAPPDATA"/Temp/rk-netlog-*` on Windows.
57+
- A commit that removes a project's `research/` is judged by #312's rule again. `rm -rf research
58+
&& git add -A` at the top level left no gate marker, so the gate answered not-gated and the
59+
commit took the corpus and turned the gate off with no record; `git rm -r research`, with the
60+
gate's own ignored `.diagnostics.jsonl` left on disk, was told "no part of research/ is
61+
tracked" and a `git add research/` that exits 128; and below the top level, removing
62+
`packages/a/research` while `packages/a`'s code stayed was allowed. Each now blocks naming the
63+
ledger HEAD holds, with `git checkout HEAD --` as the fix and `research/GATE_OFF` as the recorded
64+
override. A project below the top deleted or moved whole is not judged where it was, as before
65+
(ADR-0184; review of the 2026-10-19 sweep).
66+
- The commit's scratch repository, where the suite rule runs the suite, builds in a sparse
67+
checkout of a partial clone and in a repository with Git LFS. The build dropped the sparse
68+
checkout's skip-worktree bits and asked for every blob outside the cone, which a blobless
69+
clone never fetched, so every kit commit there blocked; it now keeps the bits and skips those
70+
files, as the working tree does. Its checkout ran git-lfs's smudge filter, which made a
71+
network request from the commit hook for an object the clone did not hold and then blocked;
72+
the build now leaves LFS pointers in place (`GIT_LFS_SKIP_SMUDGE=1`, for the build only). A
73+
scratch that cannot be built quotes git's `fatal:` or `error:` line, not a progress line
74+
(review of the 2026-10-19 sweep, ADR-0183 amended).
75+
- A builder request whose search failed or found nothing on keyless search or SearXNG is
76+
recorded `failed`, as #321 meant, not `collected` with 0 pages and "pull and read the
77+
captures". The run wrote its usage row only for a page spent or a search a provider reported,
78+
and those routes report none, nor does any provider on a failed ask, so auto-collect never
79+
read the search's outcome. A search outcome now writes the row on its own; it carries
80+
`searchesUsed: 0` and moves no meter (review of the 2026-10-19 sweep).
81+
- `researcher-release` refuses a flag its subcommand does not read, an argument no flag takes
82+
and a flag given twice (all but `--file`) by name, exit 2, before any file is read.
83+
`conform --fiel a.json` had been stored and never read, so every file under the root was
84+
judged; a stray word was dropped; and a repeated `--schema` or `--root` was replaced by the
85+
last, in silence - the contract #286 and #289 gave the other CLIs (review of the 2026-10-19
86+
sweep, F2).
87+
- An elided quote in Japanese, Chinese or another script without word spaces is checked
88+
against its capture again (review of the 2026-10-19 sweep). The short-quote floor, measured
89+
per fragment since ADR-0162, counted words on spaces, so a Japanese sentence was one word
90+
and under 40 characters: two full sentences joined by an ellipsis warned `quote-too-short`
91+
and were never compared with the capture, and one with an invented figure passed with that
92+
warning instead of failing `quote-not-found`. A fragment is now weighed as `similarity.mjs`
93+
weighs text (ADR-0176), a character of such a script with its marks counting a third of a
94+
word, so nine characters reach the three-word floor. A quote in any other script is judged
95+
as before (ADR-0162 amended).
96+
- `researcher-release validate` names a ledger record, an envelope or a pointer that canonical
97+
JSON refuses (an unpaired surrogate, the escape `\ud800`) instead of crashing. The #340 fix
98+
guarded the ledger's payload hash but not the record hash on the next line or the record
99+
schema's const/enum comparison, and the envelope's record hash and the pointer's hashes were
100+
never guarded, so such a line ended `validate`, with or without `--ledger`, with a stack, exit
101+
1 - FAIL's code - and nothing on stdout. Each is now the finding of the hash it has no
102+
canonical bytes for (`LEDGER-RECORD-HASH`, `ENV-RECORD-HASH`, `PTR-LINK`, `PTR-HASH`, FAIL;
103+
`PTR-SIGNATURE`, INCOMPLETE) or `LEDGER-SCHEMA-SCHEMA-CANONICAL` (FAIL), and a throw left in
104+
validation is `VALIDATE-ERROR`, INCOMPLETE, exit 2, with a report, as in `path-authority`
105+
(review of the 2026-10-19 sweep).
106+
- The commit hook finds a kit checkout below the top level under `git --literal-pathspecs
107+
commit` too. That option hands the hook `GIT_LITERAL_PATHSPECS=1`, the hook's glob for the
108+
tracked runners then matched nothing, and the watchdog stayed at 120 s - so the suite the gate
109+
still ran was killed and the commit decided by posture, allowed on a fail-open machine. The
110+
hook's one `git ls-files` now sets `GIT_LITERAL_PATHSPECS` and `GIT_NOGLOB_PATHSPECS` to 0, as
111+
the gate's own lookup already dropped them (review of the 2026-10-19 sweep).
112+
- A builder request whose id holds `u-<digits>` (`fact-u-9`, `u-3-pricing`) no longer blocks
113+
its project. The collector tags the plan entries it adds `U-1 request fact-u-9`, and #291's
114+
check read the id's `u-9` as an unknown the contract does not declare: `research` refused the
115+
project's own plan, dry run included, until the `why` was edited by hand, and auto-collect's
116+
run exited before its accounting, so the request was recorded failed, its pages were charged
117+
to the daily cap and the collector paused. A tagged `why` is now read by its tag, for the
118+
plan check and for the closed-unknown search skip alike (review of the 2026-10-19 sweep).
119+
- `disclosure --topic "<text>"` looks for the subject again. Merging #303 with #286/#289 left
120+
a second `checkFlagValues` call that did not name `--topic`, and a flag a call does not name
121+
takes no value, so every topic - `--topic x` or `--topic=x`, as README and `collect.yml` spell
122+
it - was refused with exit 3, and `--topic no` (`false`, `0`, `off`) or `--topic yes` probed
123+
without the subject, "not checked". One call now names `--repository`, `--run` and `--topic`
124+
(review of the 2026-10-19 sweep, F1).
8125
- The commit gate's suite rule runs the suite on the commit, not the working tree. It had run
9126
the checkout's `bin/selftest.mjs` in place, so an unstaged red edit blocked a green commit (a
10127
25-40 minute run on the operator's PC), and a red file staged and then fixed only on disk was

‎CONTEXT.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ human; if a decision is genuinely wrong, write a new ADR that supersedes it.
7878

7979
| Term | Meaning |
8080
|---|---|
81-
| **gated project** | A project holding any one **gate marker**. |
81+
| **gated project** | At the repository's top level, a project holding any one **gate marker**. Below the top level, a folder holding `research/DISCOVERY.md` and either `research/raw/` or a contract `new-project` rendered (ADR-0182). A commit that removes the evidence HEAD holds for a project is judged as one even with no marker left on disk, unless the project is below the top level and keeps no tracked path - deleted or moved whole (ADR-0184). |
8282
| **gate marker** | One of exactly four artifacts: `research/DISCOVERY.md`, `research/plan.json`, `research/EVIDENCE.md`, `research/raw/`. Declared once, in `research-kit/lib/scaffold.mjs`; the predicate derives from it. |
8383
| **gate verdict** | The single answer to *is this project gated, and does it pass?* — `evaluate()` in `research-kit/lib/gate.mjs`, consumed by all three callers so they cannot disagree. |
8484
| **verdict context** | The unit the verdict reads: the research corpus, the machine-side gate state, and the operator's evidence policy. Owned by `verdictContext()` in `research-kit/lib/preflight.mjs` — preflight, doctor, and the gate path all consume it, so the same snapshot cannot yield a different verdict. |
@@ -167,7 +167,7 @@ Added 2026-09-21 under ADR-0032. The vocabulary of the ZIP that leaves this kit.
167167
| **structural refusal** | A ZIP fault decided from the **central directory**, before one byte is inflated: traversal, absolute path, drive letter, backslash, NUL, duplicate, case collision, symlink, entry count, declared size, compression ratio. The directory is attacker-controlled too, so `inflateRawSync` also runs with `maxOutputLength` — the ceiling belongs to the runtime rather than to a field the attacker wrote. |
168168
| **transport, not storage** | What an artifact is. Workflow artifacts last at most 90 days on a public repository and vanish with the run that produced them, so anything that must persist is committed instead. |
169169
| **source** (of a capture) | The text a capture's Markdown was converted from - Firecrawl's `rawHtml`, the keyless transport's decoded HTML, the browser's rendered DOM - kept LF-folded beside the capture as `<capture>.source.html` and named in the fetch's ledger entry (`source`, `sourceSha256`), where it is verified like the capture (ADR-0140). A verbatim text or JSON capture has none, being its own source. Not a capture: nothing cites, grades or exports it; it exists so that a conversion defect can be repaired after the fact. |
170-
| **net log** | Chromium's own record of every request it made during a render (`--log-net-log`), which the browser transport reads for the origin's HTTP status and the page's final URL, because `--dump-dom` reports neither (ADR-0137). The guard child has it written to a private folder and deletes it after the read; it never reaches the corpus. A render whose log names no response for the page is **status not observed**: the capture is graded `partial` saying so, and the status is never guessed from the page's text. |
170+
| **net log** | Chromium's own record of every request it made during a render (`--log-net-log`), which the browser transport reads for the origin's HTTP status and the page's final URL, because `--dump-dom` reports neither (ADR-0137). The guard child has it written to the render's folder - which the transport makes, which holds the browser's profile too, and which is removed after the read and again by the transport once the child is gone (ADR-0185); it never reaches the corpus. A render whose log names no response for the page is **status not observed**: the capture is graded `partial` saying so, and the status is never guessed from the page's text. |
171171
| **corroboration** | How many INDEPENDENT readings a closed unknown rests on, and the thirteenth check. Three shapes: one row (`single-source`), several rows from one host (`one-voice`), several hosts (`independent`). The middle one is the distinction a count cannot make — two pages from one vendor catch a misreading and cannot catch the vendor being wrong about itself. It reports and never fails on its own: a vendor's own reference is the authority on that vendor, and demanding a second opinion about it would be ceremony. `evidencePolicy=strict` is where an operator says otherwise. |
172-
| **suite rule** | The commit gate's check in the kit's own checkout only (ADR-0120): a commit staging a path under `research-kit/` is allowed when `bin/selftest.mjs`, run by the gate and read through its result file, reports no failures. The checkout is recognised by four markers (`research-kit/lib/core.mjs`, `bin/gate.mjs`, `bin/selftest.mjs`, `test/`); a scaffolded project has none and never pays. An unsupported test is not red; a runner that produced no result is a block; a run past the **suite budget** is a block; a docs-only commit owes nothing. Overridden by `git commit --no-verify`, which nothing records: git skips the hook that would. The suite budget is 65 minutes - twice the slowest honest run measured on a supported host - or, when the operator sets `RESEARCH_KIT_GATE_TIMEOUT`, that gate watchdog less 120 s, never more than half of one under 240 s (ADR-0142). |
172+
| **suite rule** | The commit gate's check in the kit's own checkout only (ADR-0120): a commit staging a path under `research-kit/` is allowed when `bin/selftest.mjs`, run by the gate and read through its result file, reports no failures. It runs on the commit, not the working tree: in a scratch repository built from the index being committed, so the runner, the tests and the library are the commit's own; a scratch that cannot be built, or an index that changes while the suite runs, is a block (ADR-0183). The checkout is recognised by four markers (`research-kit/lib/core.mjs`, `bin/gate.mjs`, `bin/selftest.mjs`, `test/`), at the top level or below it (#309); a scaffolded project has none and never pays. An unsupported test is not red; a runner that produced no result is a block; a run past the **suite budget** is a block; a docs-only commit owes nothing. Overridden by `git commit --no-verify`, which nothing records: git skips the hook that would. The suite budget is 65 minutes - twice the slowest honest run measured on a supported host - or, when the operator sets `RESEARCH_KIT_GATE_TIMEOUT`, that gate watchdog less 120 s, never more than half of one under 240 s (ADR-0142). |
173173
| **the suite describes the kit, not the machine** | What `bin/selftest.mjs` guarantees before the first test runs (ADR-0123): the run has a scratch home of its own (`HOME`, `USERPROFILE`; `APPDATA`, `LOCALAPPDATA` on Windows), no `RESEARCH_KIT_*` variable but the ones that describe the host or the run (the result file, the unsupported opt-in, the browser, the gate timeout, the live opt-ins, the harness's `RESEARCH_KIT_TEST_*` seams), and no vendor key. A test that spawns a kit command had read the operator's machine config, deployed kit, CLI login and keys, so a `strict` posture config turned 18 tests red and a Firecrawl CLI installed and not logged in two more - a verdict about the machine, not the kit. A test that wants a config, a deployed kit or a key makes its own. |

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,7 @@ Environment variables take precedence where one exists: `RESEARCH_KIT_TRANSPORT`
643643
| the run stops with `credits ran out` and exits 2 | the Firecrawl account is empty | top up and run the same command, or run it with `--fallback` to finish on the free transports; `research --status` shows the budget |
644644
| `research.mjs` or `decompose.mjs` exits 2 with "builder" | this machine is declared a builder | collect on the collector, or `install-hooks.mjs --role collector` |
645645
| tests print `UNSUP PYTHON-NOT-FOUND` and block | no Python 3.11+ | install it; a green suite without it would claim two languages agree while testing one |
646-
| Windows: `git add` refuses with a long-path error | `MAX_PATH` | `git config core.longpaths true` |
646+
| Windows: `git clone`, `checkout` or `add` fails with `Filename too long` | `MAX_PATH` (260): the clone's folder plus the repository's longest paths, under `docs/decisions/`, pass it | clone into a short folder, or `git clone -c core.longpaths=true ...`; in an existing clone, `git config core.longpaths true` |
647647
| the commit gate blocks with "is not staged with them" | in this repository, a declared code path changed without `docs/ARCHITECTURE.md` | update the map in the same commit |
648648

649649
## Supported platforms

0 commit comments

Comments
 (0)