Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ced1912
fix(dispatch): validate repository path components
StepenkoAnatoli Oct 12, 2026
9a1459c
fix(dispatch): refuse multiline collection items
StepenkoAnatoli Oct 12, 2026
d374f61
fix(collect): redact rejected URL diagnostics
StepenkoAnatoli Oct 12, 2026
822fe1a
fix(dispatch): diagnose non-JSON dispatch responses
StepenkoAnatoli Oct 12, 2026
dd80242
fix(runtime): reject non-origin proxy URLs
StepenkoAnatoli Oct 12, 2026
cb23853
fix(doctor): respect disabled proxy startup choices
StepenkoAnatoli Oct 13, 2026
e41fcad
fix(machine): reject non-object config snapshots
StepenkoAnatoli Oct 13, 2026
4882744
fix(collect): validate client references before dispatch
StepenkoAnatoli Oct 13, 2026
369c361
fix(download): resolve output folders before returning links
StepenkoAnatoli Oct 13, 2026
319c3ad
fix(mcp): report invalid downloads as validation failures
StepenkoAnatoli Oct 13, 2026
38ca79b
fix(transport): refuse inherited registry names
StepenkoAnatoli Oct 13, 2026
bda0319
fix(timeline): keep multiline errors in one table row
StepenkoAnatoli Oct 13, 2026
fe7f338
fix(selftest): refuse every unmatched test group
StepenkoAnatoli Oct 13, 2026
dee72a6
fix(download): isolate default package paths and refuse links
StepenkoAnatoli Oct 13, 2026
cbc6e3c
fix(hooks): include configured hooks in scoped reads
StepenkoAnatoli Oct 13, 2026
1dae0ab
fix(doctor): shape install records before reading paths
StepenkoAnatoli Oct 13, 2026
211caab
fix(artifact): require provenance from packaged evidence
StepenkoAnatoli Oct 13, 2026
aa1d673
fix(evidence): restrict recorded capture reads to the raw folder
StepenkoAnatoli Oct 13, 2026
dfaf34c
fix(artifact): rederive package approval from contents
StepenkoAnatoli Oct 13, 2026
b439cf9
fix(dispatch): diagnose GitHub rate-limited responses
StepenkoAnatoli Oct 13, 2026
7a73d9d
fix(config): distinguish held snapshots from broken sources
StepenkoAnatoli Oct 13, 2026
63e0e52
fix(artifact): reject invalid format versions as malformed
StepenkoAnatoli Oct 13, 2026
87652bf
fix(artifact): flush piped reports before exit
StepenkoAnatoli Oct 13, 2026
6295179
fix(artifact): keep hostile metadata on one report line
StepenkoAnatoli Oct 13, 2026
40f7f7f
fix(artifact): refuse file and directory path collisions
StepenkoAnatoli Oct 13, 2026
a1d020e
Merge main into the selftest group fix
StepenkoAnatoli Oct 13, 2026
5f68082
docs(adr): repair links and check decision bodies
StepenkoAnatoli Oct 13, 2026
6203f9b
Fix FI sidecar array index regex so Node accepts indices past 0
jay23606 Oct 14, 2026
0dc3ff0
merge: integrate verified collection and artifact fixes
StepenkoAnatoli Oct 14, 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
85 changes: 85 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,91 @@ Each release of Research-Kit, newest first. The version is `KIT_VERSION` in

## Unreleased

- The legacy approval compatibility regression keeps its historical hash reference
on the recorded probe date while exercising fresh approval packages, so crossing
midnight does not invalidate the test's reference assertion.
- FI sidecar conformance accepts array indices after zero in both patch traversal
and removal; a synthetic two-entry packet pins Node/Python agreement (#337).

- ADR body links are checked by the existing hygiene test, ADR-0121 links resolve,
and a current guide explains unshipped artifacts named by dated plans (#292).

- GitHub request helpers refuse repository components outside `[A-Za-z0-9._-]+`,
including `.` and `..`, before any request is made (#379). Query/fragment
delimiters, encoded separators, whitespace and control characters cannot alter
the API path.

- Each collection query and URL is refused when it contains CR or LF before trimming
or joining into workflow input (#376). MCP and `collect-remote` cannot turn one
declared item into extra searches or page fetches on the runner.

- Rejected collection URLs pass through the existing credential redactor before
MCP tool results or `collect-remote` stderr can echo them (#378). Regression
coverage includes caller credentials in URL userinfo and query parameters.

- Dispatch responses with a successful HTTP status and a non-JSON body now report
`BAD_BODY`, with the existing proxy/maintenance-page remedy (#377). `NO_RUN_ID`
remains reserved for JSON responses without an integer workflow run ID.

- Proxy URLs containing a non-root path, query or fragment take the existing
invalid-proxy path rather than enabling Node proxy support and crashing in
Undici (#380). Requests remain direct, the existing diagnostic names the
variable and accepted spelling, and `doctor` warns. A bare trailing slash stays
valid.

- `doctor` warns when an explicit Node startup choice leaves a configured proxy
off (#381): `NODE_USE_ENV_PROXY` accepts only `1`, and `--no-use-env-proxy`
disables it. The diagnostic shares the runtime choice reader and respects
command-line flag precedence over `NODE_OPTIONS` and the environment variable;
it never overrules the operator or echoes the variable value.

- Machine config snapshots accept only non-array JSON objects (#368). Scalar or
array configs cannot create or replace a good snapshot; unreadable configs with
unusable backups fall back to fail-closed and role unknown.

- MCP collect and collect-remote refuse unsafe client references before dispatch
(#375), using the existing shared identifier rule. The CLI also refuses a bare
--client-ref, and the checker rejects trailing line breaks.

- Corpus download folders resolve to absolute paths before writes and links (#372),
so relative MCP out_dir values return a link to the file actually saved.

- MCP fetch_corpus renders non-PASS validation findings and remedies in its text
result and omits the resource link instead of describing corrupt or unsupported
downloads as a collected corpus awaiting review (#371).

- Fetch and search registries refuse inherited object members as provider names
at every precedence level (#349), before a run or doctor can treat them as adapters.

- Timeline details fold multiline errors into one table row before pipe escaping
(#354). Regenerated the root timeline without changing its provenance ledger.

- Scoped hooksPath reads expand Git include and includeIf directives, preserving the previous hooks folder through installation and removal (#369).

- selftest refuses each unmatched group before starting tests, including when another requested group matches (#332).

- Default corpus downloads use a fresh private temp folder and refuse symlinks at the package filename, preventing shared-name overwrites and redirected writes (#373).

- Install-state reads reject non-object records and ill-typed paths; doctor reports a named deploy warning instead of a raw path stack (#365).

- Artifact ledger checks follow actual capture entries and evidence rows, even when the manifest relabels captures and declares zero; genuinely empty failed-collection scaffolds remain valid (#358).

- Evidence context refuses Raw cells outside research/raw and names directories before reading; the ADR-0109 trigger also moves corpus and artifact recorded-path readers to projectFile (#351).

- Artifact validation no longer trusts an `APPROVED_BRIEF` manifest by itself (#357, ADR-0160). It re-derives approval from checked package contents with a portable baseline, preserves false claims, and accepts only verified format-1 pre-0069 stamp hashes.

- GitHub rate-limited 403 responses from dispatch and run reads report the wait and reset time instead of a token-permission remedy (#374).

- Broken machine-config diagnostics name the actual config and distinguish settings held by a last-good snapshot from fail-closed fallback (#366).

- Missing, malformed and below-oldest artifact format versions are invalid manifests; only newer numeric majors receive the upgrade remedy (#361).

- The artifact CLI drains stdout and stderr before ending its reports, preserving large piped JSON and text results on POSIX (#359).

- Artifact validation hides malformed manifest metadata and escapes control characters in text diagnostics, so package-supplied strings cannot forge verdict lines (#360).

- `artifact validate` rejects ZIP entries where a file is also an ancestor directory of another entry, before extraction, with a stable `ZIP-PATH-COLLISION` failure (#362).

- The README opens with an accessible workflow preview and linked CI, license,
last-commit and stars badges. A contributor guide explains setup, verification,
issue labels and the existing commit/PR protocol.
Expand Down
35 changes: 18 additions & 17 deletions docs/ARCHITECTURE.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ its own tests. Moving them is a refactor of working, tested code with no defect
**Trigger that ends the deferral:** a new reader of recorded paths, or a defect in any of
those three, is written against `projectFile`, and the other two move with it.

Implementation (2026-10-13, #351): the evidence-context private-file defect fired the
deferral's trigger. Its Raw reader, corpus capture/Raw checks, and the artifact Raw/ledger
readers now use `projectFile`. Capture reads also stay inside the capture folder by real
path, and missing or non-file inputs are named before any content read.

## Rejected alternatives

- **Move every reader now.** Three working readers rewritten in one change, with no defect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
- **Status:** accepted
- **Area:** `lib/machine.mjs` (`shape`, `readMachineConfig`, `posture`), `lib/doctor.mjs`
(`config-ill-typed`), `githooks/pre-commit` (the sh posture reader)
- **Refines:** [ADR-0020](0020-the-machine-config-has-three-states.md) and
[ADR-0023](0023-a-role-the-kit-cannot-establish-does-not-collect.md), one level down
- **Refines:** [ADR-0020](0020-five-trusted-boundaries-that-did-not-hold.md) and
[ADR-0023](0023-an-unestablished-role-does-not-collect.md), one level down
- **Lifts the freeze (ADR-0117)** for one item: the `config-ill-typed` doctor finding.

## Context
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# ADR-0160 - Package approval is re-derived with a portable baseline

- **Date:** 2026-10-13
- **Status:** accepted
- **Area:** artifact approval derivation and validation
- **Refines:** ADR-0032; retains ADR-0069 and ADR-0138 compatibility
- **Defect:** #357, qualified gap-audit rank 2

## Evidence

The [decision research](../decisions/2026-10-13-portable-artifact-authorization/research/BRIEF.md)
has nine pinned first-party source captures and a verified ledger. Its three unknowns
are closed and its own preflight passes. The retained profile probe uses explicitly
synthetic passing corpora with the actual historical writer at
`3bac1f26ddbf8cb6a794626e53dc2af313860940`, plus the current writer at `3fa03fa`.
These are historical-format fixtures generated by those real writers, not archived user
corpora. Both the unstamped and early-stamped writer outputs are format 1.0.0 and claim
approval. The early stamp's historical input hash matches its covered inputs but differs
from the current hash; applying only current producer currency rules rejects that valid
historical profile. The current writer's stamp explicitly contains `gate=unknown` while
the early one has no gate field, even though `draftStamp` reports unknown for both.

## Decision

- Move the existing approval derivation to one shared module so the producer and
validator use the same gate, map, findings and authored-brief rules without an import
cycle. The producer keeps its current API and machine-specific preflight behavior.
- For a package claiming approval, the validator materializes the already checked
project bytes and re-derives approval with an explicit portable context. It uses the
shared unpromoted pluralist findings and default 180-day freshness horizon, never
consumer policy, hooks or overrides. Report that baseline and its warning findings.
- A package declaring authorization false is never upgraded. Invalid bytes, schema or
provenance still prevent approval before any derivation can authorize a build.
- Current matching stamps retain their normal currency check. Authored unstamped
briefs retain ADR-0138's approval path with unknown currency disclosed.
- Only format major 1 may use pre-0069 stamp compatibility: the last valid literal
marker must have no gate field, and its inputs must match the exact historical hash
over topic, intent, unknowns and evidence rows. Report this compatibility explicitly.
Current gate, map, findings and authored-section checks still run on that corpus.
A mismatch in the historical covered inputs is not accepted, and an explicit
`gate=unknown` marker does not qualify as a missing-field legacy marker.
- Resolve a blank Raw cell through `captureOf`, as the shared corpus resolver does.
An unresolved or unreadable row is not reviewed; extractor-identical findings remain
negative evidence of an unreviewed row. This does not grade semantic correctness.

## Rejected alternatives

- **Use the consumer's normal preflight context.** The manifest carries neither the
producer's strict policy nor its custom freshness horizon; a consumer policy or hook
cannot establish the producer's exact verdict and makes authorization nonportable.
- **Reject every old input hash.** The actual historical writer probe produces a valid
early approval that fails that test solely because ADR-0069 expanded the hash.
- **Ignore all historical stamps.** Comparing the historical covered inputs preserves
a check that would otherwise disappear; current and legacy mismatches remain refusals.
- **Compare false claims to an approving derived state and upgrade them.** A conservative
producer declaration remains conservative; the validator cannot supply approval the
incoming package explicitly withheld.

## Consequences and trigger

No package format or public CLI option changes. The result's existing warning channel
discloses the portable baseline and legacy compatibility. This checks consistency with
the packaged corpus rather than establishing who reviewed it; ADR-0032's self-attested
provenance and ADR-0013's semantic-review boundary remain in force. A future format with
an explicit policy or a new stamp profile must define its own derivation/compatibility
rules before this legacy exception is extended beyond major 1.
2 changes: 2 additions & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,6 @@ directory and are treated as ADR-equivalent.
| [0158](0158-a-small-core-extraction-is-a-proposal-not-a-plan.md) | **A small-core extraction is a proposal, not a plan.** The 2026-10-10 audit's core boundary sketch (93 files, 25,565 physical lines at that head; release-evidence validation, the panel and optional surfaces as candidates for separate distribution) stays a proposal until the ADR-0145 results exist and a measured dependency review of the seam is recorded under `docs/decisions/` (ADR-0030). No boundary, command, distribution or freeze exception adopted. Rejected: extracting the release layer now, rejecting the proposal, keeping it only in working notes. |
| [0159](0159-the-22-line-starts-at-22-22-1-because-earlier-builds-lose-windows-1252.md) | **The 22 line starts at 22.22.1, because 22.13.0 to 22.22.0 lose windows-1252.** Those builds' `TextDecoder` reads `windows-1252` as Latin-1 (nodejs/node#60888), so a page's euro signs and curly quotes are captured as control characters with no warning; `nodeLine` reports them as `fail` naming the defect and the first good build, the README says 22.22.1+ or 24+, the hard floor every command refuses below stays at 22. Rejected: a table decoder for 0x80-0x9F behind a runtime probe, refusing those builds in `checkNode`, documenting only. |

| [0160](0160-package-approval-is-rederived-with-a-portable-baseline.md) | Claimed package approval is re-derived from its checked corpus with an unpromoted portable gate; false claims remain false, and major-1 early stamps require their actual historical input hash and a missing gate field. |

Domain vocabulary lives in [`CONTEXT.md`](../../CONTEXT.md).
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# The corpus's line endings are integrity, not style.
#
# A capture's body hash is taken over the LF bytes the collector fetched. git's smudge
# filter rewrites every text file on a default Windows checkout (core.autocrlf=true), so
# without this file the whole corpus fails `body-unmodified` on arrival with nothing
# actually wrong with it. This file outranks a reader's core.autocrlf. See ADR-0020.

research/raw/* text eol=lf
*.jsonl text eol=lf
research/*.md text eol=lf
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
node_modules/
.DS_Store

# Credentials never belong in a research project (AGENTS.md Rule 6). Note that
# `firecrawl env` writes the key into .env, which is why the rule says never to run it
# inside a repository.
.env
.env.*
*.pem
*.key

# The Firecrawl CLI saves scrape output into .firecrawl/ in whatever directory it runs
# from. That is a vendor cache, not evidence - the kit's own captures live in
# research/raw/ with a ledger entry behind each one.
.firecrawl/

# The kit's local review logs are not evidence.
research/raw/.usage.jsonl
research/raw/.failures.jsonl
research/raw/.diagnostics.jsonl
research/raw/.fetches.lock
# The gate's override log is this machine's record of a bypass (ADR-0081); doctor reads it here.
research/overrides.log

# export-warc's default output (ADR-0090): a copy of the corpus for archive tools, not
# evidence - the captures and the ledger are. A WARC written elsewhere with --out is yours.
research-corpus.warc.gz

# The chain IS evidence and must travel. The explicit negation is here so a broader
# dotfile rule added later cannot silently exclude it - this repository lost its ledger
# exactly that way once.
!research/raw/.fetches.jsonl
Loading
Loading