2026-04-30 16:38:54 -04:00
# Seahaven Slack Bot — Alex
2026-04-11 23:17:03 -04:00
2026-07-23 14:43:11 -04:00
> ## ☠️ DECOMMISSIONED 2026-07-23
>
> This stack and the Alex Slack app were fully torn down on 2026-07-23 and this repository is
> archived read-only. The CloudFormation stack `seahaven-slack-bot` (328440206208/us-east-1),
> the Bedrock agent/KB/AOSS collection, the `bot.seahaven.com` endpoint, and the Slack app were
> deleted. Conversation-table exports and KB documents were backed up to
> `~/Desktop/seahaven-slack-bot-decommission-2026-07-23/` on Adam's workstation.
> The vendor secrets (`seahaven/slack/*`, `seahaven/qbo/oauth`, `seahaven/google/maps-api-key`,
> `seahaven/notion/api-key`) were **retained** in Secrets Manager for reuse.
>
> **Successor:** the Sea Haven MCP platform (`Sea-Haven-Industries/sh-mcp`) — see its
> `docs/design.md`. Everything below describes the architecture as it existed at decommission.
2026-06-11 14:14:04 -04:00




2026-04-30 16:38:54 -04:00
Internal Slack assistant for Sea Haven Industries, powered by AWS Bedrock. Employees can DM Alex or @mention Alex in any channel to ask about vendors, payments, work orders, purchase orders, Amazon sites, company policies, and more.
2026-04-11 23:17:03 -04:00
## Architecture
```
2026-04-30 16:38:54 -04:00
Slack (WebSocket)
2026-04-11 23:17:03 -04:00
│
▼
2026-04-30 16:38:54 -04:00
ECS Fargate (Socket Mode) ← persistent WebSocket connection to Slack
│
├── DM (message.im) ──────────────────┐
├── @mention (app_mention) ────────────┤
│ async invoke
│ ▼
│ slack-processor Lambda ← calls Bedrock Agent, logs to DynamoDB, posts reply
│ │
│ ▼
│ Bedrock Agent — Alex (Claude Sonnet 4.5)
│ ├── Knowledge Base (AOSS + S3)
│ ├── QBO_Lookup action group ← QuickBooks vendor search (VPC, static IP)
│ ├── Google_Maps_Lookup action group ← fallback vendor search
│ └── WO_PO_Lookup action group ← WO, PO, site, and payment lookups (DynamoDB direct)
2026-04-11 23:17:03 -04:00
│
2026-04-30 16:38:54 -04:00
└── App Home opened ──────────────────→ app-home Lambda ← publishes Block Kit capabilities view
API Gateway (bot.seahaven.com)
2026-04-11 23:17:03 -04:00
│
2026-04-13 20:26:06 -04:00
└── GET /qbo/*
▼
qbo-oauth Lambda (VPC, static IP) ← OAuth 2.0 connect/callback/disconnect/launch
Outbound IP: 52.202.83.13 (NAT Gateway in seahaven-vpc)
2026-04-13 00:00:39 -04:00
EventBridge (daily 02:00 UTC)
│
2026-04-13 15:42:27 -04:00
├── notion-sync Lambda ← exports Office Operations Notion pages → S3 → KB ingestion
├── po-sync Lambda ← exports purchase-orders DynamoDB → S3 → KB ingestion
└── workorder-sync Lambda ← exports WorkOrders + Comments DynamoDB → S3 → KB ingestion
2026-04-11 23:17:03 -04:00
```
2026-04-30 16:38:54 -04:00
### What Alex Can Do
- **Vendor Lookup** — search QBO for existing vendors, KB for approved lists, Google Maps for new options
- **Payment & Invoice Status** — check if a vendor has been paid, if an invoice is scheduled, look up by check number
- **Work Order Lookups** — status, assignments, comments, and full history by WO number
- **Purchase Order Lookups** — status, line items, supplier, and ship-to details by PO number
2026-07-15 18:51:53 -04:00
- **Site Lookups** — Amazon facility address and details by site code
2026-04-30 16:38:54 -04:00
- **Company Policies & SOPs** — employee handbook, SA8000 compliance, and operational procedures
2026-04-11 23:17:03 -04:00
### Vendor Query Priority
1. **QBO** — existing vetted vendors in QuickBooks Online
2. **Knowledge Base** — approved vendor lists in internal docs
3. **Google Maps** — fallback for net-new vendor discovery
## Stack
| Resource | Details |
|---|---|
| AWS Account | 328440206208, us-east-1 |
| CDK | TypeScript, v2 |
2026-04-11 23:52:44 -04:00
| LLM | Claude Sonnet 4.5 (`anthropic.claude-sonnet-4-5-20250929-v1:0` ) |
2026-04-11 23:17:03 -04:00
| Embeddings | Amazon Titan Embed Text V2 (1024 dimensions) |
| Vector Store | OpenSearch Serverless (VECTORSEARCH) |
2026-04-30 16:38:54 -04:00
| Socket Mode | ECS Fargate (`seahaven-socket-mode` ) — persistent WebSocket to Slack |
2026-04-11 23:17:03 -04:00
| Conversation Log | DynamoDB `seahaven-conversations` (90-day TTL) |
2026-04-30 16:38:54 -04:00
| Unanswered Questions | DynamoDB `seahaven-unanswered-questions` (180-day TTL) |
| Payment Data | DynamoDB `PaymentsDashboard` (via payments-dashboard) |
2026-06-05 17:26:26 -04:00
| PO Data Source | DynamoDB `purchase-orders` (read-only; owned by procurement-ingest / po-ingest) |
2026-04-13 15:42:27 -04:00
| Work Order Data Source | DynamoDB `WorkOrders` + `WorkOrderComments` (via workorder-ingest) |
2026-04-30 16:38:54 -04:00
| Site Assignments | DynamoDB `verified-sites` (auto-populated via po-ingest Streams pipeline) |
| VPC | `seahaven-vpc` (`vpc-0d3d4b67bd0cf8a68` ) — QBO Lambdas + Socket Mode |
2026-04-13 20:26:06 -04:00
| Static Outbound IP | `52.202.83.13` (NAT Gateway for Intuit IP allowlist) |
2026-04-30 16:38:54 -04:00
| QBO OAuth URLs | `bot.seahaven.com/qbo/connect` , `/qbo/callback` , `/qbo/disconnect` , `/qbo/launch` |
2026-04-11 23:17:03 -04:00
2026-06-05 17:26:26 -04:00
### Shared Resources
This bot reads the `purchase-orders` DynamoDB table **read-only** (via the `po-sync` and `wo-po-lookup` Lambdas, both granted `grantReadData` ). The table is owned by the `procurement-ingest` repo (`po-ingest` stack), which is the sole authoritative writer. Any change to the `purchase-orders` schema must be coordinated with `procurement-ingest` (owner) and `payments-dashboard` (the other read-only consumer).
2026-07-08 16:22:55 -04:00
## Cross-stack data contracts
This bot does **not own any DynamoDB tables.** It imports five tables owned by other stacks via `Table.fromTableName(...)` (`lib/constructs/bedrock-agent.ts` , `workorder-sync.ts` , `po-sync.ts` ) and reads them read-only. Because the tables are imported by name, this stack has no compile-time link to the owner: an owner-side change to a table's name, key schema, attribute names, GSIs, encryption key, or lifecycle policy will **silently break the Bedrock agent (Alex)** at runtime, not at deploy. The CMK grant-gap incident (INFRA-95 / M-3) is precedent: because `grantReadData` on an imported table does not carry KMS permissions, every read failed with `kms:Decrypt AccessDenied` until an explicit grant was added.
Owner-side changes to any of these tables must be coordinated with this repo before they ship. Treat them as cross-repo migrations, not local edits.
| Table | Owner repo / stack | Keys the bot depends on | Attributes the bot reads | Encryption |
|---|---|---|---|---|
| `WorkOrders` | `procurement-ingest` / `WorkorderIngestStack` (`cdk/wo_stack.py` ) | PK `work_order_id` (S). `GetItem` by id; full-table `Scan` in `workorder-sync` . | `work_order_id` , `description` , `wo_status` , `customer` , `site_code` , `building` , `address` , `severity` , `priority` , `assigned_to` , `date_reported` , `scheduled_start` , `due_date` , `updated_at` | Shared CMK (`/seahaven/dynamodb/cmk-arn` ) |
| `WorkOrderComments` | `procurement-ingest` / `WorkorderIngestStack` (`cdk/wo_stack.py` ) | PK `work_order_id` (S), SK `comment_id` (S). `Query` by `work_order_id` . | `work_order_id` , `created_at` (used for sort), `commenter` , `text` | Shared CMK (`/seahaven/dynamodb/cmk-arn` ) |
| `purchase-orders` | `procurement-ingest` / `po-ingest` (`cdk/po_stack.py` ) | PK `po_number` (S). `GetItem` by number; full-table `Scan` in `po-sync` . | `po_number` , `po_status` , `email_type` , `cancelled_at` , `total_amount` , `currency` , `supplier.name` , `source_system` , `order_date` , `revision_date` , `payment_terms` , `requisition_number` , `department` , `submitted_by` , `on_behalf_of` , `ship_to.{name,address,location_code,attn}` , `line_items[].{description,amount,currency,need_by,category}` | Shared CMK (`/seahaven/dynamodb/cmk-arn` ) |
2026-07-15 18:51:53 -04:00
| `verified-sites` | `procurement-ingest` / `po-ingest` (`cdk/po_stack.py` ) | PK `siteCode` (S). `GetItem` by code only. | `siteCode` , `address` , `fullAddress` , `city` , `state` , `zip` , `latitude` , `longitude` , `notes` | AWS-managed (NOT CMK) |
2026-07-08 16:22:55 -04:00
| `PaymentsDashboard` | `payments-dashboard` / `payments-dashboard` (`template.yaml` ) | PK `pk` (S), format `payment#<check_number>` . `GetItem` by pk; full-table `Scan` filtered `begins_with(pk, "payment#")` . | `pk` , `check_number` , `payee` , `amount_usd` , `method` , `status` , `send_payment_on` , `clear_status` , `cleared_date` , `invoice_numbers` , `company_subsidiary` , `bank_reference` | CMK (shared) |
2026-07-15 18:51:53 -04:00
**Resolved contract break (verified-sites `by-state` GSI, INFRA-180):** the owner removed the `by-state` GSI on 2026-06-03 (procurement-ingest audit M-20, "0 reads in 30d"), which broke the bot's state-based site listings at runtime. Per the owner's decision the state-listing path was removed from the bot entirely (query code, `lookup_site` `state` parameter, and agent instructions) rather than restoring the GSI. `lookup_site` now supports `siteCode` point lookups only.
2026-07-08 16:22:55 -04:00
2026-07-06 17:44:11 -04:00
## Documentation
The canonical map of Sea Haven's AWS infrastructure lives in Confluence. This project's `seahaven-slack-bot` stack is represented there as a Mermaid subgraph.
- **[AWS Architecture Map ](https://seahaven.atlassian.net/wiki/spaces/IT/pages/1540098 )** (Confluence, IT space, page 1540098)
2026-04-11 23:17:03 -04:00
## Prerequisites
- Node.js 22+
- AWS CDK (`npm install -g aws-cdk` )
2026-04-30 16:38:54 -04:00
- Docker Desktop (required for Lambda bundling and Socket Mode container build)
2026-04-11 23:17:03 -04:00
- AWS credentials configured locally
## Secrets Manager
2026-04-30 16:38:54 -04:00
These secrets must exist before deploying. The Slack, QBO, Maps, and app-level token secrets must be created manually before first deploy. The Notion secret is created automatically by CDK with a placeholder.
2026-04-11 23:17:03 -04:00
2026-04-13 00:00:39 -04:00
| Secret Name | Created | Structure |
|---|---|---|
| `seahaven/slack/credentials` | Manual (pre-deploy) | `{ "botToken": "xoxb-...", "signingSecret": "..." }` |
2026-04-30 16:38:54 -04:00
| `seahaven/slack/app-level-token` | Manual (pre-deploy) | Plaintext `xapp-...` token with `connections:write` scope |
| `seahaven/qbo/oauth` | Manual (pre-deploy) | `{ "clientId": "", "clientSecret": "", "refreshToken": "", "realmId": "" }` |
2026-04-13 00:00:39 -04:00
| `seahaven/google/maps-api-key` | Manual (pre-deploy) | `{ "apiKey": "" }` |
| `seahaven/notion/api-key` | Auto (CDK) | `{ "apiKey": "secret_..." }` |
2026-04-11 23:17:03 -04:00
## Deployment
```bash
npm install
npx cdk bootstrap aws://328440206208/us-east-1 # first time only
npx cdk deploy
```
2026-04-30 16:38:54 -04:00
The deploy takes ~15 minutes on first run. The AOSS collection, vector index creation, and Docker image build are the slow steps.
2026-04-11 23:17:03 -04:00
Stack outputs after deploy:
- `KBDocsBucketName` — S3 bucket to upload knowledge base documents
2026-04-30 16:38:54 -04:00
- `AgentId` — Bedrock Agent ID (Alex)
2026-04-11 23:17:03 -04:00
## Post-Deployment Setup
### 1. Upload knowledge base documents
2026-04-30 16:38:54 -04:00
Upload SA8000 compliance docs, SOPs, and the employee handbook to the S3 bucket printed in stack outputs.
2026-04-11 23:17:03 -04:00
```bash
aws s3 cp ./your-docs/ s3://< KBDocsBucketName > / --recursive
```
### 2. Trigger KB sync
```bash
aws bedrock-agent start-ingestion-job \
2026-04-30 16:38:54 -04:00
--knowledge-base-id < from Bedrock console > \
2026-04-11 23:17:03 -04:00
--data-source-id < DataSourceId > \
--region us-east-1
```
2026-04-13 00:00:39 -04:00
### 3. Configure Notion sync
1. Create a Notion internal integration at **Settings → Connections → Develop or manage integrations**
2. Name it `seahaven-office-ops` and associate it with the **Office Operations** teamspace
2026-04-30 16:38:54 -04:00
3. Copy the integration token and update: **Secrets Manager → `seahaven/notion/api-key` → Edit**
2026-04-13 00:00:39 -04:00
### 4. Configure Slack app
2026-04-11 23:17:03 -04:00
In the [Slack API dashboard ](https://api.slack.com/apps ):
2026-04-30 16:38:54 -04:00
1. **Basic Information** → update Display Name to "Alex", upload avatar
2. **Socket Mode** → enable, generate app-level token with `connections:write` scope
3. Store the app-level token in Secrets Manager as `seahaven/slack/app-level-token`
4. **Event Subscriptions** → enable (no Request URL needed with Socket Mode)
5. Subscribe to bot events: `message.im` , `app_mention` , `app_home_opened`
6. **OAuth & Permissions** → Bot Token Scopes: `chat:write` , `im:history` , `app_mentions:read` , `channels:history` , `groups:history`
7. **App Home** → enable Home Tab and Messages Tab
8. Reinstall app to workspace
9. Invite Alex to channels: `/invite @Alex`
2026-04-11 23:17:03 -04:00
## Project Structure
```
bin/
seahaven-slack-bot.ts CDK app entry point
lib/
2026-04-30 16:38:54 -04:00
seahaven-slack-bot-stack.ts Main stack
2026-04-11 23:17:03 -04:00
constructs/
2026-04-30 16:38:54 -04:00
knowledge-base.ts Bedrock KB + AOSS + S3 (via @cdklabs L2 construct)
bedrock-agent.ts Alex — Bedrock Agent + QBO/Maps/WO-PO-Site-Payment action groups
slack-handler.ts Processor Lambda + App Home Lambda + QBO OAuth + API Gateway
socket-mode.ts ECS Fargate service running Slack Socket Mode client
conversation-log.ts DynamoDB conversation + unanswered questions tables
notion-sync.ts EventBridge daily cron + notion-sync Lambda
po-sync.ts EventBridge daily cron + po-sync Lambda
workorder-sync.ts EventBridge daily cron + workorder-sync Lambda
services/
socket-mode/ Socket Mode ECS service (Dockerfile, TypeScript, @slack/socket -mode)
2026-04-11 23:17:03 -04:00
lambda/
2026-04-30 16:38:54 -04:00
slack-processor/ Calls agent, writes DynamoDB, posts Slack reply
app-home/ Publishes Block Kit capabilities view to App Home tab
qbo-oauth/ OAuth 2.0 connect/callback/disconnect/launch for QuickBooks
qbo-lookup/ Bedrock action group — QuickBooks vendor search
maps-lookup/ Bedrock action group — Google Maps Places search
wo-po-lookup/ Bedrock action group — WO, PO, site, and payment lookups (DynamoDB direct)
notion-sync/ Daily Notion → S3 export, triggers KB ingestion
po-sync/ Daily purchase-orders DynamoDB → S3 export, triggers KB ingestion
workorder-sync/ Daily WorkOrders DynamoDB → S3 export, triggers KB ingestion
2026-04-11 23:17:03 -04:00
scripts/
2026-04-30 16:38:54 -04:00
create-aoss-index.ts Manual fallback for AOSS index creation (not needed in normal deploy)
2026-04-11 23:17:03 -04:00
```
## Known Maintenance Items
2026-04-30 16:38:54 -04:00
- **QBO OAuth** — the refresh token auto-rotates on every API call (persisted back to Secrets Manager). If the token ever expires (100 days of inactivity), reconnect via `https://bot.seahaven.com/qbo/connect` .
- **Notion sync** runs daily at 02:00 UTC automatically. Manual trigger: invoke `seahaven-notion-sync` .
- **PO sync** runs daily at 02:00 UTC. Scans `purchase-orders` DynamoDB table (from [po-ingest ](https://github.com/Sea-Haven-Industries/po-ingest )). Manual trigger: invoke `seahaven-po-sync` .
- **Work order sync** runs daily at 02:00 UTC. Scans `WorkOrders` and `WorkOrderComments` (from [workorder-ingest ](https://github.com/Sea-Haven-Industries/workorder-ingest )). Manual trigger: invoke `seahaven-workorder-sync` .
- **Site assignments** are auto-populated from the `verified-sites` DynamoDB table, maintained by the po-ingest DynamoDB Streams pipeline. No manual seeding required.
- **Unanswered questions** are logged to `seahaven-unanswered-questions` when Alex detects it couldn't answer a question. Review via DynamoDB console — records have `status: "pending"` and 180-day TTL.
- **Socket Mode** — the ECS Fargate task maintains a persistent WebSocket connection to Slack. Monitor via CloudWatch Logs (`socket-mode` log stream). The service auto-restarts on failure.