Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 13 additions & 4 deletions .github/workflows/collect.yml
Original file line number Diff line number Diff line change
Expand Up @@ -410,15 +410,24 @@ jobs:
# than failing, because the ladder's last rung is the fetch provider.
SERPAPI_API_KEY: ${{ secrets.SERPAPI_API_KEY }}
RESEARCH_KIT_TRANSPORT: firecrawl-cli
# 'auto' becomes the empty string, which is falsy in the ladder and therefore
# means "no explicit choice" rather than "a provider called auto". Pinning a
# value is what makes two runs of the same topic comparable.
RESEARCH_KIT_SEARCH_TRANSPORT: ${{ inputs.search_transport == 'auto' && '' || inputs.search_transport }}
# NOT a ${{ cond && '' || value }} ternary. In GitHub Actions the empty string
# is FALSY, so the true-branch collapses and the expression yields the third
# operand - it passed the literal "auto", which is not a provider, and every
# default dispatch failed with "unknown search provider". The input is carried
# raw and blanked in the shell below, where an empty string is just an empty
# string.
SEARCH_TRANSPORT_INPUT: ${{ inputs.search_transport }}
MAX_PAGES: ${{ inputs.max_pages }}
DEPTH: ${{ inputs.depth }}
run: |
set -e
cd "$SCRATCH"
# 'auto' means "no explicit choice": leave the variable unset so the ladder
# falls through to its own detection. Any other value pins the provider.
if [ "${SEARCH_TRANSPORT_INPUT:-auto}" != "auto" ]; then
export RESEARCH_KIT_SEARCH_TRANSPORT="$SEARCH_TRANSPORT_INPUT"
echo "search side: pinned to $SEARCH_TRANSPORT_INPUT by dispatch input"
fi
# Which side won, recorded in the log before anything is spent. A run that
# silently changed meters would be the kind of thing nobody notices until a bill.
if [ -n "${SERPAPI_API_KEY:-}" ]; then
Expand Down
2 changes: 1 addition & 1 deletion research-kit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ node research-kit/bin/selftest.mjs # all of it
node research-kit/bin/selftest.mjs gate hook # just these files
```

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

Expand Down
29 changes: 29 additions & 0 deletions research-kit/test/workflow-embedded-scripts.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,32 @@ test('the extractor finds a deliberately broken script, so the check is not asle
assert.equal(found.length, 1, 'the extractor did not find an obvious embedded script');
assert.throws(() => new vm.Script(found[0].body), 'a broken script compiled cleanly, so the parse check proves nothing');
});

test('no workflow uses the GitHub Actions falsy-ternary footgun', () => {
// `${{ cond && '' || value }}` does NOT mean "empty when cond". In GitHub Actions the
// empty string is falsy, so the true-branch collapses and the expression yields the
// THIRD operand. collect.yml used it to blank an 'auto' input and instead passed the
// literal string "auto" into RESEARCH_KIT_SEARCH_TRANSPORT, so every DEFAULT dispatch
// died with `unknown search provider "auto"`.
//
// It survived review because the comparison runs that exercised the feature all pinned
// an explicit provider. The default path - the one almost every real dispatch takes -
// was never run until a research collection tried it.
// COMMENT LINES ARE SKIPPED, and finding that out is why this comment exists: the first
// version of this guard flagged the sentence above, which describes the footgun in order
// to warn about it. A check that cannot tell a value from prose about the value reports
// the documentation as the defect.
const dir = path.join(KIT_ROOT, '..', '.github', 'workflows');
const offenders = [];
for (const file of fs.readdirSync(dir).filter((f) => f.endsWith('.yml'))) {
const lines = fs.readFileSync(path.join(dir, file), 'utf8').split(/\r?\n/);
lines.forEach((line, i) => {
if (line.trim().startsWith('#')) return;
for (const m of line.matchAll(/\$\{\{[^}]*&&\s*''\s*\|\|[^}]*\}\}/g)) {
offenders.push(`${file}:${i + 1}: ${m[0].trim()}`);
}
});
}
assert.deepEqual(offenders, [],
'a ternary whose true-branch is the empty string yields its third operand instead');
});
Loading