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
|
||||
|
||||
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`:
|
||||
|
||||
@@ -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**.
|
||||
|
||||
### 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.
|
||||
|
||||
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