From 0db7b8140b43954556a5e8a8d0eebe8b4af62ad3 Mon Sep 17 00:00:00 2001 From: ahmed mohammed Date: Tue, 22 Sep 2026 08:40:57 +0300 Subject: [PATCH] feat: docker, readme, jev goal gate Dockerfile/compose written but never built or run on this machine (no Docker). smoke.sh now works in both modes and fails loudly. Co-Authored-By: Claude Opus 4.7 --- .dockerignore | 16 ++++++++++ Dockerfile | 27 +++++++++++++++++ README.md | 6 ++-- docker-compose.yml | 10 +++++++ .../specs/2026-09-21-sql-server-qai.md | 30 +++++++++++++++++++ scripts/smoke.sh | 23 ++++++++++++++ 6 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100755 scripts/smoke.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..877fdd2 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,16 @@ +.next +node_modules +data +.git +.hallmark +docs +design-system +tests +tests-e2e +fixtures +.superpowers +.env* +.playwright-data +.auth +playwright-report +test-results diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a66a7b0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +FROM node:24-slim AS deps +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci + +FROM node:24-slim AS build +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . +ENV NEXT_TELEMETRY_DISABLED=1 +RUN npm run build + +FROM node:24-slim AS run +WORKDIR /app +ENV NODE_ENV=production NEXT_TELEMETRY_DISABLED=1 DATA_DIR=/data PORT=3000 +RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/* \ + && mkdir -p /data && chown node:node /data +COPY --from=build --chown=node:node /app/.next ./.next +COPY --from=build --chown=node:node /app/public ./public +COPY --from=build --chown=node:node /app/node_modules ./node_modules +COPY --from=build --chown=node:node /app/package.json ./package.json +COPY --from=build --chown=node:node /app/next.config.* ./ +USER node +VOLUME /data +EXPOSE 3000 +HEALTHCHECK --interval=30s --timeout=3s CMD curl -fsS localhost:3000/api/healthz +CMD ["npm", "start"] diff --git a/README.md b/README.md index 6ac8881..f70568b 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,15 @@ Getting the XML: SSMS → **Ctrl+M** (include actual execution plan) → run the ## Run it -This repo runs as a plain Node process. Containerization is written (`Dockerfile`, Task 12 of the plan) but is not -required for local work — `npm run dev` is the normal path on a small machine. +This repo runs as a plain Node process. `Dockerfile` and `docker-compose.yml` are ready, but the image has **not +been built or run on this machine** (Docker is not installed here); treat it as untested until +`bash scripts/smoke.sh --docker` says otherwise. Local work is `npm run dev`. ```bash npm install cp .env.example .env.local # then fill in APP_SECRET and DATA_DIR npm run dev # http://localhost:3000 +bash scripts/smoke.sh # healthz, login page, signed-out redirect, analyze needs a session ``` `.env.local`: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..610a00f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,10 @@ +services: + qai: + build: . + ports: ["3000:3000"] + environment: + APP_SECRET: ${APP_SECRET:?set APP_SECRET in .env, 16+ chars} + volumes: ["qai-data:/data"] + restart: unless-stopped +volumes: + qai-data: diff --git a/docs/superpowers/specs/2026-09-21-sql-server-qai.md b/docs/superpowers/specs/2026-09-21-sql-server-qai.md index 60ebbe9..aae18f1 100644 --- a/docs/superpowers/specs/2026-09-21-sql-server-qai.md +++ b/docs/superpowers/specs/2026-09-21-sql-server-qai.md @@ -39,6 +39,18 @@ The demo value is Jev as the decision-maker: the generative model proposes, Report actions: **Export to PDF**, **Copy**. History actions: **rename**, **delete**. +### Getting the input, and the two keys + +The portal cannot reach the SQL Server. The user brings the plan XML, and the app must say so in the run screen: +SSMS → **Ctrl+M** (include actual execution plan) → run the query → save the Execution Plan tab as `.sqlplan`, or +copy the Showplan XML cell; without SSMS, prefix the batch with `SET STATISTICS XML ON;` and copy the XML result +cell. A `.sqlplan` file can be dropped in (SSMS writes UTF-16 with a BOM; decode both) or pasted. + +Both model credentials are typed by the logged-in user in `/setup` or `/settings` — never server config, so every +user brings their own analyst endpoint and their own TypeSafe/Jev key (`console.typesafe.ai/keys`). Stored keys come +back masked, and a blank field means keep the stored one. Nothing leaves the container except the digest, and only +to the endpoints that user configured. + ## Actors (two models, strict roles) - **Analyst** — any OpenAI-compatible endpoint the user configures (`base_url`, @@ -82,3 +94,21 @@ Report actions: **Export to PDF**, **Copy**. History actions: **rename**, **dele Paste a real slow-query plan → under 60 s → a ranked set of remediations where the top pick is defensible because Jev's probabilities are on screen, and the winning SQL is one click from the clipboard. + +## Acceptance + +What has to be true before this counts as done, and how each part is proven: + +- **Offline unit suites** (`npm test`, no keys, no network): a real ShowPlan digests to the right numbers; input + that is not a plan is refused with a named reason; analyst output is schema-checked and 2–4 options are enforced; + Jev's scores rank, an unknown Jev label cannot lead, and a silent Jev degrades to a labelled composite order; + credentials seal/open/mask and a blank field never wipes a stored key. +- **One live run** against the user's real analyst endpoint and a real Jev key: every SSE stage fires, several + candidates come back, Jev returns a verdict with confidence, and OpenUI Lang arrives in deltas. +- **Browser QA** (Chromium): signup → guide → settings → run → rendered report; Copy puts SQL on the clipboard; + print emulation flips to paper and hides the rail and buttons; rename sticks and delete confirms first; no + horizontal scroll from 320 px up. +- **A smoke script** (`scripts/smoke.sh`) that a container or a plain `npm start` both pass: health, auth pages, + signed-out redirect, and `/api/analyze` refusing an anonymous caller. +- **A goal gate**: Jev scores the written plan against the goal in this document and answers the named-gap question + with `nothing` before implementation starts. diff --git a/scripts/smoke.sh b/scripts/smoke.sh new file mode 100755 index 0000000..22d45b9 --- /dev/null +++ b/scripts/smoke.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# The one command that proves the app answers. Two modes, same checks: +# bash scripts/smoke.sh # app already running (BASE=http://localhost:3000) +# bash scripts/smoke.sh --docker # via docker compose (Docker is not installed on this machine) +set -uo pipefail +BASE=${BASE:-http://localhost:3000} +fail=0 +check() { # check