fabro/lib
David Julia 3819b6e19f
fix(slack): make per-button action_id unique to satisfy Slack's invalid_blocks check
Slack rejects chat.postMessage with `invalid_blocks` when two block elements
in the same message share an `action_id`. The interview block builder
previously stamped every button with the constant `"interview.answer"`, so
any multi-button question (yes/no, confirmation, multiple_choice with N>1)
came back with:

  {
    "ok": false,
    "error": "invalid_blocks",
    "errors": [
      "`action_id` \"interview.answer\" already exists [json-pointer:/blocks/1/elements/1/action_id]"
    ]
  }

SlackService::handle_event in fabro-server swallows the error via
`if let Ok(posted) = self.client.post_message(...)`, so this failure was
invisible at INFO log levels.

Outbound: append a per-button suffix to the action_id:
  - YesNo/Confirmation -> `interview.answer.yes` / `interview.answer.no`
  - MultipleChoice     -> `interview.answer.<index>`

The selected option key is still carried in the button `value` payload, so
the action_id only needs to be unique for routing — not semantic. Using the
option index (not the raw key) keeps suffixes short, ASCII, and dodges
Slack's 255-char action_id cap when author-supplied option keys are long.

Inbound: parse_interaction now matches both the legacy exact-prefix shape
(for in-flight messages posted by older builds) and the suffixed shape via a
pre-computed `ANSWER_ACTION_ID_PREFIX_DOT` constant (avoids per-parse
format! allocation). A lookalike like `interview.answers.yes` is correctly
rejected.

Tests:
- 4 new parse_interaction tests (suffixed yes/no, suffixed multi-choice,
  legacy exact prefix, lookalike rejection)
- Updated existing block-builder tests to assert per-button uniqueness and
  prove the option key still survives in the value payload
- Sync test that ANSWER_ACTION_ID_PREFIX_DOT matches the canonical prefix
- Updated one dispatch.rs and one connection.rs fixture to the suffixed
  shape for clarity; left one legacy fixture in each to document the
  backwards-compatibility branch
- 74/74 fabro-slack tests pass (was 69/69)
- `cargo +nightly-2026-04-14 fmt --check --all` and
  `cargo +nightly-2026-04-14 clippy -p fabro-slack --all-targets -- -D warnings`
  both clean

Verified end-to-end against a real Slack workspace: a multiple_choice
"Approve Plan" gate now renders correctly in the configured channel with
distinct [A] Approve / [R] Revise buttons.
2026-05-12 21:01:16 -06:00
..
crates fix(slack): make per-button action_id unique to satisfy Slack's invalid_blocks check 2026-05-12 21:01:16 -06:00
packages/fabro-api-client feat(model): support open provider catalog data (#245) 2026-05-12 15:42:49 -04:00