Files
hermes-agent/tests
Teknium a6e934e0fd feat(gateway): 'decline' unauthorized-DM behavior — one-time polite decline instead of pairing code
Port from qwibitai/nanoclaw#3260: adds a third unauthorized_dm_behavior
option, 'decline'. Instead of replying with a pairing code (pair) or
staying silent (ignore), the gateway sends one short, polite decline to
the unknown sender, then stays silent toward that sender for 24 hours.

- gateway/config.py: accept 'decline' in the normalizer; new
  unauthorized_dm_decline_message for custom decline text (round-trips
  through to_dict/from_dict).
- gateway/pairing.py: persisted decline stamps (_declined.json) on
  PairingStore with has_recent_decline/record_decline; stamps are
  pruned on write and recorded BEFORE delivery so a send failure can't
  become a decline storm (nanoclaw's stamp-first pattern).
- gateway/run.py: decline branch in the unauthorized-sender path;
  groups still always silently ignore.
- docs: security.md + configuration.md updated.

Adapted from TypeScript (NanoClaw's pending_sender_approvals 'decline:'
stamp rows) to Hermes' existing PairingStore JSON persistence; the
owner-FYI half of nanoclaw's flow is intentionally not ported — Hermes
logs the unauthorized attempt, and pairing remains the owner-visible
grant path.

Rebase onto the decomposed gateway (salvage, #88028):
- The unauthorized-sender path moved from gateway/run.py to
  gateway/run_inbound.py::_hm_admit_event; the decline branch is a sibling
  helper _hm_send_unauthorized_decline next to _hm_offer_pairing_code.
- gateway/config.py now validates the enum via _normalize_choice; the
  accepted set is the module constant UNAUTHORIZED_DM_BEHAVIORS (used by
  both from_dict and get_unauthorized_dm_behavior so a per-platform
  `extra.unauthorized_dm_behavior: decline` is honoured too). The default
  decline text lives in config as DEFAULT_UNAUTHORIZED_DM_DECLINE_MESSAGE.
- Tests trimmed from 5 to 2 invariant tests (send-once-then-silent through
  the real inbound path; config round-trip + real PairingStore stamp
  lifecycle with a patched clock instead of rewriting the JSON file).
2026-09-15 03:44:13 -07:00
..
…