fix(spec): regenerate drifted skills output and gate it in CI #8912
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 & Type Check | |
| on: | |
| push: | |
| branches: | |
| - main | |
| pull_request: | |
| branches: | |
| - main | |
| jobs: | |
| lint: | |
| name: ESLint | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v7 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "20" | |
| - name: Enable Corepack | |
| run: corepack enable | |
| - name: Get pnpm store directory | |
| shell: bash | |
| run: | | |
| echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV | |
| - name: Setup pnpm cache | |
| uses: actions/cache@v6 | |
| with: | |
| path: ${{ env.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm-store-v3- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| # Enforces the no-restricted-imports guard against @objectstack/spec root | |
| # namespace imports (the dormant rule was never run in CI). Syntactic | |
| # only, so no build step needed. | |
| - name: ESLint | |
| run: pnpm lint | |
| # Raw NUL guard (#3127): one literal U+0000 byte makes grep/ripgrep treat | |
| # the whole file as binary and silently return ZERO matches — the file drops | |
| # out of code search and out of every grep-based lint, with no error saying | |
| # so. Nothing else catches it: git sniffs only the first 8000 bytes to decide | |
| # binary-ness, and protocol.ts carried its NUL at offset 147230, so it kept | |
| # diffing as ordinary text through review. That blind spot let six files | |
| # accumulate the same defect. Authors must write the unicode escape instead. | |
| - name: Raw NUL byte guard | |
| run: pnpm check:nul-bytes | |
| # Docs/skills authoring guard (#2035 / ADR-0059): TS code blocks in | |
| # Markdown/MDX are not type-checked or ESLinted, so skills/ and | |
| # content/docs/ can drift back to teaching the bare `: Page = {}` literal | |
| # while the examples (which ARE linted) stay clean. This fails on any bare | |
| # metadata literal for the 16 factory domains in a doc code block. | |
| - name: Doc/skill authoring guard | |
| run: pnpm check:doc-authoring | |
| # ADR-0090 D3 vocabulary ratchet: "role" is reserved-forbidden in docs | |
| # and skills. Existing occurrences are frozen in the baseline (better-auth | |
| # boundary, ARIA samples, educational mentions); NEW occurrences fail. | |
| # Improvements ratchet the baseline down via --update. | |
| - name: Reserved-word ("role") docs ratchet | |
| run: pnpm check:role-word | |
| # Authorization resolution must stay single-sourced (resolveAuthzContext, | |
| # @objectstack/core). Guards against a duplicate resolver copy drifting on a | |
| # security path (the REST-vs-dispatcher sys_user_role drift) and against an | |
| # entry point silently dropping the delegation. | |
| - name: Single authz resolver guard | |
| run: pnpm check:authz-resolver | |
| # Release-notes drift guard: the platform is one version-locked train, so | |
| # every released @objectstack/spec major must have a curated, navigable | |
| # release page at content/docs/releases/v<major>.mdx. Catches the gap that | |
| # let v10–v14 ship with no page while spec was already at 14.x. | |
| - name: Release-notes drift guard | |
| run: pnpm check:release-notes | |
| typecheck: | |
| name: TypeScript Type Check | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v7 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '20' | |
| - name: Enable Corepack | |
| run: corepack enable | |
| - name: Verify pnpm version | |
| run: pnpm --version | |
| - name: Get pnpm store directory | |
| shell: bash | |
| run: | | |
| echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV | |
| - name: Setup pnpm cache | |
| uses: actions/cache@v6 | |
| with: | |
| path: ${{ env.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm-store-v3- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Type check (@objectstack/spec) | |
| run: pnpm --filter @objectstack/spec exec tsc --noEmit | |
| # Generated-docs gate: content/docs/references/** is generated from the spec | |
| # by `gen:schema && gen:docs` and committed. Nothing regenerated it in CI, so | |
| # it drifted silently — #3076 added RowCrudActionOverride to the spec and the | |
| # public reference docs never learned the type existed. Regenerates and fails | |
| # on any difference. | |
| # | |
| # Deliberately lives in this job, not in ci.yml's "Build Docs": that job is | |
| # gated on a `docs` paths-filter that does not include packages/spec/**, so it | |
| # skips exactly the spec-only PRs that cause this drift (#3076 was one). This | |
| # job has no paths filter and is a required status check, so the gate cannot go | |
| # dormant. It reads src/ + json-schema/ via tsx and needs no build, so it runs | |
| # before the workspace build and fails in ~2s. | |
| - name: Check generated reference docs are in sync with the spec | |
| run: pnpm --filter @objectstack/spec check:docs | |
| # Generated-skills gate: skills/ is generated from the spec by | |
| # `gen:skill-refs` + `gen:react-blocks` and committed, and nothing checked | |
| # it — both had a `gen:` script but no `check:` sibling, so `check:docs` | |
| # above (which only covers content/docs/references/) and `check:skill-docs` | |
| # (only SKILL.md frontmatter → README + skills-reference.mdx) both miss it. | |
| # It drifted: 8 files were stale on main, and skills/objectstack-{data, | |
| # platform}/references/_index.md still pointed agents at | |
| # data/dataset.zod.ts — renamed to data/seed.zod.ts back in #1620. | |
| # | |
| # This bites harder than the docs case: skills/ is PUBLISHED to third | |
| # parties via `npx skills add objectstack-ai/framework --all`, so stale | |
| # output ships to users. Same job as check:docs and for the same reason — | |
| # it is a required status check with no paths filter, so the gate cannot go | |
| # dormant. Both read src/ via tsx and need no build, so they fail in ~2s. | |
| - name: Check generated skill references are in sync with the spec | |
| run: pnpm --filter @objectstack/spec check:skill-refs | |
| - name: Check generated react-blocks contract is in sync with the spec | |
| run: pnpm --filter @objectstack/spec check:react-blocks | |
| # Example apps are AI-authoring reference templates; a red typecheck is a | |
| # bad signal to copy from. tsup transpiles them without a full typecheck, | |
| # so build alone will not catch type drift — typecheck them explicitly. | |
| # They import from built workspace packages, so the packages must be built | |
| # first for cross-package type resolution to succeed. The examples' | |
| # dependency closure is requested EXPLICITLY (`./examples/*^...` = deps | |
| # of the examples, not the examples themselves): the bare `./packages/*` | |
| # glob only matches direct children, and the connector packages the | |
| # showcase imports were previously built only by accident — through | |
| # dogfood's dependency chain, which broke when dogfood moved to | |
| # packages/qa/ (#3037). | |
| - name: Build workspace packages | |
| run: pnpm exec turbo run build --filter='./packages/*' --filter='./examples/*^...' | |
| - name: Type check example apps | |
| run: pnpm --filter './examples/*' run typecheck | |
| # Backward-compatibility gate: a frozen third-party-style consumer | |
| # (#2035). Unlike the examples it must NOT be migrated to accommodate a | |
| # spec change — a red typecheck here means the spec dropped/narrowed an | |
| # export a published-spec third party already uses. See the package README. | |
| - name: Type check downstream consumer contract | |
| run: pnpm --filter @objectstack/downstream-contract run typecheck | |
| # Public API-surface gate (#2035): the spec package IS the third-party API. | |
| # A removed/renamed export silently breaks every consumer pinned to a | |
| # published release. This diffs the built export surface against the | |
| # committed snapshot; intentional changes regenerate it via | |
| # `pnpm --filter @objectstack/spec gen:api-surface`. Runs after the build | |
| # step above (reads the built dist). | |
| - name: Check @objectstack/spec public API surface | |
| run: pnpm --filter @objectstack/spec run check:api-surface |