Skip to content

fix(skills): stop naming app as an expression root in two published guides #4165

fix(skills): stop naming app as an expression root in two published guides

fix(skills): stop naming app as an expression root in two published guides #4165

name: Inert vi.mock Specifiers
# Two gates over ONE population, in one home. Both read the `vi.mock` call sites
# of this tree and both catch a mock that is silently not doing what it looks
# like it is doing: `check-vi-mock-specifiers.mjs` catches a specifier that
# resolves to no file (an inert stand-in), and `check-vi-mock-inherit.mjs`
# catches a factory that hand-lists its exports (a frozen export surface,
# objectui#6849). They share the call-site pattern deliberately -- a population
# that drifted between them would be a hole neither one reports -- so they share
# a workflow rather than each registering a required context of its own.
#
# The workflow `name:` and the job `name:` are therefore BROADER than the older
# of the two gates. They are left unchanged on purpose: those two strings are
# the check-run context that branch protection and
# `scripts/dependabot-merge-gate.mjs` name, and renaming a required context
# silently un-requires it.
#
# Why this is its own workflow rather than a step in `ci.yml` or `lint.yml`: a
# module mock can be written into any package, in any shape of pull request, and
# both of those workflows decide inside the job whether the change "needs a full
# run" — with an exclusion list that skips every expensive step on a
# markdown-only or changeset-only change. This gate costs a checkout plus one
# `node` call, so there is nothing to gain by putting it behind that switch and
# a whole class of pull request to lose.
#
# Same shape and the same reasoning as `docs-links.yml`, `control-bytes.yml`,
# `skills-paths.yml`, `changeset-presence.yml` and `pre-install-import-graph.yml`,
# whose headers record the conclusion this repository has now reached five
# times: a gate that cannot see the pull request shape most likely to trip it
# "rebuilds the hole it exists to close". One gate, one home.
#
# Hence: no `paths` and no `paths-ignore` here, deliberately.
# `scripts/__tests__/check-vi-mock-specifiers.test.ts` fails if either is ever
# added, and fails too if a second workflow starts running the same script.
# Reporting on every pull request is also what makes the check requirable, and
# `scripts/dependabot-merge-gate.mjs` classifies it as a required context — an
# unclassified blocking check is one a Dependabot merge would be let past
# (objectui#6135).
#
# It needs no install and no build — a checkout plus one `node` call over ~3.7k
# tracked source files, measured at ~2.7s on this tree. Keep it that way; the
# import graph is builtins plus repo-relative modules only, which
# `pre-install-import-graph.yml` enforces (objectui#6148).
on:
pull_request:
branches: [main, develop]
push:
branches: [main, develop]
# Merge queue (objectui#3523 — see `ci.yml`'s trigger block for the full note
# and the measurements behind it). A required check that does not report on a
# queue build stalls the queue until the ruleset's 60-minute timeout fails it,
# so an unfiltered gate that can become required subscribes here from the
# start. `types:` is named although `checks_requested` is currently the only
# activity type GitHub defines for `merge_group`.
merge_group:
types: [checks_requested]
workflow_dispatch:
concurrency:
group: vi-mock-specifiers-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
vi-mock-specifiers:
name: Inert vi.mock Specifier Check
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: '22.x'
# A `vi.mock` whose relative specifier resolves to no file does NOT error.
# Vitest registers the mock against a module id nothing imports, the run
# proceeds with the real module everywhere, and the suite passes —
# identically to a correct one, with no warning and no smaller assertion
# count. objectui#5646: the one known instance (PR #5645) passed even with
# the code under test reverted to the shape the suite was written to
# catch, and only an ablation leg exposed it.
#
# This gate is GREEN AT REST — there are zero unresolvable specifiers in
# the tree and there should stay zero — so it prints its census rather
# than a bare "OK", and it FAILS if the population collapses to nothing.
# A scan that silently finds nothing reads as coverage, which is this
# gate's own defect one level up.
- name: Check every relative vi.mock specifier resolves
run: node scripts/check-vi-mock-specifiers.mjs
# The sibling property of the same call sites: a factory that HAND-LISTS
# the exports it returns freezes the mock's export surface. The next
# export any module in the file's import graph reads AT MODULE SCOPE then
# kills the file during COLLECTION -- `Test Files 3 failed | 546 passed`
# with `Tests 6694 passed`, ZERO failed assertions, because the tests in
# those files never ran (objectui#6768). It reads as flake to whoever
# sees it next, and the bill lands on whoever added the export.
#
# The recogniser is SEMANTIC, never a grep for `importOriginal`: that
# spelling mis-counted eleven correct files as broken AND missed one
# broken file entirely (objectui#6849). It asks whether the factory
# OBTAINS the real module -- a callback parameter under any name, or
# `vi.importActual` of the same specifier -- and SPREADS it.
- name: Check every covered vi.mock factory inherits the real export surface
run: node scripts/check-vi-mock-inherit.mjs