8a7d6129 added the external-contract question for every code change that
handles data and updated the pinned tests to match, including the legacy
test "code condensed Edit hint for an unknown profile is unchanged". Its own
evaluation then found the gated arm did not improve (25 to 23 of 48), so the
question has no evidence behind it as a default.
- Restore the test expectations 8a7d6129 changed: questionIdsFor, the
byte-identical code text for src/a.py, the condensed hints, and the hook
question lists default to data-schema without external-contract.
- Add questionIdsFor/condensedHintFor cases with { externalContract: true }.
- Add hook cases: GATEGUARD_EXTERNAL_CONTRACT_QUESTION=1/true/ON asks it
beside data-schema and in the unknown-profile set, never without data;
unset, 0, false and off keep the original four.
Against main: target-class 5 failures, fact-force 10 failures.
Each of these is credited on main, reproduced against upstream 2d515e4f:
- rg -t py / -T js / --type-not js, fd -e py, and the Grep tool's
type: "py" credit an Edit of tokenizer.js. Type and extension values
are skipped as plain value flags, so they never filter.
- Glob **/tokenizer.py, rg tokenizer src/*.py, and
git grep tokenizer -- '*.py' credit tokenizer.js. The Glob pattern is
never a filter, and a glob operand scopes as its literal directory.
- Glob **/tokenizer.js, find -name tokenizer.js, rg -g tokenizer.js and
Grep glob: "tokenizer.js" credit tokenizer.js, although each search
could only find the target itself.
- An Edit of "// note<U+2028>foo(1);" to "// note<U+2028>foo(2);" passes
as comment-only, although JavaScript ends the comment at U+2028 and
foo(2) runs.
Scenario 21-bypass-filter-forms adds ten must-deny steps and four
controls. Four hook controls that asserted exact-name lookups still
credit (find -name widget.py, Grep glob widget.py, Get-ChildItem
-Filter widget.py) now assert the lookup is denied; each keeps its
original purpose, that the exclusion parser leaves a positive include
alone, through a wildcard include (widget.*).
Replace JSON-interpolated child JavaScript with fixed preload and summarizer programs. Pass paths and dates through dedicated fixture environment variables; use a fixed quoted exec wrapper so configured executable and script paths remain data. Preserve real Date.now, deadlines, calendar rollover, captured resume metadata and no-next-day duplicate assertions. Actual13/0 Node18.20.8, Node20.20.2 with Bun setting and Node24.9.0; actual13/0 with spaces/apostrophes/literal dollar in fixture paths; next-day negative control12/1; lint and diff checks pass. Addresses CodeQL393/394/395 without suppression or dismissal.
The Stop hook captures its session filename date before waiting for a slow summary, while the fixture previously recomputed it after timeout. Advance only the fixture calendar when the fake summarizer starts and assert persistence under the captured date, matching resume metadata, and no next-day duplicate. Keep Date.now, child delay and lifecycle deadlines unchanged. Actual workflow baseline and corrected fixture13/0; mismatched-day lookup negative control12/1; Node20 with Bun setting and Node18.20.8 both13/0; lint and diff checks pass.
Verify PCRE2 on every hosted test platform and coverage runner. Preserve dependency PATH for real scanner delegates and report missing prerequisites explicitly, keeping production fail-closed behavior and hermetic missing/error controls intact.
Isolate spawned Stop hooks through TMPDIR/TMP/TEMP, open transcript fixtures exclusively with private modes, and derive intentional stale-race fixture identity from its opened descriptor.
Three open review findings still pass the gate with a missing dependency,
each reproduced against the real registry:
- const ratio = {} / 2; require('../x') -- `}` is treated as a statement
boundary, so the division reads as a regex that swallows the require.
a.of / 2 does the same through the keyword list.
- [...require('../x')] -- the member-call guard reads the last dot of the
spread as member access and skips the call.
- require('../x', null) -- a literal counts only when the call closes right
after it, so the specifier is demoted to a dynamic warning, which never
fails.
14 cases, 11 failing. Controls that already pass: a regex after a function
body, a class body and a nested block.
Two first-round review findings were never addressed, and both reproduce
on the current head:
- records.require('./label'), obj?.import('./x') and obj . require('./y')
are method calls, but the call pattern's word boundary is satisfied by
the dot, so a self-contained object method fails every projection for a
file the script never loads.
- The dynamic-argument pattern allows one level of nested parentheses, so
require(path.join(__dirname, getName())) produces no dynamic warning
while require(getName()) does.
Control: a genuine require beside the member calls is still a dependency.
Review on dd14493e found four more ways the closure gate misjudges a
carrier, each reproduced against that head:
- require('./helper') resolves when only helper.cjs or helper.mjs is
planned, but Node's CommonJS search tries only .js, .json and .node.
- In `if (ok) {} /"/.test(text); require(...)` the regex after the block
is read as division and its quote swallows the require.
- `obj.\nif(x) / 2` is a method call, but the condition check only saw
the character immediately before `if`, so the division read as a regex
and swallowed a later require. Introduced by the previous fix.
- A tagged template named `from` was recorded as an import; with exact
import resolution it now fails a carrier that never loads that path.
Introduced by the previous fix.
Controls: an extensionless require still resolves to a .json file, and a
template literal passed to import() is still a dependency.
Review on aec97150 found four ways the closure gate passes a carrier that
fails at run time. Each is reproduced here against the current head:
- import('./helper') with only helper.js planned is accepted, but Node's
ESM resolver does no extension search.
- import('./data') with only data/index.json (or index.js) planned is
accepted, but Node rejects directory imports even from CommonJS.
- require('./lib\helper.js') with lib/helper.js planned is accepted
because backslashes were normalized, but Linux and macOS read the
backslash as part of the filename.
- In `if (ok) /['"]/.test(text); require('./missing')` the regex after
the condition's closing paren is read as division, its quote opens a
fake string, and the require disappears.
Controls that must keep passing: an exact import() of a planned file, an
extensionless require through the CommonJS search, and division after a
method named `if` or a call nested inside a condition. extractReferences
gains an `imports` list so the resolver can tell the two forms apart.