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
24 changes: 24 additions & 0 deletions .changeset/8953-test-path-roots-gate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
---

Tooling and test-only: `scripts/check-test-path-roots.mjs` rejects a test that
resolves a path below `process.cwd()`, closing a class that produced 13 defects
in one day (objectui#7799) and that root `AGENTS.md` has taught with nothing
behind it since PR objectui#8952 (objectui#8953).

Nothing ships. No runtime source changed; the three repaired files are test
files, and the gate and its pin live under `scripts/`.

The detector is not a `process.cwd` grep, deliberately: one of objectui#7799's
own 13 defects was invisible to that card's census regex because it spelled the
read through `(globalThis as unknown as {…}).process.cwd()`. The scan starts at
the FILESYSTEM CALL and resolves what its path argument is rooted at, following
the file's own bindings, so a root laundered through a `const` — the shape of
both `examples/schema-catalog` instances, whose read lines carry no `cwd` at all
— is caught where a text search finds nothing. It classifies every root it can
and PRINTS the number it cannot, so a clean run is never read as a claim about
the whole class.

Readings on `87f174c00`: 1880 filesystem calls across 386 of 3036 test files;
8 cwd-rooted reads in 3 files, all repaired here and green under BOTH
invocations afterwards (repo root 611 tests, package directories 599 + 12).
21 changes: 21 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,27 @@ jobs:
if: steps.relevant.outputs.should_run == 'true'
run: node scripts/check-vi-mock-override-shape.mjs

# ── Test path roots (objectui#8953) ───────────────────────────────────
# A test that resolves a repository path from `process.cwd()` reads a
# different tree under a package's own `test` script than under the form
# CI runs, so the same assertion reaches two verdicts. AGENTS.md has
# taught that rule since PR objectui#8952 and nothing enforced it;
# objectui#7799 repaired 13 instances of it in a single day.
#
# This step is a SECOND signal, not the gate. The gate is
# `scripts/__tests__/check-test-path-roots.test.ts`, which runs inside
# `Test (shard N/4)` — a required context that subscribes `merge_group`,
# so it blocks a queue build. This step reaches the same verdict sooner
# and with a message aimed at the author.
#
# After `pnpm install`, unlike the entry-guard step above: the scan parses
# every test file with the TypeScript compiler, because the defect is a
# RESOLVED ROOT and not a spelling — one of objectui#7799's own 13 was
# invisible to that card's census regex.
- name: Verify no test resolves a repository path from the process cwd
if: steps.relevant.outputs.should_run == 'true'
run: node scripts/check-test-path-roots.mjs


# ── The cross-repo closer's outcome contract (#5261) ──────────────────
# `cross-repo-issue-closer.yml` carries ~250 lines of inline
Expand Down
29 changes: 29 additions & 0 deletions content/docs/guide/ci-cd-pipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,35 @@ first, which is what stops a scanner that recognises nothing from reporting a cl
reads declared return types with the TypeScript parser, and every pre-install gate's import graph is
held to node builtins plus local modules
([#8903](https://github.com/objectstack-ai/objectui/issues/8903)).
- Then `scripts/check-test-path-roots.mjs` — a test that reads the filesystem inside an assertion
must root its paths on **its own file**, never on `process.cwd()`. The cwd is not one place here:
a package's own `test` script moves Vitest's root up to the repository root and leaves
`process.cwd()` down in the package directory, so a path assembled from the cwd reads a
**different tree** depending on which invocation started it, and a single assertion reaches two
verdicts — measured at `7 passed` from the repository root and `2 failed / 5 passed` from the
package directory, cwd the only variable
([#7791](https://github.com/objectstack-ai/objectui/issues/7791)). Root `AGENTS.md` had taught
that rule with nothing behind it, and what a taught-only rule costs is also measured:
[#7799](https://github.com/objectstack-ai/objectui/issues/7799) repaired thirteen instances of the
class in a single day without closing it, and `gridArrayArmOrderby-8973.test.tsx` — written
**after** that sweep — arrived carrying the same defect
([#8953](https://github.com/objectstack-ai/objectui/issues/8953)). It is deliberately not a
`process.cwd` grep, because one of those thirteen was invisible to the census regex that found the
other twelve: it spelled the read through a `globalThis` cast, to dodge a browser `process` shim.
So the scan starts at the **filesystem call** and resolves what its path argument is rooted at
through the file's own bindings — which catches a root laundered through a `const`, on a line that
holds no `cwd` at all — and it decides what counts as a filesystem call by **import provenance**
rather than by name, because a test that declares its own `writeFile` into a temporary directory
is twelve false positives for anything reading the spelling. Across the tree, name-matching
produced 28 violations and provenance produces 8.
- ⚠️ **Read that gate's green for what it is: it declares its own blind spot on every run.** The
census line ends with `N root(s) NOT CLASSIFIED` — 376 as this was written, enumerated by
`--blind` — because root resolution stops at the module edge, so a root arriving as a function
parameter or from an import is invisible to it. That is the largest gap and it is structural: one
of the three files repaired alongside the gate handed `process.cwd()` straight to a helper that
did the reads, and was found by a human reading the file, not by the gate. A clean run is a
verdict on the roots this gate can classify, never a clean bill of health for the class — which is
the same over-reading the card itself is about.
- Then `scripts/check-cross-repo-closer-outcome.mjs` — it extracts the ~250 lines of inline
`github-script` out of `cross-repo-issue-closer.yml` with a real parser, never a retyped copy,
runs it under doubles the way `actions/github-script` does, and pins each exit's outcome: which
Expand Down
43 changes: 35 additions & 8 deletions examples/schema-catalog/test/catalog-gallery-render.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,32 @@ import { SchemaRenderer, SchemaRendererContext, toRenderableSchema } from '@obje
import fs from 'node:fs';
import path from 'node:path';
import { allExamples } from '../src/index.js';

/**
* The repo root, derived from THIS FILE's own location — never from
* `process.cwd()` (objectui#7799, and the gate that closed the class,
* objectui#8953).
*
* What stood at the read sites below was `path.join(process.cwd(), …)`
* under the comment "`process.cwd()` is the repo root by construction:
* `scripts/vitest-invocation-guard.mjs` refuses any run whose Vitest root is
* not it". THAT PREMISE IS FALSE. The guard rejects a run whose VITEST root is
* not the repo root; this package's own `test` script — `vitest run --root ../..
* examples/schema-catalog/`, which is what `pnpm --filter … test` and
* `turbo run test` both run — sets that root correctly while leaving
* `process.cwd()` in the package directory. The guard passes and the cwd is the
* package, so `apps/site/app/components` resolved to a path that does not
* exist and every read below threw.
*
* Spelled in string operations, copying the landed precedent of objectui#7791
* (PR #7796) and objectui#7799 (PR #7806): only BARE `import.meta.url` is read
* here and taken apart by hand.
*/
const SELF_DEPTH_BELOW_REPO_ROOT = 4; // examples / schema-catalog / test / this file
const REPO_ROOT = decodeURIComponent(new URL(import.meta.url).pathname)
.split('/')
.slice(0, -SELF_DEPTH_BELOW_REPO_ROOT)
.join('/');
// Plain-JS CI helper; types are inferred from the `.mjs` source (`allowJs`), the
// same route `scripts/__tests__/known-schema-types-derivation-5115.test.ts`
// takes. objectui#6024 reuses this derivation rather than re-deriving: a second
Expand Down Expand Up @@ -732,9 +758,8 @@ describe('objectui#4616 — every catalog entry renders in the docs gallery', ()
* reads the host and pins what the sweep assumes about it.
*/
describe('the docs-site gallery host registers the same set', () => {
// `process.cwd()` is the repo root by construction: `scripts/vitest-
// invocation-guard.mjs` refuses any run whose Vitest root is not it.
const siteDir = path.join(process.cwd(), 'apps/site/app/components');
// Rooted at this file, never at the cwd — see `REPO_ROOT` above.
const siteDir = path.join(REPO_ROOT, 'apps/site/app/components');
const read = (f: string) => fs.readFileSync(path.join(siteDir, f), 'utf8');

it('loads every package this pin loads, in this pin’s order', () => {
Expand Down Expand Up @@ -923,10 +948,12 @@ const PLUGIN_CATEGORIES = [
].sort();

/**
* `process.cwd()` is the repo root by construction — `scripts/vitest-
* invocation-guard.mjs` refuses any run whose Vitest root is not it.
* Rooted at this file, never at the cwd — see `REPO_ROOT` above. This one is
* the shape objectui#8953's gate states it cannot see: the repository path is
* resolved by the HELPER, on this file's behalf, so no filesystem call here
* carries the cwd and nothing scanning this file's own reads would find it.
*/
const derivedRegistry = deriveRegistryKeys(process.cwd());
const derivedRegistry = deriveRegistryKeys(REPO_ROOT);

/**
* category → the key set `packages/<category>` registers, joined on the
Expand Down Expand Up @@ -1332,7 +1359,7 @@ describe('objectui#6025 — the gallery DECLARES the packages its entries need',
* this file green while the docs page went back to an empty view.
*/
describe('objectui#5113 — the docs-site hosts supply the same fixture', () => {
const siteDir = path.join(process.cwd(), 'apps/site/app/components');
const siteDir = path.join(REPO_ROOT, 'apps/site/app/components');
const read = (f: string) => fs.readFileSync(path.join(siteDir, f), 'utf8');

it('the host fixture exposes every method this mirror implements', () => {
Expand Down Expand Up @@ -1486,7 +1513,7 @@ describe('objectui#6317 — a `select` field declares the options its rows use',

it('the host fixture declares the SAME field surface, options included', () => {
const hostSource = fs.readFileSync(
path.join(process.cwd(), 'apps/site/app/components/galleryDataSource.ts'),
path.join(REPO_ROOT, 'apps/site/app/components/galleryDataSource.ts'),
'utf8',
);
expect(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,31 @@ import fs from 'node:fs';
import path from 'node:path';
import { examplesByCategory } from '../src/index.js';

/**
* The repo root, derived from THIS FILE's own location — never from
* `process.cwd()` (objectui#7799, and the gate that closed the class,
* objectui#8953).
*
* What stood at the read site below was `path.join(process.cwd(), …)` under the
* comment "`process.cwd()` is the repo root by construction:
* `scripts/vitest-invocation-guard.mjs` refuses any run whose Vitest root is
* not it". THAT PREMISE IS FALSE. The guard rejects a run whose VITEST root is
* not the repo root; this package's own `test` script — `vitest run --root ../..
* examples/schema-catalog/` — sets that root correctly while leaving
* `process.cwd()` in the package directory. The guard passes and the cwd is the
* package, so `expect(fs.existsSync(siteDir)).toBe(true)` was asserting against
* a path that does not exist under that invocation.
*
* Spelled in string operations, copying the landed precedent of objectui#7791
* (PR #7796) and objectui#7799 (PR #7806): only BARE `import.meta.url` is read
* here and taken apart by hand.
*/
const SELF_DEPTH_BELOW_REPO_ROOT = 4; // examples / schema-catalog / test / this file
const REPO_ROOT = decodeURIComponent(new URL(import.meta.url).pathname)
.split('/')
.slice(0, -SELF_DEPTH_BELOW_REPO_ROOT)
.join('/');

registerLayout();

/** `DashboardRenderer`'s retired inline-analytics placeholder (framework#3320). */
Expand Down Expand Up @@ -205,9 +230,8 @@ describe('plugin-dashboard catalog entries render in the docs gallery (objectui#
* things this pin assumes about them.
*/
it('the docs-site gallery host still registers the dashboard packages and passes the dataset stub', () => {
// `process.cwd()` is the repo root by construction: `scripts/vitest-
// invocation-guard.mjs` refuses any run whose Vitest root is not it.
const siteDir = path.join(process.cwd(), 'apps/site/app/components');
// Rooted at this file, never at the cwd — see `REPO_ROOT` above.
const siteDir = path.join(REPO_ROOT, 'apps/site/app/components');
expect(fs.existsSync(siteDir)).toBe(true);
const registrations = fs.readFileSync(path.join(siteDir, 'registerCatalogBlocks.ts'), 'utf8');
expect(registrations).toContain('@object-ui/plugin-dashboard');
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
"check:lockfile-integrity": "node scripts/check-lockfile-integrity.mjs",
"merge-queue-head": "node scripts/check-merge-queue-head.mjs",
"check:pre-install-import-graph": "node scripts/check-pre-install-import-graph.mjs",
"check:test-path-roots": "node scripts/check-test-path-roots.mjs",
"check:vi-mock-specifiers": "node scripts/check-vi-mock-specifiers.mjs",
"check:vi-mock-inherit": "node scripts/check-vi-mock-inherit.mjs",
"check:vi-mock-override-shape": "node scripts/check-vi-mock-override-shape.mjs",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,36 @@ import { resetRetiredSortSpellingReports } from '@object-ui/core';
// Registers `object-grid` and its `view:grid` alias.
import '../index';

/**
* The repo root, derived from THIS FILE's own location — never from
* `process.cwd()` (objectui#7799, and the gate that closed the class,
* objectui#8953).
*
* The read below stood on `join(process.cwd(), …)` under the comment "Read off
* the vitest root — this project's `import.meta.url` is not a file URL, so the
* sibling `import.meta`-relative idiom does not work here". BOTH HALVES OF THAT
* ARE FALSE, and each had already been falsified before this file was written:
*
* - `import.meta.url` IS a `file:` URL in this project. objectui#7800
* (comment 5555131785) measured it across three packages and both cwds; the
* sibling `packages/plugin-grid/src/__tests__/groupedPartialDisclosure-7189.test.tsx`
* has derived its root this way since PR #7806. What Vite rewrites is the
* TWO-ARGUMENT `new URL(rel, import.meta.url)`, which is why only the bare
* form is read here and taken apart by hand.
* - "the vitest root" and `process.cwd()` are not the same directory. This
* package's own `test` script — `vitest run --root ../.. packages/plugin-grid/`,
* which is what `pnpm --filter @object-ui/plugin-grid test` and
* `turbo run test` both run — sets the VITEST root to the repo root and
* leaves the cwd in `packages/plugin-grid/`, so the path below resolved to
* `packages/plugin-grid/packages/plugin-grid/src/ObjectGrid.tsx` and the read
* threw (objectui#7791, objectui#7799).
*/
const SELF_DEPTH_BELOW_REPO_ROOT = 5; // packages / plugin-grid / src / __tests__ / this file
const REPO_ROOT = decodeURIComponent(new URL(import.meta.url).pathname)
.split('/')
.slice(0, -SELF_DEPTH_BELOW_REPO_ROOT)
.join('/');

function makeAdapter() {
return {
find: vi.fn().mockResolvedValue({
Expand Down Expand Up @@ -191,9 +221,8 @@ describe('object-grid — the arms this card deliberately does NOT move', () =>
// documented at the read site as deliberate — it is the shape the server
// names in its own error messages and it survives a field name containing
// a space. Both shapes are accepted by `normalizeSortNodes`.
// Read off the vitest root — this project's `import.meta.url` is not a
// file URL, so the sibling `import.meta`-relative idiom does not work here.
const src = readFileSync(join(process.cwd(), 'packages/plugin-grid/src/ObjectGrid.tsx'), 'utf8');
// Rooted at this file, never at the cwd — see `REPO_ROOT` above.
const src = readFileSync(join(REPO_ROOT, 'packages/plugin-grid/src/ObjectGrid.tsx'), 'utf8');

// Instrument check FIRST, in both directions: a probe that silently read
// the wrong file (or an empty one) would make every `toContain` below a
Expand Down
Loading
Loading