Skip to content

ci(replatform): tail Harper's internal log (not just stdout) on failure #15

ci(replatform): tail Harper's internal log (not just stdout) on failure

ci(replatform): tail Harper's internal log (not just stdout) on failure #15

name: Replatform Parity
# Regression gate for the Harper replatform (app/). Scoped to the replatform
# branch only — does not run on the team's normal PRs. Builds the Docusaurus
# site (ground truth), stands up Harper with the app, ingests the corpus, and
# runs the parity harness in --strict mode.
on:
push:
branches: [replatform]
pull_request:
branches: [replatform]
concurrency:
group: replatform-parity-${{ github.ref }}
cancel-in-progress: true
jobs:
parity:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: '24'
- name: Build Docusaurus (ground truth)
run: |
npm ci
npm run build
- name: Install app deps + Harper
run: |
npm --prefix app ci
npm install -g harper
- name: Typecheck
run: npm --prefix app run typecheck
- name: Unit tests
run: npm --prefix app test
- name: Install & start Harper
env:
TC_AGREEMENT: 'yes'
HDB_ADMIN_USERNAME: HDB_ADMIN
HDB_ADMIN_PASSWORD: ${{ github.run_id }}-parity
ROOTPATH: ${{ runner.temp }}/hdb
HTTP_PORT: '9936'
OPERATIONSAPI_NETWORK_PORT: '9935'
# Enables the /admin/dev-login affordance so the integration + e2e
# tests can establish an admin session without the Google round-trip.
ADMIN_DEV_LOGIN: 'true'
# Trust X-Forwarded-For so the chat tests can isolate their per-IP quota
# (this runner is a trusted environment).
CHAT_TRUST_PROXY: 'true'
# Generation model for chat answers, the multi-turn condenser, and the
# faithfulness monitor. OPTIONAL: without the ANTHROPIC_API_KEY repo
# secret the server runs the deterministic dev stub — chat still streams,
# but the model-gated cache tests self-skip and the grounding eval's
# multi-turn cases exercise a no-op condenser. Add the secret to run
# those for real (each CI run then makes a modest number of API calls).
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
# Embedding model for @embed / the semantic lane. Requires the
# GEMINI_API_KEY repo secret; HARPER_CONFIG merges the models section
# into the instance config at startup. Without it, @embed writes fail
# and ingest aborts (the @embed ingest-time coupling — see #1594).
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
HARPER_CONFIG: >-
{"models":{"embedding":{"gemini-embedding-001":{"backend":"openai",
"baseUrl":"https://generativelanguage.googleapis.com/v1beta/openai/",
"apiKey":"${GEMINI_API_KEY}","model":"gemini-embedding-001"}}}}
run: |
harper install --TC_AGREEMENT=yes --HDB_ADMIN_USERNAME="$HDB_ADMIN_USERNAME" \
--HDB_ADMIN_PASSWORD="$HDB_ADMIN_PASSWORD" --ROOTPATH="$ROOTPATH" \
--HTTP_PORT=9936 --OPERATIONSAPI_NETWORK_PORT=9935
# Run the app as a COMPONENT — `harper run .` loads app/ (its custom
# resources: /Ingest, /api/*, /admin/*), whereas `harper start` boots a
# bare instance with none of them (so ingest 404'd). Backgrounded so the
# later steps run against it; env (incl. the API keys) is inherited.
(cd app && ROOTPATH="$ROOTPATH" nohup harper run . > "$ROOTPATH/harper-run.log" 2>&1 &)
for i in $(seq 1 45); do
curl -sf -o /dev/null "http://127.0.0.1:9936/" && break || sleep 2
done
curl -s -o /dev/null -w "Harper up: HTTP %{http_code} on /\n" "http://127.0.0.1:9936/" || true
# Fail fast (with the app log) if the component didn't register its routes:
# a 404 on /Ingest means the app didn't load; any other code means it did.
ingest_code=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:9936/Ingest")
echo "/Ingest probe: HTTP $ingest_code"
if [ "$ingest_code" = "404" ]; then
echo "::error::/Ingest 404 — app component did not load its routes"
tail -80 "$ROOTPATH/harper-run.log" || true
exit 1
fi
- name: Ingest corpus
env:
HARPER_CLI_USERNAME: HDB_ADMIN
HARPER_CLI_PASSWORD: ${{ github.run_id }}-parity
run: node app/scripts/ingest.ts --target http://127.0.0.1:9936
# Surface the server-side error when a step above fails (e.g. an ingest 500
# from an @embed write when GEMINI_API_KEY is missing) instead of leaving
# only the client's opaque "500 after 5 retries".
- name: Harper server log (on failure)
if: failure()
run: |
echo "=== harper run stdout ==="
tail -40 "${{ runner.temp }}/hdb/harper-run.log" 2>/dev/null || echo "(none)"
echo "=== Harper internal log(s) — the real @embed / request errors ==="
find "${{ runner.temp }}/hdb" -name '*.log' -not -name harper-run.log \
-exec sh -c 'echo "── $1 ──"; tail -120 "$1"' _ {} \; 2>/dev/null || echo "(no logs found)"
- name: Integration tests
env:
HARPER_TARGET: http://127.0.0.1:9936
HARPER_CLI_USERNAME: HDB_ADMIN
HARPER_CLI_PASSWORD: ${{ github.run_id }}-parity
run: npm --prefix app run test:integration
- name: Parity gate (strict)
# Also records a ParityRun row for the admin Validation trend — needs the
# admin credentials, or recordMetric() silently no-ops.
env:
HARPER_CLI_USERNAME: HDB_ADMIN
HARPER_CLI_PASSWORD: ${{ github.run_id }}-parity
run: node app/scripts/parity.ts --strict --target http://127.0.0.1:9936
- name: Search relevance gate
# Keyword-primary ranking makes the eval deterministic (MRR 0.907,
# stable across rebuilds), so a real floor holds. 0.85 leaves headroom
# for golden-set edits without flaking. Also records an EvalRun row.
env:
HARPER_CLI_USERNAME: HDB_ADMIN
HARPER_CLI_PASSWORD: ${{ github.run_id }}-parity
run: node app/scripts/search-eval.ts --min-mrr 0.85 --target http://127.0.0.1:9936
- name: Chat grounding eval
# Retrieval-only (retrieveOnly) — no model key or quota needed. Gates that
# chat retrieval still surfaces the right doc page for NL questions.
run: node app/scripts/chat-grounding.ts --min-recall 0.6 --target http://127.0.0.1:9936
- name: Install Playwright browser
run: npx --prefix app playwright install --with-deps chromium
- name: E2E tests
env:
HARPER_TARGET: http://127.0.0.1:9936
HARPER_CLI_USERNAME: HDB_ADMIN
HARPER_CLI_PASSWORD: ${{ github.run_id }}-parity
run: npm --prefix app run test:e2e
- name: Upload parity report
if: always()
uses: actions/upload-artifact@v4
with:
name: parity-report
path: app/parity-report.json