You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit ad9111e
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: docs/ARCHITECTURE.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -426,6 +426,7 @@ each line — is in the ADR named beside it and in
426
426
| A reviewer note is **not a citation**: `stripReviewNotes` in lib/corpus.mjs removes `[single-witness: ...]` and `[render-reviewed: ...]` before `citedIds` runs. Those notes are prose written INSIDE the same cell the citations are read from, so explaining why no second witness exists - by pointing at a row that demonstrates it - turned that row into a citation, and the unknown read as corroborated while its own note denied it. It caught its author three times in two days across three projects; the guard was right every time and the mechanism was the problem. The raw cell is untouched, because the check that reads the reason needs it | ADR-0038 |
427
427
| A finding a reviewer has **considered and overruled** is recorded, not repeated forever: `[single-witness: …]` in an unknown's Evidence cell and `[render-reviewed: …]` in an evidence row's Finding cell turn a true-but-permanent warning into a pass **that prints the reason**. A reason under 30 characters is named (`single-witness-unreasoned`) rather than obeyed, and a note left on an unknown that has since been corroborated is reported (`single-witness-stale`) so the acknowledgement cannot rot. The render note lives on the ROW because `verifyLedger` hashes the whole capture file — a capture is evidence and stays byte-immutable, a judgement about it is corpus prose |[ADR-0038](adr/0038-a-judgement-the-gate-can-read.md)|
428
428
| The collector lock is exclusive **on a local filesystem, and not promised on a network one**: Node states "the exclusive flag might not work with network file systems", a condition ADR-0020 never carried. Measured 2026-09-22 - 24 racers, 1 winner, 23 EEXIST on win32 - so the guarantee holds where it was tested and nothing detects where it is standing. Evidence: `docs/decisions/2026-09-22-wx-lock/`| ADR-0020 |
429
+
| With a SerpAPI key present the search side asks **BOTH providers and interleaves by rank** (`mergeByRank`, lib/research-run.mjs). The meters are separate, so each is asked once rather than either more. Round-robin is the point: on 2026-09-22 one provider returned eight US financial-regulation pages for a question about EU deforestation and the other returned seventeen on topic, and concatenation would have spent the whole page budget before reaching the working one. Every merged row carries the provider that surfaced it, so the ledger keeps a claim about search quality checkable. An explicitly pinned provider stays single, because pinning exists to make two runs comparable | ADR-0027 |
429
430
| The SEARCH side is **selectable at dispatch**: collect.yml takes a search_transport input (auto | serpapi | firecrawl-cli) and collect-remote.mjs a matching --search-transport, passed as RESEARCH_KIT_SEARCH_TRANSPORT. ADR-0027 built the ladder and the collector never exposed it, so a dispatched run always took whatever auto-detect picked and two providers could not be compared on one topic through the kit at all. auto becomes the EMPTY STRING, falsy in the ladder, so the default is the previous behaviour exactly | ADR-0027 |
430
431
| A **rate limit is an instruction to wait, not a failure**: `rateLimitWaitMs` (lib/firecrawl.mjs) reads the delay the vendor states in its own error text, and `collectOne` sleeps and retries, bounded at two. Firecrawl free tier allows 10 requests a minute and the collector did not throttle, so a run started inside a busy minute shed six of eight fetches and recorded them as ordinary failures. The vendor number is used rather than an invented backoff, for the reason the kit researched for Retry-After: when a server says when to come back, that is the number. Non-rate-limit errors are NOT retried - a 404 does not become a 200 if you wait | ADR-0005 |
431
432
| A collection reports **how much of the topic own vocabulary reached the captures** - `topicMatch` in lib/research-run.mjs, printed by bin/research.mjs right after collecting and deciding nothing. It exists because a run returned eight captures about US financial regulation for a question about EU deforestation and passed every structural check: no check asks whether the evidence is about the topic. Two thresholds were measured and REJECTED - per-capture 0.5 flags RFC 9728 in the agent-interface corpus, which never says MCP and is an independent witness precisely because it does not; per-corpus at-least-one-strong flags delivery-architecture, which scores identically to the genuine failure because its topic is internal vocabulary and its evidence is GitHub docs. Telling those apart needs meaning (ADR-0013), so the number is surfaced at the moment a reviewer can act on it and the grade does not move | ADR-0013 |
0 commit comments