Files
minerops/backend/app/discovery/repository.py
T
lussierj 8b0d32cd6b security(discovery): enforce Zone isolation and retire unsafe legacy writers
Bind Agent observations, review, enrichment and telemetry to current Zone
and inventory authority. Preserve immutable MAC ownership and historical
snapshots with stale-result rejection and transactional security audits.

Retire unbound direct/range/scheduled discovery and one/bulk legacy summary
writes in both modes. Require reconciled exact inventory identity or
explicit Zone-scoped IP selection for CSV metadata imports.

Independent Blocker #1 application-security re-audit: PASS on tree
cf863aadc9. Evidence: 141 focused PostgreSQL/HTTP
tests passed; Chromium and PowerShell checks passed. Two documented baseline
source assertions remain unchanged. Production deployment is not approved.
2026-10-17 00:45:04 +00:00

1261 lines
48 KiB
Python

"""Transactional discovery authority and append-only evidence, using existing schema.
All writers acquire bounded table locks in the same order. The miners lock also
serializes INSERT phantoms with legacy writers which do not take our advisory
lock. This deliberately favors correctness over fleet-scale write concurrency.
No miner I/O occurs here. No discovered address authorizes a physical command.
"""
from contextlib import contextmanager
from datetime import datetime, timezone, timedelta
from hashlib import sha256
import ipaddress
import json
import re
import math
from uuid import UUID, uuid4, uuid5, NAMESPACE_URL
import jwt
import psycopg
from psycopg.rows import dict_row
from psycopg.types.json import Jsonb
from fastapi import HTTPException
from app.config import settings
from app.agent_zones.repository import default_connection_factory
TTL = timedelta(minutes=15)
MAX_BATCH = 128
def reject(reason, code=409):
raise HTTPException(status_code=code, detail=reason)
def object_body(body):
if not isinstance(body, dict):
reject("discovery_object_required", 422)
return body
def address(value):
try:
text = str(value)
if "%" in text or "/" in text:
raise ValueError()
return str(ipaddress.ip_address(text))
except (ValueError, TypeError):
reject("discovery_invalid_ip", 422)
def mac(value):
if value in (None, ""):
return None
if not isinstance(value, str) or not re.fullmatch(
r"(?:[0-9a-fA-F]{12}|(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}|(?:[0-9a-fA-F]{2}-){5}[0-9a-fA-F]{2})",
value,
):
reject("discovery_invalid_mac", 422)
key = value.replace(":", "").replace("-", "").lower()
if key in {"000000000000", "ffffffffffff"} or int(key[:2], 16) & 1:
reject("discovery_invalid_mac", 422)
return ":".join(key[i : i + 2] for i in range(0, 12, 2)).upper()
def digest(value):
return sha256(
json.dumps(value, sort_keys=True, default=str, separators=(",", ":")).encode()
).hexdigest()
def text(value, limit=255):
if value is None:
return None
if not isinstance(value, str) or len(value) > limit:
reject("discovery_invalid_text", 422)
return value.strip() or None
def number(value, integer=False):
if value is None or value == "":
return None
try:
if isinstance(value, bool):
raise ValueError()
parsed = float(value)
if not math.isfinite(parsed):
raise ValueError()
return int(parsed) if integer else parsed
except (ValueError, TypeError, OverflowError):
reject("discovery_invalid_measurement", 422)
def insert(cur, table, values):
# Identifiers originate exclusively from fixed mappings in this module.
cur.execute(
"INSERT INTO "
+ table
+ "("
+ ",".join(values)
+ ") VALUES ("
+ ",".join(["%s"] * len(values))
+ ")",
tuple(values.values()),
)
def status_snapshot(cur, row, raw, api3=False):
raw = object_body(raw or {})
if api3:
msg = object_body(object_body(raw.get("msg") or {}).get("summary") or {})
names = {
"hash-average": "mhs_av",
"power-realtime": "power_w",
"power-rate": "power_rate_w_per_th",
"chip-temp-avg": "chip_temp_avg_c",
"chip-temp-max": "chip_temp_max_c",
"environment-temperature": "env_temp_c",
}
else:
msg = object_body(raw.get("Msg") or raw.get("msg") or {})
names = {
"MHS av": "mhs_av",
"MHS 1m": "mhs_1m",
"MHS 15m": "mhs_15m",
"HS RT": "hs_rt",
"Power": "power_w",
"Power Rate": "power_rate_w_per_th",
"Env Temp": "env_temp_c",
"Chip Temp Min": "chip_temp_min_c",
"Chip Temp Max": "chip_temp_max_c",
"Chip Temp Avg": "chip_temp_avg_c",
}
values = {
"miner_id": row["id"],
"observed_ip": row["current_ip"],
"raw_summary_json": Jsonb(raw),
}
for key, column in names.items():
if key in msg:
values[column] = number(msg[key])
if api3 and values.get("mhs_av") is not None:
values["mhs_av"] *= 1000000
for key, column in (
{
"fan-speed-in": "fan_speed_in_rpm",
"fan-speed-out": "fan_speed_out_rpm",
"bootup-time": "uptime_seconds",
}
if api3
else {
"Fan Speed In": "fan_speed_in_rpm",
"Fan Speed Out": "fan_speed_out_rpm",
"Uptime": "uptime_seconds",
}
).items():
if key in msg:
values[column] = number(msg[key], True)
insert(cur, "miner_status_snapshots", values)
def pool_snapshot(cur, row, pool, agent_id):
object_body(pool)
values = {
"miner_id": row["id"],
"ip_address": row["current_ip"],
"mac_address": row["current_mac"],
"raw_pool_json": Jsonb(pool),
"agent_id": agent_id,
}
for keys, column in [
(("URL", "url"), "url"),
(("User", "account", "user"), "worker_user"),
(("Status", "status"), "pool_status"),
]:
v = next((pool[k] for k in keys if k in pool), None)
if v is not None:
values[column] = text(v, 2048)
for keys, column in [
(("POOL", "id"), "pool_index"),
(("Accepted", "accepted"), "accepted"),
(("Rejected", "rejected"), "rejected"),
]:
v = next((pool[k] for k in keys if k in pool), None)
if v is not None:
values[column] = number(v, True)
insert(cur, "miner_pool_snapshots", values)
class DiscoveryRepository:
def __init__(self, connection_factory=default_connection_factory):
self.connection_factory = connection_factory
@contextmanager
def transaction(self):
with self.connection_factory() as conn:
with conn.cursor(row_factory=dict_row) as cur:
self.lock(cur)
yield cur
@staticmethod
def lock(cur):
cur.execute("SET LOCAL lock_timeout = '5s'")
cur.execute("SET LOCAL statement_timeout = '15s'")
cur.execute("SELECT pg_advisory_xact_lock(743190031)")
cur.execute(
"LOCK TABLE minerops_site_agents, agent_zones, agent_zone_network_ranges IN SHARE MODE"
)
cur.execute("LOCK TABLE miners IN SHARE ROW EXCLUSIVE MODE")
@staticmethod
def audit(cur, action, entity, value, actor=None, old=None):
cur.execute(
"""INSERT INTO audit_logs
(actor_user_id, action, entity_type, entity_id, old_value_json, new_value_json)
VALUES (%s, %s, 'discovery', %s, %s, %s)""",
(
actor,
action,
entity,
Jsonb(old) if old is not None else None,
Jsonb(value),
),
)
@staticmethod
def context(cur, agent_id):
cur.execute(
"""SELECT a.agent_id, a.site_id, a.agent_zone_id,
a.credential_created_at, a.credential_revoked_at,
z.updated_at AS zone_version, z.is_enabled, s.name AS site_name
FROM minerops_site_agents a JOIN agent_zones z ON z.id=a.agent_zone_id
AND z.site_id=a.site_id JOIN sites s ON s.id=a.site_id
WHERE a.agent_id=%s FOR SHARE OF a,z""",
(agent_id,),
)
row = cur.fetchone()
if not row or not row["is_enabled"] or row["credential_revoked_at"] is not None:
reject("discovery_routing_unavailable")
cur.execute(
"SELECT network::text FROM agent_zone_network_ranges WHERE agent_zone_id=%s ORDER BY network,id FOR SHARE",
(row["agent_zone_id"],),
)
row["networks"] = [v["network"] for v in cur.fetchall()]
if not row["networks"]:
reject("discovery_network_unassigned")
row["authority"] = digest(
{
k: row[k]
for k in (
"agent_id",
"site_id",
"agent_zone_id",
"credential_created_at",
"zone_version",
"networks",
)
}
)
return row
@staticmethod
def in_network(ctx, ip):
ip = address(ip)
if not any(
ipaddress.ip_address(ip) in ipaddress.ip_network(n) for n in ctx["networks"]
):
reject("discovery_address_outside_zone")
@staticmethod
def identity(body, agent_id):
object_body(body)
if body.get("agent_id") not in (None, "", agent_id):
reject("discovery_agent_identity_conflict", 403)
@staticmethod
def fingerprint(row):
return (
digest(
{
k: row.get(k)
for k in (
"id",
"site_id",
"agent_zone_id",
"current_mac",
"current_ip",
"updated_at",
)
}
)
if row
else None
)
def match(self, cur, ctx, item):
"""All supplied identifiers must agree. No global IP search or ownership claim."""
if (
item.get("ip_address")
and item.get("ip")
and address(item["ip_address"]) != address(item["ip"])
):
reject("discovery_identity_conflict")
if (
item.get("mac_address")
and item.get("mac")
and mac(item["mac_address"]) != mac(item["mac"])
):
reject("discovery_identity_conflict")
ip = address(item.get("ip_address") or item.get("ip"))
self.in_network(ctx, ip)
supplied_mac = mac(item.get("mac_address") or item.get("mac"))
candidates = []
if item.get("miner_id"):
try:
mid = UUID(str(item["miner_id"]))
except ValueError:
reject("discovery_invalid_miner_id", 422)
cur.execute(
"SELECT * FROM miners WHERE id=%s AND agent_zone_id=%s FOR UPDATE",
(mid, ctx["agent_zone_id"]),
)
row = cur.fetchone()
if not row:
reject("discovery_identity_conflict")
candidates.append(row)
if supplied_mac:
key = supplied_mac.replace(":", "").lower()
cur.execute(
"""SELECT m.* FROM miner_mac_ownerships o JOIN miners m ON m.id=o.miner_id
WHERE o.normalized_mac=%s FOR UPDATE OF m""",
(key,),
)
row = cur.fetchone()
if row:
if (
row["agent_zone_id"] != ctx["agent_zone_id"]
or mac(row["current_mac"]) != supplied_mac
):
reject("discovery_identity_conflict")
candidates.append(row)
else:
cur.execute(
"""SELECT * FROM miners WHERE agent_zone_id=%s
AND regexp_replace(lower(current_mac),'[^0-9a-f]','','g')=%s FOR UPDATE""",
(ctx["agent_zone_id"], key),
)
rows = cur.fetchall()
if len(rows) > 1:
reject("discovery_identity_ambiguous")
candidates.extend(rows)
serial = text(item.get("miner_sn"))
if serial:
cur.execute(
"SELECT * FROM miners WHERE miner_sn=%s ORDER BY id LIMIT 2 FOR UPDATE",
(serial,),
)
serial_rows = cur.fetchall()
if len(serial_rows) > 1 or any(
r["agent_zone_id"] != ctx["agent_zone_id"] for r in serial_rows
):
reject("discovery_identity_conflict")
candidates.extend(serial_rows)
cur.execute(
"SELECT * FROM miners WHERE agent_zone_id=%s AND host(current_ip)=%s ORDER BY id LIMIT 2 FOR UPDATE",
(ctx["agent_zone_id"], ip),
)
rows = cur.fetchall()
if len(rows) > 1:
reject("discovery_identity_ambiguous")
candidates.extend(rows)
if len({r["id"] for r in candidates}) > 1:
reject("discovery_identity_conflict")
row = candidates[0] if candidates else None
if row and (
str(row["current_ip"]) != ip
or (supplied_mac and mac(row["current_mac"]) != supplied_mac)
or (serial and row["miner_sn"] != serial)
):
reject("discovery_identity_conflict")
return row
@staticmethod
def observation_version(row):
return row["updated_at"].isoformat()
def observed(self, cur, ctx, item, parent=None):
ip = address(item.get("ip_address"))
self.in_network(ctx, ip)
canonical_mac = mac(item.get("mac_address"))
if item.get("discovered_at"):
try:
reported = datetime.fromisoformat(
item["discovered_at"].replace("Z", "+00:00")
)
if (
reported.tzinfo is None
or not -timedelta(minutes=5)
<= datetime.now(timezone.utc) - reported
<= TTL
):
raise ValueError()
except (ValueError, AttributeError, TypeError):
reject("discovery_stale_report", 422)
# Input is kept verbatim; authoritative provenance exists only in audit_logs.
oid = str(
uuid5(
NAMESPACE_URL,
"minerops-discovery:" + ctx["authority"] + ":" + digest(item),
)
)
cur.execute(
"SELECT * FROM minerops_agent_discoveries WHERE id=%s FOR UPDATE", (oid,)
)
existing = cur.fetchone()
if existing:
return existing
status = "new"
reason = None
matched = None
try:
matched = self.match(
cur,
ctx,
{
**item,
"ip_address": ip,
"mac_address": canonical_mac,
"miner_sn": item.get("miner_sn"),
},
)
except HTTPException as exc:
if exc.status_code != 409:
raise
status = "zone_conflict"
reason = exc.detail
if matched:
status = "known"
cur.execute(
"""INSERT INTO minerops_agent_discoveries
(id,agent_id,agent_zone_id,site_name,room_name,ip_address,mac_address,model,hostname,miner_sn,
raw_payload,discovery_status,matched_miner_id)
VALUES (%s,%s,%s,%s,%s,%s::inet,%s,%s,%s,%s,%s,%s,%s) RETURNING *""",
(
oid,
ctx["agent_id"],
ctx["agent_zone_id"],
ctx["site_name"],
text(item.get("room_name")),
ip,
canonical_mac,
text(item.get("model")),
text(item.get("hostname")),
text(item.get("miner_sn")),
Jsonb(item),
status,
matched["id"] if matched else None,
),
)
row = cur.fetchone()
self.audit(
cur,
"discovery.observed",
oid,
{
"agent_id": ctx["agent_id"],
"authority": ctx["authority"],
"payload_hash": digest(item),
"miner_fingerprint": self.fingerprint(matched),
"reason": reason,
"parent_observation_id": parent,
"version": self.observation_version(row),
},
)
return row
def ingest(self, agent_id, body):
self.identity(body, agent_id)
items = body.get("discoveries")
if not isinstance(items, list) or len(items) > MAX_BATCH:
reject("discovery_invalid_batch", 422)
if len({address(object_body(i).get("ip_address")) for i in items}) != len(
items
):
reject("discovery_duplicate_batch_address", 422)
with self.transaction() as cur:
ctx = self.context(cur, agent_id)
cur.execute(
"""SELECT count(*) FROM audit_logs WHERE action='discovery.observed'
AND new_value_json->>'agent_id'=%s AND created_at>clock_timestamp()-interval '1 minute'""",
(agent_id,),
)
if cur.fetchone()["count"] + len(items) > 1024:
reject("discovery_rate_limit", 429)
rows = [self.observed(cur, ctx, object_body(i)) for i in items]
return {
"ok": True,
"received": len(items),
"inserted": len(rows),
"matched_existing": sum(r["discovery_status"] == "known" for r in rows),
"staged_new": sum(r["discovery_status"] == "new" for r in rows),
"conflicts": sum(
r["discovery_status"] == "zone_conflict" for r in rows
),
"agent_zone_id": str(ctx["agent_zone_id"]),
"routing_reason": None,
"errors": [],
"discoveries": [self.present(r) for r in rows],
}
@staticmethod
def present(row):
return {
**{
k: (
str(v)
if isinstance(
v,
(UUID, datetime, ipaddress.IPv4Address, ipaddress.IPv6Address),
)
else v
)
for k, v in row.items()
if k != "raw_payload"
},
"version": row["updated_at"].isoformat(),
}
def listing(self):
with self.connection_factory() as conn, conn.cursor(
row_factory=dict_row
) as cur:
cur.execute(
"SELECT * FROM minerops_agent_discoveries ORDER BY created_at DESC,id LIMIT 100"
)
return {
"ok": True,
"discoveries": [self.present(r) for r in cur.fetchall()],
}
def observation(self, cur, oid, version):
try:
UUID(str(oid))
except (ValueError, TypeError):
reject("discovery_id_required", 422)
cur.execute(
"SELECT * FROM minerops_agent_discoveries WHERE id=%s FOR UPDATE",
(str(oid),),
)
row = cur.fetchone()
if not row:
reject("discovery_not_found", 404)
ctx = self.context(cur, row["agent_id"])
if row["agent_zone_id"] != ctx["agent_zone_id"]:
reject("discovery_stale_authority")
cur.execute(
"SELECT new_value_json,created_at FROM audit_logs WHERE entity_type='discovery' AND entity_id=%s AND action='discovery.observed' ORDER BY created_at LIMIT 2",
(str(oid),),
)
proofs = cur.fetchall()
if len(proofs) != 1:
reject("discovery_unverified_history")
proof = proofs[0]
if proof["new_value_json"].get("authority") != ctx["authority"] or proof[
"new_value_json"
].get("payload_hash") != digest(row["raw_payload"]):
reject("discovery_stale_authority")
if self.observation_version(row) != proof["new_value_json"].get("version"):
cur.execute(
"SELECT new_value_json FROM audit_logs WHERE entity_id=%s AND action='discovery.reviewed' ORDER BY created_at DESC LIMIT 1",
(str(oid),),
)
decision = cur.fetchone()
if not decision:
reject("discovery_unverified_history")
saved = decision["new_value_json"]
response = saved.get("response", {})
expected_state = {"ignore": "ignored", "non_miner": "non_miner"}.get(
saved.get("action")
)
if expected_state is None:
expected_state = (
"known"
if proof["new_value_json"].get("miner_fingerprint")
else "added"
)
if (
response.get("version") != self.observation_version(row)
or response.get("miner_id")
!= (str(row["matched_miner_id"]) if row["matched_miner_id"] else None)
or row["discovery_status"] != expected_state
):
reject("discovery_unverified_history")
self.in_network(ctx, str(row["ip_address"]))
if datetime.now(timezone.utc) - row["created_at"] > TTL:
reject("discovery_stale_observation")
cur.execute(
"""SELECT 1 FROM minerops_agent_discoveries WHERE agent_id=%s AND agent_zone_id=%s
AND ip_address=%s AND id<>%s AND created_at>=%s LIMIT 1""",
(
row["agent_id"],
row["agent_zone_id"],
row["ip_address"],
row["id"],
row["created_at"],
),
)
if cur.fetchone():
reject("discovery_superseded_observation")
return row, ctx, proof["new_value_json"]
def review(self, body, user):
object_body(body)
oid = body.get("discovery_id")
version = body.get("version")
action = body.get("action")
if not isinstance(action, str) or action not in {
"add_to_inventory",
"ignore",
"non_miner",
}:
reject("discovery_invalid_action", 422)
with self.transaction() as cur:
row, ctx, proof = self.observation(cur, oid, version)
cur.execute(
"SELECT new_value_json FROM audit_logs WHERE entity_id=%s AND action='discovery.reviewed' ORDER BY created_at DESC LIMIT 1",
(oid,),
)
prior = cur.fetchone()
if prior:
decision = prior["new_value_json"]
if (
decision["request_version"] == version
and decision["action"] == action
):
return decision["response"]
reject("discovery_already_reviewed")
if self.observation_version(row) != version:
reject("discovery_version_conflict")
if row["discovery_status"] not in {"new", "known"}:
reject("discovery_not_reviewable")
matched = self.match(
cur,
ctx,
{
"ip_address": str(row["ip_address"]),
"mac_address": row["mac_address"],
"miner_sn": row["miner_sn"],
},
)
if self.fingerprint(matched) != proof["miner_fingerprint"]:
reject("discovery_inventory_changed")
mid = matched["id"] if matched else None
state = {"ignore": "ignored", "non_miner": "non_miner"}.get(
action, "known" if mid else "added"
)
if action == "add_to_inventory" and not mid:
if not row["mac_address"]:
reject("discovery_enrichment_required")
cur.execute(
"""INSERT INTO miners (site_id,agent_zone_id,current_mac,current_ip,hostname,miner_sn,model,miner_type,status,notes)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,'discovered','Added by audited observation approval') RETURNING id""",
(
ctx["site_id"],
ctx["agent_zone_id"],
row["mac_address"],
row["ip_address"],
row["hostname"],
row["miner_sn"],
row["model"],
row["model"],
),
)
mid = cur.fetchone()["id"]
cur.execute(
"UPDATE minerops_agent_discoveries SET discovery_status=%s,matched_miner_id=%s,updated_at=clock_timestamp() WHERE id=%s RETURNING *",
(state, mid, oid),
)
updated = cur.fetchone()
response = {
"ok": True,
"action": action,
"discovery_id": oid,
"miner_id": str(mid) if mid else None,
"version": self.observation_version(updated),
"message": "Discovery decision saved.",
}
self.audit(
cur,
"discovery.reviewed",
oid,
{
"agent_id": ctx["agent_id"],
"agent_zone_id": str(ctx["agent_zone_id"]),
"action": action,
"request_version": version,
"response": response,
},
user["id"],
self.present(row),
)
return response
def token(self, ctx, row):
return jwt.encode(
{
"purpose": "minerops.discovery.miner",
"agent_id": ctx["agent_id"],
"authority": ctx["authority"],
"miner_id": str(row["id"]),
"fingerprint": self.fingerprint(row),
"exp": datetime.now(timezone.utc) + TTL,
},
settings.jwt_secret_key,
algorithm="HS256",
)
def known(self, agent_id):
with self.transaction() as cur:
ctx = self.context(cur, agent_id)
cur.execute(
"""SELECT m.*,r.name AS room_name FROM miners m LEFT JOIN rooms r
ON r.id=m.room_id AND r.site_id=m.site_id WHERE m.agent_zone_id=%s
AND m.current_ip IS NOT NULL ORDER BY m.id FOR SHARE OF m""",
(ctx["agent_zone_id"],),
)
rows = cur.fetchall()
result = []
for r in rows:
self.in_network(ctx, str(r["current_ip"]))
result.append(
{
"miner_id": str(r["id"]),
"miner_version": self.token(ctx, r),
"ip_address": str(r["current_ip"]),
"mac_address": r["current_mac"],
"room_name": r["room_name"] or ctx["site_name"],
"model": r["model"],
"status": r["status"],
}
)
return {"ok": True, "count": len(result), "miners": result}
def bound_miner(self, cur, ctx, item):
object_body(item)
try:
proof = jwt.decode(
item.get("miner_version", ""),
settings.jwt_secret_key,
algorithms=["HS256"],
options={"require": ["exp"]},
)
except jwt.PyJWTError:
reject("discovery_miner_version_required")
if (
proof.get("purpose") != "minerops.discovery.miner"
or proof.get("agent_id") != ctx["agent_id"]
or proof.get("authority") != ctx["authority"]
or proof.get("miner_id") != str(item.get("miner_id"))
):
reject("discovery_stale_authority")
row = self.match(cur, ctx, item)
if not row or proof.get("fingerprint") != self.fingerprint(row):
reject("discovery_inventory_changed")
return row
def queue(self, body, user, kind="enrich_match_discovery"):
object_body(body)
with self.transaction() as cur:
if kind == "enrich_match_discovery":
row, ctx, proof = self.observation(
cur, body.get("discovery_id"), body.get("version")
)
if (
self.observation_version(row) != body.get("version")
or row["discovery_status"] != "new"
):
reject("discovery_not_enrichable")
item = {
"ip_address": str(row["ip_address"]),
"mac_address": row["mac_address"],
"miner_sn": row["miner_sn"],
}
matched = self.match(cur, ctx, item)
if self.fingerprint(matched) != proof["miner_fingerprint"]:
reject("discovery_inventory_changed")
payload = {
"discovery_id": row["id"],
"version": body["version"],
"ip_address": str(row["ip_address"]),
}
entity = row["id"]
else:
if not isinstance(body.get("agent_id"), str):
reject("discovery_agent_id_required", 422)
ctx = self.context(cur, body["agent_id"])
payload = {}
entity = None
if kind != "enrich_match_discovery":
try:
payload["request_id"] = str(
UUID(str(body.get("request_id") or uuid4()))
)
except (ValueError, TypeError):
reject("discovery_request_id_invalid", 422)
payload.update(
{
"schema_version": 1,
"agent_zone_id": str(ctx["agent_zone_id"]),
"authority": ctx["authority"],
}
)
# Queue requests are idempotent for the same principal/type/observation version.
qid = str(
uuid5(
NAMESPACE_URL,
"minerops-discovery-command:"
+ kind
+ ":"
+ digest(payload)
+ ":"
+ str(user["id"]),
)
)
cur.execute(
"""INSERT INTO minerops_agent_command_queue (id,agent_id,command_type,command_status,
requested_by_user_id,requested_by_username,request_payload) VALUES (%s,%s,%s,'pending',%s,%s,%s)
ON CONFLICT(id) DO NOTHING RETURNING id""",
(
qid,
ctx["agent_id"],
kind,
str(user["id"]),
user["username"],
Jsonb(payload),
),
)
if cur.fetchone():
self.audit(
cur,
"discovery.command_queued",
entity,
{
"command_id": qid,
"agent_id": ctx["agent_id"],
"type": kind,
"payload": payload,
},
user["id"],
)
return {
"ok": True,
"command_id": qid,
"message": "Scoped read command queued.",
}
def validate_command(self, cur, agent_id, command):
ctx = self.context(cur, agent_id)
p = command["request_payload"]
if (
not isinstance(p, dict)
or p.get("schema_version") != 1
or p.get("agent_zone_id") != str(ctx["agent_zone_id"])
or p.get("authority") != ctx["authority"]
):
reject("discovery_command_stale_authority")
if command["command_type"] == "enrich_match_discovery":
row, _, proof = self.observation(
cur, p.get("discovery_id"), p.get("version")
)
if (
row["agent_id"] != agent_id
or self.observation_version(row) != p["version"]
or row["discovery_status"] != "new"
or str(row["ip_address"]) != p.get("ip_address")
):
reject("discovery_command_stale_observation")
if (
self.fingerprint(
self.match(
cur,
ctx,
{
"ip_address": p["ip_address"],
"mac_address": row["mac_address"],
"miner_sn": row["miner_sn"],
},
)
)
!= proof["miner_fingerprint"]
):
reject("discovery_inventory_changed")
return ctx
def target(self, agent_id, body):
self.identity(body, agent_id)
try:
UUID(str(body.get("command_id")))
except (ValueError, TypeError):
reject("discovery_command_id_required", 422)
with self.transaction() as cur:
cur.execute(
"SELECT * FROM minerops_agent_command_queue WHERE id=%s AND agent_id=%s FOR UPDATE",
(body.get("command_id"), agent_id),
)
command = cur.fetchone()
if (
not command
or command["command_status"] != "claimed"
or command["command_type"] != "enrich_match_discovery"
):
reject("discovery_enrichment_command_required")
self.validate_command(cur, agent_id, command)
p = command["request_payload"]
if p["discovery_id"] != body.get("discovery_id") or p[
"version"
] != body.get("version"):
reject("discovery_command_observation_conflict")
return {"ok": True, "ip_address": p["ip_address"]}
def enrich(self, agent_id, body):
self.identity(body, agent_id)
with self.transaction() as cur:
ctx = self.context(cur, agent_id)
if body.get("discovery_id"):
try:
UUID(str(body.get("command_id")))
except (ValueError, TypeError):
reject("discovery_command_id_required", 422)
row, _, proof = self.observation(
cur, body["discovery_id"], body.get("version")
)
if (
row["agent_id"] != agent_id
or row["discovery_status"] != "new"
or body.get("version") != self.observation_version(row)
):
reject("discovery_not_enrichable")
cur.execute(
"SELECT * FROM minerops_agent_command_queue WHERE id=%s AND agent_id=%s FOR UPDATE",
(body.get("command_id"), agent_id),
)
command = cur.fetchone()
if (
not command
or command["command_status"] != "claimed"
or command["command_type"] != "enrich_match_discovery"
):
reject("discovery_enrichment_command_required")
self.validate_command(cur, agent_id, command)
if command["request_payload"]["discovery_id"] != row["id"]:
reject("discovery_command_observation_conflict")
if address(body.get("ip_address")) != str(row["ip_address"]):
reject("discovery_identity_conflict")
item = {
k: body.get(k)
for k in (
"ip_address",
"mac_address",
"model",
"hostname",
"miner_sn",
)
}
item["parent_observation_id"] = row["id"]
item["command_id"] = command["id"]
child = self.observed(cur, ctx, item, parent=row["id"])
return {
"ok": True,
"matched": bool(child["matched_miner_id"]),
"discovery": self.present(child),
"message": "Enrichment appended; review the new observation.",
}
row = self.bound_miner(cur, ctx, body)
changes = {
k: text(body.get(k))
for k in (
"hostname",
"model",
"miner_type",
"api_version",
"firmware_version",
"platform",
)
}
changes = {k: v for k, v in changes.items() if v is not None}
if changes:
sql = ", ".join(k + "=%s" for k in changes)
cur.execute(
"UPDATE miners SET "
+ sql
+ ",updated_at=clock_timestamp() WHERE id=%s AND agent_zone_id=%s RETURNING *",
(*changes.values(), row["id"], ctx["agent_zone_id"]),
)
updated = cur.fetchone()
else:
updated = row
self.audit(
cur,
"discovery.miner_enriched",
row["id"],
{
"agent_id": agent_id,
"changes": changes,
"raw_version": body.get("raw_version"),
"raw_miner_info": body.get("raw_miner_info"),
},
old={"fingerprint": self.fingerprint(row)},
)
return {
"ok": True,
"matched": True,
"miner": {
k: (
str(v)
if isinstance(
v,
(
UUID,
datetime,
ipaddress.IPv4Address,
ipaddress.IPv6Address,
),
)
else v
)
for k, v in updated.items()
},
"message": "Enrichment saved without changing ownership or history.",
}
def snapshots(self, agent_id, body, kind="miner"):
self.identity(body, agent_id)
items = (
body.get("miners")
if kind == "miner"
else body.get("pool_snapshots", body.get("snapshots"))
)
if not isinstance(items, list) or len(items) > MAX_BATCH:
reject("discovery_invalid_batch", 422)
with self.transaction() as cur:
ctx = self.context(cur, agent_id)
for item in items:
row = self.bound_miner(cur, ctx, item)
if kind == "miner":
if type(item.get("online")) is not bool:
reject("discovery_invalid_online", 422)
status = "online" if item["online"] else "offline"
cur.execute(
"UPDATE miners SET status=%s,last_seen_at=CASE WHEN %s THEN clock_timestamp() ELSE last_seen_at END,updated_at=clock_timestamp() WHERE id=%s AND agent_zone_id=%s",
(status, item["online"], row["id"], ctx["agent_zone_id"]),
)
summary = object_body(
object_body(item.get("responses") or {}).get("summary") or {}
)
raw = summary.get("json")
if raw is None:
try:
raw = json.loads(summary.get("raw") or "{}")
except (ValueError, TypeError):
reject("discovery_invalid_summary", 422)
status_snapshot(cur, row, raw)
else:
pools = item.get("pools", [])
if not isinstance(pools, list) or len(pools) > 32:
reject("discovery_invalid_pools", 422)
for p in pools:
object_body(p)
pool_snapshot(cur, row, p, agent_id)
self.audit(
cur,
"discovery." + kind + "_snapshot",
row["id"],
{"agent_id": agent_id, "payload": item},
)
if kind == "miner":
cur.execute(
"""INSERT INTO minerops_agent_snapshots(agent_id,agent_zone_id,site_name,miner_count,payload)
VALUES (%s,%s,%s,%s,%s) RETURNING id,received_at""",
(
agent_id,
ctx["agent_zone_id"],
ctx["site_name"],
len(items),
Jsonb(body),
),
)
receipt = cur.fetchone()
else:
receipt = {}
return {
"ok": True,
"applied": len(items),
"inserted": len(items),
"matched": len(items),
"miner_count": len(items),
"snapshot_id": receipt.get("id"),
"message": "Scoped telemetry appended.",
}
def api3(self, agent_id, body):
self.identity(body, agent_id)
items = body.get("items")
if not isinstance(items, list) or len(items) > MAX_BATCH:
reject("discovery_invalid_batch", 422)
with self.transaction() as cur:
ctx = self.context(cur, agent_id)
counts = dict(
processed=len(items),
matched=len(items),
api3_ok=0,
api3_failed=0,
miner_updates=0,
status_rows=0,
pool_rows=0,
hashboard_rows=0,
)
for item in items:
object_body(item)
device = object_body(item.get("device_info") or {})
message = object_body(device.get("msg") or {})
network = object_body(message.get("network") or {})
if network.get("ip") and address(network["ip"]) != address(
item.get("ip")
):
reject("discovery_identity_conflict")
if network.get("mac") and mac(network["mac"]) != mac(item.get("mac")):
reject("discovery_identity_conflict")
row = self.bound_miner(cur, ctx, item)
if type(item.get("api3_supported")) is not bool:
reject("discovery_invalid_api3_supported", 422)
miner = object_body(message.get("miner") or {})
system = object_body(message.get("system") or {})
power = object_body(message.get("power") or {})
reported_serial = text(miner.get("miner-sn"))
if (
reported_serial
and row["miner_sn"]
and reported_serial != row["miner_sn"]
):
reject("discovery_identity_conflict")
metadata = {
"model": text(miner.get("type")),
"miner_type": text(miner.get("type")),
"api_version": text(system.get("api")),
"api3_api_version": text(system.get("api")),
"api3_api_switch": text(system.get("apiswitch")),
"firmware_version": text(system.get("fwversion")),
"platform": text(system.get("platform")),
}
metadata = {k: v for k, v in metadata.items() if v is not None}
if metadata:
assignments = [
k
+ (
"=COALESCE(NULLIF(" + k + ",''),%s)"
if k in {"model", "miner_type"}
else "=%s"
)
for k in metadata
]
cur.execute(
"UPDATE miners SET "
+ ",".join(assignments)
+ " WHERE id=%s AND agent_zone_id=%s",
(*metadata.values(), row["id"], ctx["agent_zone_id"]),
)
counts["api3_ok" if item["api3_supported"] else "api3_failed"] += 1
counts["miner_updates"] += 1
cur.execute(
"UPDATE miners SET api3_supported=%s,api3_last_seen_at=clock_timestamp(),api3_last_error=%s,updated_at=clock_timestamp() WHERE id=%s AND agent_zone_id=%s",
(
item["api3_supported"],
text(item.get("api3_error")),
row["id"],
ctx["agent_zone_id"],
),
)
cur.execute(
"""INSERT INTO miner_api3_enrichment_snapshots
(miner_id,agent_id,site_name,ip_address,mac_address,api_supported,api_error,device_info,miner_setting,miner_status,fan_setting,custom_data,api_version,api_switch,model,miner_sn,power_sn,psu_model)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)""",
(
row["id"],
agent_id,
ctx["site_name"],
row["current_ip"],
row["current_mac"],
item["api3_supported"],
text(item.get("api3_error")),
*[
Jsonb(item.get(k))
for k in (
"device_info",
"miner_setting",
"miner_status",
"fan_setting",
"custom_data",
)
],
metadata.get("api_version"),
metadata.get("api3_api_switch"),
metadata.get("model"),
reported_serial,
text(power.get("sn")),
text(power.get("model")),
),
)
status = object_body(item.get("miner_status") or {})
status_msg = object_body(status.get("msg") or {})
summary = object_body(status_msg.get("summary") or {})
if item["api3_supported"] and summary:
status_snapshot(cur, row, status, api3=True)
counts["status_rows"] += 1
pools = status_msg.get("pools") or []
boards = status_msg.get("edevs") or []
if (
not isinstance(pools, list)
or len(pools) > 32
or not isinstance(boards, list)
or len(boards) > 32
):
reject("discovery_invalid_telemetry_batch", 422)
for pool in pools:
pool_snapshot(cur, row, pool, agent_id)
counts["pool_rows"] += 1
for board in boards:
object_body(board)
values = {
"miner_id": row["id"],
"ip_address": row["current_ip"],
"mac_address": row["current_mac"],
"raw_hashboard_json": Jsonb(board),
}
for key, column in {
"slot": "slot",
"hash-average": "hash_average_ths",
"factory-hash": "factory_hash_ths",
"freq": "freq",
"effective-chips": "effective_chips",
"chip-temp-min": "chip_temp_min_c",
"chip-temp-avg": "chip_temp_avg_c",
"chip-temp-max": "chip_temp_max_c",
}.items():
if key in board:
values[column] = number(
board[key], column in {"slot", "effective_chips"}
)
insert(cur, "miner_api3_hashboard_snapshots", values)
counts["hashboard_rows"] += 1
self.audit(
cur,
"discovery.api3_snapshot",
row["id"],
{"agent_id": agent_id, "payload": item},
)
return {
"data": {"ok": True, **counts},
"meta": {},
"error": None,
}