* test(#4590): add no-rendered-text-length-assert ESLint rule Enforces ADR-456's typed-surface mandate for one specific bug shape: a test assertion whose pass/fail depends on the length/substring content of a template literal that interpolates an OS-derived path (os.tmpdir(), os.homedir(), path.join/resolve/..., or a PATH_RETURNING_FNS resolver). Because macOS's default tmpdir prefix is longer than Linux's, such an assertion can pass on one runner and fail on another -- the defect class behind #4421's incident (git show 4e75b836e9), already fixed there by pinning to a typed field per ADR-456 Sec(c) before this rule existed to catch a recurrence. Two repo-wide sweeps against the real tests/ tree narrowed the rule to a sound scope: an initial design that traced call arguments (to approximate the historical incident's cross-file render-function shape) produced false positives on ordinary fs.readFileSync(path.join(...)) + assert.match patterns; a second design that matched any bare direct path-returning call produced 45 false positives on path suffix/prefix/non-emptiness checks. The shipped rule matches only a path-returning expression interpolated into a template literal, directly or via one identifier hop -- disclosed in the rule's own "Known boundaries" as not covering the literal cross-file incident shape, which would require tracing into a callee's body. Phase 1 of epic #4589 (CI test-matrix Linux-primary migration) -- Phase 2's safety argument depends on this class of OS-dependent test assertion being enforced going forward, not merely fixed once. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(#4590): address code-review findings on no-rendered-text-length-assert Reletter the "Known boundaries" doc-comment list (a)-(e), fixing a gap left by an earlier edit pass and every stale cross-reference to it. Collapse isDirectPathTaint/isTaintedInterpolation's duplicated TemplateLiteral-walk into one recursive relationship (isTaintedInterpolation now delegates a nested-template-literal case back to isDirectPathTaint instead of re-implementing the .some() traversal) -- behavior unchanged, confirmed by re-running the repo-wide sweep (still zero false positives). Found by the Standards-axis /code-review pass on this PR. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: sim <sim@local> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -80,6 +80,7 @@ Replace all three with a single coherent mechanism: **AST-based ESLint rules in
|
||||
| `no-exact-case-env-access` | `DEFECT.WINDOWS-EXACT-CASE-ENV-ACCESS` | `src/**/*.cts`, `gsd-core/bin/**`, `scripts/**`, `hooks/**` |
|
||||
| `require-full-tmpdir-triad` | `DEFECT.WINDOWS-TEST-PORTABILITY` (#4220) | tests |
|
||||
| `no-unbounded-dirname-walk` | `DEFECT.WINDOWS-TEST-PORTABILITY` (#4020 / #4220) | tests, scripts |
|
||||
| `no-rendered-text-length-assert` | ADR-456 §(c) typed-surface mandate (not a `DEFECT.WINDOWS-*` class — see #4590 amendment below) | tests |
|
||||
|
||||
**Amendment (2026-08-18, epic #3411 Phase 3 / #3619).** `no-private-binary-resolution` is the
|
||||
first catalog entry added after the original seven, and it extends this architecture to a
|
||||
@@ -186,6 +187,43 @@ A repo-wide sweep for other instances of either pattern (beyond the incident sit
|
||||
none: `require-full-tmpdir-triad` and `no-unbounded-dirname-walk` both ran clean against the rest
|
||||
of the tree once the three live sites were fixed.
|
||||
|
||||
**Amendment (2026-09-09, epic #4589 Phase 1 / #4590).** `no-rendered-text-length-assert` extends
|
||||
this catalog's zero-escape-hatch discipline to a bug class outside the `DEFECT.WINDOWS-*`
|
||||
taxonomy: a test assertion whose pass/fail depends on the length or substring content of a
|
||||
TEMPLATE LITERAL that INTERPOLATES an OS-derived path-returning expression (`os.tmpdir()`,
|
||||
`os.homedir()`, or a `PATH_RETURNING_FNS` resolver) alongside other rendered content. Because
|
||||
macOS's default tmpdir prefix (`/private/var/folders/…`) is longer than Linux's, such an assertion
|
||||
can pass on one runner and fail on another — the root cause behind #4421's incident
|
||||
(`git show 4e75b836e9`, `tests/state-todos-render.test.cjs`), which had already been fixed by
|
||||
pinning the assertion to a typed field (`json.todos[0].needs`) per ADR-456 §(c) before this rule
|
||||
existed to catch a recurrence. The rule catches the same DEFECT CLASS written directly in a test
|
||||
file — an inline template literal that itself embeds a path-returning expression and is then
|
||||
length/substring-probed — not the literal cross-file production-render-function incident shape
|
||||
itself (a call whose return value happens to embed one of its own arguments); detecting the latter
|
||||
would require tracing into the callee's own function body, which is out of scope for a
|
||||
single-file AST rule. A bare path-returning expression probed directly, with no surrounding
|
||||
template literal (e.g. `p.endsWith('.md')`, `resolved.startsWith(root)`, `dir.length > 0`), is
|
||||
never flagged: it is a direct, deterministic check on the path value itself, not an assertion
|
||||
about other content that happens to share a rendered string with a variable-length path.
|
||||
|
||||
Reaching this sound scope took two successive repo-wide sweeps. The first targeted an initial
|
||||
broader design that traced one hop into a resolved call's own arguments to approximate the
|
||||
cross-file production-render-function shape; that design proved unsound, producing dozens of
|
||||
false positives on ordinary `fs.readFileSync(path.join(...))` + `assert.match` patterns (correct
|
||||
code, not instances of the defect), because a call's return value cannot be soundly assumed to
|
||||
embed one of its own arguments just because that argument is a path. The call-argument tracing was
|
||||
removed in favor of a one-hop receiver model that flagged ANY direct path-returning call as taint,
|
||||
with or without a template literal. The second sweep, run against that narrower rule, found the
|
||||
one-hop-receiver model was itself still too broad: it produced 45 false positives across `tests/`
|
||||
of exactly one shape — a bare path value probed for a structural property of its own (suffix,
|
||||
prefix, or non-emptiness), e.g. `.endsWith('.md')` file-extension checks, `.startsWith(root)`
|
||||
path-confinement checks, and `.length > 0` non-emptiness checks — none of which are instances of
|
||||
the #4421 OS-tmpdir-length hazard. Bare-direct-path-call matching was removed, restricting the
|
||||
rule to fire ONLY when the asserted-on value is a template literal interpolating a path-returning
|
||||
expression. The known miss (the literal cross-file-render-function shape, a receiver-side
|
||||
two-hop chain, or a path value arriving as a function parameter) is disclosed in the rule's own
|
||||
header rather than attempted unsoundly.
|
||||
|
||||
**Taxonomy coverage.** This catalog addresses every `DEFECT.WINDOWS-*` class plus
|
||||
`DEFECT.TEST-SHELL-PIPELINE-NONPORTABLE` in `CONTEXT.md`, to the extent each is *statically*
|
||||
detectable. `DEFECT.TEST-SHELL-PIPELINE-NONPORTABLE` has two parts: (a) the CRLF / literal-`\n`
|
||||
|
||||
Reference in New Issue
Block a user