From 2e909026a7a1444d45fbe27fbdf7435e8cfc13e4 Mon Sep 17 00:00:00 2001 From: mpabi Date: Sun, 12 Apr 2026 18:30:30 +0200 Subject: [PATCH] feat(sol): align canary ingestor and api auth --- .../workflows/deploy-trade-r001-canary.yaml | 88 ++++++++++++++++++- environments/sol/trade-r001-canary/README.md | 5 +- .../trade-r001-canary/api-token-seed-job.yaml | 76 ++++++++++++++++ .../assets/ingestor/dlob-ingestor.mjs | 38 ++++++-- .../sol/trade-r001-canary/kustomization.yaml | 1 + 5 files changed, 199 insertions(+), 9 deletions(-) create mode 100644 environments/sol/trade-r001-canary/api-token-seed-job.yaml diff --git a/.gitea/workflows/deploy-trade-r001-canary.yaml b/.gitea/workflows/deploy-trade-r001-canary.yaml index 63e30e2..897c5e4 100644 --- a/.gitea/workflows/deploy-trade-r001-canary.yaml +++ b/.gitea/workflows/deploy-trade-r001-canary.yaml @@ -41,7 +41,7 @@ jobs: env: KUBECONFIG: /tmp/kubeconfig run: | - kubectl -n trade-r001-canary delete job postgres-migrate hasura-bootstrap --ignore-not-found=true + kubectl -n trade-r001-canary delete job postgres-migrate hasura-bootstrap api-token-seed --ignore-not-found=true - name: Apply shared host access infrastructure env: @@ -79,6 +79,7 @@ jobs: run: | kubectl -n trade-r001-canary wait --for=condition=complete job/postgres-migrate --timeout=300s kubectl -n trade-r001-canary wait --for=condition=complete job/hasura-bootstrap --timeout=300s + kubectl -n trade-r001-canary wait --for=condition=complete job/api-token-seed --timeout=300s - name: Wait for application rollouts env: @@ -104,6 +105,60 @@ jobs: restart_count="$(kubectl -n trade-r001-canary get pod "$pod_name" -o jsonpath='{.status.containerStatuses[0].restartCount}')" test "${restart_count}" = "0" kubectl -n trade-r001-canary logs "$pod_name" --tail=20 + kubectl -n trade-r001-canary exec -i "$pod_name" -- node - <<'JS' + const endpoint = 'http://hasura:8080/v1/graphql'; + const adminSecret = process.env.HASURA_ADMIN_SECRET; + const query = ` + query { + drift_ticks(limit: 5, order_by: { ts: desc }) { + symbol + source + raw + ts + } + } + `; + const allowed = new Set(['dlob_hot_derived_latest', 'dlob_all_derived_latest']); + + async function check() { + const response = await fetch(endpoint, { + method: 'POST', + headers: { + 'content-type': 'application/json', + 'x-hasura-admin-secret': adminSecret, + }, + body: JSON.stringify({ query }), + signal: AbortSignal.timeout(10000), + }); + const payload = await response.json(); + const rows = payload?.data?.drift_ticks || []; + if (!rows.length) { + throw new Error('No rows in drift_ticks after trade-ingestor rollout'); + } + const from = rows[0]?.raw?.from || null; + if (!allowed.has(from)) { + throw new Error(`Unexpected drift_ticks raw.from: ${from}`); + } + console.log(JSON.stringify(rows[0], null, 2)); + } + + (async () => { + for (let attempt = 0; attempt < 12; attempt += 1) { + try { + await check(); + return; + } catch (error) { + if (attempt === 11) { + throw error; + } + await new Promise((resolve) => setTimeout(resolve, 5000)); + } + } + })().catch((error) => { + console.error(String(error && error.message ? error.message : error)); + process.exit(1); + }); + JS - name: Verify canary namespace connectivity env: @@ -213,6 +268,37 @@ jobs: process.exit(1); }); JS + token="$(kubectl -n trade-r001-canary get secret trade-frontend-tokens -o jsonpath='{.data.read\.json}' | base64 -d | jq -r .token)" + kubectl -n trade-r001-canary exec -i "$pod_name" -- env API_TOKEN="$token" node - <<'JS' + const headers = { Authorization: `Bearer ${process.env.API_TOKEN}` }; + + async function getJson(url) { + const response = await fetch(url, { + headers, + signal: AbortSignal.timeout(10000), + }); + const payload = await response.json(); + if (!response.ok || !payload?.ok) { + throw new Error(`${url} failed: ${response.status} ${JSON.stringify(payload)}`); + } + return payload; + } + + (async () => { + const ticks = await getJson('http://trade-api:8787/v1/ticks?symbol=SOL-PERP&limit=5'); + if (!Array.isArray(ticks.ticks) || !ticks.ticks.length) { + throw new Error('No SOL-PERP ticks from trade-api'); + } + const chart = await getJson('http://trade-api:8787/v1/chart?symbol=SOL-PERP&tf=1m&limit=20'); + if (!Array.isArray(chart.candles) || !chart.candles.length) { + throw new Error('No SOL-PERP candles from trade-api'); + } + console.log(JSON.stringify({ ticks: ticks.ticks.at(-1), chart: chart.candles.at(-1) }, null, 2)); + })().catch((err) => { + console.error(String(err && err.message ? err.message : err)); + process.exit(1); + }); + JS kubectl -n trade-r001-canary exec -i "$pod_name" -- node - <<'JS' const targets = [ 'http://hasura:8080/healthz', diff --git a/environments/sol/trade-r001-canary/README.md b/environments/sol/trade-r001-canary/README.md index f8ff627..224c0c3 100644 --- a/environments/sol/trade-r001-canary/README.md +++ b/environments/sol/trade-r001-canary/README.md @@ -50,9 +50,10 @@ Minimal canary namespace for migration baseline `R001` on `sol`. - The canary workflow re-runs: - `postgres-migrate` - `hasura-bootstrap` + - `api-token-seed` before it waits for `Hasura`, `trade-api`, `trade-frontend`, `trade-ingestor`, and the DLOB hot/all-path deployments to become healthy. -- The current canary `trade-ingestor` is intentionally pinned to the schema already reconstructed on `sol` and reads from `dlob_stats_latest`. -- The exact live `R001` ingestor path that reads `dlob_*_derived_latest` remains a follow-up substep after the DLOB writer chain is reconstructed. +- The canary `trade-ingestor` now follows the live `R001` path on `sol`: it reads `dlob_hot_derived_latest` first for hot markets and falls back to `dlob_all_derived_latest`. +- `api-token-seed` restores the frontend read token in `api_tokens`, so `trade-api` and `trade-frontend` can be validated against the reconstructed derived tables after each deploy. ## Operator Flow diff --git a/environments/sol/trade-r001-canary/api-token-seed-job.yaml b/environments/sol/trade-r001-canary/api-token-seed-job.yaml new file mode 100644 index 0000000..7a3eb21 --- /dev/null +++ b/environments/sol/trade-r001-canary/api-token-seed-job.yaml @@ -0,0 +1,76 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: api-token-seed + namespace: trade-r001-canary +spec: + backoffLimit: 1 + template: + spec: + restartPolicy: OnFailure + containers: + - name: seed + image: postgres:16-alpine + imagePullPolicy: IfNotPresent + command: + - sh + - -lc + - | + set -euo pipefail + + read_json="$(tr -d '\n'