0046 — Slack replies on held-message cards reach prod through Smith's socket relay

The problem

propflowai parks an outbound message it may not send on its own as an EscalationHold. Examples are a review-mode email, a Supervised text, and a staff-forced rerun. It posts a "Held for review" card into the property's Slack channel, and the card says to reply approve or reject: why. propflowai #9948 (2026-09-19) built POST /api/slack/events to act on that reply. #11250 (2026-09-23) added the staff-identity check.

Slack never called that route. The Slack app has run in Socket Mode since agent-smith #2729 (2026-06-30). A Socket Mode app delivers every event over a websocket to Smith on the mini and ignores an Event Subscriptions Request URL. The route's own header listed "point the Request URL at this route" as a config step "for Fede to do". That step could not be done, and the route was described as safe to deploy dark. Zero traffic therefore looked like the expected state, not a fault.

Measured 2026-10-07 on hold eschold_36b1d058… (Northridge):

Why it was built this way. Two gaps stacked, and each hid the other. Zero traffic was expected, so nobody noticed that even a delivered event would have hit the middleware's 401. The 9-19 PR's header records a repo-wide grep that found "NO existing Slack inbound path anywhere in this codebase". The grep was true of propflowai and false of the system. The inbound path lived in the agent-smith repo, and this catalog (SYSTEMS.md) had no entry for Smith's Socket Mode receiver, so checking the catalog would not have caught it either. The fair-housing hold flow, built 2026-08-30, had already solved the same problem the right way, on the mini (clara_hold_review). Nobody connected the two.

Options considered

The decision

  1. One path: Smith's socket listener relays a human thread reply to POST /api/slack/events when its thread root was posted by our bot. It does this ahead of the channel gate, because the cards live in property channels Smith does not own. There is no flag.
  2. Authentication is a relay secret, not Slack's. The relay signs with SLACK_RELAY_SIGNING_SECRET using Slack's own v0 HMAC and 5-minute freshness scheme, in x-propflow-relay-* headers. The route still accepts a real Slack signature, so leaving Socket Mode later needs no code change. The secret is stored in SSM at /propflow/prod/SLACK_RELAY_SIGNING_SECRET, in Vercel production, and in propflowai/.env.local on the mini.
  3. Prod decides what is a hold reply. The route answers hold: <id> when the thread is a pending hold. The relay then consumes the reply and prod posts the outcome line. When the thread is not a pending hold, Smith's normal routing runs unchanged. The mini never parses card prose to decide this. It uses the card's /admin/escalation-holds/ link only to decide whether a relay failure deserves a line in the thread.
  4. What is not relayed: a reply that @-mentions Smith, because the person is asking Smith something and the brain answers it, and a fair-housing hold card, which clara_hold_review keeps.
  5. Failure is said, not swallowed. If prod cannot be reached on a visible hold card, one thread line says nothing was sent and points at the card's PropFlow link.

Consequences