ConusAI vendor v0.1.0-beta.114
ci / gate (push) Waiting to run

This commit is contained in:
ConusAI vendor v0.1.0-beta.114
2026-11-14 09:56:45 +00:00
commit 1ce8d23c35
867 changed files with 122104 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
# SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
# One image, three processes (spec §13). Node 24 LTS, nothing but the app in the final stage, and
# a non-root user that owns only the generated-code directories it writes.
#
# The `@conusai-kit` and `@conusai-sdk` scopes are served by the ConusAI vendor registry. Its URL
# and token arrive as a BuildKit secret the install stage copies to the user-level ~/.npmrc, the
# only config pnpm trusts for credentials, and never reach a layer
# (docs/how-to/private-package-registries.md).
FROM node:24-slim AS deps
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
PNPM_HOME=/pnpm \
CI=1
WORKDIR /app
# The manifests alone, so a source change does not re-resolve the dependency graph.
COPY package.json pnpm-workspace.yaml .npmrc pnpm-lock.yaml ./
COPY config/package.json ./config/
COPY modules/package.json ./modules/
COPY apps/web/package.json ./apps/web/
COPY apps/worker/package.json ./apps/worker/
RUN corepack enable pnpm
# A customer repository commits pnpm-lock.yaml (decision AT-D6), and the install is frozen
# against it: an image is built from exactly the dependency graph the gate ran.
#
# The credential arrives under one secret id per build path: plain `npmrc` for a preset
# single-image build, `conusai_npmrc` for a compose build (the platform prefixes its compose
# secrets so a tenant's own can never collide). Mount both and take whichever was delivered —
# a build that asked for only one id got an empty mount on the other path, and pnpm fell back
# to the public registry where the vendor scopes 404 (stage, deployment 4, 2026-10-20).
#
# The trap removes the copied credential before the shell exits: a file left behind would be
# committed to the cached deps layer, token and all.
RUN --mount=type=secret,id=conusai_npmrc,target=/run/secrets/conusai_npmrc,required=false \
--mount=type=secret,id=npmrc,target=/run/secrets/npmrc,required=false \
trap 'rm -f /root/.npmrc' EXIT; \
if [ -s /run/secrets/conusai_npmrc ]; then \
cp /run/secrets/conusai_npmrc /root/.npmrc; \
elif [ -s /run/secrets/npmrc ]; then \
cp /run/secrets/npmrc /root/.npmrc; \
fi; \
pnpm install --frozen-lockfile --ignore-scripts
FROM deps AS build
WORKDIR /app
COPY . .
# An explicit first-party deployment variant; the shipped scaffold remains unchanged.
# Selection requests scopes. Actual platform services and grants require owner approval.
ARG CONUSAI_APP_MODULE_PROFILE=default
RUN case "$CONUSAI_APP_MODULE_PROFILE" in \
default) ;; \
full) for module in activities archive catalog crm inventory invoicing ledger documents knowledge agent guides support; do \
pnpm kit module:on "$module" || exit; \
done ;; \
*) echo 'unsupported application module profile' >&2; exit 64 ;; \
esac
# config, modules and apps/worker emit JavaScript; apps/web builds the Start shell (`vite build` to
# apps/web/dist). pnpm runs them in dependency order, so the libraries are built before what imports them.
RUN pnpm -r run build \
&& if [ "$CONUSAI_APP_MODULE_PROFILE" = full ]; then pnpm openapi; fi
# The anchor catalogue (`data-guide` and `guideAnchor` literals), then the code-guides bundle # kit:guides
# conusai.json declares as guides.bundle: public content the platform reads out of this image # kit:guides
# and publishes after a healthy deploy (ADR-045); no publication credential is ever in it. # kit:guides
RUN pnpm kit guides:anchors --out guides-anchors.json # kit:guides
RUN node apps/worker/dist/guides-bundle.js /app/.conusai/guides-bundle.json guides-anchors.json # kit:guides
# The image keeps what it runs and nothing else: the formatter, the type-checker and the test
# runner are build-time tools. The workspace links the three processes resolve stay, because they
# are dependencies and not devDependencies.
RUN pnpm prune --prod --ignore-scripts
FROM node:24-slim AS runtime
ENV NODE_ENV=production \
COREPACK_ENABLE_DOWNLOAD_PROMPT=0
WORKDIR /app
# node:24-slim ships a `node` user (uid 1000). Application code stays root-owned, so a
# compromised process cannot rewrite it; only the codegen output directories are writable.
#
# The image keeps node_modules: apps/web/dist/server is not standalone. Vite leaves the kit and every
# other package external, and server.mjs imports them at run time from the install above.
COPY --from=build --chown=root:root /app /app
# Git checkouts created under the service's private umask can carry 0700 directories
# and 0600 files into the build. COPY preserves those modes: root can build them,
# but node cannot even resolve the server's packages through an unsearchable parent (KI-210).
# Keep code root-owned and deny group/other writes while allowing runtime reads
# and directory traversal. X preserves executable files without making data executable.
RUN chmod -R a+rX,go-w /app \
&& mkdir -p /app/convex/_generated /app/better-auth/functions/_generated /app/better-auth/functions/betterAuth/_generated \
&& chown -R node:node /app/convex/_generated /app/better-auth/functions/_generated /app/better-auth/functions/betterAuth/_generated
USER node
# Every process is started by compose.yaml with its own command; there is no default, because a
# container that silently starts the wrong one is worse than one that does not start.
CMD ["node", "-e", "console.error('start this image with one of the commands in compose.yaml: web, worker or migrate'); process.exit(64)"]