Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Almanac configuration. Copy to .env and fill in values.
# SlackKnowledgeBot configuration. Copy to .env and fill in values.
# Validation lives in src/config/index.ts (Zod). Missing required values fail-fast at startup.

# ── Slack ────────────────────────────────────────────────
Expand All @@ -8,11 +8,11 @@ SLACK_APP_TOKEN=xapp-replace-me

# ── AWS core ─────────────────────────────────────────────
AWS_REGION=us-west-2
DYNAMODB_TABLE_TOKENS=almanac-tokens-staging
DYNAMODB_TABLE_AUDIT=almanac-audit-staging
DYNAMODB_TABLE_IDENTITY_CACHE=almanac-identity-cache-staging
SQS_AUDIT_QUEUE_URL=https://sqs.us-west-2.amazonaws.com/000000000000/almanac-audit-staging
SQS_AUDIT_DLQ_URL=https://sqs.us-west-2.amazonaws.com/000000000000/almanac-audit-dlq-staging
DYNAMODB_TABLE_TOKENS=slack-knowledge-bot-staging-tokens
DYNAMODB_TABLE_AUDIT=slack-knowledge-bot-staging-audit
DYNAMODB_TABLE_IDENTITY_CACHE=slack-knowledge-bot-staging-identity-cache
SQS_AUDIT_QUEUE_URL=https://sqs.us-west-2.amazonaws.com/000000000000/slack-knowledge-bot-staging-audit
SQS_AUDIT_DLQ_URL=https://sqs.us-west-2.amazonaws.com/000000000000/slack-knowledge-bot-audit-dlq-staging
# Retrieval backend. Empty → null backend (retriever returns empty).
# postgres:// or postgresql:// → pgvector. CDK-deployed tasks can leave
# this blank and inject PG* individually; the app composes the URL.
Expand All @@ -24,7 +24,7 @@ PGHOST=
PGPORT=5432
PGUSER=
PGPASSWORD=
PGDATABASE=almanac
PGDATABASE=slack_knowledge_bot
KMS_KEY_ID=arn:aws:kms:us-west-2:000000000000:key/00000000-0000-0000-0000-000000000000
REDIS_URL=rediss://your-elasticache-primary:6379

Expand All @@ -49,14 +49,14 @@ GOOGLE_OAUTH_CLIENT_ID=replace-me
GOOGLE_OAUTH_CLIENT_SECRET=replace-me

# ── App ──────────────────────────────────────────────────
APP_BASE_URL=https://almanac-staging.nanocorp.internal
APP_BASE_URL=https://slack-knowledge-bot-staging.nanocorp.internal
RATE_LIMIT_USER_PER_HOUR=20
RATE_LIMIT_WORKSPACE_PER_HOUR=500
STALE_DOC_THRESHOLD_DAYS=90
CRAWL_INTERVAL_MINUTES=30
TOKEN_STORE_ENCRYPTION_CONTEXT=almanac-token-store
TOKEN_STORE_ENCRYPTION_CONTEXT=slack-knowledge-bot-token-store

# ── OAuth delegation (almanac-oauth) ─────────────────────
# ── OAuth delegation (slack-knowledge-bot-oauth) ─────────────────────
# Random string ≥ 32 bytes. Signs state cookies + outbound OAuth start URLs.
# Rotate by deploying a new value; existing in-flight state cookies become invalid.
STATE_SIGNING_SECRET=replace-with-32-plus-bytes-of-randomness
Expand Down
16 changes: 8 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,23 @@ jobs:
# to `npm ci` once a cross-platform lockfile is committed.
run: npm install --prefer-offline --no-audit --no-fund

- name: Install (almanac-oauth package)
- name: Install (slack-knowledge-bot-oauth package)
run: npm install --prefer-offline --no-audit --no-fund
working-directory: packages/oauth

- name: Lint (almanac-oauth)
- name: Lint (slack-knowledge-bot-oauth)
run: npm run lint
working-directory: packages/oauth

- name: Typecheck (almanac-oauth)
- name: Typecheck (slack-knowledge-bot-oauth)
run: npm run typecheck
working-directory: packages/oauth

- name: Test (almanac-oauth)
- name: Test (slack-knowledge-bot-oauth)
run: npm test
working-directory: packages/oauth

- name: Build almanac-oauth
- name: Build slack-knowledge-bot-oauth
run: npm run build
working-directory: packages/oauth

Expand All @@ -68,7 +68,7 @@ jobs:
# internal modules). AWS SDK clients use aws-sdk-client-mock, which
# attaches to the client instance, not `vi.mock`.
run: |
VIOLATIONS=$(grep -RnE "vi\.mock\(['\"](@aws-sdk/|@slack/bolt|@opentelemetry/|ioredis|pg|almanac-oauth|node:crypto|node:http)" src --include='*.test.ts' || true)
VIOLATIONS=$(grep -RnE "vi\.mock\(['\"](@aws-sdk/|@slack/bolt|@opentelemetry/|ioredis|pg|slack-knowledge-bot-oauth|node:crypto|node:http)" src --include='*.test.ts' || true)
if [ -n "$VIOLATIONS" ]; then
echo "::error::SDK-mock ban: vi.mock of an SDK package is forbidden — inject a port fake instead."
echo "$VIOLATIONS"
Expand Down Expand Up @@ -129,10 +129,10 @@ jobs:
run: helm lint chart

- name: Helm template (staging values)
run: helm template almanac chart -f chart/values-staging.yaml > /dev/null
run: helm template slack-knowledge-bot chart -f chart/values-staging.yaml > /dev/null

- name: Helm template (production values)
run: helm template almanac chart -f chart/values-production.yaml > /dev/null
run: helm template slack-knowledge-bot chart -f chart/values-production.yaml > /dev/null

docker:
name: docker build (no push)
Expand Down
2 changes: 1 addition & 1 deletion .gitleaks.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# gitleaks configuration for slack-knowledge-bot (internal service handle: almanac).
# gitleaks configuration for slack-knowledge-bot (internal service handle: slack-knowledge-bot).
#
# Strategy: extend the default ruleset, then allowlist only the placeholder
# strings we intentionally commit. Suppression is path- and stopword-scoped,
Expand Down
26 changes: 13 additions & 13 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

You're an AI client (or the author of one) about to run this service locally, add a knowledge source, wire a new OAuth provider, or ship it as a Platform tenant. This file gets you running in five minutes. For the wider picture — how this repo fits into the nanohype stack — read the [Platform Reference](../nanohype/docs/platform-reference.md).

> Internal service handle: **almanac**. The GitHub repo and product name are `slack-knowledge-bot`, but the npm package, OTel `service.name` / `agents.platform`, the `/almanac` slash command, and the `almanac/<env>/*` secret prefixes stay `almanac` — they're coupled to the landing-zone `almanac-platform` substrate component.
> Internal service handle: **slack-knowledge-bot**. The GitHub repo and product name are `slack-knowledge-bot`, but the npm package, OTel `service.name` / `agents.platform`, the `/slack-knowledge-bot` slash command, and the `slack-knowledge-bot/<env>/*` secret prefixes stay `slack-knowledge-bot` — they're coupled to the landing-zone `slack-knowledge-bot-platform` substrate component.

## What this repo gives you

Expand All @@ -18,7 +18,7 @@ cp .env.example .env # fill in the required keys (see CLAUDE.md > Configur
npm run dev # tsx watch src/index.ts — serves :3001 (/health + /oauth/*)
```

In Slack: `@almanac what's our vacation policy?`
In Slack: `@slack-knowledge-bot what's our vacation policy?`

```bash
npm run check # typecheck + lint + format:check + test (CI parity, one shot)
Expand All @@ -36,32 +36,32 @@ Two CRs in different groups — a `BudgetPolicy` (`governance.nanohype.dev/v1alp
apiVersion: governance.nanohype.dev/v1alpha1
kind: BudgetPolicy
metadata:
name: almanac
name: slack-knowledge-bot
namespace: tenants-protohype
spec:
platformRef: { name: almanac }
platformRef: { name: slack-knowledge-bot }
monthlyUsd: "5000" # kill-switch fires at 120% (USD 6000)
alertThresholdsPercent: [50, 80, 100]
killSwitchEnabled: true
---
apiVersion: platform.nanohype.dev/v1alpha1
kind: Platform
metadata:
name: almanac
name: slack-knowledge-bot
namespace: tenants-protohype
spec:
displayName: almanac
displayName: slack-knowledge-bot
persona: support
tenant: protohype
budget: { name: almanac }
budget: { name: slack-knowledge-bot }
identity:
allowedModelFamilies: [anthropic, amazon] # Claude (LLM) + Titan (embeddings)
extraPolicyArns: [] # app pods assume the landing-zone role directly
compliance: { soc2: true }
isolation: namespace
```

The operator reconciles the namespace `tenants-protohype`, ResourceQuota, LimitRange, default-deny NetworkPolicy, ArgoCD AppProject, and a per-Platform IRSA role trusting the `tenant-runtime` SA. **almanac's own app pods don't use that operator role** — they assume the landing-zone `almanac-platform` IRSA role directly via the chart's `aws.platformRoleArn` Helm value. `extraPolicyArns` stays empty for that reason.
The operator reconciles the namespace `tenants-protohype`, ResourceQuota, LimitRange, default-deny NetworkPolicy, ArgoCD AppProject, and a per-Platform IRSA role trusting the `tenant-runtime` SA. **slack-knowledge-bot's own app pods don't use that operator role** — they assume the landing-zone `slack-knowledge-bot-platform` IRSA role directly via the chart's `aws.platformRoleArn` Helm value. `extraPolicyArns` stays empty for that reason.

### The Helm chart (`chart/`)

Expand All @@ -73,13 +73,13 @@ The application Deployment plus everything that supports it. Templates under `ch
| `service.yaml` | ClusterIP :3001 |
| `ingress.yaml` | ingress-nginx + cert-manager TLS for `/health` and `/oauth/:provider/{start,callback}` |
| `serviceaccount.yaml` | Shared SA for the main pod + audit-consumer; `eks.amazonaws.com/role-arn` rendered from `aws.platformRoleArn` |
| `externalsecret.yaml` | ESO syncs `almanac/<env>/app-secrets` + `almanac/<env>/db-credentials` from Secrets Manager |
| `externalsecret.yaml` | ESO syncs `slack-knowledge-bot/<env>/app-secrets` + `slack-knowledge-bot/<env>/db-credentials` from Secrets Manager |
| `networkpolicy.yaml` | Default-deny + egress allow-list (AWS APIs, Slack/WorkOS/Notion/Confluence/Drive HTTPS, RDS + Redis on the VPC CIDR) |
| `audit-consumer-deployment.yaml` + `audit-consumer-scaledobject.yaml` | The SQS-drain Deployment (`dist/bin/audit-consumer.js`), KEDA-scaled 0..5 on audit queue depth |
| `prometheusrule.yaml` | Four alerts — QueryP95, LLMError, AuditTotalLoss, AuditDlqDepth |
| `grafana-dashboard.yaml` | ConfigMap loading `chart/dashboards/almanac.json` |
| `grafana-dashboard.yaml` | ConfigMap loading `chart/dashboards/slack-knowledge-bot.json` |

`values.yaml` is the base; `values-staging.yaml` / `values-production.yaml` carry the per-env deltas (image tag, `aws.platformRoleArn`, replica count). The image is `ghcr.io/nanohype/slack-knowledge-bot`. OTel attrs `agents.tenant=protohype` + `agents.platform=almanac` are set in every values file (required by the platform-tenant contract).
`values.yaml` is the base; `values-staging.yaml` / `values-production.yaml` carry the per-env deltas (image tag, `aws.platformRoleArn`, replica count). The image is `ghcr.io/nanohype/slack-knowledge-bot`. OTel attrs `agents.tenant=protohype` + `agents.platform=slack-knowledge-bot` are set in every values file (required by the platform-tenant contract).

### Required tenant files

Expand All @@ -101,7 +101,7 @@ A "connector" is a knowledge source the bot can verify per-user access against (

## Add an OAuth provider

OAuth providers live in the in-repo `packages/oauth` package (the `almanac-oauth` module, scaffolded from nanohype's `module-oauth-delegation` template) under `packages/oauth/src/oauth/providers/`. Built-ins: Notion, Google, Atlassian, Slack, HubSpot. To add one:
OAuth providers live in the in-repo `packages/oauth` package (the `slack-knowledge-bot-oauth` module, scaffolded from nanohype's `module-oauth-delegation` template) under `packages/oauth/src/oauth/providers/`. Built-ins: Notion, Google, Atlassian, Slack, HubSpot. To add one:

1. **Write the adapter** — add `packages/oauth/src/oauth/providers/<name>.ts` modeled on `notion.ts`. Export an `OAuthProvider` object (`authUrl`, `tokenUrl`, `defaultScopes`, `usePkce`, `tokenAuthStyle`, `parseTokenResponse`) and call `registerProvider("<name>", () => <name>Provider)` at module load.
2. **Wire the barrel** — add a side-effect `import "./<name>.js"` and a named re-export to `packages/oauth/src/oauth/providers/index.ts` so consumers can pass the adapter directly.
Expand Down Expand Up @@ -130,4 +130,4 @@ OAuth providers live in the in-repo `packages/oauth` package (the `almanac-oauth
- [`docs/`](docs/) — PRD, RAG architecture, QA playbook, threat model, compliance checklist, runbook, integrations, secrets, onboarding, test plan
- [Platform Reference](../nanohype/docs/platform-reference.md) — the stack-wide view
- [`eks-agent-platform`](https://github.com/nanohype/eks-agent-platform) — the operator that reconciles the Platform CR
- [`landing-zone`](https://github.com/nanohype/landing-zone) — the `almanac-platform` substrate the chart's IRSA role and data stores live in
- [`landing-zone`](https://github.com/nanohype/landing-zone) — the `slack-knowledge-bot-platform` substrate the chart's IRSA role and data stores live in
Loading
Loading