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
6 changes: 3 additions & 3 deletions .agents/docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Product direction

Interoperable Signal products (see [issue #10](https://github.com/BreadchainCoop/sigstack-bot/issues/10)):
Interoperable Signal products:

1. **Voice transcription** — same process (NEAR AI Whisper Large V3, not an in-CVM sidecar)
2. **In-chat group translation** — see [`docs/in-chat-translation.md`](../../docs/in-chat-translation.md)
Expand Down Expand Up @@ -136,9 +136,9 @@ Before finishing Rust work: `npm run ci` / `pnpm run ci` (never bare `pnpm ci`).

## Registration proxy

Ops helper on the one CVM: **:8081** (phone B). Multi-tenant “create your personal AI bot” web UX is out of scope; Stripe client site is issue #10 follow-up.
Ops helper on the one CVM: **:8081** (phone B). Multi-tenant “create your personal AI bot” web UX is out of scope; Stripe checkout for the product storefront is a separate workstream.

## Website

- **`site/`** — product educational storefront (SvelteKit static, GitHub Pages). Live URL after Pages is enabled: https://breadchaincoop.github.io/sigstack-bot/ · see [`site/README.md`](../../site/README.md) and issue [#23](https://github.com/BreadchainCoop/sigstack-bot/issues/23). Stripe checkout remains issue [#13](https://github.com/BreadchainCoop/sigstack-bot/issues/13).
- **`site/`** — product educational storefront (SvelteKit static, GitHub Pages). Live URL after Pages is enabled: https://breadchaincoop.github.io/sigstack-bot/ · see [`site/README.md`](../../site/README.md). Stripe checkout is not live yet.
- **`web/`** — legacy personal-AI framing. Do **not** treat it as the product suite storefront.
1 change: 1 addition & 0 deletions .agents/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Durable project context for coding agents (Cursor, Claude Code, etc.). Feature w
|------|---------|
| [`../../AGENTS.md`](../../AGENTS.md) | Root agent entrypoint (CE loop, commands, index) |
| [`DEVELOPMENT.md`](DEVELOPMENT.md) | TEE trust model, one-CVM ops — read when planning TEE/compose work |
| [`github-work-index.md`](github-work-index.md) | **Only** in-repo GitHub issue/PR map — do not scatter `#N` elsewhere |
| [`../skills/`](../skills/) | Domain skills (Rust, Docker, Stripe); coexist with CE plugin skills |

CE artifacts live under [`docs/brainstorms/`](../../docs/brainstorms/), [`docs/plans/`](../../docs/plans/), [`docs/solutions/`](../../docs/solutions/), and [`todos/`](../../todos/). Product docs for humans stay in [`docs/`](../../docs/). Humans: [`README.md`](../../README.md).
76 changes: 76 additions & 0 deletions .agents/docs/github-work-index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# GitHub work index

**Only** place in this repo that may cite GitHub issue or PR numbers.

Product docs, READMEs, code comments, and plans describe work by **name** and link here (or to durable docs) when a tracker ID is needed. See [`.cursor/rules/no-issue-pr-references.mdc`](../../.cursor/rules/no-issue-pr-references.mdc).

Repo: [BreadchainCoop/sigstack-bot](https://github.com/BreadchainCoop/sigstack-bot)
Issues: https://github.com/BreadchainCoop/sigstack-bot/issues
Pull requests: https://github.com/BreadchainCoop/sigstack-bot/pulls

When adding work, update **this file**—do not sprinkle `#N` into other paths.

## How to use

| Need | Do |
|------|-----|
| Relate code/docs to a ticket | Link or name the topic; agents look up the number here |
| Open / comment / close a ticket | Use `gh` against the URLs below |
| Ship a PR | Tracker refs go in the **PR body** on GitHub, not in commit subjects required for the tree |

## Commerce / Stripe / entitlements

Parent: [Website + Stripe: product suite checkout and subscriptions](https://github.com/BreadchainCoop/sigstack-bot/issues/13)

| # | Title | Notes |
|---|--------|--------|
| [52](https://github.com/BreadchainCoop/sigstack-bot/issues/52) | decision: stripe checkout and webhook hosting architecture | Decision |
| [53](https://github.com/BreadchainCoop/sigstack-bot/issues/53) | feat: entitlement model and encrypted store on cvm | `entitlements.enc` / `EntitlementsStore` |
| [54](https://github.com/BreadchainCoop/sigstack-bot/issues/54) | ops: stripe products and prices for cipherslate plans | Catalog; SKUs follow `site/src/lib/content/en.ts` |
| [55](https://github.com/BreadchainCoop/sigstack-bot/issues/55) | feat: stripe checkout session api from plan sku | Checkout session + success redirect query shape |
| [56](https://github.com/BreadchainCoop/sigstack-bot/issues/56) | feat: stripe webhooks and subscription lifecycle sync | |
| [57](https://github.com/BreadchainCoop/sigstack-bot/issues/57) | feat: !link and !claim-group for subscription binding | |
| [58](https://github.com/BreadchainCoop/sigstack-bot/issues/58) | feat: gate product commands on entitlements | |
| [59](https://github.com/BreadchainCoop/sigstack-bot/issues/59) | feat: alpha promo codes — 3-month bundle-all free path | `bundle-all-alpha` |
| [60](https://github.com/BreadchainCoop/sigstack-bot/issues/60) | ops: alpha code generation and revocation tooling | |
| [61](https://github.com/BreadchainCoop/sigstack-bot/issues/61) | feat: wire plans ctas to stripe checkout | |
| [62](https://github.com/BreadchainCoop/sigstack-bot/issues/62) | feat: checkout success cancel and alpha claim pages | `site/` commerce landings |
| [63](https://github.com/BreadchainCoop/sigstack-bot/issues/63) | feat: revise get-started flow and entitlement user comms | |
| [64](https://github.com/BreadchainCoop/sigstack-bot/issues/64) | feat: deploy commerce service and entitlements on phala cvm | |
| [65](https://github.com/BreadchainCoop/sigstack-bot/issues/65) | test: commerce and entitlement e2e test plan | |

## Product suite / architecture

| # | Title | Notes |
|---|--------|--------|
| [10](https://github.com/BreadchainCoop/sigstack-bot/issues/10) | Umbrella: product suite, website + Stripe, and Phala CVM split | Closed umbrella; one-CVM is current |
| [8](https://github.com/BreadchainCoop/sigstack-bot/issues/8) | Product: extract voice transcription to its own CVM (4 GB) | Closed; STT is remote Whisper, not a second CVM |
| [9](https://github.com/BreadchainCoop/sigstack-bot/issues/9) | Product: split translation into in-group vs Language Threads | Closed |
| [14](https://github.com/BreadchainCoop/sigstack-bot/issues/14) | Product: In-chat (group) translation | Closed |
| [15](https://github.com/BreadchainCoop/sigstack-bot/issues/15) | Product: Parallel Translation | Closed / retired into Language Threads |
| [16](https://github.com/BreadchainCoop/sigstack-bot/issues/16) | Product: Language Threads | Closed |
| [36](https://github.com/BreadchainCoop/sigstack-bot/issues/36) | feat: bilingual threads | Closed |

## Site / menus / i18n

| # | Title | Notes |
|---|--------|--------|
| [23](https://github.com/BreadchainCoop/sigstack-bot/issues/23) | Website: educate users with docs and product diagrams | Closed; `site/` |
| [27](https://github.com/BreadchainCoop/sigstack-bot/issues/27) | feat: localize bot menus for all !list-langs languages | Open |
| [51](https://github.com/BreadchainCoop/sigstack-bot/issues/51) | docs(site): per-product language support dropdown and eu/sw disclosure | Closed |

## Other open

| # | Title |
|---|--------|
| [40](https://github.com/BreadchainCoop/sigstack-bot/issues/40) | Add gated entry flow for new group chat members |
| [41](https://github.com/BreadchainCoop/sigstack-bot/issues/41) | Commands |
| [44](https://github.com/BreadchainCoop/sigstack-bot/issues/44) | User verification with kick for open groups |

## Pull requests

Do not list ephemeral PR numbers in product docs. For the current branch’s PR, use `gh pr view` / `gh pr list`. Add a short row here only when a PR is a durable cross-reference agents need often (rare).

| # | Title | Notes |
|---|--------|--------|
| — | — | (none pinned) |
42 changes: 42 additions & 0 deletions .cursor/rules/no-issue-pr-references.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
description: Never put GitHub issue or PR numbers in repo content
alwaysApply: true
---

# No issue / PR numbers outside the work index

Do **not** cite GitHub issues, pull requests, or tracker IDs in committed content—except in the single index:

**[`.agents/docs/github-work-index.md`](../../.agents/docs/github-work-index.md)**

That file is the navigation map (topic → issue/PR URL). Product docs, READMEs, code, and plans stay free of `#N`.

## Forbidden everywhere else

- Source code (comments, rustdoc, docstrings)
- Product/architecture docs under `docs/` (do not duplicate this index there)
- READMEs, compose/env examples, AGENTS.md body text (AGENTS may **link to the index**)
- Plans, brainstorms, solutions — describe work by name; link the index if a ticket must be found

Forbidden forms: `issue #62`, `#62`, `Issue 27`, `…/issues/62`, `…/pull/12`, “for #55”, “parent #13”, headings like `## Commerce landings (issue #62)`.

## Allowed

- Describe work without a number (“encrypted entitlements store”, “commerce landings”)
- Link durable repo docs (`docs/one-cvm-architecture.md`, `site/README.md`)
- Link **only** [`.agents/docs/github-work-index.md`](../../.agents/docs/github-work-index.md) when a tracker ID is needed
- Upstream library docs that are not issue/PR pages
- Tracker refs in **GitHub** issue/PR bodies (not in the tree)

```text
# BAD
/// Encrypted entitlements (issue #53).
## Commerce landings (issue #62)

# GOOD
/// Encrypted entitlements store; CRUD for future webhook / !link / gating.
## Commerce landings
# Need the ticket? → .agents/docs/github-work-index.md
```

When adding or renaming tracked work, **update the index**—do not scatter new `#N` citations. Strip leftovers in other files when you touch them.
5 changes: 3 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ docker compose -f docker/compose.yaml --env-file docker/.env up -d
| Doc | Why |
|-----|-----|
| [`.agents/docs/DEVELOPMENT.md`](.agents/docs/DEVELOPMENT.md) | TEE trust model, Phala one-CVM ops, **CVM volume / Signal identity** |
| [`.agents/docs/github-work-index.md`](.agents/docs/github-work-index.md) | **Only** in-repo map of GitHub issues/PRs (do not scatter `#N` elsewhere) |
| [`docs/one-cvm-architecture.md`](docs/one-cvm-architecture.md) | One CVM / one phone, **CVM storage (keep intact)** |
| [`docs/voice-transcription.md`](docs/voice-transcription.md) | Voice transcription product (NEAR Whisper; in-process fan-out) |
| [`docs/solutions/architecture-patterns/2026-08-13-cpu-tee-whisper-does-not-scale.md`](docs/solutions/architecture-patterns/2026-08-13-cpu-tee-whisper-does-not-scale.md) | Why STT is remote; never re-home Whisper in a CPU TEE |
Expand All @@ -69,14 +70,14 @@ docker compose -f docker/compose.yaml --env-file docker/.env up -d
| [`docs/solutions/`](docs/solutions/) | Compounded learnings from prior work |
| [`docs/plans/`](docs/plans/) | CE implementation plans |
| [`.agents/skills/`](.agents/skills/) | Domain skills (Rust, Docker, Stripe) |
| [`.cursor/rules/`](.cursor/rules/) | Cursor project rules (commits, compound loop, **CVM storage**) |
| [`.cursor/rules/`](.cursor/rules/) | Cursor project rules (commits, compound loop, **CVM storage**, **no scattered issue/PR refs**) |

## CVM storage (do not wipe)

**Never destroy live Phala volumes or replace the registered translation CVM for a routine upgrade.** Prod is **one** CVM (`0e82fa77-8b15-4dbd-89c4-9045ab911353`). Keep:

1. **Registered Signal phone** (`signal-config-translation` = phone B) — losing the volume unlinks the bot until ops re-registers (and takes over the number).
2. **Encrypted user prefs** (`group-prefs-translation` → `/data/group_prefs.enc`) — `!translate-me-on`, `!translate-all-on`, Language Threads bridges. Losing this forces every user to turn features back on.
2. **Encrypted user prefs + entitlements** (`group-prefs-translation` → `/data/group_prefs.enc` and `/data/entitlements.enc`) — `!translate-me-on`, `!translate-all-on`, Language Threads bridges, paid/alpha entitlements. Losing this forces every user to turn features back on and re-link commerce access.

Upgrade with `phala deploy --cvm-id 0e82fa77-8b15-4dbd-89c4-9045ab911353`. Do not create a new CVM, rename those volumes, or `down -v`. Do not recreate a transcription CVM or re-register phone A. TEE RAM wipe on reboot is expected; disk volumes are the identity. Details: [docs/one-cvm-architecture.md — CVM storage](docs/one-cvm-architecture.md#cvm-storage-keep-intact).

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# CipherSlate

TEE-hosted Signal bot for **voice transcription** and **group translation**, designed as an interoperable product suite (see [issue #10](https://github.com/BreadchainCoop/sigstack-bot/issues/10)).
TEE-hosted Signal bot for **voice transcription** and **group translation**, designed as an interoperable product suite.

Not a general AI chat assistant. Conversation history, tool-calling, and x402 credits have been removed from this fork.

Expand All @@ -15,7 +15,7 @@ Not a general AI chat assistant. Conversation history, tool-calling, and x402 cr

Add **one** bot to a Signal group.

**Organizer website:** [`site/`](site/) (SvelteKit → GitHub Pages) — https://breadchaincoop.github.io/sigstack-bot/ after Pages is enabled. See [site/README.md](site/README.md) and issue [#23](https://github.com/BreadchainCoop/sigstack-bot/issues/23). Legacy [`web/`](web/) is not the product storefront.
**Organizer website:** [`site/`](site/) (SvelteKit → GitHub Pages) — https://breadchaincoop.github.io/sigstack-bot/ after Pages is enabled. See [site/README.md](site/README.md). Legacy [`web/`](web/) is not the product storefront.

Details: [docs/one-cvm-architecture.md](docs/one-cvm-architecture.md) · [docs/voice-transcription.md](docs/voice-transcription.md) · [docs/in-chat-translation.md](docs/in-chat-translation.md) · [docs/language-threads.md](docs/language-threads.md) · [docs/bilingual-threads.md](docs/bilingual-threads.md)

Expand Down
52 changes: 52 additions & 0 deletions crates/signal-bot/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ pub struct Config {
/// Encrypted persistence for per-group bot preferences
#[serde(default)]
pub group_preferences: GroupPreferencesConfig,

/// Encrypted persistence for paid/alpha entitlements (separate from feature prefs)
#[serde(default)]
pub entitlements: EntitlementsConfig,
}

#[derive(Debug, Clone, Deserialize)]
Expand Down Expand Up @@ -146,6 +150,23 @@ pub struct GroupPreferencesConfig {
pub legacy_compose_hash: String,
}

#[derive(Debug, Clone, Deserialize)]
pub struct EntitlementsConfig {
/// Persist entitlements snapshot (TEE-encrypted)
#[serde(default = "default_true")]
pub persist: bool,

/// Encrypted entitlements file path (same `/data` volume as group prefs)
#[serde(default = "default_entitlements_path")]
pub storage_path: String,

/// Previous dstack `compose_hash` values (comma-separated). Used only when
/// DeriveKey is missing so AppInfo-encrypted `entitlements.enc` still decrypts
/// after a compose/image bump; the bot then re-saves with an app-id-only key.
#[serde(default)]
pub legacy_compose_hash: String,
}

// Default implementations
impl Default for SignalConfig {
fn default() -> Self {
Expand Down Expand Up @@ -218,6 +239,27 @@ impl GroupPreferencesConfig {
}
}

impl Default for EntitlementsConfig {
fn default() -> Self {
Self {
persist: default_true(),
storage_path: default_entitlements_path(),
legacy_compose_hash: String::new(),
}
}
}

impl EntitlementsConfig {
pub fn legacy_compose_hashes(&self) -> Vec<String> {
self.legacy_compose_hash
.split(',')
.map(str::trim)
.filter(|s| !s.is_empty())
.map(str::to_string)
.collect()
}
}

fn default_signal_service() -> String {
"http://signal-api:8080".into()
}
Expand Down Expand Up @@ -279,6 +321,10 @@ fn default_group_preferences_path() -> String {
"/data/group_prefs.enc".into()
}

fn default_entitlements_path() -> String {
"/data/entitlements.enc".into()
}

impl Config {
/// Load configuration from environment variables.
pub fn load() -> Result<Self> {
Expand Down Expand Up @@ -331,6 +377,7 @@ mod tests {
whisper: WhisperConfig::default(),
translate_all: TranslateAllConfig::default(),
group_preferences: GroupPreferencesConfig::default(),
entitlements: EntitlementsConfig::default(),
}
}

Expand Down Expand Up @@ -373,6 +420,11 @@ mod tests {
assert!(GroupPreferencesConfig::default()
.legacy_compose_hash
.is_empty());
assert!(EntitlementsConfig::default().persist);
assert_eq!(
EntitlementsConfig::default().storage_path,
"/data/entitlements.enc"
);
}

#[test]
Expand Down
Loading
Loading