name: Core Build (no extensions) # Split out of a single serial job that took 7m43s wall-clock: npm ci 24s, lint # ratchet 1m29s, build 2m00s, unit tests 3m31s, ratchets ~10s. None of those # stages needed the previous one's output, so they were serial only by # accident. Running them as independent jobs (and sharding the 897-file unit # suite four ways) puts the critical path on `build` at roughly 2m30s. # # The prologue each job needs is in .github/actions/setup-core. on: [pull_request] # Six concurrent jobs per push makes stale runs far more expensive than they # were when this was one job, and a superseded push has nothing worth finishing. concurrency: group: core-build-${{ github.ref }} cancel-in-progress: true permissions: contents: read jobs: checks: name: Checks runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: # Nothing here pushes, so leaving the token in .git/config only widens # what a compromised dependency in the build could reach. persist-credentials: false - uses: ./.github/actions/setup-core - name: Verify skill bodies are in sync with the seed migration # Fails if a .claude/skills/**/SKILL.md changed without regenerating the # seed migration (npm run skills:generate). Keeps prod skill content from # silently drifting out of sync. No DB needed: reads files + manifest. run: npm run skills:check - name: Verify taxonomy registry is in sync with the element lists # Fails if dev_docs/bokslut/taxonomi/** changed without regenerating # lib/bokslut/ixbrl/taxonomy/generated/ (npm run taxonomy:generate). # The iXBRL generator emits facts strictly from the generated registry, # so drift here means filings tagged against a stale concept set. run: npm run taxonomy:check - name: Verify the accounted-api agent skill is in sync with the registry # Fails if a v1 endpoint / registry schema / overlay changed without # regenerating skills/accounted-api (npm run apiskill:generate). The # skill is the installable API reference for external coding agents; # drift here means agents integrate against stale endpoint contracts. run: npm run apiskill:check - name: Lint ratchet (no new ESLint errors) # `npm run lint` was never wired into CI, so ~60 legacy errors # accumulated. This ratchet (sibling of check:guards) fails only when # a PR ADDS an error beyond scripts/checks/eslint-baseline.json; the # baseline ratchets down as legacy errors get fixed. run: npm run check:lint - name: Typecheck ratchet (no new TypeScript errors) # `npm test` does not typecheck: vitest transpiles and discards types, # so a type error passes all 18 000 tests and only surfaces in the # build. That happened twice on 2026-08-27. This also covers __tests__ # files, which the Next.js build never compiles, so it is not merely a # faster copy of the build job. # # Baselined per FILE in scripts/checks/typecheck-baseline.json (539 # legacy errors across 83 files, all in test files) and ratchets down. # Per-file rather than per-error-code on purpose: a code-keyed budget # would let a real regression hide behind a legacy fix elsewhere. run: npm run check:types - name: Antipattern ratchet (no new MFA-bypassing routes / naive öre-rounding) # Fails only if a PR ADDS a route that hand-rolls supabase.auth.getUser() # instead of the MFA-enforcing guard, or a new Math.round(x*100)/100. # Baseline lives in scripts/checks/antipatterns-baseline.json and ratchets # down as the A1 (route auth) and D1 (rounding) migrations land. run: npm run check:guards - name: Validate konteringspaket catalogue # The system booking templates are data files under packs/ rather than # rows frozen inside a migration. This gate is what makes that safe: it # checks the schema, that every BAS account actually exists in the 2026 # chart, and that each pack BALANCES when applied through the real # applyTemplate(). PR #1321 shipped seeded reference data that # contradicted the engine; that class of bug is structurally valid and # only a semantic check catches it. run: npm run validate:packs - name: Validate community registry # registry/ is the public source of truth for gnubok.se/community/registry # and takes entries by PR from outside the team. The gate checks the # frontmatter the website build consumes, and rejects JSX/import/export # in entry bodies: the site renders bodies through MDX, which would # execute those at build time. run: npm run validate:registry - name: Check no core imports from extensions run: | VIOLATIONS=$(grep -r "from '@/extensions/" lib/ app/api/ components/ --include="*.ts" --include="*.tsx" \ | grep -v "app/api/extensions/" \ | grep -v "components/extensions/" \ | grep -v "lib/extensions/_generated/" \ | grep -v "lib/extensions/loader.ts" || true) if [ -n "$VIOLATIONS" ]; then echo "ERROR: Core code imports from @/extensions/:" echo "$VIOLATIONS" exit 1 fi build: name: Build (zero extensions) runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: # Nothing here pushes, so leaving the token in .git/config only widens # what a compromised dependency in the build could reach. persist-credentials: false - uses: ./.github/actions/setup-core - run: npm run build env: # The Next.js build worker OOMs on the runner's default Node heap # since 2026-08-19 (bundle growth crossed the default old-space # ceiling; the first branch containing the whole day's merges was the # first to hit it). Public-repo runners have 16 GB, give the build 8. NODE_OPTIONS: --max-old-space-size=8192 test: # 897 unit test files (11,341 tests) take 3m31s in one process. Measured # locally, four shards split them 225/224/224/224 with the slowest at 64s, # which puts this comfortably under the build job so it stops being the # critical path. Vitest hard-errors when the shard count exceeds the # resolved file count, which is nowhere near a concern at this size. name: Unit tests (${{ matrix.shard }}/4) runs-on: ubuntu-latest strategy: # One shard failing should not hide failures in the other three: a red PR # is more useful when it lists every broken test, not just the first. fail-fast: false matrix: shard: [1, 2, 3, 4] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: # Nothing here pushes, so leaving the token in .git/config only widens # what a compromised dependency in the build could reach. persist-credentials: false - uses: ./.github/actions/setup-core # Via env rather than interpolated straight into the shell body: the value # is a static matrix integer and harmless, but keeping every `run:` free of # ${{ }} is the rule that makes the template-injection audit meaningful. - run: npm test -- --shard="$SHARD/4" env: SHARD: ${{ matrix.shard }} core-build: # One stable check name covering all six jobs above, so the PR checks list # (and any future required-status-check rule) has a single thing to read # rather than a shard-numbered matrix. `needs` alone would not be enough: # a needed job that is skipped or cancelled does not fail its dependents, # so the results are asserted explicitly. name: Core Build if: always() needs: [checks, build, test] runs-on: ubuntu-latest steps: - name: Assert every core job succeeded env: CHECKS: ${{ needs.checks.result }} BUILD: ${{ needs.build.result }} TEST: ${{ needs.test.result }} run: | set -euo pipefail echo "checks=$CHECKS build=$BUILD test=$TEST" failed=0 for r in "$CHECKS" "$BUILD" "$TEST"; do [ "$r" = "success" ] || failed=1 done if [ "$failed" -ne 0 ]; then echo "::error::One or more core-build jobs did not succeed" exit 1 fi