Repository navigation
Commit 318bdaf
fix(scripts): the SDUI manifest generator realpaths its runner directory, so a TMPDIR behind a symlink (the macOS default) no longer fails regeneration (#21077)
Fixes #20588
Clause-②: no
## What
`scripts/gen-sdui-manifest-node.mjs`, the one producer of the tracked
`sdui.manifest.json`, now `realpathSync`s its `mkdtemp` runner directory
before it builds the runner URL. The resolve hook compares against that
URL.
The pin is a new row in `scripts/check-sdui-manifest.mjs --self-test`.
It copies the real generator into a fixture tree, runs it under a
`TMPDIR` that is a symlink, and requires the output to pass `checkTree`
with check 4 required.
## Why: the mechanism, measured on Linux
The hook re-anchors a bare specifier to the modules root only when
`c.parentURL === RUNNER`. Node reports a module's URL by its
**realpath**, but `RUNNER` was built from the unresolved `os.tmpdir()`
path. Under a `TMPDIR` reached through a symlink, the two never match.
macOS's default `/var/folders/...` is such a path (`/var` is a symlink
to `/private/var`). So no runner import is re-anchored, and every
registry import fails.
The card's macOS failure reproduces on Linux by pointing `TMPDIR` at a
symlink. It was measured on the **real** objectui tree built at the pin
(`db11afd4967c`, the registry closure built with `turbo run build`). The
base is `6073bb96b8`, and the "unfixed" legs restore the base line
through `scripts/ablation-replace.mjs`:
| leg | generator | `TMPDIR` | exit | output |
|---|---|---|---|---|
| 1 | fixed | default (`/tmp`) | 0 | 107 components, sha256
`7c7b6b6ebcb1…`, `cmp`-identical to `sdui.manifest.json` at base |
| 2 | fixed | symlink to a real dir | 0 | same bytes, `cmp`-identical |
| 3 | unfixed | symlink to a real dir | **1** | 16 × `Cannot find
package '@object-ui/…'`, then `the registry enumeration failed (exit 1)`
(the card's signature) |
| 4 | unfixed | default (`/tmp`) | 0 | same bytes (control: the defect
needs the symlink) |
After each leg, `sdui.manifest.json` and
`scripts/sdui-manifest.record.json` were restored to HEAD. The record
differed only in `generatedAt`. ⛔ Neither file is in this diff, because
open PR #20990 owns the artefact.
## Premise checks
- **Site unchanged.** At base, `:180` is `const runnerDir =
mkdtempSync(join(tmpdir(), 'sdui-manifest-runner-'));` and `:190` is the
`c.parentURL === RUNNER` test.
- **Only `RUNNER` is compared.** `ANCHOR` is only handed on as
`parentURL`. In a minimal copy of the hook, a modules root reached
through a symlink resolves with or without the fix. Repo-wide, `git grep
"parentURL ==="` has exactly one hit: this hook. `ROOT` comes from the
generator's own `import.meta.url`, which Node already reports as a
realpath.
- **The command.** The card names `pnpm sdui:manifest`, but no such
script exists in `package.json`. The documented regenerate command is
`node scripts/gen-sdui-manifest-node.mjs` (AGENTS.md,
`docs/releases-maintenance.md`), and that is what was run.
## The pin, and why it lives where it does
The generator has no test of its own: no `--self-test`, and no suite
names it. `scripts/check-sdui-manifest.mjs --self-test` is the self-test
of its artefact's gate, and `lint.yml` already runs it ("SDUI manifest
is present, intact and fresh at the objectui pin"). Putting the row
there adds no CI step and no new harness.
- **The row.** `the producer, run under a TMPDIR behind a symlink,
writes a tree this gate passes`. It is declared in
`SELF_TEST_BATTERIES`, and `SELF_TEST_BATTERY_FLOOR` goes from 12 to 13.
- **The fixture.** A throwaway objectui commit sits at a fixture pin
under `.cache/objectui-SHA12/`. It holds one stub package per
`@object-ui/*` name the generator spells (read off the generator's
source, so a module it gains does not red the row) and a stub adapter at
the parser dist path. The fixture refuses to count as a fixture if its
`TMPDIR` resolves to itself.
- **The assertion.** The row expects GREEN with `requireObjectui: true`
against the fixture's own objectui, so the version leg cannot pass by
not running.
- **dispatch-gates.** A module-scope `PRODUCER =
'scripts/gen-sdui-manifest-node.mjs'` literal makes an edit to the
generator alone derive this gate. Extracted hints at base were
`packages/core/package.json`, `sdui.manifest.json/**`,
`scripts/sdui-manifest.record.json` and `.objectui-sha/**`. Now they
also include `scripts/gen-sdui-manifest-node.mjs`. `dispatch-gates
scripts/gen-sdui-manifest-node.mjs` now lists both `check-sdui-manifest`
invocations as matched via that hint.
**Ablation (one-time proof).** The fix was committed first
(`6cb74c13c4`). Then `scripts/ablation-replace.mjs` restored the base
line: anchor 1 to 0, blob `2df8285f06ae` to `7c351d47dc70`. The
self-test exited **1**: the producer printed 16 × `Cannot find package
'@object-ui/…'` imported from `…/tmp-real/sdui-manifest-runner-…`, and
the row read RED with `sdui.manifest.json is MISSING`. The file was
restored with blob == HEAD (`2df8285f06ae`) and `git diff HEAD` empty.
The self-test then exited 0 with 13 cases.
## Local verification (at HEAD `6cb74c13c4`)
Every command `dispatch-gates --commands` derives, with its exit code
captured before any pipe:
```text
node scripts/check-ci-filter-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-declaration-mirrors.mjs :: exit 0
node scripts/check-declaration-mirrors.mjs --self-test :: exit 0
node scripts/check-scripts-symbol-anchors.mjs :: exit 0
node scripts/check-scripts-symbol-anchors.mjs --self-test :: exit 0
node scripts/check-sdui-manifest.mjs :: exit 0
node scripts/check-sdui-manifest.mjs --self-test :: exit 0
node scripts/check-self-test-wired.mjs :: exit 0
node scripts/check-self-test-wired.mjs --self-test :: exit 0
node scripts/check-self-test-workflow-commands.mjs :: exit 0
node scripts/check-self-test-workflow-commands.mjs --self-test :: exit 0
node scripts/check-whole-set-label-write.mjs :: exit 0
node scripts/check-whole-set-label-write.mjs --self-test :: exit 0
node scripts/pm/bare-root-worklist.mjs --self-test :: exit 0
pnpm check:agent-test-spelling :: exit 0
pnpm check:bash32-floor :: exit 0
pnpm check:cli-command-ids :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:entry-guard :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:parse-guard :: exit 0
pnpm check:pm-dispatch-gates :: exit 0
pnpm check:pnpm-filter-targets :: exit 0
pnpm check:ratchet-remedy-authority :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:watch-hint-literal :: exit 0
```
Verdict lines: `✓ check-sdui-manifest self-test: 13 cases behave (…; the
producer, run under a symlinked TMPDIR, writes a tree that passes).` ·
`check-nul-bytes: OK (scanned 9639 text file(s) …; no raw ASCII control
bytes).`
- **Union reconciliation:** `dispatch-gates --repo
objectstack-ai/objectstack --commands` (no paths) was run after the last
commit. Its verdict: `✓ dispatch-gates --ran: 31 derived famil(ies)
accounted for — 31 run, 0 NOT-MEASURED (a DERIVED zero — all 31 recorded
an exit code and none of them is 3).`
- **eslint, a proven narrowing.** ① Population: `--print-config`
resolves a config for both files. ② Count: `--format json` reports 2
files, 0 errors and 0 warnings, with `--no-inline-config`. ③ Invariance:
this repo's `eslint.config.mjs` never enables type-aware linting (no
`parserOptions.project`), and its local plugins are per-file. So this
diff cannot move the verdict for any file it does not touch.
- **Not run locally:** the `Test Core` steps that `scripts/**` schedules
in `ci.yml`. They are CI's own shell (`NOT MEASURED`, declared to CI).
The two value-bearing families (`check-shard-attestation`,
`check-test-completeness`) take `$RUNNER_TEMP` and matrix values and
have no local invocation.
## Acceptance notes
- **The objectui install for the measurement needed a flag.** The
objectui pin's `.npmrc` sets `engine-strict=true`, and `jsdom@30.0.1`
requires node `^22.22.2`. This container runs `22.22.0`, so the install
used `--config.engine-strict=false`. jsdom is a test dependency and is
outside the generator's import closure. This is a local environment fact
only; nothing was filed.
- **The changeset label.** Root `scripts/**` publishes nothing, so this
PR takes the `skip-changeset` label.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01JAhu8u8QfBvRjVZDox7CP9)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent 88b484e commit 318bdaf
2 files changed
Lines changed: 131 additions & 6 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
87 | 87 | | |
88 | 88 | | |
89 | 89 | | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
90 | 102 | | |
91 | 103 | | |
92 | | - | |
| 104 | + | |
93 | 105 | | |
94 | | - | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
95 | 118 | | |
96 | 119 | | |
97 | 120 | | |
| |||
132 | 155 | | |
133 | 156 | | |
134 | 157 | | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
135 | 166 | | |
136 | 167 | | |
137 | 168 | | |
| |||
393 | 424 | | |
394 | 425 | | |
395 | 426 | | |
| 427 | + | |
396 | 428 | | |
397 | 429 | | |
398 | 430 | | |
| |||
401 | 433 | | |
402 | 434 | | |
403 | 435 | | |
404 | | - | |
| 436 | + | |
405 | 437 | | |
406 | 438 | | |
407 | 439 | | |
| |||
469 | 501 | | |
470 | 502 | | |
471 | 503 | | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
472 | 579 | | |
473 | 580 | | |
474 | 581 | | |
| |||
505 | 612 | | |
506 | 613 | | |
507 | 614 | | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
508 | 623 | | |
509 | 624 | | |
510 | 625 | | |
| |||
606 | 721 | | |
607 | 722 | | |
608 | 723 | | |
609 | | - | |
| 724 | + | |
| 725 | + | |
610 | 726 | | |
611 | 727 | | |
612 | 728 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
67 | 67 | | |
68 | 68 | | |
69 | 69 | | |
70 | | - | |
| 70 | + | |
71 | 71 | | |
72 | 72 | | |
73 | 73 | | |
| |||
177 | 177 | | |
178 | 178 | | |
179 | 179 | | |
180 | | - | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
181 | 190 | | |
182 | 191 | | |
183 | 192 | | |
| |||
0 commit comments