Skip to content

Commit 39ca587

Browse files
test(ui-merge): the ui tests stop reading objectui's nested workflows — Lint UI and Repo Gates readers pin the root lint-ui job, governance pins retire with their roster rows (#26013)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} --> _Requested by **Tesla** · [project thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoAqVfFyhsxKuFcEP9YTBj25)_ Part of #25712 (4b PR 2d-3b: the ui tests that read a nested workflow whose root carrier is on `main`). Part of objectstack#25677. Clause-②: no Metadata-protocol: none ## 维护者速读(草稿) - **改了什么:** - 23 个 ui 测试文件原来读取 `ui/.github/workflows/` 下的 objectui workflow,现在不再读。 - 读 `lint.yml` 的 5 个文件和读 `repo-gates.yml` 的 4 个文件,改为按路径读根目录 `ci.yml` 的 `lint-ui` 作业(#25902 已把 Repo Gates 移入该作业)。`markdown-test-inputs` 改为读根目录 `filter` 作业的 `ui` 过滤器和 `test-ui` 的 `if:`。 - 其余 13 个文件里断言治理 workflow 接线的用例退役。每个退役用例都在原处写明 #25697 roster 的行号,由该行负责以后迁移。读脚本、fixture、配置或 `package.json` 的用例全部保留。 - `check-governed-queue-guard.mjs` 自带的 `--self-test` 里也有读 workflow 的接线断言,同样退役。 - **为什么改:** GitHub 只运行仓库根目录的 `.github/workflows/`,嵌套的那些 workflow 永远不会运行,对它们做断言等于什么都没检查。2d-3c 要删除 `ui/.github/workflows/**`,这些测试必须先不再读它们。 - **风险与代价(含回滚):** 不动任何 workflow,也不动受管面。被退役的接线断言要等 sub-issue 4 把对应 workflow 迁到根目录时再按路径补回。回滚 = revert 本 PR。 - **席位意见:**(留空) - **你要做的:** 无。Tier S,由席位落地。 ## 席位核对 (left for the dispatch seat) ## Why GitHub runs only the repository root's `.github/workflows/`. Since the cutover (#25825), objectui's own workflows sit at `ui/.github/workflows/` and run nowhere, so a pin on one of them asserts nothing GitHub does. 2d-3c deletes that directory. Before it can, every test that reads it has to stop. This PR handles the readers whose root carrier is already on `main`: - 2d-3a (#25957, merged): the pre-install import graph and the prompt-keys gate; - 2d-3b (this PR, Tier S): 23 test files and one gate's self-test. It folds the planned 2d-3b1 and 2d-3b2, since #25902 (the root carrier of `repo-gates.yml`) merged first and the combined diff stays under 3000 lines; - 2d-3c (Tier H, after this PR): deletes `ui/.github/workflows/**` and `ui/.github/dependabot.yml`, and retires the action-ref scan. ## What changes - **The Lint UI readers retarget to the root `lint-ui` job, by path** (roster row 16, ported in PR A): - `bash32-floor-wiring` and `entry-guard-wiring` read the job's steps through `helpers/root-ci.ts` and pin the one step that runs both legs of the alias. - `check-test-path-roots` and `check-vi-mock-override-shape` pin the job's `pnpm --dir ui run` step. - The vi.mock sibling case now asserts that the gate is not among the steps any workflow runs before an install, read through the pre-install scan. - The skip-switch case now asserts that the `ui` filter matches the files the gate reads. - `lint-workflow` changes three things: - The ratchet-prose surface is the `lint-ui` job: its text for the rule-name check, its comments for the count check. - The trigger claims read the root `on:` block. It must still subscribe `pull_request` and filter no paths, and the job's `if:` must not read the event name. - The ignore-list claim becomes "the `ui` filter still matches a ui `.ts`/`.tsx` source, and the job excludes no path". - **The Repo Gates readers retarget to the same job** (objectui's `repo-gates.yml`, carried by #25902's "objectui's Repo Gates" block): - `check-control-bytes` and `check-docs-route-eager-closure`: the alias is a package script, the root Lint UI job runs it, the root trigger gates pull requests (and, for the eager closure, the queue) with no paths filter, and the `ui` filter matches the files the gate reads. - `check-doc-snippet-emitted-census`: the census leg runs after `ui-gate-build` and before the gate, in the root job. A new case pins that the alias the job runs is this script. - `check-doc-fence-languages`: one step runs both legs; no other `ci.yml` job and no other root workflow runs the guard; nothing before it in the job installs; the job's header comments state no count. - **The governance-workflow pins retire with their roster rows.** Each retired case is named, by its title, in a comment where it stood, with its row and the port that brings it back. The verdict, fixture, config and `package.json` cases in the same files are unchanged. - **`ui/scripts/check-governed-queue-guard.mjs`.** Its `--self-test` asserted ten facts about `governed-surface-guard.yml`, and its test file's collection failure hid them. They retire against row 11. The required-context register pin stays. The self-test now passes 183 cases. - **`markdown-test-inputs.test.ts`** ran the "Decide whether this change needs a full run" step out of objectui's `ci.yml`. The root `test-ui` (row 7) was ported without that step, so the step-driven legs retire, named in the header. - Two root pins replace them. The `filter` job's `ui` globs match the documents the objectui#8857, #9096 and #9142 shapes changed, and `test-ui`'s `if:` reads that filter. - The affected-test selector picks the readers. `select-affected-tests.test.ts` pins that already. - **`ui/scripts/markdown-test-inputs.mjs`.** `--audit` is clean. The ledger changes in five places: - Dropped: `check-changeset-presence`'s workflow walker, and `check-links-workflow`'s entry, which no longer reads markdown. - `check-control-bytes` and `check-docs-route-eager-closure` gain `reads: []` entries. Their new markdown literals probe the `ui` filter's globs and open no document, so they are recorded as `notRead`. - `check-doc-fence-languages` records its probe as `notRead` and drops its `walker`, since its only walk was of the nested workflows directory. ### Disposition table Roster = the #25697 workflow audit (comment 6388182608). | # | Test file | Cases before → after | Retired | Retargeted | Carried by now | |---|---|---|---|---|---| | 1 | `bash32-floor-wiring` | 9 → 9 | (none) | the `lint.yml` step → `lint-ui`'s `check:bash32-floor` step, both legs | root `ci.yml` `lint-ui` (row 16) | | 2 | `entry-guard-wiring` | 7 → 7 | (none) | same, `check:entry-guard` | row 16 | | 3 | `check-test-path-roots` | 23 → 23 | (none) | `lint-ui` runs `check:test-path-roots` | row 16 | | 4 | `check-vi-mock-override-shape` | 28 → 28 | (none) | `lint-ui` step after the install; pre-install scan; `ui` filter | row 16, plus the root `filter` job | | 5 | `lint-workflow` | 5 → 5 | `lint.yml`'s header prose, with `lint.yml` | workflow surface → `lint-ui` job; trigger claims → root `on:` plus the `ui` filter | rows 7 and 16 | | 6 | `check-control-bytes` | 38 → 38 | (none) | `repo-gates.yml` → `lint-ui`; trigger and paths claims → root `on:` plus the `ui` filter | `lint-ui`'s Repo Gates block (#25902) | | 7 | `check-docs-route-eager-closure` | 22 → 22 | (none) | same | #25902 | | 8 | `check-doc-snippet-emitted-census` | 39 → 40 | (none) | census order → `lint-ui` (`ui-gate-build` < census < gate); **new:** the alias runs this script | #25902 | | 9 | `check-doc-fence-languages` | 33 → 33 | (none) | one step, one home, needs-no-install, no-count header → `lint-ui`; paths → root `on:` plus the `ui` filter | #25902 | | 10 | `check-changeset-overwrite` | 21 → 17 | 4 `changeset-guard.yml` pins | (none) | **row 3** (→ sub-issue 4) | | 11 | `check-changeset-no-major` | 26 → 24 | 2 `changeset-guard.yml` pins | (none) | **row 3**; the repo-state cases run in Test UI | | 12 | `check-changeset-claims` | 111 → 99 | 3 reachability, 6 delivery, 3 comment-count pins | (none) | **row 4** (one read **row 22**) | | 13 | `check-changeset-presence` | 53 → 46 | 7 reachability pins | (none) | **rows 4 and 3**; root counterpart is `pr-automation.yml` `Check Changeset` | | 14 | `render-changeset-claims-comment` | 28 → 27 | the marker-spelled-once pin | (none) | **row 4** | | 15 | `check-eager-closure-budget` | 154 → 148 | 4 workflow contract pins, the workflow's must-hit control, the `merge_group` absence pin; the workflow leaves both prose populations | (none) | **row 22**, "kept, follow-up port" into `Build & E2E UI` | | 16 | `render-budget-comment` | 36 → 23 | the 13-case `performance-budget.yml contract` | (none) | **row 22** | | 17 | `check-governed-queue-guard` (+ the `.mjs` self-test) | 16 → 9 | 8 workflow pins (one only partly), plus 10 self-test assertions | (none) | **row 11**: root `governed-surface-guard.yml`, whose register governs `ui/.github/workflows/**` | | 18 | `check-merge-queue-head` | 40 → 32 | 11 workflow pins (two only partly) | (none) | **row 20**; root counterpart is `merge-queue-triage.yml` | | 19 | `check-new-cross-file-line-citations` | 37 → 33 | 4 workflow pins | (none) | **row 15** | | 20 | `shadcn-check-report` | 28 → 23 | 6 workflow pins (one only partly) | (none) | **row 26**, "kept, follow-up port" | | 21 | `check-links-workflow` | 9 → 4 | 5 workflow pins and the workflow surface of the count pin | (none) | **row 6**; the root `check-links.yml` sweeps the root's trees, not `ui/` | | 22 | `check-required-check-set` | 27 → 22 | 5 patrol pins; `dependabot-auto-merge.yml` leaves `REPAIRED` | (none) | **rows 25 and 9**; root counterpart is `required-set-patrol.yml` | | 23 | `markdown-test-inputs` | 25 → 17 | 4 + 5 decision-step cases, the #9142 FIRES case, the real-step half of its depth-1 case | **new:** the `ui` filter matches the three shapes' documents; `test-ui` reads the filter | **row 7** (`test-ui`), plus the affected-test selector | When a test file shows "(one only partly)", the case kept its non-workflow half under a new title. Untouched here, and listed for 2d-3c (the PM files its decision card): - `check-governed-queue-guard`'s `every declared surface exists in this tree` goes red once `ui/.github/workflows/` is gone. The register's `.github/workflows/**` row was a maintainer ruling (objectui#12255), so this PR does not decide it. - `ui/.github/labeler.yml` stays, pending sub-issue 4 (F12 = B). ### Follow-up rows (not folded here) - **F18.** After this PR the `markdown-test-inputs` ledger has no root CI consumer. Its audit runs in Test UI, and the root `test-ui` job decides from the `ui` filter and the selector, never from the ledger. Sub-issue 4 decides whether the ledger stays as an audit or retires. - **F19.** `ui/scripts/__tests__/workflow-checks.ts`'s parser (`readWorkflows`, `triggerBlock`, `producedCheckNames`) has no caller after this PR. Its `repoRoot` export still has two, `check-lockfile-integrity` and `check-lockfile-dedupe`. It can be trimmed with 2d-3c or sub-issue 4. - **F20.** `ui/scripts/check-cross-repo-closer-outcome.mjs` takes objectui's `ui/.github/workflows/cross-repo-issue-closer.yml` as its subject, and only objectui's `lint.yml` ran it. Nothing runs it now, and after 2d-3c its subject is gone. It goes with the cross-repo-issue-closer roster row in sub-issue 4. ## Verification Head `249928d5`, on `main` `3518664c`. 25 files, +606 / −1544. Test runs are from `ui/`, per `ui/AGENTS.md`. | Run | Result | |---|---| | The 23 modified test files, after the rebase onto `3518664c` | 23 files, 766 tests passed | | Whole `scripts/__tests__` suite (at `45026511`, before the rebase; `main` brought one `ui/scripts` change since, `check-i18n-dead-keys.mjs`, which no touched file reads) | 183 files passed, 2 skipped; 5323 tests passed, 2 skipped | | Whole suite in the 2d-3c layout (`ui/.github/workflows/` and `ui/.github/dependabot.yml` moved aside) | 4 files red, exactly the set 2d-3c owns: `check-action-ref-convention` (12 cases; 2d-3c retires it), `check-governed-queue-guard` (1 case, the open question above), `check-pre-install-import-graph` (sections 4 and 5; collection), `ci-cd-pipeline-doc` (2d-3c deletes it; collection). No hidden reader is left. | | `node scripts/check-governed-queue-guard.mjs --self-test` | 183 cases pass | | `pnpm type-check:scripts` | the one error is `packages/components/src/renderers/action/resolve-icon.ts(265,69)` TS2339, identical on unmodified `main`; none in a touched file | | `pnpm exec eslint --max-warnings=0` (all 25 touched files), `markdown-test-inputs --audit` | both exit 0; the audit reads "115 candidate test files, all adjudicated" | Gate sweep: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derives 18 families for this diff, unchanged after the rebase. All 18 ran and exited 0. `--ran` reconciliation: 18 derived, 18 run, 0 NOT-MEASURED, 0 UNRUN. Push-environment battery (`GITHUB_ACTIONS=true GITHUB_EVENT_NAME=push node scripts/pm/check-dispatch-gates.mjs`): ✓ 2182 cases pass, 0 failed (fast tier 1917 in 442.3 s, slow tier 265 in 1234.8 s), exit 0. The rebase brought no `scripts/pm/` change. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01A6SEitNngAMugGbuJHWsDS --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 39a78b1 commit 39ca587

25 files changed

Lines changed: 606 additions & 1544 deletions

‎ui/scripts/__tests__/bash32-floor-wiring.test.ts‎

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import { fileURLToPath } from 'node:url';
66
import { parse as parseYaml } from 'yaml';
77

88
import { selfTestCases, stripAnsi } from './helpers/child-verdict';
9+
import { rootCi, uiRun, UI_JOBS } from './helpers/root-ci';
910

1011
const ROOT = path.resolve(fileURLToPath(import.meta.url), '../../..');
1112
const GATE = 'scripts/check-bash32-floor.mjs';
13+
const ALIAS = 'check:bash32-floor';
1214

1315
/**
1416
* objectui#7692: this repository held its own shell to nothing.
@@ -36,10 +38,12 @@ const GATE = 'scripts/check-bash32-floor.mjs';
3638
* passes:
3739
*
3840
* 1. the script exists and `package.json`'s alias points at it;
39-
* 2. `lint.yml` really runs it, in a step that executes, on pull requests;
40-
* 3. the workflow spelling and the alias spelling name the SAME script, so
41-
* `pnpm check:bash32-floor` and the pre-install `node` invocation cannot
42-
* drift into checking different things;
41+
* 2. the root `Lint UI` job (`lint-ui` in the repository root's `ci.yml`,
42+
* where objectstack#25697 ported `lint.yml`) really runs it, in a step
43+
* that executes, on pull requests;
44+
* 3. that step runs the alias, and the alias names the script, so
45+
* `pnpm check:bash32-floor` and the pre-install CI step cannot drift into
46+
* checking different things;
4347
* 4. it runs BEFORE `pnpm install` — placed after, an install failure takes
4448
* the gate with it and the tree goes unjudged with the job red for an
4549
* unrelated reason;
@@ -50,26 +54,30 @@ const GATE = 'scripts/check-bash32-floor.mjs';
5054
* whose loss would put the card straight back.
5155
*/
5256
describe('check-bash32-floor is wired, not merely present', () => {
53-
const workflow = parseYaml(fs.readFileSync(path.join(ROOT, '.github/workflows/lint.yml'), 'utf8'));
54-
const steps: Array<Record<string, unknown>> = workflow.jobs.lint.steps;
55-
const gateSteps = steps.filter((s) => typeof s.run === 'string' && (s.run as string).includes(GATE));
57+
// objectstack#25712: the job that runs it is the root `ci.yml`'s `lint-ui`,
58+
// where objectstack#25697 ported objectui's `lint.yml` (roster row 16), read
59+
// by path. It spells the gate through its alias, so `ALIAS` is what a step
60+
// runs and the alias case below is what ties that to the file.
61+
const workflow = parseYaml(rootCi());
62+
const steps: Array<Record<string, unknown>> = workflow.jobs[UI_JOBS.lint].steps;
63+
const gateSteps = steps.filter((s) => typeof s.run === 'string' && (s.run as string).includes(uiRun(ALIAS)));
5664

5765
it('the gate script exists', () => {
5866
expect(fs.existsSync(path.join(ROOT, GATE))).toBe(true);
5967
});
6068

6169
it('package.json aliases it, and the alias points at the script that exists', () => {
6270
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
63-
const alias = pkg.scripts['check:bash32-floor'];
71+
const alias = pkg.scripts[ALIAS];
6472
expect(alias).toBeTruthy();
6573
expect(alias).toContain(GATE);
6674
});
6775

68-
it('lint.yml runs it — exactly one step, both legs', () => {
76+
it('the root Lint UI job runs it — exactly one step, both legs', () => {
6977
expect(gateSteps).toHaveLength(1);
7078
const run = gateSteps[0].run as string;
71-
expect(run).toContain(`node ${GATE} --self-test`);
72-
expect(run.split('\n').some((l) => l.trim() === `node ${GATE}`)).toBe(true);
79+
expect(run).toContain(`${uiRun(ALIAS)} --self-test`);
80+
expect(run.split('\n').some((l) => l.trim() === uiRun(ALIAS))).toBe(true);
7381
});
7482

7583
it('that step is not disabled — it runs whenever the job runs its other steps', () => {
@@ -82,7 +90,7 @@ describe('check-bash32-floor is wired, not merely present', () => {
8290
expect(others).toContain(condition);
8391
});
8492

85-
it('lint.yml still gates pull requests, or the step above is inert', () => {
93+
it('the root ci.yml still gates pull requests, or the step above is inert', () => {
8694
// `on` parses as the boolean `true` in YAML 1.1; `yaml` gives back `on`.
8795
const on = workflow.on ?? workflow[true as unknown as string];
8896
expect(Object.keys(on)).toContain('pull_request');

‎ui/scripts/__tests__/check-changeset-claims.test.ts‎

Lines changed: 38 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,14 @@ import {
3535
* What this file pins, in the order the gate can fail:
3636
*
3737
* 1. **It can start on the pull request that needs it.** The falsifying change
38-
* is an ordinary source change, so the gate lives in the workflow with NO
39-
* path filter. That absence is the load-bearing property and is pinned here:
40-
* adding a `paths:` filter to that workflow would blind this gate silently,
41-
* which is the exact failure mode `changeset-guard.yml` can afford and this
42-
* one cannot.
38+
* is an ordinary source change, so the gate lived in the workflow with NO
39+
* path filter, and that absence was pinned here. Retired with
40+
* objectstack#25712, with the delivery pins of section 1b and the comment
41+
* step's count pins in the corpus section: they read objectui's
42+
* `changeset-presence.yml`, which runs nowhere under the monorepo (GitHub
43+
* runs the root `.github/workflows/` alone). It is row 4 of the
44+
* objectstack#25697 workflow roster (→ sub-issue 4), and a port brings them
45+
* back against its root workflow, by path.
4346
* 2. **Both directions of the verdict**, against throwaway repositories rather
4447
* than this one's history, so they stay decidable when the history moves. A
4548
* report that fires, and a FIRING CONTROL — the same fixture with the named
@@ -61,24 +64,6 @@ import {
6164
*/
6265
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
6366
const GATE = 'scripts/check-changeset-claims.mjs';
64-
const WORKFLOW = '.github/workflows/changeset-presence.yml';
65-
66-
/**
67-
* A workflow's YAML with whole-line comments removed.
68-
*
69-
* Required, not cosmetic: this workflow's header discusses its own trigger set
70-
* at length, so a scan that counted the prose would report filter entries the
71-
* file does not have. Same helper, same reason, as in the sibling changeset
72-
* gate tests.
73-
*/
74-
function withoutComments(yaml: string): string {
75-
return yaml
76-
.split('\n')
77-
.filter((line) => !/^\s*#/.test(line))
78-
.join('\n');
79-
}
80-
81-
const workflowYaml = withoutComments(fs.readFileSync(path.join(repoRoot, WORKFLOW), 'utf8'));
8267

8368
// ── fixture repositories ─────────────────────────────────────────────────────
8469

@@ -200,88 +185,27 @@ function lastCommitRange(fixture: Fixture): string[] {
200185
}
201186

202187
// ── 1. the gate can start on the pull request that needs it ──────────────────
203-
204-
describe('changeset-presence.yml — where this gate has to live', () => {
205-
it('exists, and the workflow runs it', () => {
206-
expect(fs.existsSync(path.join(repoRoot, GATE)), `${GATE} must exist for the workflow to run it`).toBe(true);
207-
expect(workflowYaml).toMatch(new RegExp(`run:\\s*node\\s+${GATE.replace(/[.]/g, '\\.')}`));
208-
});
209-
210-
it('gives the job the history its diff needs', () => {
211-
// The gate compares this change against its MERGE BASE. checkout's default
212-
// is a depth-1 clone where `git merge-base` has nothing to find, and an
213-
// unresolvable base is exit 1 in the script rather than a skip — so getting
214-
// this wrong is a red build, not a silent pass. Pinned so it stays that way.
215-
expect(workflowYaml).toMatch(/fetch-depth:\s*0/);
216-
});
217-
218-
it('carries NO paths filter — the property this gate depends on', () => {
219-
// `changeset-guard.yml` can afford a `paths: ['.changeset/**']` filter
220-
// because a change that modifies a changeset touches `.changeset/**` BY
221-
// DEFINITION. This gate's subject is the opposite: an ordinary source change
222-
// that falsifies somebody else's pending claim, which is under no obligation
223-
// to touch `.changeset/**` at all. A path filter added here would blind the
224-
// gate with no red anywhere, so its ABSENCE is pinned.
225-
expect(workflowYaml).not.toMatch(/^\s*paths(-ignore)?:/m);
226-
});
227-
});
228-
188+
//
189+
// Retired with objectstack#25712: `exists, and the workflow runs it`, `gives the
190+
// job the history its diff needs` and `carries NO paths filter — the property
191+
// this gate depends on`.
192+
//
229193
// ── 1b. the finding is DELIVERED, not archived (objectui#9140) ───────────────
230-
231-
describe('delivery — the ruling that the finding reaches the pull request', () => {
232-
// objectui#9140 measured the "REQUEST TO READ" at zero answers out of four
233-
// live instances, because it was addressed to a job log nobody opens on a
234-
// green check. The director's ruling (maintainer 「同意」) moves WHERE the
235-
// finding is read and ⛔ nothing else: exit 0, not a required context,
236-
// nothing blocks.
237-
it('hands this run\'s finding set to the renderer instead of re-measuring it', () => {
238-
// One measurement, two consumers. A report that re-derives its own subject
239-
// can disagree with the log it claims to report, and the disagreement is
240-
// invisible to both sides.
241-
expect(workflowYaml).toMatch(/node\s+scripts\/check-changeset-claims\.mjs\s+--json\s+claims\.json/);
242-
expect(workflowYaml).toMatch(
243-
/node\s+scripts\/render-changeset-claims-comment\.mjs\s+--from\s+claims\.json/,
244-
);
245-
});
246-
247-
it('posts through the channel the Console Performance Budget report uses', () => {
248-
expect(workflowYaml).toMatch(/uses:\s*actions\/github-script@/);
249-
const budget = withoutComments(
250-
fs.readFileSync(path.join(repoRoot, '.github/workflows/performance-budget.yml'), 'utf8'),
251-
);
252-
expect(budget).toMatch(/uses:\s*actions\/github-script@/);
253-
});
254-
255-
it('holds `pull-requests: write` on the JOB, leaving the declaration gate with a read-only token', () => {
256-
// A job-level block REPLACES the workflow-level one, so `contents: read`
257-
// has to be named again or checkout loses it. The workflow-level block must
258-
// stay read-only: the declaration gate above needs nothing but a checkout,
259-
// and a grant it does not use is a grant nobody is auditing.
260-
expect(workflowYaml).toMatch(/permissions:\n\s+contents: read\n\s+pull-requests: write/);
261-
expect(workflowYaml).toMatch(/^permissions:\n\s+contents: read\n/m);
262-
expect(workflowYaml).not.toMatch(/^permissions:\n\s+contents: read\n\s+pull-requests/m);
263-
});
264-
265-
it('⛔ never subscribes `pull_request_target`, which would run this on a fork\'s branch with a write token', () => {
266-
expect(workflowYaml).not.toMatch(/pull_request_target/);
267-
});
268-
269-
it('comments only on a pull request', () => {
270-
// A `merge_group` build has no pull request to comment on, and the gate
271-
// still has to REPORT there — so the delivery steps are conditioned and the
272-
// measuring step is not.
273-
const conditions = workflowYaml.match(/if: \$\{\{ github\.event_name == 'pull_request' \}\}/g) ?? [];
274-
expect(conditions.length).toBe(2);
275-
});
276-
277-
it('cannot paint this job red by failing to post', () => {
278-
// A fork's pull request gets a read-only token and the API call fails for a
279-
// reason that says nothing about changesets. Report-only means the gate
280-
// declines to fail on its FINDINGS; it must not start failing on its
281-
// PLUMBING either.
282-
expect(workflowYaml).toMatch(/continue-on-error: true/);
283-
});
284-
});
194+
//
195+
// Retired with objectstack#25712: the six delivery cases (the run hands its
196+
// `--json` finding set to `render-changeset-claims-comment.mjs`, posts through
197+
// `actions/github-script` as `performance-budget.yml` does, holds
198+
// `pull-requests: write` on the job alone, never subscribes
199+
// `pull_request_target`, comments only on a pull request, and cannot paint the
200+
// job red by failing to post). The ruling they pinned stands: the finding
201+
// reaches the pull request, exit 0, not a required context, nothing blocks.
202+
//
203+
// Every one of them read objectui's `changeset-presence.yml` (one also read its
204+
// `performance-budget.yml`, roster row 22). Both run nowhere under the
205+
// monorepo: GitHub runs the root `.github/workflows/` alone, and no root
206+
// workflow runs this gate. `changeset-presence.yml` is row 4 of the
207+
// objectstack#25697 workflow roster (→ sub-issue 4); a port brings these pins
208+
// back against its root workflow, by path.
285209

286210
// ── 2. both directions of the verdict ────────────────────────────────────────
287211

@@ -953,35 +877,16 @@ describe('the corpus is the prose this change publishes about itself', () => {
953877
expect(gate).toContain('check-new-cross-file-line-citations.mjs');
954878
});
955879

956-
it('delivers a born-false-only finding instead of leaving it in the job log', () => {
957-
// objectui#9140 measured the job-log channel at zero answers out of four.
958-
// The comment step decides whether to post from the finding COUNT, so a run
959-
// whose only finding is born-false has to be counted there too.
960-
expect(workflowYaml).toContain('(measured.bornFalse ?? []).length');
961-
});
962-
963-
it('⭐ delivers a self-contradiction-only finding too (objectui#9841)', () => {
964-
// ⚠️ ASSERTED AS ITS OWN TERM, ⛔ never as the whole sum. A pin that matched
965-
// the sum verbatim would redden on a harmless rewording and — worse — would
966-
// be repaired by pasting whatever the file now says, which is how a dropped
967-
// term gets blessed. This one goes red for exactly one reason: this reading
968-
// stopped being counted, and its findings stopped creating the comment.
969-
expect(workflowYaml).toContain('(measured.selfContradiction ?? []).length');
970-
});
971-
972-
it('counts EVERY reading the gate carries — one term per reading, none left out', () => {
973-
// The standing obligation, stated where the next reading's author will read
974-
// it: a reading that is measured, rendered and then not counted here is
975-
// delivered to a job log nobody opens. That is objectui#9842's shape, and it
976-
// is the reason this surface was extended rather than left for later.
977-
for (const term of [
978-
'measured.findings.length',
979-
'(measured.bornFalse ?? []).length',
980-
'(measured.selfContradiction ?? []).length',
981-
]) {
982-
expect(workflowYaml).toContain(term);
983-
}
984-
});
880+
// Retired with objectstack#25712: `delivers a born-false-only finding instead
881+
// of leaving it in the job log`, `⭐ delivers a self-contradiction-only finding
882+
// too (objectui#9841)` and `counts EVERY reading the gate carries — one term
883+
// per reading, none left out`. Each read the comment step's finding count in
884+
// objectui's `changeset-presence.yml` (`measured.findings.length`,
885+
// `(measured.bornFalse ?? []).length`,
886+
// `(measured.selfContradiction ?? []).length`), which runs nowhere under the
887+
// monorepo. The obligation is the port's: the root workflow that delivers
888+
// this gate's findings counts every reading it carries, and these pins come
889+
// back against it, by path (roster row 4, → sub-issue 4).
985890
});
986891

987892
describe('the boundary control the first ablation of this change exposed', () => {

‎ui/scripts/__tests__/check-changeset-no-major.test.ts‎

Lines changed: 14 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,15 @@ const mask: (source: string) => string = maskComments;
4848
* group's own manifests (objectui#12334, under the maintainer's ruling on
4949
* objectui#12030). The fixtures below pin both directions of that computation.
5050
*
51-
* These tests are the second lock. `.github/workflows/changeset-guard.yml`
52-
* catches it on the PR that adds the changeset; the repo-state test below
53-
* catches it in `pnpm test` even if that workflow is ever removed or its
54-
* trigger stops matching.
51+
* These tests are the second lock. objectui's `changeset-guard.yml` caught it
52+
* on the PR that added the changeset; the repo-state test below catches it in
53+
* `pnpm test` even if that workflow is removed or its trigger stops matching.
54+
* Under the monorepo that workflow runs nowhere (GitHub runs the root
55+
* `.github/workflows/` alone), so the repo-state test, in `Test UI`, is the
56+
* lock that runs until sub-issue 4 ports the guard (objectstack#25712).
5557
*/
5658
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
5759
const changesetDir = path.join(repoRoot, '.changeset');
58-
const workflowDir = path.join(repoRoot, '.github/workflows');
5960
const GATE = path.join(repoRoot, 'scripts/check-changeset-no-major.mjs');
6061

6162
interface Entry {
@@ -410,37 +411,11 @@ describe('the repository itself', () => {
410411
});
411412
});
412413

413-
describe('changeset-guard.yml', () => {
414-
const guard = fs.readFileSync(path.join(workflowDir, 'changeset-guard.yml'), 'utf8');
415-
416-
it('runs the guard script', () => {
417-
expect(guard).toContain('node scripts/check-changeset-no-major.mjs');
418-
});
419-
420-
it('triggers on `.changeset/**` — the paths every other workflow ignores', () => {
421-
// The inverse pin, and the reason `ci.yml` still cannot host this check: on
422-
// a changeset-only PR its `Decide whether this change needs a full run` step
423-
// finds nothing outside its exclusion list (markdown and `.changeset/**`
424-
// among it) and skips every step below itself, so no gate in that workflow
425-
// ever reads the changeset. This one has to run outside that decision.
426-
//
427-
// Not the reason this comment used to give — "a changeset-only PR matches
428-
// its `paths-ignore` twice over, so no job in it would ever run". Since
429-
// objectui#3523 step 2, `paths-ignore` is gone from the `pull_request`
430-
// trigger and survives only on `push`: such a PR does start `ci.yml` and
431-
// does produce its contexts (measured — PR #3856, one markdown file, 16
432-
// checks; PR #4339, one line added to AGENTS.md, 17). The conclusion
433-
// outlived the premise; objectui#3857 pinned the correction.
434-
//
435-
// That surviving `push` copy is what the two assertions below read, and it
436-
// is why they stay green — `ci.yml` genuinely still declares `paths-ignore`
437-
// with markdown in it, on `push`. The in-job exclusion list is held
438-
// identical to that filter by `scripts/__tests__/merge-queue-reporting.test.ts`,
439-
// so the pair cannot drift apart silently.
440-
expect(guard).toMatch(/paths:\s*\n\s*- '\.changeset\/\*\*'/);
441-
442-
const ci = fs.readFileSync(path.join(workflowDir, 'ci.yml'), 'utf8');
443-
expect(ci).toContain("paths-ignore:");
444-
expect(ci).toContain("- '**/*.md'");
445-
});
446-
});
414+
// `changeset-guard.yml` (`runs the guard script`, and `triggers on
415+
// .changeset/** — the paths every other workflow ignores`) retired with
416+
// objectstack#25712. Both cases read objectui's `changeset-guard.yml` and its
417+
// `ci.yml`, which run nowhere under the monorepo: GitHub runs the root
418+
// `.github/workflows/` alone, and no root workflow runs this gate. The guard is
419+
// row 3 of the objectstack#25697 workflow roster (→ sub-issue 4); a port brings
420+
// these pins back against its root workflow, by path. The repo-state cases
421+
// above are the lock that runs meanwhile.

0 commit comments

Comments
 (0)