Skip to content

Commit 2d80715

Browse files
Merge #412: refuse missing validator flag values
what changed: path-authority and researcher-release refuse missing, empty or next-flag value tokens by name, with usage and exit 2 before resolving paths. why: Invalid CLI input previously reached Node path handling and produced an internal message or TypeError stack. what it touched: Both parsers, their regression tables, architecture descriptions, changelog and the 1,838-test inventory. what you verified: verified - red-first regressions, 40 current focused tests, normal full commit gate accepted 3770719, and all seven CI jobs passed in run 38558206514. Main and PR head checked immediately before merge. what you got wrong and fixed: nothing to report.
2 parents 194bcfb + 3770719 commit 2d80715

7 files changed

Lines changed: 47 additions & 2 deletions

File tree

‎CHANGELOG.md‎

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

66
## Unreleased
77

8+
- `path-authority` and `researcher-release` name a value flag with no value and print usage with exit 2 before resolving paths, including when another flag follows (#288).
9+
810
- `property-replay --all` refuses a missing regression directory with exit 2 and
911
names it; an existing empty directory explicitly reports no captured regressions.
1012
JSON results identify the directory and zero count (#307).

‎docs/ARCHITECTURE.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,14 +277,17 @@ markers - they wrote `research/` into any folder until 2026-09-28, `prior` a PRI
277277
`doctor --fix-arity` and `install` name a write the system refused - `could not write <file>:
278278
<CODE> (why)`, exit 2, via `writeFailure` - where they printed a stack trace, 2026-09-28),
279279
`path-authority.mjs` (offline Git-origin/path-authority snapshot validation and
280-
schema conformance),
280+
schema conformance; refuses a missing value for `--file`, `--schema` or `--root` by
281+
name before reading any input, as a usage error with exit 2),
281282
`researcher-release.mjs` (ported 2026-09-20, ADR-0029 — three subcommands over the two
282283
validators: `validate` for R28–R32 release evidence, `conform` for schema-only checking,
283284
`fi-validate` for an FI bundle. It **duplicates no validation**: every check comes from
284285
`release-validator.mjs` or `fi-validator.mjs` through their public exports. Reads **no
285286
environment variable at all**, so it cannot depend on a credential or a config. Status
286287
maps to exit code — `PASS` 0, `FAIL`/`REOPEN` 1, `INCOMPLETE` 2, `BLOCKED` 3, unknown 4 —
287288
and a **usage error exits 2 from every subcommand**, printing the usage block to stderr;
289+
its argument parser names a value flag without a value before resolving any path,
290+
including when the next token is another flag;
288291
`test/support-policy.test.mjs` pins that by running the CLI. It writes exactly one thing,
289292
only when asked: `fi-validate --report <file>`),
290293
`selftest.mjs` (every requested group must match a test file before any test starts; an unmatched group is named and refused with exit 2, including beside a valid group; #332. CLI regressions isolate child report paths from the parent suite; the whole suite, queued by the harness — which **awaits** every test,

‎research-kit/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,7 @@ node research-kit/bin/selftest.mjs # all of it
568568
node research-kit/bin/selftest.mjs gate hook # just these files
569569
```
570570

571-
1836 tests, offline, no key and no network. The runner **awaits** every test, so `ok` means
571+
1838 tests, offline, no key and no network. The runner **awaits** every test, so `ok` means
572572
the assertions settled (ADR-0021), and each test is raced against a watchdog
573573
(`RESEARCH_KIT_TEST_TIMEOUT`, default 60s).
574574

‎research-kit/bin/path-authority.mjs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ function parse(argv) {
2222
for (let i = 1; i < argv.length; i += 1) {
2323
const arg = argv[i];
2424
if (arg === '--json') { result.json = true; continue; }
25+
if (['--file', '--schema', '--root'].includes(arg)
26+
&& (argv[i + 1] === undefined || argv[i + 1] === '' || argv[i + 1].startsWith('--'))) {
27+
return { error: `${arg} needs a value` };
28+
}
2529
if (arg === '--file') { result.files.push(argv[++i]); continue; }
2630
if (arg === '--schema') { result.schema = argv[++i]; continue; }
2731
if (arg === '--root') { result.root = argv[++i]; continue; }

‎research-kit/bin/researcher-release.mjs‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ function argsAfterCommand(argv) {
2929
for (let i = 1; i < argv.length; i += 1) {
3030
const arg = argv[i];
3131
if (arg === '--json') { result.json = true; continue; }
32+
if (arg.startsWith('--')
33+
&& (argv[i + 1] === undefined || argv[i + 1] === '' || argv[i + 1].startsWith('--'))) {
34+
return { error: `${arg} needs a value` };
35+
}
3236
if (arg === '--file') { result.files ??= []; result.files.push(argv[++i]); continue; }
3337
if (arg.startsWith('--')) { result[arg.slice(2).replaceAll('-', '')] = argv[++i]; continue; }
3438
result._.push(arg);
@@ -47,6 +51,7 @@ function exitFor(status) {
4751
function main(argv = process.argv.slice(2)) {
4852
if (argv.includes('--help') || argv.includes('-h')) { process.stdout.write(`${usage()}\n`); return 0; }
4953
const parsed = argsAfterCommand(argv);
54+
if (parsed.error) { console.error(`${parsed.error}\n${usage()}`); return 2; }
5055
let result;
5156
if (parsed.command === 'validate') {
5257
if (!parsed.root || !parsed.package || !parsed.registry || !parsed.roles || !parsed.pointers) {

‎research-kit/test/path-authority-validator.test.mjs‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,20 @@ test('path-authority validator rejects out-of-root paths, symlink claims, host m
102102
assert(result.errors.some((error) => error.code === 'SNAP-ORIGIN-UNPROVEN'), JSON.stringify(result.errors));
103103
});
104104

105+
test('path-authority names a missing value before validating any input', () => {
106+
for (const flag of ['--file', '--schema', '--root']) {
107+
for (const tail of [[], ['--json']]) {
108+
const args = ['research-kit/bin/path-authority.mjs', 'validate', flag, ...tail];
109+
const run = spawnSync(process.execPath, args, { encoding: 'utf8' });
110+
const output = `${run.stdout}${run.stderr}`;
111+
assertEqual(run.status, 2, `${args.join(' ')}: ${output}`);
112+
assert(run.stderr.includes(`${flag} needs a value`), output);
113+
assert(run.stderr.includes('Usage:'), output);
114+
assert(!/TypeError|paths\[0\]|ERR_INVALID_ARG_TYPE/.test(output), output);
115+
}
116+
}
117+
});
118+
105119
test('path-authority CLI conform and validate are offline, deterministic, and read-only', async () => {
106120
const root = tempRoot();
107121
try {

‎research-kit/test/release-validator.test.mjs‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,23 @@ test('researcher-release validate emits the contract status and exit code withou
188188
}
189189
});
190190

191+
test('researcher-release names a missing value before resolving paths', () => {
192+
for (const flag of ['--file', '--schema', '--root']) {
193+
for (const tail of [[], ['--json']]) {
194+
const args = [
195+
'research-kit/bin/researcher-release.mjs', 'conform', '--schema', 'missing-schema.json',
196+
'--file', 'missing-record.json', flag, ...tail,
197+
];
198+
const run = spawnSync(process.execPath, args, { encoding: 'utf8' });
199+
const output = `${run.stdout}${run.stderr}`;
200+
assertEqual(run.status, 2, `${args.join(' ')}: ${output}`);
201+
assert(run.stderr.includes(`${flag} needs a value`), output);
202+
assert(run.stderr.includes('Usage:'), output);
203+
assert(!/TypeError|paths\[0\]|ERR_INVALID_ARG_TYPE/.test(output), output);
204+
}
205+
}
206+
});
207+
191208
test('researcher-release conform validates explicit files and maps schema failure to exit 1', async () => {
192209
const root = tempRoot();
193210
try {

0 commit comments

Comments
 (0)