Skip to content

fix(scripts): name-fold loader gate rule 2 resolves the fixtures directory, not one spelling of it - #24236

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-24050-fold-loaders-resolved-path
Oct 18, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-24050-fold-loaders-resolved-path

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #24050

Clause-②: no. A repo gate's internal matcher; scripts/ ships in no package, so nothing publishes (skip-changeset).

One file: scripts/check-position-name-fold-loaders.mjs (+164 / -33). Branched from main at 6e43b7bc3f; head e1b31d6ba.

What changed

Rule 2 resolves the guarded directory instead of matching one spelling of it. It used to count a file only when its text held the repo-relative literal packages/metadata/src/__fixtures__. Now every path spelling on a line carrying __fixtures__ is folded (case; a backslash read as /; ., .. and // segments resolved) and read two ways:

  • as written: the folded spelling holds the directory's four segments in a row. This covers a repo-relative path, ROOT + '/packages/…', a URL, a vendored prefix, and a climb that comes back in;
  • from the file that holds it: a relative spelling, joined to that file's own directory, lands on the directory or under it.

Segments are compared, never substrings. So packages/services/service-metadata/src/__fixtures__ stays another package's directory, even though it contains the string metadata/src/__fixtures__. The literal substring check still runs first, unchanged, so rule 2 counts every file it counted at BASE.

  • referencesArtifact(text, relPath) now takes the holding file's repo-relative path, and throws a TypeError without it. A caller that dropped it would otherwise lose every relative climb with no error. scanTree now computes rel before the call.

  • Rule 3's "lies in the guarded directory" test (isOtherPackageFixturePath) now uses the same folded comparison (liesInFixturesDir), so the file has one definition of it. This changes no verdict. A token that rule 3 sets aside holds only word characters, ., - and /, and it sits between rule-3 delimiters, which are all rule-2 delimiters too. So it is exactly one rule-2 spelling, and if its folded form lies in the guarded directory, rule 2 has already answered true.

  • classifyReference's self test is unchanged. It judges the real path the walk returns, not a spelling.

  • The header's soundness sentence (:40–:51, formerly :41–:43) now says what the scan checks:

    • the basename;
    • a path that resolves to the directory once folded and joined to the holding file's directory;
    • __fixtures__ with hotcrm or .artifact on one line.

    It also says what the scan does not follow:

    • a computed path: a concatenation, a .replace, a variable, or a base other than the holding file's directory or the repo root;
    • a wildcard in a directory segment with no marker on its line.

    The rule-2 and rule-3 docblocks are updated to match.

Self-test: 30 cases now 39; the matcher battery's floor goes from 20 to 29

There are nine new matcher cases. The existing cases now pass a holding file (PROBE_FILE = 'packages/objectql/src/loader.ts'), and their verdicts are unchanged. The roster and its floor (4) are unchanged.

new case expected at BASE
relative climb readdirSync(resolve(HERE, '../../../metadata/src/__fixtures__')), held by packages/objectql/src/loaders/catalog.ts true false: red
the same text, held by packages/services/service-metadata/src/loaders/catalog.ts (it lands in packages/services/metadata/…) false false
case variant readdirSync('packages/Metadata/src/__fixtures__') true false: red
backslash-separated spelling of the directory (source text with escaped backslashes) true false: red
readdirSync('packages/services/service-metadata/src/__fixtures__') false false
`packages/objectql/src/__fixtures__/hotcrm-${kind}.artifact.json` true true
glob('packages/objectql/src/__fixtures__/hotcrm-?.artifact.json') true true
glob('packages/objectql/src/__fixtures__/hotcrm-[ab].artifact.json') true true
glob('packages/objectql/src/__fixtures__/x-{a,hotcrm}.artifact.json') true true

The "at BASE" column comes from feeding each new case's text and holding file to BASE's own referencesArtifact (the 6e43b7bc3f blob, which ignores the file argument).

Deviation from Acceptance 2, measured. "Each must fail against today's matcher" holds for the relative climb and the case variant (and the backslash case). It is false for the four token-boundary cases: today's matcher already answers true for all four. Record 6225074650's probe measured the same ("the head handles all four").

Those four cases pin rule 3's path token running past $, ?, [ and {. Their red is shown by legs L6 to L9 below: each leg cuts the token at one of those characters, and only its own case goes red.

{a,hotcrm} sits in the middle of a name (x-{a,hotcrm}) on purpose. Directly after __fixtures__/, a cut at { leaves a token ending in /, and the literal-file test keeps such a token anyway, so the case would pin nothing. The record found the old ${name} case pins nothing for the same reason.

Ablations: each leg mutates the committed head, runs --self-test, then restores

Each leg goes through scripts/ablation-replace.mjs in WRAP mode. Per leg, it checks on disk that the anchor count went from 1 to 0 and that the blob changed. It proves the restore by blob == HEAD (5e94de5be75b) and an empty git diff HEAD. An outer trap also restores the absolute path from HEAD. This is a root script, so there is no build or dist/ leg.

leg mutation red cases (of 39)
L1 rule 2 reverted to BASE (literal substring only) 3: relative climb, case variant, backslash
L2 case fold off 1: case variant
L3 backslash fold off 1: backslash
L4 holder-relative reading deleted 1: relative climb
L5 segment run replaced by the bare substring metadata/src/__fixtures__ 2: the service-metadata false case, and the same climb from the other holder
L6 path token cut at $ 1: the ${kind} case
L7 path token cut at ? 1: hotcrm-?
L8 path token cut at [ 1: hotcrm-[ab]
L9 path token cut at { 1: x-{a,hotcrm}

After the last leg: worktree blob 5e94de5be75b equals the HEAD blob, git diff HEAD is 0 bytes, and git status --porcelain is empty.

The gate over the real tree, against BASE

A scratch probe imported both blobs and ran them over the same walk: 10246 files, worktree at e1b31d6ba.

  • Rule 2 at BASE counts 1 file: scripts/check-position-name-fold-loaders.mjs.

  • Rule 2 now counts 4 files: that one, plus three tests in packages/metadata/src/. Each names the directory by join(HERE, '__fixtures__/…'), resolved from its own directory:

    • plugin-artifact-forward-conversion.test.ts
    • plugin-artifact-forward-conversion-retired-after.test.ts
    • plugin-unbound-form-predicate-roots.test.ts

    All three were already references through rule 1 or rule 3.

  • References: 8 at BASE and 8 now, the same set (6 test, 2 declared instrument, 0 loader). The gate reports no new reference on main.

  • The gate prints: has no non-test loader. 10246 file(s) scanned under packages, examples, apps, scripts, tools, .github; 8 reference(s): 6 test, 2 declared instrument, 0 loader.

  • Gate wall time on this box is about 1.5 s, against about 1.3 s at BASE.

Verification, on head e1b31d6ba

  • node scripts/check-position-name-fold-loaders.mjs --self-test exits 0: ✓ check-position-name-fold-loaders self-test: 39 cases pass. At BASE it exits 0 with 30 cases.
  • node scripts/check-position-name-fold-loaders.mjs exits 0, with the line quoted above.
  • Gate families were derived by node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack with no paths: 32 commands, the same 32 the seat derived. All 32 ran locally and exited 0, pnpm check:pm-dispatch-gates included (✓ dispatch-gates self-test (fast tier): 1884 cases pass).
  • --ran reconciliation: 32 derived, 32 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero; all 32 recorded an exit code).
  • pnpm exec eslint --no-inline-config on the one file exits 0. The repo-wide pnpm lint is CI's.
  • The control-byte self-scan of the file found nothing.

Acceptance notes

  • The .. guard is still undiscriminated. No case discriminates rule 3's .. guard, the first test in isOtherPackageFixturePath. The card's Measured section names it, but its Acceptance does not ask for a case. Now that rule 2 resolves climbs, a .. token that lands in the guarded directory is rule 2's. The guard only keeps a token that normalises to another package's literal file, which is conservative and not load-bearing. Observation; carrier: none (承接者:无).
  • A wildcard directory segment with no marker is still unseen. An example is glob('packages/*/src/__fixtures__/*.json'). The header now says so. Reach: no file under the scan roots spells such a glob. Not filed; carrier: none (承接者:无).
  • Rule 1 (the basename) is still case-sensitive. The card excludes rule-1 changes. A case-variant basename joined to a variable base is a computed path, which the header already places outside. Observation; carrier: none (承接者:无).

Generated by Claude Code

…rectory, not one spelling of it

Rule 2 of check-position-name-fold-loaders counted a file only when its
text held the repo-relative literal packages/metadata/src/__fixtures__,
so a relative climb from the file that holds it, a case variant or a
backslash-separated spelling, with neither hotcrm nor .artifact on the
line, was never a reference.

Each path spelling on a line carrying __fixtures__ is now folded (case,
backslash, ., .. and // segments) and read two ways: as written, holding
the directory's four segments in a row, and joined to the directory of
the file that holds it. Segments are compared, never substrings, so
packages/services/service-metadata/src/__fixtures__ stays another
package's directory. The literal substring is still asked first.
referencesArtifact now takes the holding file's repo-relative path and
throws without it, since a dropped path would lose every climb silently.
Rule 3's in-the-guarded-directory test uses the same folded comparison.

The self-test gains nine matcher cases (floor 20 -> 29), the header's
soundness sentence now says what the scan checks and what it cannot
follow, and the tree scan reads the same eight references as before.

Claude-Session: https://claude.ai/code/session_01Nk17Z26X5DB59iQztDBADH
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:services seat 1 (#6021) · marchtian · session_01Nk17Z26X5DB59iQztDBADH · 2026-10-18T00:45Z · head e1b31d6ba6. It lands through the merge queue once every check on this head is green.

  • Shape: a draft PR to main. Its first line is Fixes #24050, and that is the only closing keyword in the body. Clause-②: no is at a line start in the body. The PR carries skip-changeset (a root script, not a published package). Card and PR assignee: marchtian. Not governed: check-governed-merges reads 0 of 1 path, and 197 changed lines ≤ 3000.
  • The change, read by the seat (scripts/check-position-name-fold-loaders.mjs):
    • Rule 2 asks the old literal substring first, so it counts every file it counted before. It then folds each path spelling on a __fixtures__ line (case, \ read as /, and ., .. and // resolved) and reads it two ways:
      • as written, by whole segments: /packages/metadata/src/__fixtures__/ must appear as a run;
      • joined to the holding file's directory.
    • service-metadata stays aside. Segments are compared, never substrings, so packages/services/service-metadata/src/__fixtures__ is pinned false, both bare and as a climb from a service-metadata holder.
    • referencesArtifact(text, relPath) now requires the holder's path, and throws without it. Its one caller, scanTree, passes it.
    • Rule 3's guarded-directory test shares the folded comparison. It answers only after rule 2, so no verdict changes.
    • The header says what the scan checks and what it cannot follow (a computed path, a wildcard directory segment with no marker), as Acceptance 3 asks.
  • The real tree, BASE vs head: rule 2 counts 1 file at BASE and 4 at head (the gate itself plus three packages/metadata/src tests). The reference set is the same 8 (6 test, 2 instrument, 0 loader), so main gains no new reference and the gate stays green.
  • Self-test: 30 cases become 39, and the matcher battery floor goes from 20 to 29.
    • Against BASE's matcher, the relative climb, the case variant and the backslash spelling answer false (red).
    • Ablated 9 legs. Each fold, the holder reading and the segment run turns its own cases red. Each PATH_TOKEN cut at $, ?, [ and { turns its one boundary case red. Restored with blob equal to HEAD and git diff HEAD empty.
  • Accepted deviation, ruled by the seat:
    • Acceptance 2 says each of the six required cases must fail against today's matcher. The dev measured that false for the four boundary cases (hotcrm-${kind}, hotcrm-?, hotcrm-[ab], x-{a,hotcrm}): they already answer true at BASE, as the originating record's probe also found.
    • Their value is as pins on PATH_TOKEN's boundary. Each is red under its own ablation leg, so they pin what the card wanted pinned. Accepted as measured.
    • The two extra cases (a backslash spelling, and a climb from a service-metadata holder) are accepted. They are why the floor is 29 and not 27.
  • Gates on this head: at this read, 19 checks succeeded and 9 were skipped by design, and 8 were running. The skips are Build Core (the filter's core output is false for a scripts-only diff), Check Changeset and Check PR Size (the labeled event when skip-changeset was added), and the roster set. There were no failures. Locally: 32 derived families, 32 exit 0.
  • Out-of-scope findings: Acceptance notes only:
    • rule 3's .. guard is undiscriminated but not load-bearing;
    • a wildcard directory glob with no marker is outside the scan (the header says so, and there are 0 such globs in the tree);
    • rule 1's case-sensitivity is excluded by the claim.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 18, 2026 00:53
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 18, 2026 00:53
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 18, 2026
Merged via the queue into main with commit 7be3cc7 Oct 18, 2026
44 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-24050-fold-loaders-resolved-path branch October 18, 2026 01:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants