# The gates.
#
# This repo makes two claims about itself — that it is Apache-2.0 clean, and that
# a stranger can self-host it with no account, no key and no network — and until
# this file existed nothing checked either one. Both had been verified per-lens,
# in a unit test or by hand on a box that already had everything, and never once
# end to end on an empty environment. A design critic put it exactly right:
# until one of these jobs exists, self-hostable is a design intention, not a
# property.
#
# Three jobs, one for each claim that can actually be measured:
#
#   clean-clone       a fresh checkout installs, builds and passes its tests
#   zero-config-boot  the API answers health when handed nothing at all
#   no-binary-art     src/** carries no committed binary assets
#   dependency-terms  every locked package uses a reviewed permissive license
#
# They are deliberately independent and run in parallel: a broken build should
# not hide a licensing regression.
#
# This runs on Gitea Actions, not GitHub. The syntax is GitHub-compatible and the
# `actions/*` steps resolve through the instance's configured action registry.

name: gates

on:
  push:
    branches: [master]
  pull_request:
  workflow_dispatch:

# A superseded push has nothing to tell us, and these jobs build a Docker image
# and bind a port between them.
concurrency:
  group: gates-${{ github.ref }}
  cancel-in-progress: true

jobs:
  # ---- Job 1: a stranger clones this and it works ----
  #
  # The whole job is the dev-kit promise from CONTRACT.md §0. Note what is absent
  # and is meant to stay absent: no `env:` block, no `secrets.*` anywhere, and no
  # dependency cache. The cache is the interesting omission — a warm cache would
  # make this job pass on a lockfile that no longer resolves from a clean state,
  # which is precisely the failure a stranger would hit first.
  clean-clone:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          # The server runs TypeScript directly by stripping types at load, so
          # the floor is real: server/package.json says >=22.18. 24 is what the
          # image in server/Dockerfile uses.
          node-version: "24"

      - name: npm ci
        run: npm ci

      - name: npm run build
        # tsc --noEmit, then vite build. A dynamic import of an uninstalled
        # package fails here, which is the reason the dependency policy is an
        # allowlist rather than a count — see CONTRACT.md §6.
        run: npm run build

      - name: client tests
        run: npm test

      - name: server tests
        run: npm test --workspace @lumbridge/tera-api

  # ---- Job 2: the box boots on nothing ----
  #
  # Two independent server designs defaulted the weather source to a provider
  # that needs a contact string and then failed hard without one, which breaks
  # the only acceptance test this repo has. This job exists specifically to catch
  # that class of bug, and it passes today.
  #
  # The gate starts the real entry point under a genuinely empty environment
  # rather than `docker compose up`, which is the wording in CONTRACT.md §0. The
  # property asserted is identical — the compose file adds only TERA_HOST and
  # container hardening, and every other variable in it carries a `:-` default so
  # an empty environment resolves it to the empty string — while a job that needs
  # docker-in-docker present on the runner would be measuring the runner instead
  # of the repo. `node scripts/check-zero-config-boot.mjs --compose` runs the
  # literal compose form for anyone who has a Docker.
  zero-config-boot:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: "24"

      - name: npm ci
        run: npm ci

      - name: health on an empty environment
        run: node scripts/check-zero-config-boot.mjs

  # ---- Job 3: no committed binary art ----
  #
  # Scoped strictly to tracked files under src/**, because a self-hoster is told
  # to put their own legally-clean art in public/props/ and friends and an
  # earlier design would have failed their build for doing exactly that. The
  # script enumerates with `git ls-files` and has no filesystem-walk fallback.
  # See CONTRACT.md §7.
  #
  # No install step: the check is dependency-free on purpose, so it stays
  # runnable by hand and cannot be broken by a bad lockfile.
  no-binary-art:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: "24"

      - name: no binary art under src/
        run: node scripts/check-no-binaries.mjs

  # ---- Job 4: package terms stay inside the reviewed perimeter ----
  #
  # Lockfile metadata makes this dependency-free. A new license stops here and
  # gets reviewed explicitly instead of arriving transitively with a routine
  # version bump.
  dependency-terms:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: "24"

      - name: dependency license allowlist
        run: node scripts/check-dependency-licenses.mjs

      - name: SPDX SBOM can be generated from the lockfile
        run: npm sbom --package-lock-only --sbom-format=spdx > /tmp/tera.spdx.json
