fix(skills): stop naming app as an expression root in two published guides
#8138
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Lint | |
| # Until #2923 this workflow was `workflow_dispatch`-only, so ESLint had never | |
| # gated a PR. That mattered more than it looked: every `object-ui/*` rule that | |
| # `eslint.config.js` sets to `error` is a ratchet — added *specifically* so a | |
| # new violation fails CI, with its existing sites pre-cleaned first so the rule | |
| # lints clean on the day it lands. While nothing ran them, every one of them was | |
| # inert. | |
| # | |
| # `eslint.config.js` is the single list of those rules, and this comment | |
| # deliberately neither counts them nor names them: it used to hand-count, and | |
| # the count was stale by the time anyone read it (#3261). A hand-copied | |
| # enumeration drifts by construction, and a stale one still reads as | |
| # authoritative — `content/docs/guide/ci-cd-pipeline.md` avoids the number for | |
| # the same reason. `scripts/__tests__/lint-workflow.test.ts` holds that in | |
| # place: it fails if a count or a rule name reappears here, if the config stops | |
| # setting any `object-ui/*` rule to `error`, or if this workflow stops gating | |
| # pull requests. | |
| # | |
| # `--max-warnings` is deliberately not set: warnings repo-wide run into the | |
| # thousands, dominated by `no-explicit-any` plus React Compiler rules the config | |
| # downgrades on purpose — known historical debt, not a signal. This gate is | |
| # about errors. No exact figure here, for the reason above: this paragraph used | |
| # to carry a hand-maintained warning count and percentage that nothing | |
| # recomputed and nothing alarmed on as they aged (#3274), and | |
| # `scripts/check-lint-coverage.mjs` held a copy of the same number that would | |
| # have gone stale on its own clock. The order of magnitude is the whole | |
| # argument; the integer never was. | |
| on: | |
| push: | |
| branches: [main, develop] | |
| paths-ignore: | |
| - '**/*.md' | |
| - 'content/**' | |
| - 'docs/**' | |
| - '.changeset/**' | |
| # No `paths-ignore` here any more (objectui#3523, step 2) — it skipped the | |
| # whole workflow on a docs-only / changeset-only PR, so the `Lint` context was | |
| # absent exactly where a required check must still report. The path decision | |
| # moved into the job below. `push` above keeps its copy: nothing judges a push | |
| # to `main`. | |
| pull_request: | |
| branches: [main, develop] | |
| # ── Merge queue (objectui#3523) ──────────────────────────────────────── | |
| # The merge queue is ENFORCED on this repository by a ruleset — a direct push | |
| # to `main` returns 405 `Changes must be made through the merge queue` | |
| # (measured in #3243). Until this trigger landed, not one of the repository's | |
| # workflows subscribed `merge_group`: repo-wide `event=merge_group` runs stood | |
| # at total_count = 0, historically. A queue with nothing subscribed to it can | |
| # only have an EMPTY required-check set, so it rebuilt each PR on the current | |
| # `main` and let it through without validating anything. | |
| # | |
| # That is not a theoretical hole; it was cashed in on 2026-08-07. #3498 landed | |
| # a `scripts/` type gate, itself fully green, that left a TS2578 on `main`; | |
| # #3503, #3510 and #3516 then merged between 02:11Z and 02:15Z with `Type | |
| # Check` at conclusion=failure, and #3505 hot-fixed the result. objectstack | |
| # went through the same frames (objectstack#6067 -> #5615). | |
| # | |
| # `types:` is spelled out although `checks_requested` is the ONLY activity | |
| # type GitHub defines for `merge_group` today — the two spellings are | |
| # equivalent right now (objectstack's `ci.yml` and `lint.yml` use the bare | |
| # `merge_group:` form and produce queue builds normally, 3552 of them). Naming | |
| # the type means a second activity type added later cannot silently start | |
| # queue builds this workflow was never written for. | |
| # | |
| # `concurrency` below needs no merge-queue special case, and that was checked | |
| # rather than assumed: on `merge_group` the `github.event.pull_request` half of | |
| # the group expression is null, so the group falls back to `github.ref`, which | |
| # on a queue build is the queue's own generation — measured on objectstack, | |
| # `gh-readonly-queue/main/pr-6594-251e888ac9ace8226f3a8450951e5b40a0a84c2c`. | |
| # It can collide with neither a pull-request group (a bare PR number) nor a | |
| # push group (`refs/heads/main`), so a queue build and the PR build it came | |
| # from never cancel each other. | |
| merge_group: | |
| types: [checks_requested] | |
| workflow_dispatch: | |
| concurrency: | |
| group: lint-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| lint: | |
| name: Lint | |
| runs-on: ubuntu-latest | |
| # ── The job ceiling, DERIVED FOR THIS JOB (objectui#7270) ──────────── | |
| # Before this line the only backstop was GitHub's 360-minute default. This | |
| # repository has been bitten twice by an unbounded step whose only backstop | |
| # was the job ceiling — the `apt-get` hang (objectui#5304) and the cache-save | |
| # stall that ejected an all-green pull request (objectui#6577) — and both | |
| # times the ceiling converted a transient fault into a `cancelled` check, | |
| # which the merge queue cannot tell from `failure`. `Lint` is a required | |
| # context on `pull_request` and `merge_group` alike, so at 360 the same | |
| # fault holds a shared serial queue for six hours. | |
| # | |
| # ⛔ NOT inherited from `ci.yml`'s 10/15/20/30/40 — objectui#7048 fences | |
| # exactly that. The arithmetic below is this job's own. | |
| # | |
| # - Population: the last 300 successful runs of this workflow, window | |
| # 2026-09-05T11:51Z .. 2026-09-06T04:11Z, n=300 `Lint` jobs. Per-job | |
| # wall clock from the Actions jobs endpoint (`completed_at` minus | |
| # `started_at`), never the run's total. | |
| # - min 9s / median 5m16s / p95 5m46s / max 7m14s. | |
| # - Bimodal at the LOW end only: 11 runs finished in 9-14s, which is the | |
| # `Decide whether this change needs a full run` gate skipping every | |
| # step; the other 289 ran 3m27s-7m14s. A low mode cannot raise a | |
| # ceiling, so it is recorded and not used. | |
| # - By event, no material difference, so one ceiling covers all three: | |
| # merge_group n=103 (median 5m17s, max 6m06s), pull_request n=142 | |
| # (median 5m15s, max 6m39s), push n=55 (median 5m14s, max 7m14s). | |
| # - max/p95 = 1.25 — no fat tail, so the multiplier is not widened. | |
| # | |
| # Ceiling = the smallest round number that is both >= 3x max (21.7min) and | |
| # >= max + 15min (22.2min) => 25. That is ~3.5x the slowest run ever | |
| # observed here, so it cannot fire on a healthy-but-slow runner; a wedge | |
| # dies in 25 minutes instead of 6 hours. | |
| # | |
| # ⛔ Raising this toward 360 is the ruled-out non-fix (objectui#6577, | |
| # objectui#7048): the hang just runs longer and the gate still reports | |
| # `cancelled`. | |
| timeout-minutes: 25 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v7 | |
| with: | |
| submodules: true | |
| # `fetch-depth: 0` for the gate step below (objectui#3523): it diffs | |
| # against the merge base, which a depth-1 clone cannot resolve. | |
| fetch-depth: 0 | |
| # ── Always report; run only when it matters (objectui#3523) ────────── | |
| # `on.pull_request.paths-ignore` used to skip this whole workflow on a | |
| # docs-only or changeset-only pull request, so the `Lint` context was | |
| # simply absent there — and a required check that never reports leaves the | |
| # PR pending forever (in the merge queue, until the ruleset's 60-minute | |
| # timeout fails it). The filter moved from the trigger into the job: the | |
| # job always runs and always reports, the paths decide only whether the | |
| # expensive steps execute. `ci.yml`'s `docs` job is the in-repo precedent | |
| # for the shape, and its `type-check` job carries the long version of this | |
| # note. The list below IS the `paths-ignore` it replaced; the `push` | |
| # trigger keeps its copy, because nothing judges a push to `main`. | |
| # | |
| # Fails OPEN: if the diff cannot be computed the job runs everything, | |
| # rather than reporting green having linted nothing (objectstack#4928). | |
| - name: Decide whether this change needs a full run | |
| id: relevant | |
| run: | | |
| if [ "${{ github.event_name }}" != 'pull_request' ]; then | |
| echo 'should_run=true' >> "$GITHUB_OUTPUT" | |
| echo 'Not a pull request: push is filtered at the trigger, and a merge_group build is the last validation before main. Running everything.' | |
| exit 0 | |
| fi | |
| if ! CHANGED=$(git diff --name-only \ | |
| '${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}' -- \ | |
| . \ | |
| ':(exclude,glob)**/*.md' \ | |
| ':(exclude,glob)content/**' \ | |
| ':(exclude,glob)docs/**' \ | |
| ':(exclude,glob).changeset/**'); then | |
| echo 'should_run=true' >> "$GITHUB_OUTPUT" | |
| echo 'Could not diff against the merge base. Running everything rather than skipping silently.' | |
| exit 0 | |
| fi | |
| if [ -n "$CHANGED" ]; then | |
| echo 'should_run=true' >> "$GITHUB_OUTPUT" | |
| echo "$CHANGED" | |
| else | |
| echo 'should_run=false' >> "$GITHUB_OUTPUT" | |
| echo 'Only ignored paths changed. Skipping the steps below; this check still reports.' | |
| fi | |
| - name: Enable Corepack and download the pinned pnpm | |
| if: steps.relevant.outputs.should_run == 'true' | |
| run: bash scripts/ci-setup-pnpm.sh | |
| - name: Verify pnpm version | |
| if: steps.relevant.outputs.should_run == 'true' | |
| run: pnpm --version | |
| - name: Setup Node.js | |
| if: steps.relevant.outputs.should_run == 'true' | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '22.x' | |
| cache: 'pnpm' | |
| # Every package must run ESLint or be declared a known gap. turbo skips | |
| # scriptless packages silently, so without this a package reads as clean | |
| # because nothing linted it. Runs before install: only reads package.json. | |
| - name: Verify lint coverage | |
| if: steps.relevant.outputs.should_run == 'true' | |
| run: node scripts/check-lint-coverage.mjs | |
| # ── One entry guard, one predicate (objectui#6092) ─────────────────── | |
| # A `scripts/**` CLI has to answer "did node run me, or did something | |
| # import me?" before it does anything, and the hand-typed answers in this | |
| # tree had drifted into NINE spellings across 28 `.mjs` files. Every one | |
| # of them fails the same way: node resolves symlinks for the module graph | |
| # but leaves `process.argv[1]` as the caller typed it, so a script reached | |
| # through a symlink compares two different paths, answers false, and does | |
| # NOTHING — exit 0, no output. The wrappers that spawn these tools hold | |
| # `result.status` only, so an inert child is a green gate. Measured on a | |
| # real blocking gate in this repository (objectui#6078): | |
| # `check-skills-paths.mjs` run directly on a deliberately broken tree | |
| # exits 1 with 696 bytes naming the dead path; run through a symlink | |
| # against the SAME tree it exits 0 with no output. | |
| # | |
| # `scripts/invoked-as.mjs` landed here in #5984 with a header stating that | |
| # a gate enforced the single-spelling rule. It did not exist, and the | |
| # sweep it described had not happened either (objectui#6078). This is that | |
| # gate. It lands BEFORE the conversion on purpose: the worklist grew while | |
| # the card sat open, so a sweep with nothing under it can be undone | |
| # silently by the next pull request. The 29 existing guards are baselined, | |
| # SHRINK-ONLY, and the script's own header says why that is safe. | |
| # | |
| # Runs before install, next to `check-lint-coverage.mjs` above and for the | |
| # same reason: it reads sources with no dependency beyond node builtins | |
| # and two local modules, so nothing it needs is in `node_modules`. Placed | |
| # before `pnpm install` it also cannot be made green by an install | |
| # failure. `--self-test` runs FIRST and is the half that stops the gate | |
| # rotting into decoration — it drives the scanner over fixture sources, | |
| # including the nine spellings measured in this tree, and pins the | |
| # baseline in every direction it can move. | |
| # | |
| # Invoked as `node` rather than through the `pnpm check:entry-guard` alias | |
| # for the pre-install placement, matching `check-lint-coverage.mjs` above | |
| # and `check-cross-repo-closer-outcome.mjs` below. The alias exists in | |
| # `package.json` for local use and `scripts/__tests__/entry-guard-wiring.test.ts` | |
| # holds the two spellings to the same script. | |
| - name: Verify every scripts/ entry guard goes through one predicate | |
| if: steps.relevant.outputs.should_run == 'true' | |
| run: | | |
| node scripts/check-entry-guard.mjs --self-test | |
| node scripts/check-entry-guard.mjs | |
| # ── The ported objectstack tooling is a PINNED copy (objectui#6642) ─── | |
| # `scripts/pm/check-half-states.mjs` came from objectstack (objectui#5791) | |
| # under a workflow header calling it a verbatim copy and enumerating the | |
| # three things a re-sync must not clobber. Nothing checked either half. | |
| # Measured 2026-08-28, before this step existed: the ported copy stood at | |
| # 9,340 lines against upstream's 12,948 — a 4,637-line `diff` — and its | |
| # own `--self-test` ran 1,116 cases where upstream's ran 1,574. So 458 | |
| # predicate cases had landed upstream and never arrived here, while the | |
| # patrol went on rendering a confident report with the corresponding rows | |
| # simply missing. | |
| # | |
| # The direction of harm is this repository's least visible one: a drifted | |
| # copy does not fail, it REPORTS. It became load-bearing once already — | |
| # objectui#6641 had to hand-port H22's closure floor into this copy, | |
| # because wiring the new environment variable in the workflow alone would | |
| # have set a variable this copy did not read. | |
| # | |
| # The gate reverses the DECLARED divergences out of each ported file and | |
| # requires the reconstruction to hash to the pinned upstream digest, so | |
| # drift beyond the declared set is byte-detectable in both directions — | |
| # an edit here, or upstream moving. ⛔ It fetches nothing: a gate that | |
| # reached api.github.com would be red on a network hiccup and green on a | |
| # cached 200, and this repo's whole reason for owning a patrol is that a | |
| # check which cannot read its input must never read as clean (#4690). | |
| # | |
| # Runs before install, next to the two gates above and for the same | |
| # reason: node builtins and one local module only, so an install failure | |
| # cannot take it down with it. `--self-test` runs FIRST — it drives the | |
| # real comparer over fixtures (parity holds, drift outside a region, | |
| # drift inside one, an ambiguous anchor, the pin-bump procedure, and | |
| # every malformed-pin shape), which is what stops a comparer that | |
| # recognises nothing from reading as a clean tree. | |
| - name: Verify the ported objectstack tooling still matches its pin | |
| if: steps.relevant.outputs.should_run == 'true' | |
| run: | | |
| node scripts/check-upstream-port-parity.mjs --self-test | |
| node scripts/check-upstream-port-parity.mjs | |
| # ── The bash 3.2 floor on this repository's own shell (objectui#7692) ── | |
| # macOS ships bash 3.2.57 and no bash 4+, for licensing reasons. CI runs | |
| # bash 5, where every bash-4-only construct works — so a `mapfile`, | |
| # a `declare -A` or an unguarded `$EPOCHSECONDS` in a hand-run script is | |
| # invisible to a normal green run: the defect AND its repair both read as | |
| # green, and the only reader who ever sees the failure is a contributor on | |
| # a Mac at the moment they most need the script to work. | |
| # | |
| # This repository held its shell to nothing. Measured on `origin/main` | |
| # `28cfff4` with a control beside the zero: 0 tracked files named | |
| # `bash32`/`bash-32`, against 47 `scripts/check-*.mjs` gates that do exist. | |
| # `check-control-bytes` judges bytes in every text file and | |
| # `check-shell-escape-residue` judges markdown FENCES ("206 file(s) and | |
| # 1309 fenced block(s)"), so neither is this. | |
| # | |
| # ⭐ The scan roots are `scripts/**`, `.claude/hooks/**` and `e2e/**`, and | |
| # the third one is the point rather than a detail: two of the four shell | |
| # files this repository wrote itself live in `e2e/live/ci/`. Upstream's | |
| # roots (`.githooks/**` instead of `e2e/**`) would have walked past half | |
| # the population while printing a green line. | |
| # | |
| # This is a RATCHET, not a repair: the gate's first full-population run on | |
| # this tree is green — 12 shell files, 19 constructs, 0 findings. The card | |
| # that filed it had grepped 10 of the 19 by hand; this step is what makes | |
| # the other 9 a measurement instead of an assumption, and what stops the | |
| # first violation being typed. | |
| # | |
| # Ported from objectstack `scripts/check-bash32-floor.mjs` at `6136293` | |
| # and deliberately NOT registered in `scripts/upstream-port-pin.json` — | |
| # that ledger carries ONE global `upstream.ref` (`bf10deb` today) which | |
| # `--resync` rewrites for every entry, so registering this file would mean | |
| # either shipping `bf10deb`'s older, weaker construct table or re-syncing | |
| # three unrelated ported files. The gate's own header carries the full | |
| # argument and names the follow-up. | |
| # | |
| # Runs before install, next to the three gates above and for the same | |
| # reason: node builtins, `scripts/invoked-as.mjs` and `git ls-files` only, | |
| # so nothing it needs is in `node_modules` and an install failure cannot | |
| # make it green. Invoked as `node` rather than through the | |
| # `pnpm check:bash32-floor` alias for that placement, matching the steps | |
| # above; `scripts/__tests__/bash32-floor-wiring.test.ts` holds the two | |
| # spellings to the same script. `--self-test` runs FIRST and is the half | |
| # that stops the scanner rotting into decoration — a pattern that matches | |
| # nothing reports a clean tree forever, so every row is driven against a | |
| # real instance of its construct AND against the 3.2 replacement the | |
| # failure text tells you to write. | |
| # | |
| # Cost, measured on this tree so the job ceiling question is answered | |
| # rather than assumed: both invocations together run in well under two | |
| # seconds. `timeout-minutes: 25` at the job header is DERIVED from this | |
| # job's own distribution (objectui#7270, max 7m14s) and is untouched. | |
| - name: Verify this repository's shell holds the bash 3.2 floor | |
| if: steps.relevant.outputs.should_run == 'true' | |
| run: | | |
| node scripts/check-bash32-floor.mjs --self-test | |
| node scripts/check-bash32-floor.mjs | |
| # ── Cache bookkeeping cannot void a recorded verdict (objectui#7048) ── | |
| # THE ORDERING, carried here from the type-check split in `ci.yml` | |
| # (objectui#6577, PR #7047) because it is what a future reader needs in | |
| # order to judge whether these steps may be touched: the verdict is | |
| # recorded by the checking steps; everything after them is bookkeeping, | |
| # and bookkeeping must never discard an answer the gate already produced. | |
| # | |
| # Why the SPLIT rather than a timeout on one step: combined | |
| # `actions/cache` declares `main: dist/restore/index.js` plus | |
| # `post: dist/save/index.js`, so its save is a step the RUNNER generates | |
| # at job end (`Post Turbo Cache`). No workflow syntax attaches | |
| # `timeout-minutes` or `continue-on-error` to a generated post step. | |
| # Measured once, on `ci.yml`'s type-check cache: a 1-second save took | |
| # 789s, ran that job into its ceiling, and the resulting `cancelled` — | |
| # which the merge queue cannot tell from `failure` — ejected an all-green | |
| # pull request (objectui#6577). `actions/cache`'s own `save-always` | |
| # deprecation text points at this same split. | |
| # | |
| # ⚠️ This job's ceiling is `timeout-minutes: 25`, DERIVED at the job | |
| # header above from this job's own run distribution (objectui#7270). | |
| # Until that line existed the ceiling was GitHub's 360-minute default, | |
| # and `Lint` is a required context on `pull_request` and `merge_group` | |
| # alike, so a stalled save would have held the shared serial queue for up | |
| # to six hours before reporting `cancelled`. The bound below is still on | |
| # the bookkeeping step alone, and both numbers are needed: 25 minutes is | |
| # the backstop, 5 minutes is what a healthy save is allowed. | |
| # | |
| # The restore half is deliberately left UNBOUNDED: a restore stall fails | |
| # BEFORE any verdict exists — a gate that did not run, which is honest — | |
| # rather than a recorded verdict discarded. | |
| - name: Restore Turbo Cache | |
| id: turbo-cache | |
| if: steps.relevant.outputs.should_run == 'true' | |
| uses: actions/cache/restore@v6 | |
| with: | |
| path: .turbo/cache | |
| key: turbo-${{ runner.os }}-${{ github.sha }} | |
| restore-keys: | | |
| turbo-${{ runner.os }}- | |
| - name: Install dependencies | |
| if: steps.relevant.outputs.should_run == 'true' | |
| run: pnpm install --frozen-lockfile | |
| - name: Run linter | |
| if: steps.relevant.outputs.should_run == 'true' | |
| run: pnpm lint | |
| # ── The cross-repo closer's outcome contract (#5261) ────────────────── | |
| # `cross-repo-issue-closer.yml` carries ~250 lines of inline | |
| # github-script, and until this step existed it was code nobody had ever | |
| # seen run: it fires only on a merge, its conclusion is required by | |
| # nothing, and every one of its runs so far has been green. That last part | |
| # is the problem rather than the reassurance — measured over every merged | |
| # pull request in this repository, its close loop has had a live target | |
| # roughly one and a half times a day since it landed, took the same | |
| # `already closed -- skipping` exit every time, and left no backlink on | |
| # any of them. Greenly. The header of the workflow carries the figures. | |
| # | |
| # This step is the exercise: the shipped script is extracted from the YAML | |
| # with a real parser (never retyped) and run under doubles the way | |
| # actions/github-script runs it, as one AsyncFunction body. The scenarios | |
| # pin the target parse, the target KIND, and the outcome of every exit — | |
| # which of setFailed / warning / job summary fires, and which API calls | |
| # were made. | |
| # | |
| # Assertion 0 is the compile, and it is not theoretical: the framework's | |
| # copy of this workflow was taken down twice in one day by a `SyntaxError` | |
| # in the inline block, i.e. a script that never ran at all, on a | |
| # post-merge workflow whose red nothing else in CI can see. | |
| # | |
| # `--self-test` runs FIRST and is the half that stops the battery rotting | |
| # into decoration: it mutates the shipped script — downgrade the verdict | |
| # to a warning, break out of the loop instead of isolating, drop the | |
| # same-repo skip, collapse the already-closed branch, strip the backlink | |
| # marker, drop the pull-request guard, and the rest — and requires the | |
| # battery to go RED for each, naming the scenario that catches it. Neither | |
| # the mutations nor the scenarios are counted here: a hand-copied | |
| # enumeration drifts by construction, which is the lesson this workflow's | |
| # own header records. A mutation whose anchor no longer exists is a | |
| # failure too, so rewriting the workflow cannot leave them silently | |
| # matching nothing. | |
| # | |
| # Invoked as `node` rather than through a `pnpm check:*` alias, matching | |
| # `check-lint-coverage.mjs` above. No network, no build; well under a | |
| # second. | |
| - name: Cross-repo closer outcome contract | |
| if: steps.relevant.outputs.should_run == 'true' | |
| run: | | |
| node scripts/check-cross-repo-closer-outcome.mjs --self-test | |
| node scripts/check-cross-repo-closer-outcome.mjs | |
| # ── The product's own check command, on the product's own tree ──────── | |
| # `objectui check` is what a consumer runs against their schema tree, and | |
| # the root `check` script points that same command at this repository. | |
| # Nothing ran it. It exited 1 with 64 errors on `main` — and had done | |
| # since the first `tsconfig.json` grew a comment — until someone ran it by | |
| # hand while measuring something unrelated (objectui#5237, fixed by | |
| # #5245). A shipped command sitting red on its own repository is the | |
| # dogfood invariant failing, and the reason it could sit there is that no | |
| # gate ever asked. This is that gate (objectui#5246). | |
| # | |
| # The build step below is not a convenience. The root script is | |
| # `node packages/cli/dist/cli.js check`, and this job installs without | |
| # building, so without it the step dies on a missing file. That failure | |
| # mode is worse than no gate at all: it is red for a reason that has | |
| # nothing to do with the tree being checked, and the obvious repair from | |
| # outside is to delete the step — leaving the hole exactly as it was, now | |
| # with a commit saying it was considered. | |
| # | |
| # `...` is pnpm's dependency closure: the CLI plus every workspace | |
| # package it depends on, in topological order, derived from the graph | |
| # rather than listed here. The closure is the requirement and not just | |
| # tidiness — `dist/cli.js` imports `@object-ui/types`' built output at | |
| # startup, so building the CLI package alone produces a binary that | |
| # cannot load (objectui#5237 records the same import as the reproduce | |
| # recipe's first step). | |
| # | |
| # Deliberately NOT `turbo run build`, although the Turbo cache above is | |
| # restored by this point and would usually make it free. A turbo cache | |
| # HIT restores a task's recorded outputs, and an entry recorded with an | |
| # empty output set replays as "cache hit, replaying logs" plus FULL | |
| # TURBO while writing no `dist/` at all — measured on this repo, where | |
| # the CLI then died with ERR_MODULE_NOT_FOUND on the types import above. | |
| # A blocking gate must not be able to fail for a reason that lives in a | |
| # cache rather than in the tree it is judging, which is the same argument | |
| # as the paragraph above one level down. Building through pnpm has no | |
| # cache layer to replay, and it costs well under a minute. | |
| - name: Build the CLI the self-check runs | |
| if: steps.relevant.outputs.should_run == 'true' | |
| run: pnpm --filter '@object-ui/cli...' build | |
| # Errors only, which is the command's existing behaviour rather than a | |
| # setting chosen here: a parse failure increments the error count, a | |
| # non-zero count is the only thing that exits 1, and the unknown-schema- | |
| # type arm prints and moves on. So this step blocks on the same arm | |
| # `pnpm lint` above does, and for the same reason — errors are a signal, | |
| # the warning stream is known debt. Nothing here promotes those warnings | |
| # to failures, and no output-suppressing flag hides them either: they stay | |
| # visible in the log and non-blocking. That arm belongs to objectui#5127, | |
| # which is open; whatever it settles changes what this step PRINTS, never | |
| # what it fails on. No count is quoted, for the reason the `--max-warnings` | |
| # paragraph in the header gives. | |
| - name: Verify the CLI's own check command passes on this repository | |
| if: steps.relevant.outputs.should_run == 'true' | |
| run: pnpm check | |
| # The bookkeeping half of the split documented at the restore step above, | |
| # placed HERE — after the last checking step — because that is exactly | |
| # where the post phase it replaces already ran. Nothing about which | |
| # commits this job accepts or rejects moves with it. | |
| # | |
| # `timeout-minutes: 5` is DERIVED FOR THIS SITE, and landing on the same | |
| # number PR #7047 chose is a result rather than a copy — objectui#7048 | |
| # fences inheriting that 5, so here is this site's own arithmetic. This | |
| # job's own save of this cache measured 6s on 2026-09-02 (`Post Turbo | |
| # Cache`, job 100097466846); the whole job was 5m14s. 5 minutes is 50x | |
| # the measured save, so it cannot fire on a working runner, and it is a | |
| # bound on a step whose only alternative backstop is a 360-minute default | |
| # ceiling. | |
| # | |
| # `continue-on-error: true` is the other half, and without it the bound | |
| # would only trade a `cancelled` gate for a red one. A cache that failed | |
| # to upload costs the next run some time; it says nothing whatsoever | |
| # about the code under test, so it must not be allowed to speak for it. | |
| # | |
| # Behaviour preserved, spelled out so the equivalence is checkable: | |
| # - `cache-hit != 'true'` reproduces the combined action's own "exact | |
| # hit on the primary key ⇒ do not save" skip. A `restore-keys` prefix | |
| # match leaves `cache-hit` false and the save still runs, exactly as | |
| # the combined action behaved. | |
| # - the condition names no status function, so the implicit `success()` | |
| # still applies — matching the combined action's `post-if: success()`. | |
| # - same `path` and same `key` as the restore step above. | |
| - name: Save Turbo Cache | |
| if: >- | |
| steps.relevant.outputs.should_run == 'true' | |
| && steps.turbo-cache.outputs.cache-hit != 'true' | |
| uses: actions/cache/save@v6 | |
| timeout-minutes: 5 | |
| continue-on-error: true | |
| with: | |
| path: .turbo/cache | |
| key: turbo-${{ runner.os }}-${{ github.sha }} |