1 Commits

Author SHA1 Message Date
Kargo 31915d30b9 chore(kargo): update image tag official-bots to 0.36.0 on eu-central-1-prod 2026-06-23 22:09:15 +00:00
7 changed files with 35 additions and 492 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ spec:
sources:
- repoURL: ghcr.io/akuity/kargo-charts
chart: kargo
targetRevision: 1.10.8
targetRevision: 1.10.7
helm:
valueFiles:
- $values/kargo/eu-central-1/values.yaml
@@ -124,7 +124,7 @@ images:
- name: ghcr.io/now-chess/now-chess-systems/io
newTag: 0.23.0
- name: ghcr.io/now-chess/now-chess-systems/official-bots
newTag: 0.40.0
newTag: 0.36.0
- name: ghcr.io/now-chess/now-chess-systems/rule
newTag: 0.18.0
- name: ghcr.io/now-chess/now-chess-systems/store
+1 -1
View File
@@ -88,7 +88,7 @@ images:
- name: ghcr.io/now-chess/now-chess-systems/io
newTag: 0.23.0-x86_v2
- name: ghcr.io/now-chess/now-chess-systems/official-bots
newTag: 0.40.0-x86_v2
newTag: 0.35.0-x86_v2
- name: ghcr.io/now-chess/now-chess-systems/rule
newTag: 0.18.0-x86_v2
- name: ghcr.io/now-chess/now-chess-systems/store
-328
View File
@@ -1197,331 +1197,3 @@ spec:
- name: spark-output
persistentVolumeClaim:
claimName: spark-analytics-output
---
apiVersion: v1
kind: Service
metadata:
name: spark-accuracy-blunder-driver
labels:
app.kubernetes.io/name: spark-analytics
app.kubernetes.io/part-of: nowchess
spec:
clusterIP: None
selector:
app: spark-accuracy-blunder
ports:
- name: driver
port: 44167
targetPort: 44167
- name: blockmanager
port: 44777
targetPort: 44777
---
apiVersion: v1
kind: Service
metadata:
name: spark-clock-pressure-driver
labels:
app.kubernetes.io/name: spark-analytics
app.kubernetes.io/part-of: nowchess
spec:
clusterIP: None
selector:
app: spark-clock-pressure
ports:
- name: driver
port: 44167
targetPort: 44167
- name: blockmanager
port: 44777
targetPort: 44777
---
apiVersion: v1
kind: Service
metadata:
name: spark-smurf-anomaly-driver
labels:
app.kubernetes.io/name: spark-analytics
app.kubernetes.io/part-of: nowchess
spec:
clusterIP: None
selector:
app: spark-smurf-anomaly
ports:
- name: driver
port: 44167
targetPort: 44167
- name: blockmanager
port: 44777
targetPort: 44777
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: spark-accuracy-blunder
labels:
app.kubernetes.io/name: spark-analytics
app.kubernetes.io/part-of: nowchess
spec:
schedule: "0 9 * * 6"
suspend: true
concurrencyPolicy: Forbid
jobTemplate:
spec:
backoffLimit: 1
template:
metadata:
labels:
app: spark-accuracy-blunder
app.kubernetes.io/name: spark-analytics
app.kubernetes.io/part-of: nowchess
spec:
serviceAccountName: spark-analytics
restartPolicy: OnFailure
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app.kubernetes.io/name: spark-worker
topologyKey: kubernetes.io/hostname
securityContext:
fsGroup: 185
imagePullSecrets:
- name: ghcr-pull-secret
containers:
- name: spark-driver
image: ghcr.io/now-chess/now-chess-systems/analytics:latest
command:
- /opt/spark/bin/spark-submit
- --master
- spark://spark-master:7077
- --conf
- spark.executor.instances=1
- --conf
- spark.executor.cores=1
- --conf
- spark.executor.memory=512m
- --conf
- spark.driver.memory=512m
- --conf
- spark.driver.host=spark-accuracy-blunder-driver
- --conf
- spark.driver.port=44167
- --conf
- spark.blockManager.port=44777
- --class
- de.nowchess.analytics.AccuracyBlunderJob
- file:///app/analytics.jar
- /spark-output/accuracy
env:
- name: NOWCHESS_JDBC_URL
valueFrom:
secretKeyRef:
name: ncs-db-secrets
key: STORE_DB_URL
- name: NOWCHESS_DB_USER
valueFrom:
secretKeyRef:
name: ncs-db-secrets
key: STORE_DB_USER
- name: NOWCHESS_DB_PASS
valueFrom:
secretKeyRef:
name: ncs-db-secrets
key: STORE_DB_PASSWORD
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: "1"
memory: 1Gi
volumeMounts:
- name: spark-output
mountPath: /spark-output
volumes:
- name: spark-output
persistentVolumeClaim:
claimName: spark-analytics-output
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: spark-clock-pressure
labels:
app.kubernetes.io/name: spark-analytics
app.kubernetes.io/part-of: nowchess
spec:
schedule: "0 10 * * 6"
suspend: true
concurrencyPolicy: Forbid
jobTemplate:
spec:
backoffLimit: 1
template:
metadata:
labels:
app: spark-clock-pressure
app.kubernetes.io/name: spark-analytics
app.kubernetes.io/part-of: nowchess
spec:
serviceAccountName: spark-analytics
restartPolicy: OnFailure
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app.kubernetes.io/name: spark-worker
topologyKey: kubernetes.io/hostname
securityContext:
fsGroup: 185
imagePullSecrets:
- name: ghcr-pull-secret
containers:
- name: spark-driver
image: ghcr.io/now-chess/now-chess-systems/analytics:latest
command:
- /opt/spark/bin/spark-submit
- --master
- spark://spark-master:7077
- --conf
- spark.executor.instances=1
- --conf
- spark.executor.cores=1
- --conf
- spark.executor.memory=512m
- --conf
- spark.driver.memory=512m
- --conf
- spark.driver.host=spark-clock-pressure-driver
- --conf
- spark.driver.port=44167
- --conf
- spark.blockManager.port=44777
- --class
- de.nowchess.analytics.ClockPressureJob
- file:///app/analytics.jar
- /spark-output/clock-pressure
env:
- name: NOWCHESS_JDBC_URL
valueFrom:
secretKeyRef:
name: ncs-db-secrets
key: STORE_DB_URL
- name: NOWCHESS_DB_USER
valueFrom:
secretKeyRef:
name: ncs-db-secrets
key: STORE_DB_USER
- name: NOWCHESS_DB_PASS
valueFrom:
secretKeyRef:
name: ncs-db-secrets
key: STORE_DB_PASSWORD
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: "1"
memory: 1Gi
volumeMounts:
- name: spark-output
mountPath: /spark-output
volumes:
- name: spark-output
persistentVolumeClaim:
claimName: spark-analytics-output
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: spark-smurf-anomaly
labels:
app.kubernetes.io/name: spark-analytics
app.kubernetes.io/part-of: nowchess
spec:
schedule: "0 11 * * 6"
suspend: true
concurrencyPolicy: Forbid
jobTemplate:
spec:
backoffLimit: 1
template:
metadata:
labels:
app: spark-smurf-anomaly
app.kubernetes.io/name: spark-analytics
app.kubernetes.io/part-of: nowchess
spec:
serviceAccountName: spark-analytics
restartPolicy: OnFailure
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app.kubernetes.io/name: spark-worker
topologyKey: kubernetes.io/hostname
securityContext:
fsGroup: 185
imagePullSecrets:
- name: ghcr-pull-secret
containers:
- name: spark-driver
image: ghcr.io/now-chess/now-chess-systems/analytics:latest
command:
- /opt/spark/bin/spark-submit
- --master
- spark://spark-master:7077
- --conf
- spark.executor.instances=1
- --conf
- spark.executor.cores=1
- --conf
- spark.executor.memory=512m
- --conf
- spark.driver.memory=512m
- --conf
- spark.driver.host=spark-smurf-anomaly-driver
- --conf
- spark.driver.port=44167
- --conf
- spark.blockManager.port=44777
- --class
- de.nowchess.analytics.SmurfAnomalyJob
- file:///app/analytics.jar
- /spark-output/smurf-anomaly
- "15"
env:
- name: NOWCHESS_JDBC_URL
valueFrom:
secretKeyRef:
name: ncs-db-secrets
key: STORE_DB_URL
- name: NOWCHESS_DB_USER
valueFrom:
secretKeyRef:
name: ncs-db-secrets
key: STORE_DB_USER
- name: NOWCHESS_DB_PASS
valueFrom:
secretKeyRef:
name: ncs-db-secrets
key: STORE_DB_PASSWORD
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: "1"
memory: 1Gi
volumeMounts:
- name: spark-output
mountPath: /spark-output
volumes:
- name: spark-output
persistentVolumeClaim:
claimName: spark-analytics-output
+32 -93
View File
@@ -44,12 +44,6 @@ data:
("weekly-activity", "Weekly Activity", "analytics_weekly_activity"),
("rating-mismatch", "Rating Mismatch (Upsets)", "analytics_rating_mismatch"),
("termination-stats", "Termination Types", "analytics_termination_stats"),
("accuracy-by-rating", "Accuracy by Rating", "analytics_accuracy_by_rating"),
("blunder-outcome", "Blunder Outcome", "analytics_blunder_outcome"),
("clock-by-rating", "Clock Pressure by Rating", "analytics_clock_by_rating"),
("scramble-outcome", "Scramble Outcome", "analytics_scramble_outcome"),
("smurf-anomaly", "Smurf Anomaly Scores", "analytics_smurf_anomaly"),
("flagged-smurfs", "Flagged Smurfs", "analytics_flagged_smurfs"),
]
CSS = """
@@ -187,52 +181,37 @@ data:
if (btn) btn.disabled = !hasKey();
}
async function nimStream(payload, onDelta) {
async function nimFetch(payload) {
const ctrl = new AbortController();
const tid = setTimeout(() => ctrl.abort(), 180000);
const tid = setTimeout(() => ctrl.abort(), 65000);
let resp;
try {
const resp = await fetch('/explain', {
resp = await fetch('/explain', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
signal: ctrl.signal,
});
if (resp.status === 429) {
const retryAfter = parseInt(resp.headers.get('Retry-After') || '10', 10);
const err = new Error('Rate limited — retry in ' + retryAfter + 's');
err.retryAfter = retryAfter;
throw err;
}
if (!resp.ok) {
const msg = await resp.text();
throw new Error('Proxy ' + resp.status + ': ' + msg);
}
const reader = resp.body.getReader();
const dec = new TextDecoder();
const SEP = String.fromCharCode(10, 10); // SSE frame separator (two newlines)
let buf = '', full = '';
for (;;) {
const { done, value } = await reader.read();
if (done) break;
buf += dec.decode(value, { stream: true });
let idx;
while ((idx = buf.indexOf(SEP)) >= 0) {
const frame = buf.slice(0, idx).trim();
buf = buf.slice(idx + 2);
if (!frame.startsWith('data:')) continue;
const obj = JSON.parse(frame.slice(5).trim());
if (obj.error) throw new Error(obj.error);
if (obj.delta) { full += obj.delta; onDelta(full); }
}
}
return full || '(no response)';
} finally {
clearTimeout(tid);
}
if (resp.status === 429) {
const retryAfter = parseInt(resp.headers.get('Retry-After') || '10', 10);
const err = new Error('Rate limited — retry in ' + retryAfter + 's');
err.retryAfter = retryAfter;
throw err;
}
if (!resp.ok) {
const msg = await resp.text();
throw new Error('Proxy ' + resp.status + ': ' + msg);
}
const data = await resp.json();
if (data.error) throw new Error(data.error);
return data.text || '(no response)';
}
function queueNim(payload, onDelta) {
const next = nimQueue.then(() => nimStream(payload, onDelta));
function queueNim(payload) {
const next = nimQueue.then(() => nimFetch(payload));
nimQueue = next.catch(() => {});
return next;
}
@@ -252,10 +231,7 @@ data:
textEl.textContent = '';
try {
const text = await queueNim(
{ key: key, mode: 'dataset', dataset: window.DATASET },
partial => { textEl.textContent = partial; },
);
const text = await queueNim({ key: key, mode: 'dataset', dataset: window.DATASET });
textEl.textContent = text;
} catch (e) {
textEl.className = 'explain-error';
@@ -293,13 +269,10 @@ data:
textEl.textContent = 'Analyzing…';
try {
const text = await queueNim(
{
key: key, mode: 'row',
dataset: { label: dataset.label, headers: dataset.headers, row: row },
},
partial => { textEl.textContent = partial; },
);
const text = await queueNim({
key: key, mode: 'row',
dataset: { label: dataset.label, headers: dataset.headers, row: row },
});
rowCache[idx] = text;
textEl.textContent = text;
btn.innerHTML = '✓';
@@ -501,7 +474,6 @@ data:
self.end_headers()
return
length = int(self.headers.get("Content-Length", 0))
headers_sent = False
try:
body = json.loads(self.rfile.read(length))
api_key = body.get("key", "")
@@ -560,7 +532,7 @@ data:
],
"max_tokens": max_tokens,
"temperature": 0.4,
"stream": True,
"stream": False,
}).encode("utf-8")
req = urllib.request.Request(
"https://integrate.api.nvidia.com/v1/chat/completions",
@@ -572,7 +544,10 @@ data:
method="POST",
)
try:
upstream = urllib.request.urlopen(req, timeout=110)
with urllib.request.urlopen(req, timeout=60) as r:
nim_data = json.loads(r.read())
text = nim_data.get("choices", [{}])[0].get("message", {}).get("content", "(no response)")
self._send_json({"text": text})
except urllib.error.HTTPError as he:
if he.code == 429:
retry_after = he.headers.get("Retry-After", "10")
@@ -586,46 +561,10 @@ data:
self.wfile.write(resp_body)
except BrokenPipeError:
pass
return
raise RuntimeError(f"NIM API error {he.code}: {he.reason}")
# Relay NIM's SSE stream to the browser as our own SSE.
self.send_response(200)
self.send_header("Content-Type", "text/event-stream")
self.send_header("Cache-Control", "no-cache")
self.send_header("X-Accel-Buffering", "no")
self.end_headers()
headers_sent = True
for raw in upstream:
line = raw.decode("utf-8", "replace").strip()
if not line.startswith("data:"):
continue
data = line[5:].strip()
if data == "[DONE]":
break
try:
obj = json.loads(data)
except json.JSONDecodeError:
continue
piece = obj.get("choices", [{}])[0].get("delta", {}).get("content", "")
if piece and not self._emit({"delta": piece}):
return
self._emit({"done": True})
else:
self._send_json({"error": f"NIM API error {he.code}: {he.reason}"})
except Exception as e:
if not headers_sent:
self.send_response(200)
self.send_header("Content-Type", "text/event-stream")
self.send_header("Cache-Control", "no-cache")
self.end_headers()
self._emit({"error": str(e)})
def _emit(self, obj: dict) -> bool:
try:
self.wfile.write(("data: " + json.dumps(obj) + "\n\n").encode("utf-8"))
self.wfile.flush()
return True
except (BrokenPipeError, ConnectionResetError):
return False
self._send_json({"error": str(e)})
def _send(self, body: str):
data = body.encode("utf-8")
@@ -353,70 +353,6 @@ patches:
target:
kind: CronJob
name: spark-termination-stats
# Eval-annotated dump required for per-move accuracy; the default 2013 dump
# carries no [%eval] comments — point this at an evals dump for real output.
- patch: |-
apiVersion: batch/v1
kind: CronJob
metadata:
name: spark-accuracy-blunder
spec:
jobTemplate:
spec:
template:
spec:
containers:
- name: spark-driver
env:
- name: NOWCHESS_PGN_PATH
value: "https://database.lichess.org/standard/lichess_db_standard_rated_2013-01.pgn.zst"
- name: NOWCHESS_PGN_CACHE_DIR
value: "/spark-output/.pgn-cache"
target:
kind: CronJob
name: spark-accuracy-blunder
# Clock-annotated dump required; the default 2013 dump carries no [%clk]
# comments — point this at a dump with clocks for real output.
- patch: |-
apiVersion: batch/v1
kind: CronJob
metadata:
name: spark-clock-pressure
spec:
jobTemplate:
spec:
template:
spec:
containers:
- name: spark-driver
env:
- name: NOWCHESS_PGN_PATH
value: "https://database.lichess.org/standard/lichess_db_standard_rated_2013-01.pgn.zst"
- name: NOWCHESS_PGN_CACHE_DIR
value: "/spark-output/.pgn-cache"
target:
kind: CronJob
name: spark-clock-pressure
- patch: |-
apiVersion: batch/v1
kind: CronJob
metadata:
name: spark-smurf-anomaly
spec:
jobTemplate:
spec:
template:
spec:
containers:
- name: spark-driver
env:
- name: NOWCHESS_PGN_PATH
value: "https://database.lichess.org/standard/lichess_db_standard_rated_2013-01.pgn.zst"
- name: NOWCHESS_PGN_CACHE_DIR
value: "/spark-output/.pgn-cache"
target:
kind: CronJob
name: spark-smurf-anomaly
- patch: |-
apiVersion: apps/v1
kind: Deployment
@@ -9,10 +9,6 @@ metadata:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
# AI /explain streams Server-Sent Events — disable response buffering so
# chunks flush to the client immediately, and allow gaps between chunks.
nginx.ingress.kubernetes.io/proxy-buffering: "off"
nginx.ingress.kubernetes.io/proxy-read-timeout: "120"
spec:
ingressClassName: nginx
rules: