diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index efc75863..c1ce54b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,43 @@ jobs: - run: bun run lint - run: bun run typecheck + # The two packages that are deployables in their own right rather than root workspaces. Root + # `typecheck` is `bun run --filter '*' typecheck`, and `--filter '*'` enumerates `workspaces`, which + # is app, server and worker. So both of these ship a `typecheck` script that nothing has ever run: + # `agent-computer` holds the only `spawn` in the deployment and `supervisor` is the only thing + # holding a Docker socket, which is a poor pair to leave untyped. Both are clean today, so this + # changes nothing about main and only stops the next change being the first one a type checker sees. + # + # Not by adding them to `workspaces`: each is built from its own lockfile and the Dockerfile depends + # on that, so they are installed separately here for the same reason they are installed separately + # there. A matrix rather than two jobs, so each package reports its own result and a third one is a + # line in the list below and nothing in `verify`, which sees the whole matrix as one entry. + deployables: + name: types (${{ matrix.package }}) + runs-on: ubuntu-latest + strategy: + # One red package must not hide whether the other is red too. + fail-fast: false + matrix: + package: [agent-computer, supervisor] + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version: 1.3.14 + # Two installs, and the root one is load-bearing. Both packages set `types: ["bun"]` in + # their tsconfig while `@types/bun` is a root devDependency, so tsc resolves it by walking up + # to the root node_modules. Without it the typecheck fails with TS2688 on a fresh checkout. + # This is the order the image uses for the same reason: Dockerfile installs at the root + # before installing agent-computer. + - run: bun install --frozen-lockfile + - run: bun install --frozen-lockfile + working-directory: ${{ matrix.package }} + - run: bun run typecheck + working-directory: ${{ matrix.package }} + test: name: tests runs-on: ubuntu-latest @@ -220,7 +257,7 @@ jobs: name: verify runs-on: ubuntu-latest if: always() - needs: [static, test, build, migrations, image] + needs: [static, deployables, test, build, migrations, image] steps: - name: Require every check env: diff --git a/Dockerfile b/Dockerfile index 473ba1a6..105ff461 100644 --- a/Dockerfile +++ b/Dockerfile @@ -45,8 +45,13 @@ COPY server/package.json server/package.json COPY worker/package.json worker/package.json RUN bun install --frozen-lockfile +# The lockfile travels with the manifest, because `--frozen-lockfile` with no lockfile in the context +# is not an error: bun resolves afresh, succeeds, and the flag has decorated nothing. With both files +# here, the tree in the image is the tree this repository resolved and committed. Bun is already +# pinned twenty-odd lines above for the same reason; this is the install below it. COPY agent-computer/package.json agent-computer/package.json -RUN cd agent-computer && bun install +COPY agent-computer/bun.lock agent-computer/bun.lock +RUN cd agent-computer && bun install --frozen-lockfile # A second tree with the build-time dependencies left out, for the runtime stage to take. Vite, # biome and the test tooling are a gigabyte that nothing in a running container imports. diff --git a/agent-bot/Dockerfile b/agent-bot/Dockerfile index 6251f01a..249f57d9 100644 --- a/agent-bot/Dockerfile +++ b/agent-bot/Dockerfile @@ -5,8 +5,10 @@ FROM oven/bun:1.3-alpine WORKDIR /app RUN chown bun:bun /app USER bun -COPY --chown=bun:bun agent-bot/package.json ./agent-bot/ -RUN cd agent-bot && bun install +# The lockfile as well as the manifest: `--frozen-lockfile` with no lockfile present resolves afresh +# and exits 0, so the file has to be here for the flag to mean anything. +COPY --chown=bun:bun agent-bot/package.json agent-bot/bun.lock ./agent-bot/ +RUN cd agent-bot && bun install --frozen-lockfile # Preserve the repo layout so `../../shared/bot-prompt` resolves the same in the image and locally. COPY --chown=bun:bun shared ./shared diff --git a/agent-computer/Dockerfile b/agent-computer/Dockerfile index 594db89e..fd15ec09 100644 --- a/agent-computer/Dockerfile +++ b/agent-computer/Dockerfile @@ -11,8 +11,10 @@ RUN apt-get update && apt-get install -y --no-install-recommends unzip \ ENV PATH="/root/.bun/bin:${PATH}" WORKDIR /app -COPY agent-computer/package.json ./ -RUN bun install +# The lockfile as well as the manifest: `--frozen-lockfile` with no lockfile present resolves afresh +# and exits 0, so the file has to be here for the flag to mean anything. +COPY agent-computer/package.json agent-computer/bun.lock ./ +RUN bun install --frozen-lockfile COPY agent-computer/src ./src diff --git a/agent-langgraph/Dockerfile b/agent-langgraph/Dockerfile index a8223fa8..614d766e 100644 --- a/agent-langgraph/Dockerfile +++ b/agent-langgraph/Dockerfile @@ -5,8 +5,10 @@ FROM oven/bun:1.3-alpine WORKDIR /app -COPY agent-langgraph/package.json ./agent-langgraph/ -RUN cd agent-langgraph && bun install +# The lockfile as well as the manifest: `--frozen-lockfile` with no lockfile present resolves afresh +# and exits 0, so the file has to be here for the flag to mean anything. +COPY agent-langgraph/package.json agent-langgraph/bun.lock ./agent-langgraph/ +RUN cd agent-langgraph && bun install --frozen-lockfile # The repo's own layout, kept exactly, so `../../shared/bot-prompt` resolves the same here as it # does on a laptop. Flattening src to /app/src would change the depth and break only on deploy. diff --git a/supervisor/Dockerfile b/supervisor/Dockerfile index e45689f0..5a682967 100644 --- a/supervisor/Dockerfile +++ b/supervisor/Dockerfile @@ -10,8 +10,11 @@ FROM ghcr.io/spiffe/spire-server:1.15.1 AS spire FROM oven/bun:1.3.14-alpine WORKDIR /app -COPY supervisor/package.json ./ -RUN bun install +# The lockfile as well as the manifest: `--frozen-lockfile` with no lockfile present resolves afresh +# and exits 0. This is the process whose compromise costs the host, so what it runs is the tree that +# was reviewed, not whatever the registry had this morning. +COPY supervisor/package.json supervisor/bun.lock ./ +RUN bun install --frozen-lockfile COPY --from=spire /opt/spire/bin/spire-server /usr/local/bin/spire-server