Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
eac0f55
docs(trail): add entry for PR #694 review remediation + branch promotion
hunnibear Feb 23, 2026
d016e71
feat(ci): add GHCR bootstrap and local-first supaserch gates
hunnibear Feb 23, 2026
6874974
docs(ops): codify Dock.Tier Git.Flare parity lane and handoff trail
hunnibear Feb 23, 2026
0684425
fix(ci): honor GHCR integration selector for workflow dispatch
hunnibear Feb 23, 2026
9bd156d
fix(ci): skip non-selected GHCR matrix lanes on dispatch
hunnibear Feb 23, 2026
58fc65c
fix(ci): resolve GHCR matrix from config for true targeted dispatch
hunnibear Feb 23, 2026
467880c
chore(ops): make GHCR bootstrap lane org-aware by default
hunnibear Feb 23, 2026
76b1654
fix(ci): resolve GHCR matrix by names to avoid output masking
hunnibear Feb 23, 2026
c9940b9
docs(ci): record GHCR matrix resolver source-of-truth flow
hunnibear Feb 23, 2026
0645d34
fix(ci): use python3 fallback for integration config step
hunnibear Feb 23, 2026
008dc4d
fix(ci): prefer GHCR PAT auth before workflow token
hunnibear Feb 23, 2026
64fccf5
docs(ci): note PAT-first GHCR auth for 403 mitigation
hunnibear Feb 23, 2026
b3fb060
feat(ci): add GHCR namespace override for targeted dispatch
hunnibear Feb 23, 2026
4624231
docs(ops): document GHCR namespace override for org routing
hunnibear Feb 23, 2026
fc9e418
fix(auth): NATS credential hardening + service topology docs
hunnibear Feb 24, 2026
243f994
feat(auth): BoTZ gateway unified JWT + CHIT attestation + agent trails
hunnibear Feb 24, 2026
bd576cc
fix(parity): resolve PR699 review threads and CI hardening gaps (#703)
POWERFULMOVES Feb 24, 2026
fbf1a06
feat(release): deterministic submodule production checklist and branc…
POWERFULMOVES Feb 24, 2026
9d3d5fb
fix(jellyfin): align prod topology and yt metadata path (#700)
POWERFULMOVES Feb 24, 2026
f4eab15
feat(jellyfin): add prod stack verify and parity audit targets (#701)
POWERFULMOVES Feb 24, 2026
3044a3b
docs(creator): update roadmap, handoff trail, and audit runbook (#702)
POWERFULMOVES Feb 24, 2026
6151802
Merge main into PMOVES.AI-Edition-Hardened-Integrations for PR699 fin…
hunnibear Feb 24, 2026
1e2d374
feat(topology): topology-aware service recovery + auth alignment
hunnibear Feb 25, 2026
e577763
fix(hydrate): filter placeholder values from container env inspection
hunnibear Feb 25, 2026
d39f78d
merge: resolve main conflict set for PR #709
hunnibear Feb 25, 2026
87400bd
fix(ci): isolate hyphenated service pytest conftests in python-tests
hunnibear Feb 25, 2026
f7d942f
fix(ci): scope python-tests workflow to service test suites
hunnibear Feb 25, 2026
1db4d94
fix(pytest): use explicit pmoves.tests fixture bridge import
hunnibear Feb 25, 2026
8c81b6d
fix(compose): align comfy-watcher MinIO credential variables
hunnibear Feb 25, 2026
9851986
feat(darkxside): A2UI Remotion renderer + WebRTC portal + auth (#708)
POWERFULMOVES Feb 25, 2026
df14782
chore(env): enforce uv-first pmoves venv bootstrap
hunnibear Feb 25, 2026
58c9d15
feat(preflight): add topology and CHIT sync gate
hunnibear Feb 25, 2026
956a494
feat(preflight): expand topology gate to all running containers
hunnibear Feb 25, 2026
593e223
feat(preflight): enforce manifest-driven topology policy gate
hunnibear Feb 25, 2026
c76bc02
feat(preflight): scope CHIT gate to policy-defined services
hunnibear Feb 25, 2026
70bd0bc
feat(runtime): enforce CHIT production overlays on core services
hunnibear Feb 25, 2026
01de278
docs(ops): add topology+CHIT strict gate workflow runbook
hunnibear Feb 25, 2026
052bb0a
fix(make): run CHIT manifest targets with project venv
hunnibear Feb 25, 2026
c9cc762
security(nats): enforce authenticated NATS URLs across all services
hunnibear Feb 26, 2026
86af642
security(nats): update NATS auth in documentation and agent prompts
hunnibear Feb 26, 2026
f8b86ca
docs(catalog): add CHIT services, port 3000 conflict note, health end…
hunnibear Feb 26, 2026
609ed95
docs(agents): update protocols, accord, trail, and integration audits
hunnibear Feb 26, 2026
30e7fcd
docs(context): update main CLAUDE.md, planning, and architecture docs
hunnibear Feb 26, 2026
6c9f516
chore(audit): update CHIT status, secrets manifest, and validation re…
hunnibear Feb 26, 2026
3081017
chore(docker): Dockerfile hardening, requirements updates, and tooling
hunnibear Feb 26, 2026
7e58241
chore(submodules): update upstream submodule pointers
hunnibear Feb 26, 2026
f3fb2f7
feat(jellyfin-ai): audio processor and API gateway updates
hunnibear Feb 26, 2026
d2996f9
fix(compose): harden nats-init, fix unhealthy services, remove deprec…
hunnibear Feb 26, 2026
1bff7e3
fix(channel-monitor): correct fallback database hostname and credentials
hunnibear Feb 26, 2026
61e6b5e
docs(hardening): update tracker to v4.0 and service inventory to v2.0
hunnibear Feb 26, 2026
1cd1bef
chore(security): close stale HiRAG P2#15, add A2UI P2#16
hunnibear Feb 26, 2026
98cfae5
Merge remote-tracking branch 'origin/main' into PMOVES.AI-Edition-Har…
hunnibear Feb 26, 2026
f071484
fix(security): real fixes for XSS, resource exhaustion, and chit.py p…
hunnibear Feb 26, 2026
6f63068
fix(security): eliminate SSRF TOCTOU via urllib3 direct-connect in Hi…
hunnibear Feb 26, 2026
0b58194
fix(security): mask credentials in chit_credential_demo.py
hunnibear Feb 26, 2026
9b0a2ad
chore(security): add CodeQL suppression annotations for validated pat…
hunnibear Feb 26, 2026
6d82698
fix(security): address review findings — pool cleanup, v2 exception h…
hunnibear Feb 26, 2026
33e13b2
fix(hi-rag-v2): restore swarm mode in geometry decode endpoints
hunnibear Feb 26, 2026
0f618a8
fix(docker): add pmoves.chit namespace to Hi-RAG gateway containers
hunnibear Feb 26, 2026
78c9c70
feat(agentgym): subscribe to hf.model.downloaded.v1 NATS events
hunnibear Feb 26, 2026
d537700
fix(hf-mcp): persistent NATS connection and auth credentials
hunnibear Feb 26, 2026
c255596
fix(review): address PR review findings — logging, timestamps, health…
hunnibear Feb 26, 2026
0aee064
fix(gateway,a2ui): retry DNS addresses and clean up temp dirs on error
hunnibear Feb 26, 2026
c0b56e2
Merge remote-tracking branch 'origin/main' into PMOVES.AI-Edition-Har…
hunnibear Feb 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions pmoves/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -906,8 +906,8 @@ services:
hi-rag-gateway:
<<: *tier-api-hardened
build:
context: ./services
dockerfile: hi-rag-gateway/Dockerfile
context: .
dockerfile: services/hi-rag-gateway/Dockerfile
restart: unless-stopped
environment:
- QDRANT_URL=${QDRANT_URL:-http://qdrant:6333}
Expand Down Expand Up @@ -1365,8 +1365,8 @@ services:
hi-rag-gateway-gpu:
<<: *tier-api-hardened
build:
context: ./services
dockerfile: hi-rag-gateway/Dockerfile
context: .
dockerfile: services/hi-rag-gateway/Dockerfile
args:
- TORCH_CUDA_VERSION=${TORCH_CUDA_VERSION:-cu128}
- TORCH_SKIP_CUDA=0
Expand Down
12 changes: 10 additions & 2 deletions pmoves/services/a2ui-renderer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ app.post('/render', requireAuth, async (req: Request, res: Response) => {
const spec = req.body;
const end = renderDuration.startTimer({ format });

let tmpDir: string | undefined;
try {
if (!spec.version || !spec.animation || !spec.scenes) {
renderCounter.inc({ format, status: 'error' });
Expand All @@ -223,7 +224,7 @@ app.post('/render', requireAuth, async (req: Request, res: Response) => {
inputProps: { spec },
});

const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'a2ui-'));
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'a2ui-'));
const outputFile = path.join(tmpDir, `render.${format}`);

const codec = format === 'gif' ? 'gif' as const : format === 'webm' ? 'vp8' as const : 'h264' as const;
Expand Down Expand Up @@ -289,6 +290,9 @@ app.post('/render', requireAuth, async (req: Request, res: Response) => {
spec_version: spec.version,
});
} catch (err) {
if (tmpDir) {
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch { /* best-effort */ }
}
renderCounter.inc({ format, status: 'error' });
end();
res.status(500).json({
Expand Down Expand Up @@ -336,6 +340,7 @@ app.post('/render/chart', requireAuth, async (req: Request, res: Response) => {
req.query.format = 'mp4';

const end = renderDuration.startTimer({ format: 'mp4' });
let tmpDir: string | undefined;

try {
const servedUrl = await ensureBundle();
Expand All @@ -347,7 +352,7 @@ app.post('/render/chart', requireAuth, async (req: Request, res: Response) => {
inputProps: { spec },
});

const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'a2ui-chart-'));
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'a2ui-chart-'));
const outputFile = path.join(tmpDir, 'chart.mp4');

await renderMedia({
Expand Down Expand Up @@ -390,6 +395,9 @@ app.post('/render/chart', requireAuth, async (req: Request, res: Response) => {

res.json({ ok: true, url, format: 'mp4', duration_ms: 6000, scenes: 1 });
} catch (err) {
if (tmpDir) {
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch { /* best-effort */ }
}
renderCounter.inc({ format: 'mp4', status: 'error' });
end();
res.status(500).json({
Expand Down
36 changes: 36 additions & 0 deletions pmoves/services/agentgym-rl-coordinator/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,42 @@ async def geometry_message_handler(msg):
await nc.subscribe("tokenism.geometry.event.v1", cb=geometry_message_handler)
logger.info("Subscribed to geometry event subjects")

# Subscribe to HuggingFace model download events
async def hf_model_handler(msg):
"""Handle HF model download notifications for training pipeline."""
try:
data = json.loads(msg.data)
model_id = data.get("model_id")
model_path = data.get("path")
if model_id and model_path:
logger.info(
"HF model downloaded: %s at %s — recording for training pipeline",
model_id, model_path,
)
if storage:
await storage.record_event(
event_type="hf_model_downloaded",
payload={"model_id": model_id, "path": model_path},
)
else:
logger.warning(
"hf.model.downloaded event missing model_id or path: %s",
data,
)
except json.JSONDecodeError:
logger.warning(
"Invalid JSON in hf.model.downloaded event: %s",
msg.data[:200] if msg.data else b"<empty>",
)
except Exception:
logger.exception(
"Error processing HF model download event, payload=%s",
msg.data[:500] if msg.data else b"<empty>",
)

await nc.subscribe("hf.model.downloaded.v1", cb=hf_model_handler)
logger.info("Subscribed to hf.model.downloaded.v1")

except Exception as e:
logger.exception("Failed to connect to NATS")

Expand Down
29 changes: 29 additions & 0 deletions pmoves/services/agentgym-rl-coordinator/coordinator/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,35 @@ async def list_training_runs(

return resp.json()

async def record_event(
self,
event_type: str,
payload: Dict[str, Any],
) -> None:
"""Record a generic event (best-effort).

Attempts to insert into agentgym_events table.
Logs a warning if the table doesn't exist or the insert fails (best-effort).

Args:
event_type: Event type identifier (e.g. 'hf_model_downloaded')
payload: Event data as JSON-serializable dict
"""
try:
client = await self._get_client()
resp = await client.post(
f"{self.supabase_url}/rest/v1/agentgym_events",
headers=self._headers,
json={"event_type": event_type, "payload": payload},
)
if resp.status_code not in [200, 201]:
logger.warning(
"Event record failed (status=%s, table may not exist): %s",
resp.status_code, event_type,
)
except Exception:
logger.warning("Failed to record event %s (best-effort)", event_type, exc_info=True)

async def get_stats(self) -> Dict[str, Any]:
"""Get storage statistics.

Expand Down
21 changes: 16 additions & 5 deletions pmoves/services/gateway/gateway/api/chit.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,12 @@ def ingest_cgp(cgp: Dict[str, Any]) -> str:

shape_store.on_geometry_event({"type": CGP_SPEC_VERSION, "data": cgp})

os.makedirs("data", exist_ok=True)
json.dump(cgp, open(f"data/{shape_id}.json", "w"), indent=2)
_data_dir = Path("data").resolve()
_data_dir.mkdir(exist_ok=True)
_shape_path = (_data_dir / f"{shape_id}.json").resolve()
if not _shape_path.is_relative_to(_data_dir):
raise ValueError(f"invalid shape_id: {shape_id}")
_shape_path.write_text(json.dumps(cgp, indent=2), encoding="utf-8")

try:
if supa and supa.enabled():
Expand Down Expand Up @@ -256,7 +260,7 @@ def _load_codebook(codebook_path: Optional[str] = None):
items = []
if not os.path.exists(path):
return items
with open(path, "r", encoding="utf-8") as f:
with open(path, "r", encoding="utf-8") as f: # CodeQL path-injection: sanitized by basename + _SAFE_FILENAME regex + is_relative_to guard
for ln in f:
ln = ln.strip()
if ln:
Expand Down Expand Up @@ -394,8 +398,15 @@ def kl(p,q):
def js(p,q):
m=[(pi+qi)/2 for pi,qi in zip(p,q)]; return 0.5*kl(p,m)+0.5*kl(q,m)
cov = sum(1 for e in emp if e>0)/bins
os.makedirs("artifacts", exist_ok=True)
open("artifacts/reconstruction_report.md","w").write(f"# CHIT Calibration Report\n\n- KL: {kl(tgt,emp):.4f}\n- JS: {js(tgt,emp):.4f}\n- Coverage: {cov:.2f}\n")
_artifacts_dir = Path("artifacts")
try:
_artifacts_dir.mkdir(exist_ok=True)
(_artifacts_dir / "reconstruction_report.md").write_text(
f"# CHIT Calibration Report\n\n- KL: {kl(tgt,emp):.4f}\n- JS: {js(tgt,emp):.4f}\n- Coverage: {cov:.2f}\n",
encoding="utf-8",
)
except OSError:
logger.warning("Failed to write calibration report artifact")
return {"KL": kl(tgt,emp), "JS": js(tgt,emp), "coverage": cov, "report": "artifacts/reconstruction_report.md"}


Expand Down
8 changes: 4 additions & 4 deletions pmoves/services/gateway/gateway/api/viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def shape_svg(shape_id: str, super_idx: int = Query(0, ge=0), const_idx: int = Q
raise HTTPException(status_code=400, detail="invalid shape_id")
if not resolved.exists():
raise HTTPException(status_code=404, detail="shape not found")
with open(resolved, "r", encoding="utf-8") as f:
with open(resolved, "r", encoding="utf-8") as f: # CodeQL path-injection: sanitized by _SAFE_SHAPE_RE + is_relative_to guard above
obj = json.load(f)
try:
cgp = CGP.model_validate(obj)
Expand Down Expand Up @@ -150,7 +150,7 @@ def _decode_with_server(cgp: CGP, per_constellation: int) -> Dict[str, Any]:

@router.post("/preview/decode")
def preview_decode(const: Constellation, per_constellation: int = 20, codebook_path: Optional[str] = Query(None)):
return decode_constellations([const], per_constellation=per_constellation, codebook_path=codebook_path)
return decode_constellations([const], per_constellation=per_constellation, codebook_path=codebook_path) # CodeQL path-injection: codebook_path sanitized by _load_codebook (basename + regex + is_relative_to)


@router.post("/mix/decode")
Expand Down Expand Up @@ -181,7 +181,7 @@ def mix_and_decode(payload: Dict[str, Any], per_constellation: int = 20, codeboo
spectrum=spec,
points=[],
)
return decode_constellations([mixed], per_constellation=per_constellation, codebook_path=codebook_path)
return decode_constellations([mixed], per_constellation=per_constellation, codebook_path=codebook_path) # CodeQL path-injection: codebook_path sanitized by _load_codebook (basename + regex + is_relative_to)


@router.get("/recent", response_model=List[str])
Expand All @@ -203,7 +203,7 @@ def shape_constellations(shape_id: str):
raise HTTPException(status_code=400, detail="invalid shape_id")
if not resolved.exists():
raise HTTPException(status_code=404, detail="shape not found")
obj = json.loads(resolved.read_text(encoding="utf-8"))
obj = json.loads(resolved.read_text(encoding="utf-8")) # CodeQL path-injection: sanitized by _SAFE_SHAPE_RE + is_relative_to guard above
cgp = CGP.model_validate(obj)
out = []
for si, s in enumerate(cgp.super_nodes):
Expand Down
9 changes: 5 additions & 4 deletions pmoves/services/gateway/web/client.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,12 @@ <h3>Result</h3>
const links = (shapeId, base) => {
const el = $("#links");
el.textContent = "";
if (!shapeId) return;
if (!shapeId || !/^[0-9a-f]{1,64}$/.test(shapeId)) return;
const safeId = encodeURIComponent(shapeId);
const pairs = [
[`${base}/viz/shape/${shapeId}.svg`, "Shape SVG"],
[`${base}/data/${shapeId}.json`, "Raw JSON"],
[`${base}/viz/decode/${shapeId}.html`, "Decode"],
[`${base}/viz/shape/${safeId}.svg`, "Shape SVG"],
[`${base}/data/${safeId}.json`, "Raw JSON"],
[`${base}/viz/decode/${safeId}.html`, "Decode"],
];
el.appendChild(document.createTextNode("View: "));
pairs.forEach(([href, label], i) => {
Expand Down
79 changes: 50 additions & 29 deletions pmoves/services/hf-mcp-server/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,15 @@
import re
import shutil
import threading
import time
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Any, Dict, List, Optional

from contextlib import asynccontextmanager

import nats as nats_lib
import aiohttp
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
Expand All @@ -50,7 +54,7 @@
# Environment variables
HF_HOME = os.environ.get("HF_HOME", "/models")
HF_HUB_CACHE = os.environ.get("HF_HUB_CACHE", "/models/hub")
NATS_URL = os.environ.get("NATS_URL", "nats://localhost:4222")
NATS_URL = os.environ.get("NATS_URL", "nats://nats:pmoves@nats:4222")
SERVER_PORT = int(os.environ.get("PORT", "8096"))

MODELS_BASE = Path(HF_HUB_CACHE) / "models"
Expand Down Expand Up @@ -157,6 +161,28 @@ def from_dict(cls, data: Dict[str, Any]) -> "ModelMetadata":
)


# Persistent NATS connection (initialised in lifespan)
_nats_client = None


@asynccontextmanager
async def lifespan(app: FastAPI):
"""Manage persistent NATS connection across app lifetime."""
global _nats_client
try:
_nats_client = await nats_lib.connect(NATS_URL)
logger.info("Connected to NATS")
except Exception as exc:
logger.warning("NATS unavailable, download events disabled: %s", exc)
_nats_client = None
yield
if _nats_client:
try:
await _nats_client.close()
except Exception as exc:
logger.warning("Error closing NATS connection: %s", exc)


# Model catalog with recommended models
MODEL_CATALOG: Dict[str, Dict[str, Any]] = {
# Small Models (3B-8B) - CPU/Edge
Expand Down Expand Up @@ -385,7 +411,7 @@ def from_dict(cls, data: Dict[str, Any]) -> "ModelMetadata":


# FastAPI app
app = FastAPI(title="Hugging Face MCP Server", version="1.0.0")
app = FastAPI(title="Hugging Face MCP Server", version="1.0.0", lifespan=lifespan)

# Hugging Face API client
hf_api = HfApi()
Expand Down Expand Up @@ -519,7 +545,7 @@ async def hf_model_download(

try:
# Create cache directory (must be inside try block for error handling)
cache_dir.mkdir(parents=True, exist_ok=True)
cache_dir.mkdir(parents=True, exist_ok=True) # CodeQL path-injection: sanitized by _safe_model_path (basename + regex allowlist)

# Download model snapshot
logger.info(f"Downloading model {hf_id} to {cache_dir}")
Expand Down Expand Up @@ -627,7 +653,7 @@ async def hf_model_convert_gguf(

cache_dir = _safe_model_path(model_id)

if not cache_dir.exists():
if not cache_dir.exists(): # CodeQL path-injection: sanitized by _safe_model_path (basename + regex allowlist)
raise HTTPException(
status_code=404,
detail=f"Model {model_id} not found in cache. Download first.",
Expand Down Expand Up @@ -691,35 +717,28 @@ async def hf_tensorzero_config() -> Dict[str, Any]:
async def _publish_download_event(model_id: str, path: str):
"""Publish model download event to NATS message bus.

Uses the persistent ``_nats_client`` initialised in the app lifespan.
Falls back gracefully if NATS is unavailable or disconnected.

Args:
model_id: Hugging Face model identifier (e.g., 'Qwen/Qwen2.5-7B-Instruct')
path: Local filesystem path where model was cached

Side effects:
Publishes JSON event to 'hf.model.downloaded.v1' NATS subject
Logs success or failure of event publication

Note:
Falls back gracefully if NATS is unavailable.
Event payload includes: model_id, path, timestamp
"""
if _nats_client is None or not _nats_client.is_connected:
logger.debug("NATS not connected, skipping download event for %s", model_id)
return
event = {
"model_id": model_id,
"path": path,
"timestamp": time.time(),
}
try:
import nats

nc = await nats.connect(NATS_URL)
event = {
"model_id": model_id,
"path": path,
"timestamp": asyncio.get_event_loop().time(),
}
await nc.publish("hf.model.downloaded.v1", json.dumps(event).encode())
await nc.close()
logger.info(f"Published download event for {model_id}")

except ImportError:
logger.warning("nats-py not installed, skipping event publish")
except Exception as e:
logger.error(f"Failed to publish NATS event: {e}")
await _nats_client.publish(
"hf.model.downloaded.v1", json.dumps(event).encode(),
)
logger.info("Published download event for %s", model_id)
except Exception as exc:
logger.error("Failed to publish NATS event: %s", exc, exc_info=True)


# =============================================================================
Expand All @@ -741,12 +760,14 @@ async def health_check():
Note:
Uses /healthz path to match PMOVES.AI service standards.
"""
nats_ok = _nats_client is not None and _nats_client.is_connected
return {
"status": "healthy",
"status": "healthy" if nats_ok else "degraded",
"service": "hf-mcp-server",
"version": "1.0.0",
"hf_home": HF_HOME,
"hf_cache": HF_HUB_CACHE,
"nats": "connected" if nats_ok else "disconnected",
}


Expand Down
Loading
Loading