feat(lint): catch legacy pre-ADR-0021 dashboard analytics keys at aut… #9246
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: CI | |
| on: | |
| push: | |
| branches: | |
| - main | |
| pull_request: | |
| branches: | |
| - main | |
| # Superseded runs on the same PR/branch waste runners and delay feedback; | |
| # cancel them. Push runs to main group by commit ref as well, so an in-flight | |
| # main run is cancelled only by a newer main push. | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| filter: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| outputs: | |
| docs: ${{ steps.changes.outputs.docs }} | |
| core: ${{ steps.changes.outputs.core }} | |
| generated: ${{ steps.changes.outputs.generated }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v7 | |
| - uses: dorny/paths-filter@v4 | |
| id: changes | |
| with: | |
| filters: | | |
| docs: | |
| - 'apps/docs/**' | |
| - 'content/**' | |
| - 'pnpm-lock.yaml' | |
| - '.github/workflows/ci.yml' | |
| # Inputs AND outputs of every generated artifact checked by the | |
| # `check-generated` job. A path missing here makes that gate dormant on | |
| # exactly the PRs that can break it, so keep the two in lockstep. | |
| generated: | |
| # check:skill-docs — catalog source is skills/*/SKILL.md; the derived | |
| # listings are skills/README.md + the reference page below. (That page | |
| # was content/docs/guides/skills.mdx until #2584 moved it; this filter | |
| # kept watching the old path, so hand-edits to the generated block went | |
| # unchecked from then until now.) | |
| - 'skills/**' | |
| - 'content/docs/ai/skills-reference.mdx' | |
| - 'packages/spec/scripts/build-skill-docs.ts' | |
| # check:spec-changes / check:upgrade-guide — derived from the ADR-0087 | |
| # registries and the protocol version, none of which the skills paths | |
| # above cover. Both gates were dormant on spec-only PRs: a protocol bump | |
| # or a new migration could not trigger the job that verifies them. | |
| - 'packages/spec/scripts/build-spec-changes.ts' | |
| - 'packages/spec/scripts/build-upgrade-guide.ts' | |
| - 'packages/spec/src/migrations/**' | |
| - 'packages/spec/src/conversions/**' | |
| - 'packages/spec/src/kernel/protocol-version.ts' | |
| - 'packages/spec/api-surface.json' | |
| - 'packages/spec/spec-changes.json' | |
| - 'docs/protocol-upgrade-guide.md' | |
| - '.github/workflows/ci.yml' | |
| core: | |
| - 'packages/**' | |
| - 'examples/**' | |
| - 'apps/!(docs)/**' | |
| - 'package.json' | |
| - 'pnpm-lock.yaml' | |
| - 'tsconfig.json' | |
| - '.github/workflows/ci.yml' | |
| test: | |
| name: Test Core | |
| needs: filter | |
| if: needs.filter.outputs.core == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v7 | |
| with: | |
| # Full history so `turbo --affected` can diff against the PR base. | |
| fetch-depth: 0 | |
| - 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: Setup Turbo cache | |
| uses: actions/cache@v6 | |
| with: | |
| path: .turbo/cache | |
| key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }} | |
| restore-keys: | | |
| ${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}- | |
| ${{ runner.os }}-turbo-${{ github.job }}- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| # PRs: only test packages affected by the diff against the PR base. | |
| # spec sits at the root of the dependency graph, so spec-touching PRs | |
| # still run (close to) everything — but the many PRs that don't touch | |
| # spec skip the bulk of the 75-package matrix. | |
| # --concurrency=4: turbo's default (10) oversubscribes the 4-vCPU | |
| # hosted runner; matching the core count bounds peak memory and the | |
| # job is CPU-bound anyway. | |
| - name: Run affected tests (PR) | |
| if: github.event_name == 'pull_request' | |
| env: | |
| TURBO_SCM_BASE: ${{ github.event.pull_request.base.sha }} | |
| run: pnpm turbo run test --affected --concurrency=4 | |
| # Push to main: full run, but exclude spec's plain test task — the | |
| # coverage step below executes the exact same 250-file spec suite once, | |
| # with coverage. Previously the spec suite ran twice per push. | |
| - name: Run all tests (push) | |
| if: github.event_name == 'push' | |
| run: pnpm turbo run test --filter=!@objectstack/spec --concurrency=4 | |
| - name: Generate coverage report | |
| if: github.event_name == 'push' | |
| run: pnpm --filter @objectstack/spec test:coverage | |
| - name: Upload coverage reports | |
| if: github.event_name == 'push' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: coverage-report | |
| path: packages/spec/coverage/ | |
| retention-days: 30 | |
| dogfood: | |
| name: Dogfood Regression Gate | |
| needs: filter | |
| if: needs.filter.outputs.core == 'true' | |
| 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: Setup Turbo cache | |
| uses: actions/cache@v6 | |
| with: | |
| path: .turbo/cache | |
| key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }} | |
| restore-keys: | | |
| ${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}- | |
| ${{ runner.os }}-turbo-${{ github.job }}- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| # Boots real example apps in-process (in-memory SQLite) and exercises them | |
| # through the real HTTP + service stack — catches runtime regressions that | |
| # build / unit tests / spec-liveness pass over (e.g. the #2018 tz-bucketing | |
| # break, which was green on every static gate). | |
| - name: Boot example apps and exercise real user flows | |
| run: pnpm turbo run test --filter=@objectstack/dogfood | |
| # Replaces the former auto-verify dogfood tests: runs the published | |
| # `objectstack verify` engine over each example app through the CLI — | |
| # auto-derived CRUD round-trip fidelity + the cross-owner RLS invariant. | |
| # Exits non-zero on a real runtime failure, so it gates like the tests did. | |
| - name: Verify example apps via the `objectstack verify` CLI | |
| run: | | |
| pnpm turbo run build --filter=@objectstack/cli | |
| for app in examples/app-crm examples/app-showcase; do | |
| echo "::group::objectstack verify $app --rls" | |
| OS_LOG_LEVEL=error node packages/cli/bin/run.js verify --app "$app/objectstack.config.ts" --rls | |
| echo "::endgroup::" | |
| done | |
| build-core: | |
| name: Build Core | |
| needs: filter | |
| if: needs.filter.outputs.core == 'true' | |
| 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: Setup Turbo cache | |
| uses: actions/cache@v6 | |
| with: | |
| path: .turbo/cache | |
| key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }} | |
| restore-keys: | | |
| ${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}- | |
| ${{ runner.os }}-turbo-${{ github.job }}- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Build packages (excluding docs) | |
| run: pnpm build | |
| - name: Verify build outputs | |
| run: | | |
| if [ ! -d "packages/spec/dist" ]; then | |
| echo "Error: packages/spec/dist directory not found" | |
| exit 1 | |
| fi | |
| if [ ! -d "packages/spec/json-schema" ]; then | |
| echo "Error: packages/spec/json-schema directory not found" | |
| exit 1 | |
| fi | |
| echo "Build outputs verified successfully" | |
| # Capability packages (services / triggers / plugins) are loaded by the | |
| # multi-tenant runtime via a DYNAMIC import of their published entry. If a | |
| # package ships a dts-only / half-built / 0-byte `dist` (an interrupted | |
| # build, or a package retired in source but still referenced), that import | |
| # resolves to nothing and the capability SILENTLY fails to load — e.g. | |
| # record-change automation never fires, with no user-visible signal. The | |
| # build config always emits JS, so this can only happen by accident; assert | |
| # every buildable capability package actually produced its declared runtime | |
| # entry. Self-maintaining: reads each package's own `main`, skips dirs with | |
| # no package.json (e.g. a retired service-feed/service-ai leftover). | |
| - name: Verify capability packages ship a runtime entry (no dts-only / half-built) | |
| run: | | |
| fail=0 | |
| for d in packages/triggers/* packages/services/* packages/plugins/*; do | |
| [ -f "$d/package.json" ] || continue | |
| has_build=$(node -p "Boolean((require('./$d/package.json').scripts||{}).build)" 2>/dev/null || echo false) | |
| [ "$has_build" = "true" ] || continue | |
| main=$(node -p "require('./$d/package.json').main || 'dist/index.js'" 2>/dev/null || echo dist/index.js) | |
| if [ ! -s "$d/$main" ]; then | |
| echo "✗ $d: missing/empty runtime entry '$main' — dts-only or unbuilt? dynamic import would silently fail" | |
| fail=1 | |
| fi | |
| done | |
| if [ "$fail" -ne 0 ]; then exit 1; fi | |
| echo "✓ all buildable capability packages ship a runtime JS entry" | |
| - name: Analyze bundle size | |
| run: pnpm --filter @objectstack/spec analyze | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: build-output | |
| path: | | |
| packages/spec/dist/ | |
| packages/spec/json-schema/ | |
| retention-days: 30 | |
| build-docs: | |
| name: Build Docs | |
| needs: filter | |
| if: needs.filter.outputs.docs == 'true' | |
| 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: Setup Next.js cache | |
| uses: actions/cache@v6 | |
| with: | |
| path: apps/docs/.next/cache | |
| key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('apps/docs/**.[jt]s', 'apps/docs/**.[jt]sx', 'content/**/*.mdx') }} | |
| restore-keys: | | |
| ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Build Docs | |
| run: pnpm --filter @objectstack/docs build | |
| # Generated artifacts that are committed to the tree: regenerate, fail on drift. | |
| # Named for the class, not for skill docs alone — it has checked the ADR-0087 | |
| # protocol artifacts for a while, and a name that hid them is part of why their | |
| # paths went missing from the filter above. | |
| # | |
| # The generated reference docs (content/docs/references/**) are deliberately NOT | |
| # here: they need to gate spec-only PRs, and this whole job is filter-gated, so | |
| # they live in lint.yml's required, unfiltered "TypeScript Type Check" job. | |
| check-generated: | |
| name: Check Generated Artifacts | |
| needs: filter | |
| if: needs.filter.outputs.generated == 'true' | |
| 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 | |
| - name: Check skill docs are generated from SKILL.md frontmatter | |
| run: pnpm --filter @objectstack/spec check:skill-docs | |
| - name: Check spec-changes.json is regenerated with the ADR-0087 registries | |
| run: pnpm --filter @objectstack/spec check:spec-changes | |
| - name: Check the protocol upgrade guide is regenerated with the ADR-0087 registries | |
| run: pnpm --filter @objectstack/spec check:upgrade-guide |