Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
a9103ab
docs: glossary, map and ADR index say what the sweep's code does
StepenkoAnatoli Oct 19, 2026
1ee9258
disclosure: --topic is the subject to look for again, in either spelling
StepenkoAnatoli Oct 19, 2026
a84478f
research-run: a request's tagged why is read by its tag, not its id
StepenkoAnatoli Oct 19, 2026
541232e
hook: find a kit checkout below the top level under literal pathspecs
StepenkoAnatoli Oct 19, 2026
a00d8f5
release: validate names a record canonical JSON refuses, never a stack
StepenkoAnatoli Oct 19, 2026
823beda
quotes: an elided CJK quote is checked, its characters weighed as words
StepenkoAnatoli Oct 19, 2026
937d83f
researcher-release: unknown, repeated and stray arguments are refused
StepenkoAnatoli Oct 19, 2026
c2f4437
research-run: a search outcome writes the usage row on its own
StepenkoAnatoli Oct 19, 2026
9e1401a
gate: build the commit's scratch in sparse partial clones and with LFS
StepenkoAnatoli Oct 19, 2026
418f3c4
gate: a commit that removes HEAD's evidence is judged, unless its pro…
StepenkoAnatoli Oct 19, 2026
d5a0a85
browser: a render's profile and net log go with it, killed or not
StepenkoAnatoli Oct 19, 2026
070a04d
test, docs: a temp folder named "...token..." no longer fails cli.tes…
StepenkoAnatoli Oct 19, 2026
9e8d514
quotes: a spaced CJK quote is checked again, short only by both counts
StepenkoAnatoli Oct 19, 2026
bb09c5a
gate: a skip-worktree file present on disk is in the commit's scratch
StepenkoAnatoli Oct 19, 2026
5cb0b33
researcher-release: constructor or toString as a subcommand is the us…
StepenkoAnatoli Oct 20, 2026
1a83372
gate: the top level's research/ moved whole below it is a move, not a…
StepenkoAnatoli Oct 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,123 @@ Each release of Research-Kit, newest first. The version is `KIT_VERSION` in

## Unreleased

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

| Term | Meaning |
|---|---|
| **gated project** | A project holding any one **gate marker**. |
| **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). |
| **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. |
| **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. |
| **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. |
Expand Down Expand Up @@ -167,7 +167,7 @@ Added 2026-09-21 under ADR-0032. The vocabulary of the ZIP that leaves this kit.
| **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. |
| **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. |
| **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. |
| **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. |
| **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. |
| **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. |
| **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). |
| **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). |
| **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. |
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ Environment variables take precedence where one exists: `RESEARCH_KIT_TRANSPORT`
| 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 |
| `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` |
| 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 |
| Windows: `git add` refuses with a long-path error | `MAX_PATH` | `git config core.longpaths true` |
| 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` |
| 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 |

## Supported platforms
Expand Down
Loading
Loading