From f15ba2b3ca50d49d749602316173c584cbd25ef9 Mon Sep 17 00:00:00 2001 From: Jesse Merhi <79823012+jesse-merhi@users.noreply.github.com> Date: Tue, 25 Aug 2026 11:41:32 +1000 Subject: [PATCH 1/2] Deliver ClawSweeper Endor alerts to Discord --- .env.example | 2 + README.md | 6 + drizzle/0013_many_chameleon.sql | 13 + drizzle/meta/0013_snapshot.json | 2111 ++++++++++++++++++++++++++++ drizzle/meta/_journal.json | 7 + src/clawsweeperEndor/api.ts | 387 +++++ src/clawsweeperEndor/deliveries.ts | 80 ++ src/db/schema.ts | 22 + src/index.ts | 7 + src/runtime/env.ts | 2 + tests/clawsweeperEndorApi.test.ts | 309 ++++ 11 files changed, 2946 insertions(+) create mode 100644 drizzle/0013_many_chameleon.sql create mode 100644 drizzle/meta/0013_snapshot.json create mode 100644 src/clawsweeperEndor/api.ts create mode 100644 src/clawsweeperEndor/deliveries.ts create mode 100644 tests/clawsweeperEndorApi.test.ts diff --git a/.env.example b/.env.example index 1677211..a53bb68 100644 --- a/.env.example +++ b/.env.example @@ -4,6 +4,8 @@ DISCORD_CLIENT_ID= DISCORD_PUBLIC_KEY= FORWARDER_PUBLIC_KEY= DISCORD_BOT_TOKEN= +CLAWSWEEPER_ENDOR_DISCORD_CHANNEL_ID= +CLAWSWEEPER_HERMIT_TOKEN= DISCORD_DEV_GUILDS= ANSWER_OVERFLOW_API_KEY= HELPER_THREAD_WELCOME_PARENT_ID= diff --git a/README.md b/README.md index 8767141..22b18f8 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,8 @@ DEPLOY_SECRET= DISCORD_CLIENT_ID= DISCORD_PUBLIC_KEY= DISCORD_BOT_TOKEN= +CLAWSWEEPER_ENDOR_DISCORD_CHANNEL_ID= +CLAWSWEEPER_HERMIT_TOKEN= ``` Optional: @@ -126,3 +128,7 @@ bunx wrangler secret put FORWARDER_PUBLIC_KEY - Answer Overflow base URL is hardcoded to `https://www.answeroverflow.com`. - Helper thread monitor runs via Worker cron (`wrangler.jsonc` `triggers.crons`). - The old Cloudflare Gateway Durable Object path is not the active gateway setup. +- `POST /api/clawsweeper/endor-remediation/reviewed` accepts authenticated, + structured Endor review results from ClawSweeper and posts them only to + `CLAWSWEEPER_ENDOR_DISCORD_CHANNEL_ID`. The shared bearer secret is + `CLAWSWEEPER_HERMIT_TOKEN`. diff --git a/drizzle/0013_many_chameleon.sql b/drizzle/0013_many_chameleon.sql new file mode 100644 index 0000000..ec9e0a5 --- /dev/null +++ b/drizzle/0013_many_chameleon.sql @@ -0,0 +1,13 @@ +CREATE TABLE `endor_notification_deliveries` ( + `idempotency_key` text PRIMARY KEY NOT NULL, + `payload_digest` text NOT NULL, + `nonce` text NOT NULL, + `status` text DEFAULT 'pending' NOT NULL, + `channel_id` text NOT NULL, + `message_id` text, + `created_at` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL, + `updated_at` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL, + `delivered_at` text +); +--> statement-breakpoint +CREATE INDEX `idx_endor_notification_deliveries_status` ON `endor_notification_deliveries` (`status`); \ No newline at end of file diff --git a/drizzle/meta/0013_snapshot.json b/drizzle/meta/0013_snapshot.json new file mode 100644 index 0000000..ef66644 --- /dev/null +++ b/drizzle/meta/0013_snapshot.json @@ -0,0 +1,2111 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "83e1904a-1067-407d-b18f-b29f87389ad3", + "prevId": "30731fe5-aff9-413c-b7c6-cbe4ac734ebc", + "tables": { + "action_cooldown_events": { + "name": "action_cooldown_events", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "interaction_id": { + "name": "interaction_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action_kind": { + "name": "action_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "guild_id": { + "name": "guild_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "actor_expires_at": { + "name": "actor_expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_expires_at": { + "name": "target_expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel_expires_at": { + "name": "channel_expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + } + }, + "indexes": { + "action_cooldown_events_interaction_id_unique": { + "name": "action_cooldown_events_interaction_id_unique", + "columns": [ + "interaction_id" + ], + "isUnique": true + }, + "idx_action_cooldowns_actor": { + "name": "idx_action_cooldowns_actor", + "columns": [ + "guild_id", + "actor_id", + "actor_expires_at" + ], + "isUnique": false + }, + "idx_action_cooldowns_target": { + "name": "idx_action_cooldowns_target", + "columns": [ + "guild_id", + "target_id", + "target_expires_at" + ], + "isUnique": false + }, + "idx_action_cooldowns_channel": { + "name": "idx_action_cooldowns_channel", + "columns": [ + "guild_id", + "channel_id", + "channel_expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "claim_requests": { + "name": "claim_requests", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "guild_id": { + "name": "guild_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'submitted'" + }, + "github_username": { + "name": "github_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "merged_pr_count": { + "name": "merged_pr_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "review_message_id": { + "name": "review_message_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "review_thread_id": { + "name": "review_thread_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "decided_at": { + "name": "decided_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "decided_by_id": { + "name": "decided_by_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "decision_reason": { + "name": "decision_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + } + }, + "indexes": { + "idx_claim_requests_guild_user": { + "name": "idx_claim_requests_guild_user", + "columns": [ + "guild_id", + "user_id" + ], + "isUnique": true + }, + "idx_claim_requests_user_id": { + "name": "idx_claim_requests_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_claim_requests_status": { + "name": "idx_claim_requests_status", + "columns": [ + "status" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "clawhub_content_rights_cases": { + "name": "clawhub_content_rights_cases", + "columns": { + "case_id": { + "name": "case_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "form_submission_id": { + "name": "form_submission_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'submitted'" + }, + "requester_name": { + "name": "requester_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "organization": { + "name": "organization", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "clawhub_urls": { + "name": "clawhub_urls", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "explanation": { + "name": "explanation", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + } + }, + "indexes": { + "clawhub_content_rights_cases_form_submission_id_unique": { + "name": "clawhub_content_rights_cases_form_submission_id_unique", + "columns": [ + "form_submission_id" + ], + "isUnique": true + }, + "idx_clawhub_content_rights_cases_status": { + "name": "idx_clawhub_content_rights_cases_status", + "columns": [ + "status" + ], + "isUnique": false + }, + "idx_clawhub_content_rights_cases_email": { + "name": "idx_clawhub_content_rights_cases_email", + "columns": [ + "email" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "clawhub_content_rights_events": { + "name": "clawhub_content_rights_events", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "case_id": { + "name": "case_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "actor": { + "name": "actor", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + } + }, + "indexes": { + "idx_clawhub_content_rights_events_case_id": { + "name": "idx_clawhub_content_rights_events_case_id", + "columns": [ + "case_id" + ], + "isUnique": false + }, + "idx_clawhub_content_rights_events_event_type": { + "name": "idx_clawhub_content_rights_events_event_type", + "columns": [ + "event_type" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "clawhub_content_rights_files": { + "name": "clawhub_content_rights_files", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "case_id": { + "name": "case_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "original_name": { + "name": "original_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + } + }, + "indexes": { + "clawhub_content_rights_files_object_key_unique": { + "name": "clawhub_content_rights_files_object_key_unique", + "columns": [ + "object_key" + ], + "isUnique": true + }, + "idx_clawhub_content_rights_files_case_id": { + "name": "idx_clawhub_content_rights_files_case_id", + "columns": [ + "case_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "endor_notification_deliveries": { + "name": "endor_notification_deliveries", + "columns": { + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "payload_digest": { + "name": "payload_digest", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "nonce": { + "name": "nonce", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + }, + "delivered_at": { + "name": "delivered_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_endor_notification_deliveries_status": { + "name": "idx_endor_notification_deliveries_status", + "columns": [ + "status" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "form_submissions": { + "name": "form_submissions", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "form_id": { + "name": "form_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'submitted'" + }, + "auth_provider": { + "name": "auth_provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "applicant_id": { + "name": "applicant_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "applicant_username": { + "name": "applicant_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "review_channel_id": { + "name": "review_channel_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "review_message_id": { + "name": "review_message_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "review_thread_id": { + "name": "review_thread_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "decided_at": { + "name": "decided_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "decided_by_id": { + "name": "decided_by_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "decision_reason": { + "name": "decision_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "action_result": { + "name": "action_result", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + } + }, + "indexes": { + "idx_form_submissions_form_id": { + "name": "idx_form_submissions_form_id", + "columns": [ + "form_id" + ], + "isUnique": false + }, + "idx_form_submissions_status": { + "name": "idx_form_submissions_status", + "columns": [ + "status" + ], + "isUnique": false + }, + "idx_form_submissions_applicant_id": { + "name": "idx_form_submissions_applicant_id", + "columns": [ + "applicant_id" + ], + "isUnique": false + }, + "idx_form_submissions_review_message_id": { + "name": "idx_form_submissions_review_message_id", + "columns": [ + "review_message_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "helper_events": { + "name": "helper_events", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'helper_command'" + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "message_count": { + "name": "message_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "event_time": { + "name": "event_time", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "invoked_by_id": { + "name": "invoked_by_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "invoked_by_username": { + "name": "invoked_by_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "invoked_by_global_name": { + "name": "invoked_by_global_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "received_at": { + "name": "received_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + }, + "raw_payload": { + "name": "raw_payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_helper_events_event_time": { + "name": "idx_helper_events_event_time", + "columns": [ + "event_time" + ], + "isUnique": false + }, + "idx_helper_events_command": { + "name": "idx_helper_events_command", + "columns": [ + "command" + ], + "isUnique": false + }, + "idx_helper_events_thread_id": { + "name": "idx_helper_events_thread_id", + "columns": [ + "thread_id" + ], + "isUnique": false + }, + "idx_helper_events_invoked_by_id": { + "name": "idx_helper_events_invoked_by_id", + "columns": [ + "invoked_by_id" + ], + "isUnique": false + }, + "idx_helper_events_event_type": { + "name": "idx_helper_events_event_type", + "columns": [ + "event_type" + ], + "isUnique": false + }, + "idx_helper_events_thread_time": { + "name": "idx_helper_events_thread_time", + "columns": [ + "thread_id", + "event_time" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "keyValue": { + "name": "keyValue", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updatedAt": { + "name": "updatedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "lobster_encounters": { + "name": "lobster_encounters", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "interaction_id": { + "name": "interaction_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cooldown_event_id": { + "name": "cooldown_event_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "guild_id": { + "name": "guild_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_is_bot": { + "name": "target_is_bot", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "taxonomy_snapshot_id": { + "name": "taxonomy_snapshot_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "species_aphia_id": { + "name": "species_aphia_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "species_accepted_name": { + "name": "species_accepted_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "species_display_name": { + "name": "species_display_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "species_family": { + "name": "species_family", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scene_id": { + "name": "scene_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "asset_url": { + "name": "asset_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "asset_checksum": { + "name": "asset_checksum", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "headline": { + "name": "headline", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "narrative": { + "name": "narrative", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "metrics_json": { + "name": "metrics_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "accessibility_description": { + "name": "accessibility_description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "publication_status": { + "name": "publication_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "publication_failure": { + "name": "publication_failure", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "publication_failed_at": { + "name": "publication_failed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "message_bound_at": { + "name": "message_bound_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "response_status": { + "name": "response_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "response_type": { + "name": "response_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "response_actor_id": { + "name": "response_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "responded_at": { + "name": "responded_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "response_result_json": { + "name": "response_result_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "counter_actor_id": { + "name": "counter_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "counter_target_id": { + "name": "counter_target_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "counter_scene_id": { + "name": "counter_scene_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "counter_asset_url": { + "name": "counter_asset_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "counter_asset_checksum": { + "name": "counter_asset_checksum", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "counter_headline": { + "name": "counter_headline", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "counter_narrative": { + "name": "counter_narrative", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "counter_metrics_json": { + "name": "counter_metrics_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "counter_accessibility_description": { + "name": "counter_accessibility_description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + } + }, + "indexes": { + "lobster_encounters_interaction_id_unique": { + "name": "lobster_encounters_interaction_id_unique", + "columns": [ + "interaction_id" + ], + "isUnique": true + }, + "lobster_encounters_cooldown_event_id_unique": { + "name": "lobster_encounters_cooldown_event_id_unique", + "columns": [ + "cooldown_event_id" + ], + "isUnique": true + }, + "idx_lobster_encounters_message": { + "name": "idx_lobster_encounters_message", + "columns": [ + "guild_id", + "channel_id", + "message_id" + ], + "isUnique": false + }, + "idx_lobster_encounters_species": { + "name": "idx_lobster_encounters_species", + "columns": [ + "species_aphia_id" + ], + "isUnique": false + }, + "idx_lobster_encounters_publication": { + "name": "idx_lobster_encounters_publication", + "columns": [ + "publication_status" + ], + "isUnique": false + }, + "idx_lobster_encounters_response": { + "name": "idx_lobster_encounters_response", + "columns": [ + "response_status" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "nomination_approvals": { + "name": "nomination_approvals", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "nomination_id": { + "name": "nomination_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "approver_id": { + "name": "approver_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "vote_choice": { + "name": "vote_choice", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'approve'" + }, + "mutation_id": { + "name": "mutation_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + } + }, + "indexes": { + "idx_nomination_approvals_nomination_approver": { + "name": "idx_nomination_approvals_nomination_approver", + "columns": [ + "nomination_id", + "approver_id" + ], + "isUnique": true + }, + "idx_nomination_approvals_nomination_id": { + "name": "idx_nomination_approvals_nomination_id", + "columns": [ + "nomination_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "nominations": { + "name": "nominations", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "guild_id": { + "name": "guild_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "nominee_id": { + "name": "nominee_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "nominator_id": { + "name": "nominator_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'No reason provided.'" + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "target_role_id": { + "name": "target_role_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "required_approvals": { + "name": "required_approvals", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'submitted'" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completed_at": { + "name": "completed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "desired_card_revision": { + "name": "desired_card_revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "synced_card_revision": { + "name": "synced_card_revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "card_sync_started_at": { + "name": "card_sync_started_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "card_sync_failure_count": { + "name": "card_sync_failure_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "grant_started_at": { + "name": "grant_started_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "grant_failure_count": { + "name": "grant_failure_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + } + }, + "indexes": { + "idx_nominations_active_unique": { + "name": "idx_nominations_active_unique", + "columns": [ + "guild_id", + "nominee_id", + "target_role_id" + ], + "isUnique": true, + "where": "\"nominations\".\"status\" in ('submitted', 'granting')" + }, + "idx_nominations_status": { + "name": "idx_nominations_status", + "columns": [ + "status" + ], + "isUnique": false + }, + "idx_nominations_card_sync": { + "name": "idx_nominations_card_sync", + "columns": [ + "desired_card_revision", + "synced_card_revision" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "reddit_moderation_contexts": { + "name": "reddit_moderation_contexts", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "subreddit": { + "name": "subreddit", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'moderated'" + }, + "unaction": { + "name": "unaction", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'reviewed'" + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "moderator": { + "name": "moderator", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "banned_at": { + "name": "banned_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "raw_payload": { + "name": "raw_payload", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + } + }, + "indexes": { + "idx_reddit_moderation_contexts_subreddit_username": { + "name": "idx_reddit_moderation_contexts_subreddit_username", + "columns": [ + "subreddit", + "username" + ], + "isUnique": true + }, + "idx_reddit_moderation_contexts_username": { + "name": "idx_reddit_moderation_contexts_username", + "columns": [ + "username" + ], + "isUnique": false + }, + "idx_reddit_moderation_contexts_action": { + "name": "idx_reddit_moderation_contexts_action", + "columns": [ + "action" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "slap_events": { + "name": "slap_events", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "interaction_id": { + "name": "interaction_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "guild_id": { + "name": "guild_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_is_bot": { + "name": "target_is_bot", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "fish_slug": { + "name": "fish_slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fish_name": { + "name": "fish_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rarity": { + "name": "rarity", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "headline": { + "name": "headline", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "narrative": { + "name": "narrative", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "impact": { + "name": "impact", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dignity_remaining": { + "name": "dignity_remaining", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fish_condition": { + "name": "fish_condition", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "counter_actor_id": { + "name": "counter_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "counter_target_id": { + "name": "counter_target_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "counter_fish_slug": { + "name": "counter_fish_slug", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "counter_fish_name": { + "name": "counter_fish_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "counter_rarity": { + "name": "counter_rarity", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "counter_outcome": { + "name": "counter_outcome", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "counter_headline": { + "name": "counter_headline", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "counter_narrative": { + "name": "counter_narrative", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "counter_impact": { + "name": "counter_impact", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "counter_dignity_remaining": { + "name": "counter_dignity_remaining", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "counter_fish_condition": { + "name": "counter_fish_condition", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "counter_image_url": { + "name": "counter_image_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "countered_at": { + "name": "countered_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "appealed_by_id": { + "name": "appealed_by_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "appeal_ruling": { + "name": "appeal_ruling", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "appealed_at": { + "name": "appealed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + } + }, + "indexes": { + "slap_events_interaction_id_unique": { + "name": "slap_events_interaction_id_unique", + "columns": [ + "interaction_id" + ], + "isUnique": true + }, + "idx_slap_events_actor_cooldown": { + "name": "idx_slap_events_actor_cooldown", + "columns": [ + "guild_id", + "actor_id", + "created_at" + ], + "isUnique": false + }, + "idx_slap_events_target_cooldown": { + "name": "idx_slap_events_target_cooldown", + "columns": [ + "guild_id", + "target_id", + "created_at" + ], + "isUnique": false + }, + "idx_slap_events_channel_cooldown": { + "name": "idx_slap_events_channel_cooldown", + "columns": [ + "guild_id", + "channel_id", + "created_at" + ], + "isUnique": false + }, + "idx_slap_events_message": { + "name": "idx_slap_events_message", + "columns": [ + "guild_id", + "channel_id", + "message_id" + ], + "isUnique": false + }, + "idx_slap_events_outcome": { + "name": "idx_slap_events_outcome", + "columns": [ + "outcome" + ], + "isUnique": false + }, + "idx_slap_events_rarity": { + "name": "idx_slap_events_rarity", + "columns": [ + "rarity" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tracked_threads": { + "name": "tracked_threads", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_checked": { + "name": "last_checked", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "solved": { + "name": "solved", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "warning_level": { + "name": "warning_level", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "closed": { + "name": "closed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_message_count": { + "name": "last_message_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "received_at": { + "name": "received_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + }, + "raw_payload": { + "name": "raw_payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "tracked_threads_thread_id_unique": { + "name": "tracked_threads_thread_id_unique", + "columns": [ + "thread_id" + ], + "isUnique": true + }, + "idx_tracked_threads_solved": { + "name": "idx_tracked_threads_solved", + "columns": [ + "solved" + ], + "isUnique": false + }, + "idx_tracked_threads_last_checked": { + "name": "idx_tracked_threads_last_checked", + "columns": [ + "last_checked" + ], + "isUnique": false + }, + "idx_tracked_threads_received_at": { + "name": "idx_tracked_threads_received_at", + "columns": [ + "received_at" + ], + "isUnique": false + }, + "idx_tracked_threads_closed": { + "name": "idx_tracked_threads_closed", + "columns": [ + "closed" + ], + "isUnique": false + }, + "idx_tracked_threads_warning_level": { + "name": "idx_tracked_threads_warning_level", + "columns": [ + "warning_level" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 218dae8..d5a47ef 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -92,6 +92,13 @@ "when": 1784999050821, "tag": "0012_remove_form_review_locks", "breakpoints": true + }, + { + "idx": 13, + "version": "6", + "when": 1787549665613, + "tag": "0013_many_chameleon", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/clawsweeperEndor/api.ts b/src/clawsweeperEndor/api.ts new file mode 100644 index 0000000..49cae67 --- /dev/null +++ b/src/clawsweeperEndor/api.ts @@ -0,0 +1,387 @@ +import { + Container, + Routes, + Separator, + TextDisplay, + serializePayload, + type Client, + type MessagePayloadObject +} from "@buape/carbon" +import { getRuntimeEnv } from "../runtime/env.js" +import { + claimEndorDelivery, + markEndorDeliveryDelivered, + type ClaimEndorDeliveryInput, + type ClaimEndorDeliveryResult +} from "./deliveries.js" + +export type EndorRemediationNotification = { + version: 1 + type: "clawsweeper.endor_remediation_reviewed" + repo: string + prNumber: number + prUrl: string + title: string + findingSummary: string + reviewedHeadSha: string + outcome: "ready" | "needs_attention" | "unknown" + reviewSummary: string + reviewUrl: string + checks: { + state: "passing" | "pending" | "failing" | "unknown" + total: number | null + summary: string + } + mergeState: "clean" | "blocked" | "behind" | "unstable" | "unknown" + cycles: number + cleanStreak: number + idempotencyKey: string +} + +type EndorDiscordMessage = { + components: Container[] + allowedMentions: NonNullable +} + +type EndorApiDependencies = { + token: string + channelId: string + now?: () => Date + claimDelivery: (input: ClaimEndorDeliveryInput) => Promise + markDelivered: ( + idempotencyKey: string, + payloadDigest: string, + messageId: string + ) => Promise + sendMessage: ( + channelId: string, + message: EndorDiscordMessage, + nonce: string + ) => Promise<{ messageId: string }> +} + +const apiPath = "/api/clawsweeper/endor-remediation/reviewed" +const pendingRetryWindowMs = 10 * 60 * 1000 +const githubOrigin = "https://github.com" + +const jsonResponse = (value: unknown, status = 200) => + new Response(JSON.stringify(value), { + status, + headers: { "content-type": "application/json" } + }) + +const readRecord = (value: unknown): Record | null => + value !== null && typeof value === "object" && !Array.isArray(value) + ? value as Record + : null + +const requiredString = (value: unknown, maximumLength: number) => + typeof value === "string" && value.trim() && value.trim().length <= maximumLength + ? value.trim() + : null + +const integerInRange = (value: unknown, minimum: number, maximum: number) => + typeof value === "number" && + Number.isInteger(value) && + value >= minimum && + value <= maximum + ? value + : null + +const oneOf = (value: unknown, values: readonly T[]): T | null => + typeof value === "string" && values.includes(value as T) ? value as T : null + +const exactGithubUrl = (value: unknown, expectedPath: string, fragmentPattern?: RegExp) => { + const raw = requiredString(value, 2048) + if (!raw) { + return null + } + try { + const url = new URL(raw) + if ( + url.origin !== githubOrigin || + url.pathname.toLowerCase() !== expectedPath.toLowerCase() || + url.search || + (fragmentPattern ? !fragmentPattern.test(url.hash) : Boolean(url.hash)) + ) { + return null + } + return url.toString() + } catch { + return null + } +} + +export const parseEndorRemediationNotification = ( + value: unknown +): EndorRemediationNotification | null => { + const record = readRecord(value) + const checks = readRecord(record?.checks) + if (!record || !checks) { + return null + } + + const repo = requiredString(record.repo, 200) + const prNumber = integerInRange(record.prNumber, 1, Number.MAX_SAFE_INTEGER) + const title = requiredString(record.title, 256) + const findingSummary = requiredString(record.findingSummary, 1000) + const reviewedHeadSha = requiredString(record.reviewedHeadSha, 40) + const outcome = oneOf(record.outcome, ["ready", "needs_attention", "unknown"] as const) + const reviewSummary = requiredString(record.reviewSummary, 1000) + const checkState = oneOf(checks.state, [ + "passing", + "pending", + "failing", + "unknown" + ] as const) + const checkSummary = requiredString(checks.summary, 1000) + const checkTotal = checks.total === null + ? null + : integerInRange(checks.total, 0, 10000) + const mergeState = oneOf(record.mergeState, [ + "clean", + "blocked", + "behind", + "unstable", + "unknown" + ] as const) + const cycles = integerInRange(record.cycles, 1, 6) + const cleanStreak = integerInRange(record.cleanStreak, 0, 3) + const idempotencyKey = requiredString(record.idempotencyKey, 512) + + if ( + record.version !== 1 || + record.type !== "clawsweeper.endor_remediation_reviewed" || + !repo || + !repo.match(/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/) || + !prNumber || + !title || + !findingSummary || + !reviewedHeadSha?.match(/^[0-9a-f]{40}$/i) || + !outcome || + !reviewSummary || + !checkState || + !checkSummary || + (checkTotal === null && checks.total !== null) || + !mergeState || + !cycles || + cleanStreak === null || + !idempotencyKey + ) { + return null + } + + const expectedKey = [ + "clawsweeper.endor_remediation_reviewed", + repo, + String(prNumber), + reviewedHeadSha, + outcome + ].join(":") + const expectedPullPath = `/${repo}/pull/${prNumber}` + const prUrl = exactGithubUrl(record.prUrl, expectedPullPath) + const reviewUrl = exactGithubUrl( + record.reviewUrl, + expectedPullPath, + /^#issuecomment-\d+$/ + ) + if (idempotencyKey !== expectedKey || !prUrl || !reviewUrl) { + return null + } + + return { + version: 1, + type: "clawsweeper.endor_remediation_reviewed", + repo, + prNumber, + prUrl, + title, + findingSummary, + reviewedHeadSha, + outcome, + reviewSummary, + reviewUrl, + checks: { state: checkState, total: checkTotal, summary: checkSummary }, + mergeState, + cycles, + cleanStreak, + idempotencyKey + } +} + +const markdownText = (value: string) => + value + .replaceAll("\\", "\\\\") + .replace(/([`*_{}\[\]()<>#+\-.!|])/g, "\\$1") + .replace(/\s+/g, " ") + .trim() + +const markdownUrl = (value: string) => `<${value.replaceAll(">", "%3E")}>` + +export const buildEndorRemediationContainer = ( + notification: EndorRemediationNotification +) => { + const status = notification.outcome === "ready" + ? "READY" + : notification.outcome === "unknown" + ? "UNKNOWN" + : "NEEDS ATTENTION" + const accentColor = notification.outcome === "ready" + ? "#22c55e" + : notification.outcome === "unknown" + ? "#f2c94c" + : "#ef4444" + + return new Container( + [ + new TextDisplay(`### ๐Ÿ” Endor remediation reviewed โ€” ${status}`), + new TextDisplay( + `**Repository:** ${markdownText(notification.repo)}\n**Pull request:** #${notification.prNumber} โ€” ${markdownText(notification.title)}` + ), + new Separator({ divider: true, spacing: "small" }), + new TextDisplay(`**Finding summary:** ${markdownText(notification.findingSummary)}`), + new TextDisplay( + `**ClawSweeper review:** ${notification.cleanStreak}/3 clean after ${notification.cycles}/6 cycles โ€” ${markdownText(notification.reviewSummary)}` + ), + new TextDisplay( + `**CI checks:** ${notification.checks.state} โ€” ${markdownText(notification.checks.summary)}\n**GitHub merge state:** ${notification.mergeState}\n**Reviewed head:** \`${notification.reviewedHeadSha}\`` + ), + new TextDisplay( + `[Open review](${markdownUrl(notification.reviewUrl)}) ยท [Open PR](${markdownUrl(notification.prUrl)})` + ), + new TextDisplay( + "To request the existing guarded merge path, open the PR on GitHub and comment `@clawsweeper automerge`." + ) + ], + { accentColor } + ) +} + +const sha256 = async (value: string) => { + const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value)) + return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("") +} + +export const endorDeliveryIdentity = async (notification: EndorRemediationNotification) => { + const payloadDigest = await sha256(JSON.stringify(notification)) + return { + payloadDigest, + nonce: `endor-${(await sha256(notification.idempotencyKey)).slice(0, 19)}` + } +} + +const pendingIsRetryable = (createdAt: string, now: Date) => { + const createdAtMs = Date.parse(createdAt) + return Number.isFinite(createdAtMs) && now.getTime() - createdAtMs <= pendingRetryWindowMs +} + +export const handleClawSweeperEndorApi = async ( + request: Request, + dependencies: EndorApiDependencies +): Promise => { + const url = new URL(request.url) + if (url.pathname !== apiPath) { + return null + } + const bearerToken = request.headers.get("authorization")?.match(/^Bearer\s+(.+)$/i)?.[1] ?? "" + if (!dependencies.token || bearerToken !== dependencies.token) { + return jsonResponse({ error: "Unauthorized" }, 401) + } + if (request.method !== "POST") { + return jsonResponse({ error: "Method not allowed" }, 405) + } + if (!dependencies.channelId) { + return jsonResponse({ error: "Endor notification channel is not configured" }, 503) + } + + let body: unknown + try { + body = await request.json() + } catch { + return jsonResponse({ error: "Invalid JSON" }, 400) + } + const notification = parseEndorRemediationNotification(body) + if (!notification) { + return jsonResponse({ error: "Invalid Endor remediation notification" }, 400) + } + if (request.headers.get("idempotency-key") !== notification.idempotencyKey) { + return jsonResponse({ error: "Idempotency key does not match the notification" }, 400) + } + + const identity = await endorDeliveryIdentity(notification) + const claim = await dependencies.claimDelivery({ + idempotencyKey: notification.idempotencyKey, + payloadDigest: identity.payloadDigest, + nonce: identity.nonce, + channelId: dependencies.channelId + }) + if (claim.state === "conflict") { + return jsonResponse({ error: "Idempotency key was already used for different content" }, 409) + } + if (claim.delivery.status === "delivered" && claim.delivery.messageId) { + return jsonResponse({ + ok: true, + delivered: true, + duplicate: true, + messageId: claim.delivery.messageId + }) + } + if ( + claim.state === "existing" && + !pendingIsRetryable(claim.delivery.createdAt, dependencies.now?.() ?? new Date()) + ) { + return jsonResponse({ + error: "Delivery status is ambiguous; inspect the configured Discord channel before retrying" + }, 409) + } + + const message: EndorDiscordMessage = { + components: [buildEndorRemediationContainer(notification)], + allowedMentions: { roles: [], users: [] } + } + const sent = await dependencies.sendMessage( + dependencies.channelId, + message, + identity.nonce + ) + await dependencies.markDelivered( + notification.idempotencyKey, + identity.payloadDigest, + sent.messageId + ) + + return jsonResponse({ + ok: true, + delivered: true, + duplicate: claim.state === "existing", + messageId: sent.messageId + }) +} + +export const handleClawSweeperEndorApiRequest = ( + request: Request, + client: Client +): Promise => { + const env = getRuntimeEnv() + return handleClawSweeperEndorApi(request, { + token: env.CLAWSWEEPER_HERMIT_TOKEN?.trim() ?? "", + channelId: env.CLAWSWEEPER_ENDOR_DISCORD_CHANNEL_ID?.trim() ?? "", + claimDelivery: claimEndorDelivery, + markDelivered: markEndorDeliveryDelivered, + sendMessage: async (channelId, message, nonce) => { + const response = await client.rest.post(Routes.channelMessages(channelId), { + body: { + ...serializePayload(message), + nonce, + enforce_nonce: true + } + }) + const record = readRecord(response) + const messageId = requiredString(record?.id, 100) + if (!messageId) { + throw new Error("Discord did not return an Endor notification message ID") + } + return { messageId } + } + }) +} diff --git a/src/clawsweeperEndor/deliveries.ts b/src/clawsweeperEndor/deliveries.ts new file mode 100644 index 0000000..9d6477b --- /dev/null +++ b/src/clawsweeperEndor/deliveries.ts @@ -0,0 +1,80 @@ +import { and, eq, sql } from "drizzle-orm" +import { getPrimaryDb } from "../db.js" +import { + endorNotificationDeliveries, + type EndorNotificationDelivery +} from "../db/schema.js" + +export type ClaimEndorDeliveryInput = { + idempotencyKey: string + payloadDigest: string + nonce: string + channelId: string +} + +export type ClaimEndorDeliveryResult = + | { state: "claimed"; delivery: EndorNotificationDelivery } + | { state: "existing"; delivery: EndorNotificationDelivery } + | { state: "conflict"; delivery: EndorNotificationDelivery } + +const now = sql`strftime('%Y-%m-%dT%H:%M:%fZ', 'now')` + +export const claimEndorDelivery = async ( + input: ClaimEndorDeliveryInput +): Promise => { + const [created] = await getPrimaryDb() + .insert(endorNotificationDeliveries) + .values(input) + .onConflictDoNothing({ target: endorNotificationDeliveries.idempotencyKey }) + .returning() + + if (created) { + return { state: "claimed", delivery: created } + } + + const [existing] = await getPrimaryDb() + .select() + .from(endorNotificationDeliveries) + .where(eq(endorNotificationDeliveries.idempotencyKey, input.idempotencyKey)) + .limit(1) + + if (!existing) { + throw new Error("Endor delivery claim disappeared after an idempotency conflict") + } + + if ( + existing.payloadDigest !== input.payloadDigest || + existing.nonce !== input.nonce || + existing.channelId !== input.channelId + ) { + return { state: "conflict", delivery: existing } + } + + return { state: "existing", delivery: existing } +} + +export const markEndorDeliveryDelivered = async ( + idempotencyKey: string, + payloadDigest: string, + messageId: string +): Promise => { + const [updated] = await getPrimaryDb() + .update(endorNotificationDeliveries) + .set({ + status: "delivered", + messageId, + deliveredAt: now, + updatedAt: now + }) + .where( + and( + eq(endorNotificationDeliveries.idempotencyKey, idempotencyKey), + eq(endorNotificationDeliveries.payloadDigest, payloadDigest) + ) + ) + .returning({ idempotencyKey: endorNotificationDeliveries.idempotencyKey }) + + if (!updated) { + throw new Error("Endor delivery receipt could not be persisted") + } +} diff --git a/src/db/schema.ts b/src/db/schema.ts index f203a5c..807c5db 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -19,6 +19,26 @@ export const keyValue = sqliteTable("keyValue", { .$onUpdateFn(() => new Date()) }) +export const endorNotificationDeliveries = sqliteTable( + "endor_notification_deliveries", + { + idempotencyKey: text("idempotency_key").primaryKey(), + payloadDigest: text("payload_digest").notNull(), + nonce: text().notNull(), + status: text().notNull().default("pending"), + channelId: text("channel_id").notNull(), + messageId: text("message_id"), + createdAt: text("created_at") + .notNull() + .default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`), + updatedAt: text("updated_at") + .notNull() + .default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`), + deliveredAt: text("delivered_at") + }, + (table) => [index("idx_endor_notification_deliveries_status").on(table.status)] +) + export const helperEvents = sqliteTable( "helper_events", { @@ -447,6 +467,8 @@ export const lobsterEncounters = sqliteTable( export type KeyValue = typeof keyValue.$inferSelect export type NewKeyValue = typeof keyValue.$inferInsert +export type EndorNotificationDelivery = typeof endorNotificationDeliveries.$inferSelect +export type NewEndorNotificationDelivery = typeof endorNotificationDeliveries.$inferInsert export type HelperEvent = typeof helperEvents.$inferSelect export type NewHelperEvent = typeof helperEvents.$inferInsert export type TrackedThread = typeof trackedThreads.$inferSelect diff --git a/src/index.ts b/src/index.ts index bc0092c..0850508 100644 --- a/src/index.ts +++ b/src/index.ts @@ -47,6 +47,7 @@ import { runNominationCardSyncRecovery } from "./services/nominationCardSync.js" import { runThreadLengthMonitor } from "./services/threadLengthMonitor.js" import { handleContentRightsApiRequest } from "./clawhubContentRights/api.js" import { handlePublisherAbuseDigestApiRequest } from "./clawhubPublisherAbuse/api.js" +import { handleClawSweeperEndorApiRequest } from "./clawsweeperEndor/api.js" import { handleLobsterDossierRequest } from "./lobsterDossiers/server.js" export const client = new Client( @@ -152,6 +153,10 @@ export default { if (publisherAbuseDigestResponse) { return publisherAbuseDigestResponse } + const clawsweeperEndorResponse = await handleClawSweeperEndorApiRequest(request, client) + if (clawsweeperEndorResponse) { + return clawsweeperEndorResponse + } const formsResponse = await handleFormsRequest(request, client) if (formsResponse) { return formsResponse @@ -205,6 +210,8 @@ declare global { CLAWHUB_NOREPLY_FROM?: string; CLAWHUB_HERMIT_TOKEN?: string; CLAWHUB_SITE_URL?: string; + CLAWSWEEPER_ENDOR_DISCORD_CHANNEL_ID?: string; + CLAWSWEEPER_HERMIT_TOKEN?: string; } } } diff --git a/src/runtime/env.ts b/src/runtime/env.ts index 9117fec..586e4bd 100644 --- a/src/runtime/env.ts +++ b/src/runtime/env.ts @@ -8,6 +8,8 @@ declare global { CLAWHUB_BAN_APPEALS_TOKEN: string CLAWHUB_HERMIT_TOKEN?: string CLAWHUB_SITE_URL?: string + CLAWSWEEPER_ENDOR_DISCORD_CHANNEL_ID?: string + CLAWSWEEPER_HERMIT_TOKEN?: string DEPLOY_SECRET: string DISCORD_BOT_TOKEN: string DISCORD_CLIENT_ID: string diff --git a/tests/clawsweeperEndorApi.test.ts b/tests/clawsweeperEndorApi.test.ts new file mode 100644 index 0000000..8ab01c4 --- /dev/null +++ b/tests/clawsweeperEndorApi.test.ts @@ -0,0 +1,309 @@ +import { describe, expect, it } from "bun:test" +import type { Client } from "@buape/carbon" +import { + buildEndorRemediationContainer, + endorDeliveryIdentity, + handleClawSweeperEndorApi, + handleClawSweeperEndorApiRequest, + parseEndorRemediationNotification, + type EndorRemediationNotification +} from "../src/clawsweeperEndor/api.js" +import type { + ClaimEndorDeliveryInput, + ClaimEndorDeliveryResult +} from "../src/clawsweeperEndor/deliveries.js" +import { setRuntimeEnv } from "../src/runtime/env.js" +import { SqliteD1Database } from "./helpers/sqliteD1.js" + +const head = "a".repeat(40) +const idempotencyKey = [ + "clawsweeper.endor_remediation_reviewed", + "openclaw/openclaw", + "123", + head, + "ready" +].join(":") + +const validNotification: EndorRemediationNotification = { + version: 1, + type: "clawsweeper.endor_remediation_reviewed", + repo: "openclaw/openclaw", + prNumber: 123, + prUrl: "https://github.com/openclaw/openclaw/pull/123", + title: "Update a vulnerable dependency", + findingSummary: "No remaining findings", + reviewedHeadSha: head, + outcome: "ready", + reviewSummary: "Three exact-head reviews were clean", + reviewUrl: "https://github.com/openclaw/openclaw/pull/123#issuecomment-456", + checks: { + state: "passing", + total: 4, + summary: "4 checks passed" + }, + mergeState: "clean", + cycles: 3, + cleanStreak: 3, + idempotencyKey +} + +const delivery = ( + input: ClaimEndorDeliveryInput, + overrides: Partial = {} +): ClaimEndorDeliveryResult["delivery"] => ({ + ...input, + status: "pending", + messageId: null, + createdAt: "2026-08-24T00:00:00.000Z", + updatedAt: "2026-08-24T00:00:00.000Z", + deliveredAt: null, + ...overrides +}) + +const request = ( + body: unknown = validNotification, + headers: Record = {} +) => new Request("https://hermit.example/api/clawsweeper/endor-remediation/reviewed", { + method: "POST", + headers: { + authorization: "Bearer secret", + "content-type": "application/json", + "idempotency-key": idempotencyKey, + ...headers + }, + body: JSON.stringify(body) +}) + +const dependencies = (options: { + claimState?: ClaimEndorDeliveryResult["state"] + deliveryOverrides?: Partial + now?: Date +} = {}) => { + const claims: ClaimEndorDeliveryInput[] = [] + const sends: Array<{ channelId: string; message: unknown; nonce: string }> = [] + const marks: Array<{ idempotencyKey: string; payloadDigest: string; messageId: string }> = [] + return { + claims, + sends, + marks, + value: { + token: "secret", + channelId: "channel-security", + now: () => options.now ?? new Date("2026-08-24T00:01:00.000Z"), + claimDelivery: async (input: ClaimEndorDeliveryInput) => { + claims.push(input) + return { + state: options.claimState ?? "claimed", + delivery: delivery(input, options.deliveryOverrides) + } as ClaimEndorDeliveryResult + }, + markDelivered: async ( + key: string, + payloadDigest: string, + messageId: string + ) => { + marks.push({ idempotencyKey: key, payloadDigest, messageId }) + }, + sendMessage: async (channelId: string, message: unknown, nonce: string) => { + sends.push({ channelId, message, nonce }) + return { messageId: "discord-message-123" } + } + } + } +} + +const collectText = (component: unknown): string[] => { + if (!component || typeof component !== "object") { + return [] + } + const record = component as Record + const content = typeof record.content === "string" ? [record.content] : [] + const children = Array.isArray(record.components) + ? record.components.flatMap(collectText) + : [] + return [...content, ...children] +} + +describe("ClawSweeper Endor notification API", () => { + it("posts a validated notification to Hermit's fixed channel", async () => { + const deps = dependencies() + const response = await handleClawSweeperEndorApi(request(), deps.value) + + expect(response?.status).toBe(200) + expect(await response?.json()).toEqual({ + ok: true, + delivered: true, + duplicate: false, + messageId: "discord-message-123" + }) + expect(deps.claims).toHaveLength(1) + expect(deps.sends).toHaveLength(1) + expect(deps.sends[0]?.channelId).toBe("channel-security") + expect(deps.sends[0]?.nonce).toHaveLength(25) + expect(deps.marks).toEqual([{ + idempotencyKey, + payloadDigest: deps.claims[0]?.payloadDigest, + messageId: "discord-message-123" + }]) + + const sent = deps.sends[0]?.message as { + components?: unknown[] + allowedMentions?: unknown + } + expect(sent.allowedMentions).toEqual({ roles: [], users: [] }) + const text = (sent.components ?? []).flatMap(collectText).join("\n") + expect(text).toContain("Endor remediation reviewed โ€” READY") + expect(text).toContain("3/3 clean after 3/6 cycles") + expect(text).toContain("@clawsweeper automerge") + }) + + it("returns a durable receipt without sending a delivered replay", async () => { + const deps = dependencies({ + claimState: "existing", + deliveryOverrides: { + status: "delivered", + messageId: "discord-message-original", + deliveredAt: "2026-08-24T00:00:10.000Z" + } + }) + const response = await handleClawSweeperEndorApi(request(), deps.value) + + expect(response?.status).toBe(200) + expect(await response?.json()).toEqual({ + ok: true, + delivered: true, + duplicate: true, + messageId: "discord-message-original" + }) + expect(deps.sends).toHaveLength(0) + expect(deps.marks).toHaveLength(0) + }) + + it("reuses Discord's deterministic nonce for an immediate pending retry", async () => { + const first = dependencies() + const retry = dependencies({ claimState: "existing" }) + await handleClawSweeperEndorApi(request(), first.value) + const response = await handleClawSweeperEndorApi(request(), retry.value) + + expect(response?.status).toBe(200) + expect(first.sends[0]?.nonce).toBe(retry.sends[0]?.nonce) + expect(await response?.json()).toMatchObject({ duplicate: true }) + }) + + it("fails closed when an old pending receipt has ambiguous Discord state", async () => { + const deps = dependencies({ + claimState: "existing", + now: new Date("2026-08-24T01:00:00.000Z") + }) + const response = await handleClawSweeperEndorApi(request(), deps.value) + + expect(response?.status).toBe(409) + expect(deps.sends).toHaveLength(0) + expect(await response?.json()).toEqual({ + error: "Delivery status is ambiguous; inspect the configured Discord channel before retrying" + }) + }) + + it("rejects unauthenticated, malformed, and mismatched requests", async () => { + const deps = dependencies() + const unauthorized = await handleClawSweeperEndorApi( + request(validNotification, { authorization: "Bearer wrong" }), + deps.value + ) + const wrongUrl = await handleClawSweeperEndorApi( + request({ ...validNotification, prUrl: "https://evil.example/openclaw/openclaw/pull/123" }), + deps.value + ) + const wrongKey = await handleClawSweeperEndorApi( + request(validNotification, { "idempotency-key": "wrong" }), + deps.value + ) + + expect(unauthorized?.status).toBe(401) + expect(wrongUrl?.status).toBe(400) + expect(wrongKey?.status).toBe(400) + expect(deps.sends).toHaveLength(0) + }) + + it("escapes event text before rendering Discord markdown", () => { + const parsed = parseEndorRemediationNotification({ + ...validNotification, + title: "Safe [click](https://evil.example) @everyone", + findingSummary: "No finding\n[trap](https://evil.example)" + }) + expect(parsed).not.toBeNull() + if (!parsed) { + return + } + const text = collectText(buildEndorRemediationContainer(parsed)).join("\n") + expect(text).toContain("\\[click\\]\\(https://evil\\.example\\)") + expect(text).toContain("\\[trap\\]\\(https://evil\\.example\\)") + expect(text).not.toContain("[click](https://evil.example)") + }) + + it("derives stable but content-sensitive delivery identities", async () => { + const first = await endorDeliveryIdentity(validNotification) + const replay = await endorDeliveryIdentity(validNotification) + const changed = await endorDeliveryIdentity({ + ...validNotification, + reviewSummary: "Changed content" + }) + + expect(first).toEqual(replay) + expect(first.nonce).toHaveLength(25) + expect(first.payloadDigest).not.toBe(changed.payloadDigest) + expect(first.nonce).toBe(changed.nonce) + }) + + it("persists the real receipt and sends Discord with enforced nonce deduplication", async () => { + const owner = new SqliteD1Database() + const migration = await Bun.file( + new URL("../drizzle/0013_many_chameleon.sql", import.meta.url) + ).text() + for (const statement of migration.split("--> statement-breakpoint")) { + if (statement.trim()) { + await owner.exec(statement) + } + } + setRuntimeEnv({ + DB: owner as unknown as D1Database, + CLAWSWEEPER_HERMIT_TOKEN: "secret", + CLAWSWEEPER_ENDOR_DISCORD_CHANNEL_ID: "channel-security" + } as Env) + + const posts: Array<{ route: string; body: Record }> = [] + const client = { + rest: { + post: async (route: string, options: { body: Record }) => { + posts.push({ route, body: options.body }) + return { id: "discord-message-real" } + } + } + } as unknown as Client + + const first = await handleClawSweeperEndorApiRequest(request(), client) + const replay = await handleClawSweeperEndorApiRequest(request(), client) + + expect(first?.status).toBe(200) + expect(replay?.status).toBe(200) + expect(await replay?.json()).toMatchObject({ + delivered: true, + duplicate: true, + messageId: "discord-message-real" + }) + expect(posts).toHaveLength(1) + expect(posts[0]?.route).toBe("/channels/channel-security/messages") + expect(posts[0]?.body.enforce_nonce).toBe(true) + expect(posts[0]?.body.nonce).toHaveLength(25) + expect(Array.isArray(posts[0]?.body.components)).toBe(true) + + const stored = owner.database + .query("select status, message_id from endor_notification_deliveries") + .get() as { status: string; message_id: string } + expect(stored).toEqual({ + status: "delivered", + message_id: "discord-message-real" + }) + owner.close() + }) +}) From 3931677bd6d1b4822ce6c7a787b83c8720eb62eb Mon Sep 17 00:00:00 2001 From: Jesse Merhi <79823012+jesse-merhi@users.noreply.github.com> Date: Tue, 25 Aug 2026 11:53:15 +1000 Subject: [PATCH 2/2] Fix Discord Markdown escaping --- src/clawsweeperEndor/api.ts | 9 ++++----- tests/clawsweeperEndorApi.test.ts | 3 ++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/clawsweeperEndor/api.ts b/src/clawsweeperEndor/api.ts index 49cae67..4202152 100644 --- a/src/clawsweeperEndor/api.ts +++ b/src/clawsweeperEndor/api.ts @@ -209,12 +209,11 @@ export const parseEndorRemediationNotification = ( } } +const oneLineText = (value: string) => + value.replace(/[\u0000-\u001f\u007f]+/g, " ").replace(/\s+/g, " ").trim() + const markdownText = (value: string) => - value - .replaceAll("\\", "\\\\") - .replace(/([`*_{}\[\]()<>#+\-.!|])/g, "\\$1") - .replace(/\s+/g, " ") - .trim() + oneLineText(value).replace(/([\\`*_~{}\[\]()<>#+\-.!|])/g, "\\$1") const markdownUrl = (value: string) => `<${value.replaceAll(">", "%3E")}>` diff --git a/tests/clawsweeperEndorApi.test.ts b/tests/clawsweeperEndorApi.test.ts index 8ab01c4..8b925ef 100644 --- a/tests/clawsweeperEndorApi.test.ts +++ b/tests/clawsweeperEndorApi.test.ts @@ -229,7 +229,7 @@ describe("ClawSweeper Endor notification API", () => { const parsed = parseEndorRemediationNotification({ ...validNotification, title: "Safe [click](https://evil.example) @everyone", - findingSummary: "No finding\n[trap](https://evil.example)" + findingSummary: "No finding\\path\n[trap](https://evil.example)" }) expect(parsed).not.toBeNull() if (!parsed) { @@ -237,6 +237,7 @@ describe("ClawSweeper Endor notification API", () => { } const text = collectText(buildEndorRemediationContainer(parsed)).join("\n") expect(text).toContain("\\[click\\]\\(https://evil\\.example\\)") + expect(text).toContain("No finding\\\\path") expect(text).toContain("\\[trap\\]\\(https://evil\\.example\\)") expect(text).not.toContain("[click](https://evil.example)") })