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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
a5529ce542
commit
0db7b8140b
@@ -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
|
||||||
+27
@@ -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"]
|
||||||
@@ -23,13 +23,15 @@ Getting the XML: SSMS → **Ctrl+M** (include actual execution plan) → run the
|
|||||||
|
|
||||||
## Run it
|
## Run it
|
||||||
|
|
||||||
This repo runs as a plain Node process. Containerization is written (`Dockerfile`, Task 12 of the plan) but is not
|
This repo runs as a plain Node process. `Dockerfile` and `docker-compose.yml` are ready, but the image has **not
|
||||||
required for local work — `npm run dev` is the normal path on a small machine.
|
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
|
```bash
|
||||||
npm install
|
npm install
|
||||||
cp .env.example .env.local # then fill in APP_SECRET and DATA_DIR
|
cp .env.example .env.local # then fill in APP_SECRET and DATA_DIR
|
||||||
npm run dev # http://localhost:3000
|
npm run dev # http://localhost:3000
|
||||||
|
bash scripts/smoke.sh # healthz, login page, signed-out redirect, analyze needs a session
|
||||||
```
|
```
|
||||||
|
|
||||||
`.env.local`:
|
`.env.local`:
|
||||||
|
|||||||
@@ -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:
|
||||||
@@ -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**.
|
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)
|
## Actors (two models, strict roles)
|
||||||
|
|
||||||
- **Analyst** — any OpenAI-compatible endpoint the user configures (`base_url`,
|
- **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
|
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
|
top pick is defensible because Jev's probabilities are on screen, and the winning
|
||||||
SQL is one click from the clipboard.
|
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.
|
||||||
|
|||||||
Executable
+23
@@ -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 <label> <expected> <actual>
|
||||||
|
if [ "$2" = "$3" ]; then echo "ok $1"; else echo "FAIL $1: expected [$2] got [$3]"; fail=1; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "${1:-}" = "--docker" ]; then
|
||||||
|
docker compose up --build -d
|
||||||
|
trap 'docker compose down -v' EXIT
|
||||||
|
for _ in $(seq 1 40); do curl -fsS "$BASE/api/healthz" >/dev/null 2>&1 && break; sleep 2; done
|
||||||
|
fi
|
||||||
|
|
||||||
|
check "healthz" '{"ok":true}' "$(curl -fsS "$BASE/api/healthz" 2>/dev/null)"
|
||||||
|
check "login page" "200" "$(curl -fsS -o /dev/null -w '%{http_code}' "$BASE/login")"
|
||||||
|
check "signup page" "200" "$(curl -fsS -o /dev/null -w '%{http_code}' "$BASE/signup")"
|
||||||
|
check "signed-out /app redirects" "307" "$(curl -s -o /dev/null -w '%{http_code}' "$BASE/app")"
|
||||||
|
check "analyze needs a session" "401" "$(curl -s -o /dev/null -w '%{http_code}' -X POST -H 'content-type: application/json' -d '{}' "$BASE/api/analyze")"
|
||||||
|
[ $fail -eq 0 ] && echo "smoke ok" || { echo "smoke FAILED"; exit 1; }
|
||||||
Reference in New Issue
Block a user