Amin commited on
Commit
f8166a7
·
1 Parent(s): 5cf9f4e

fix: auto-provision audit signing secret, log real futures_analyze failure reasons

Browse files
hermes_overlay/tools/futures_dashboard_api.py CHANGED
@@ -13,6 +13,7 @@ import calendar
13
  from decimal import Decimal, InvalidOperation
14
  import hashlib
15
  import json
 
16
  import math
17
  import os
18
  import re
@@ -60,6 +61,7 @@ from trading.rate_limit import (
60
  )
61
 
62
  router = APIRouter()
 
63
  _paper_cycle_lock = asyncio.Lock()
64
  _reconciliation_supervisor = None
65
  _reconciliation_task: asyncio.Task | None = None
@@ -1556,9 +1558,22 @@ async def futures_analyze(payload: _CycleRequest, http_request: Request) -> JSON
1556
  except PlanVersionConflict as exc:
1557
  raise HTTPException(status_code=409, detail="Stale analysis version; refresh and analyze again") from exc
1558
  except PlanValidationError as exc:
 
 
 
 
 
 
 
 
 
1559
  raise HTTPException(status_code=422, detail="Trade plan validation failed") from exc
1560
  except Exception as exc:
1561
  public_message = "Futures analysis failed"
 
 
 
 
1562
  _dash_state.record_analysis_failure(public_message, _sanitize_text(type(exc).__name__))
1563
  raise HTTPException(status_code=503, detail=public_message) from exc
1564
 
 
13
  from decimal import Decimal, InvalidOperation
14
  import hashlib
15
  import json
16
+ import logging
17
  import math
18
  import os
19
  import re
 
61
  )
62
 
63
  router = APIRouter()
64
+ logger = logging.getLogger("hermesface.futures_dashboard_api")
65
  _paper_cycle_lock = asyncio.Lock()
66
  _reconciliation_supervisor = None
67
  _reconciliation_task: asyncio.Task | None = None
 
1558
  except PlanVersionConflict as exc:
1559
  raise HTTPException(status_code=409, detail="Stale analysis version; refresh and analyze again") from exc
1560
  except PlanValidationError as exc:
1561
+ # The client only ever sees the generic 422 message (avoids leaking
1562
+ # internal decision/geometry details), but the actual reason is
1563
+ # otherwise invisible anywhere - not even a WARNING was logged. Log
1564
+ # it server-side only so a real bug (vs. an expected fail-closed
1565
+ # block on insufficient/stale market data) is diagnosable.
1566
+ logger.warning(
1567
+ "futures_analyze: plan validation failed for %s (owner=%s): %s",
1568
+ normalized.ds4, owner_id, exc,
1569
+ )
1570
  raise HTTPException(status_code=422, detail="Trade plan validation failed") from exc
1571
  except Exception as exc:
1572
  public_message = "Futures analysis failed"
1573
+ logger.warning(
1574
+ "futures_analyze: analysis cycle failed for %s (owner=%s): %s: %s",
1575
+ normalized.ds4, owner_id, type(exc).__name__, exc,
1576
+ )
1577
  _dash_state.record_analysis_failure(public_message, _sanitize_text(type(exc).__name__))
1578
  raise HTTPException(status_code=503, detail=public_message) from exc
1579
 
scripts/sync_hf.py CHANGED
@@ -474,6 +474,38 @@ class HermesFullSync:
474
  print("[SYNC] Admin login wired: HERMES_DASHBOARD_BASIC_AUTH_USERNAME/PASSWORD "
475
  "set from HERMES_ADMIN_USERNAME/HERMES_ADMIN_PASSWORD")
476
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
477
  def _normalize_runtime_config(self):
478
  """Normalize resource and auxiliary settings for a free HF Space.
479
 
@@ -985,6 +1017,7 @@ class HermesFullSync:
985
  return None
986
 
987
  self._configure_admin_auth()
 
988
  env = os.environ.copy()
989
  env["HERMES_HOME"] = str(HERMES_DATA)
990
  env["GATEWAY_ALLOW_ALL_USERS"] = "true"
 
474
  print("[SYNC] Admin login wired: HERMES_DASHBOARD_BASIC_AUTH_USERNAME/PASSWORD "
475
  "set from HERMES_ADMIN_USERNAME/HERMES_ADMIN_PASSWORD")
476
 
477
+ def _configure_audit_signing_secret(self):
478
+ """Persist and wire HERMES_AUDIT_SIGNING_SECRET for the trading overlay.
479
+
480
+ trading.domain.audit_repo requires this env var to write tamper-evident
481
+ (HMAC-signed, hash-chained) audit rows. Without it every trade-cycle
482
+ write falls back to an unsigned "legacy" row and logs a WARNING -
483
+ every ~15s in practice. HERMES_ENV isn't "production" here so it never
484
+ blocks anything, it just silently degrades audit integrity forever.
485
+ Generate a secret once and persist it exactly like the dashboard auth
486
+ secret, so it's stable across restarts. An operator-provided
487
+ HERMES_AUDIT_SIGNING_SECRET Space secret always wins.
488
+ """
489
+ if os.environ.get("HERMES_AUDIT_SIGNING_SECRET", "").strip():
490
+ return
491
+ secret_path = HERMES_DATA / ".audit_signing_secret"
492
+ try:
493
+ if secret_path.exists():
494
+ secret = secret_path.read_text().strip()
495
+ else:
496
+ secret = secrets.token_hex(32) # 64 hex chars, well over the 32-char minimum
497
+ secret_path.parent.mkdir(parents=True, exist_ok=True)
498
+ secret_path.write_text(secret)
499
+ try:
500
+ os.chmod(secret_path, 0o600)
501
+ except Exception:
502
+ pass
503
+ if secret:
504
+ os.environ["HERMES_AUDIT_SIGNING_SECRET"] = secret
505
+ print("[SYNC] Audit integrity signing configured (persisted secret)")
506
+ except Exception as e:
507
+ print(f"[SYNC] WARNING: could not configure audit signing secret: {e}")
508
+
509
  def _normalize_runtime_config(self):
510
  """Normalize resource and auxiliary settings for a free HF Space.
511
 
 
1017
  return None
1018
 
1019
  self._configure_admin_auth()
1020
+ self._configure_audit_signing_secret()
1021
  env = os.environ.copy()
1022
  env["HERMES_HOME"] = str(HERMES_DATA)
1023
  env["GATEWAY_ALLOW_ALL_USERS"] = "true"