feat(delegate-job): add subscriber auto-reconnect (FW-01) + HMAC-SHA256 event signing (FW-05)

FW-01: job_subscriber.py now has on_disconnect callback (5-arg paho v2
  signature), reconnect_delay_set(1,16) for exponential backoff, and
  with_retry-wrapped initial connect (5 attempts). paho loop_start()
  handles auto-reconnect internally.

FW-05: publish_event.py signs payloads with HMAC-SHA256 using auth_token
  as key (replaces plaintext token in wire). mqtt_common.py adds
  verify_hmac() helper using hmac.compare_digest (timing-safe).
  job_subscriber.py validates incoming events via verify_hmac.
  PoC mode (auth_token=None) skips verification — backward compatible.

Reviewed by agy-existing (PASS) and claude-existing (FAIL: on_disconnect
  4-arg signature → fixed to 5-arg matching paho v2 CallbackAPIVersion).
This commit is contained in:
2026-06-21 06:31:39 +00:00
parent 4cea11438a
commit 3677e4aace
3 changed files with 37 additions and 7 deletions
@@ -22,6 +22,8 @@ Usage:
from __future__ import annotations
import argparse
import hashlib
import hmac
import json
import logging
import sys
@@ -79,7 +81,11 @@ def build_payload(
# registry stores the per-job token in `auth_token`; only include it on the
# wire when set so the public broker (no auth) doesn't leak anything.
if auth_token:
payload["data"]["auth_token"] = auth_token
sign_payload = {k: v for k, v in payload.items() if k != "data"}
sign_payload["data"] = {k: v for k, v in payload.get("data", {}).items() if k != "hmac_sig"}
msg = json.dumps(sign_payload, sort_keys=True, separators=(",", ":")).encode()
sig = hmac.new(auth_token.encode(), msg, hashlib.sha256).hexdigest()
payload["data"]["hmac_sig"] = sig
return payload