Skip to content

feat(automation): post_comment action + Redis SETNX idempotency#68

Merged
Musiker15 merged 1 commit into
mainfrom
feat/automation-post-comment
May 28, 2026
Merged

feat(automation): post_comment action + Redis SETNX idempotency#68
Musiker15 merged 1 commit into
mainfrom
feat/automation-post-comment

Conversation

@Musiker15
Copy link
Copy Markdown
Member

Summary

Lands the post_comment automation action — the v1-scoped action that ADR 0010 deferred until the Redis idempotency claim was in place. Closes the third automation item from the v0.2.0-beta Deferred list.

⚠️ Crypto-relevant — please double-check. The automation-posted comment is encrypted client-side under the BoardKey, same as a human comment.

How it works

  1. A rule like When card moved to Done → post comment "Closed on {{date}}.". The body template lives in enc_rule (server never sees it).
  2. On trigger, the executor renders the template ({{date}} / {{datetime}} via split/join — no regex, no ReDoS, fixed token set), encrypts the rendered text under the BoardKey (bound to comment:new), and POSTs to /api/cards/[id]/comments.
  3. Idempotency: the POST carries idempotencyKey = <ruleId>:<eventToken>. The server does SETNX automation:claim:<key> with a 60 s TTL. A lost race (racing tabs / retry) returns { deduped: true } — no duplicate comment. Redis hiccup → fail open (post the comment; a missed dedup beats a dropped comment).

Changes

  • DSL (dsl.ts): post_comment added to ACTION_TYPES + ActionPostComment schema (body 1..2000 chars).
  • Evaluator (evaluator.ts): evaluateAll now takes IdentifiedRule[] and tags each action with ruleId; new eventToken(event) builds the deterministic dedup discriminator.
  • Executor (executor.ts): new ExecutorContext (encryptor + eventToken), post_comment case, pure expandTemplate helper.
  • Comment POST route: optional idempotencyKey (charset-locked), Redis SETNX claim.
  • Rule builder (automation-panel.tsx): action picker entry + comment textarea with a token hint; summary preview.

Security notes

  • The comment is E2EE; the server stores only ciphertext, identical to a human comment.
  • idempotencyKey charset is locked to [A-Za-z0-9:_-] so it can't smuggle anything into Redis. A forged key can at most suppress an automation comment for 60 s on a board the caller already has write access to — no cross-tenant impact (RBAC still runs in Comments.create).
  • No-cascade invariant preserved: the executor posts via the REST API, never through the drawer, so a post_comment action can't re-fire a comment_added rule.

Tests

  • automation-executor.test.tsexpandTemplate (5 cases incl. unknown-token passthrough)
  • automation-dsl.test.tspost_comment accept / empty-reject / over-cap-reject; action-list count bumped to 4
  • automation-evaluator.test.tseventToken determinism + evaluateAll ruleId threading

39 automation cases green. pnpm typecheck + pnpm lint clean.

Test plan

  • CI green
  • Manual: rule "When moved to Done → post comment Closed on {{date}}", move a card, confirm one comment appears with today's date
  • Manual: trigger the same rule twice quickly (or in two tabs) → still only one comment (SETNX dedup)
  • Manual: confirm the posted comment is readable (decrypts) and the server DB row is ciphertext

🤖 Generated with Claude Code

Lands the last v1-scoped automation action that was deferred pending the
idempotency claim from ADR 0010. ⚠️ Crypto-relevant.

- DSL: `post_comment` added to ACTION_TYPES + a body-template schema
  (1..2000 chars). The body stays inside enc_rule.
- Executor: renders the template (`{{date}}` / `{{datetime}}` via
  split/join — no regex/ReDoS), encrypts under the BoardKey bound to
  `comment:new` (E2EE like a human comment), and POSTs with an
  idempotency key. New ExecutorContext carries the encryptor + event
  token so the module stays libsodium-free and server-reusable.
- Evaluator: `evaluateAll` now takes IdentifiedRule[] and tags each
  action with its ruleId; new `eventToken(event)` builds the
  deterministic dedup discriminator.
- Comment POST route: optional `idempotencyKey` (locked-down charset);
  server does `SETNX automation:claim:<key>` PX=60s; lost race →
  `{ deduped: true }` (no duplicate). Redis error → fail open.
- Rule builder: action picker + comment textarea with token hint.

No-cascade invariant still holds: the executor posts via the REST API,
not the drawer, so a post_comment can't re-trigger comment_added.

Tests: executor expandTemplate (5), DSL post_comment (3), evaluator
eventToken + ruleId threading. 39 automation cases green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Signed-off-by: Moritz Kohm <moritz.kohm@gmail.com>
Signed-off-by: Musiker15 <info@musiker15.de>
@Musiker15 Musiker15 merged commit 0025fa1 into main May 28, 2026
8 checks passed
@Musiker15 Musiker15 deleted the feat/automation-post-comment branch May 28, 2026 15:36
@Musiker15 Musiker15 mentioned this pull request May 28, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant