colibri/docs/guide/roadmap/channels-plan.md
Sam & Claude 95c487546d
Some checks failed
CI / rust (pull_request) Has been cancelled
CI / markdown (pull_request) Has been cancelled
CI / port (pull_request) Has been cancelled
CI / agent-jail-pkgs (pull_request) Has been cancelled
docs(guide): port 39 procedural docs from clawdie-ai to colibri
New docs/guide/ tree — canonical home for operator-facing procedural docs.
Starlight frontmatter added to all files. 0.12 alignment fixes applied:

- v0.11.0 → v0.12.0 throughout
- PI_TUI_PROVIDER/MODEL → DEEPSEEK_API_KEY
- Headless Codex login → Agent runtime setup (zot + RPC mode)
- /login and auth.json references removed
- pi → zot in provider-fallback spawn reference
- colibri-provider-verify (was pi-provider-smoke)
- Language cleanup: smoke test → verification, fake → test,
  can't self-fix → requires operator intervention,
  broken → unresponsive, Fix anything broken → Verify all checks pass

Two-tree model: docs/wiki/ (decisions) + docs/guide/ (procedural).
Single source of truth in colibri. clawdie-ai docs/public/ to be retired.
2026-06-26 09:16:43 +02:00

9.8 KiB

title
Channels — Implementation Plan

Status: Plan only — no implementation started Last updated: 7.apr.2026 Strategy: see channels-roadmap.md

Current state

v0.9.0 is in development. Telegram is the only channel and is working. Nothing in this plan should be implemented until v0.9.0 is validated in production.

Conclusion

Two channels for v0.9.0: Telegram (done) + Web UI (new). Signal and WhatsApp deferred until real user demand exists. This covers phone dependency, privacy, and global reach without Java or Meta.


Exact steps to v0.9.0

Pre-condition: production validate v0.9.0

Before writing a single line of channels code, confirm these work on a real install:

  • Telegram bot receives and responds to messages
  • npm run backup produces a valid tarball
  • npm run doctor reports healthy
  • Metrics endpoint responds on port 9100
  • Grafana dashboard shows real data from VictoriaMetrics
  • ZFS snapshots are being taken by Sanoid
  • Management jail starts cleanly on boot

Verify all checks pass before proceeding.


v0.9.0 implementation sequence

Step 1 — Onboarding refactor

Make setup channel-agnostic. Currently assumes Telegram unconditionally.

  • setup/onboarding.ts — no change needed (doesn't touch Telegram)
  • setup/telegram-auth.ts — wrap in a channel selection step so Telegram is one option, not the only option
  • src/index.ts — guard TELEGRAM_BOT_TOKEN check behind TELEGRAM_ENABLED=true rather than failing hard if token missing
  • .env — add TELEGRAM_ENABLED=true (default true, no breaking change)

Step 2 — Invite code system

New users register by sending a code. Operator generates codes via CLI. Full implementation was prototyped on 16.mar.2026 — see git history or re-derive from channels-plan.md decisions section above.

Files to create:

  • src/invite.tscreateInviteCode, tryConsumeInviteCode, jidToFolder
  • scripts/invite.tsnpm run invite [--ttl Nd] → prints new code
  • scripts/users.tsnpm run users → lists registered users
  • scripts/codes.tsnpm run codes → lists all active/used invite codes
  • scripts/revoke.tsnpm run revoke <jid> → removes a registered user
  • scripts/revoke-code.tsnpm run revoke-code <code> → deletes unused code
  • scripts/suspend.tsnpm run suspend <jid> / npm run unsuspend <jid>

Files to modify:

  • src/db.tsinvite_codes table (with expires_at, revoked_at), invited_by column on registered_groups, suspended_at on registered_groups
  • src/types.tsinvitedBy?: string, suspendedAt?: Date on RegisteredGroup
  • src/channels/telegram.tsonInviteCode? callback in opts, check suspension
  • src/index.ts — wire callback, auto-register on valid code, reject suspended JIDs
  • package.json — add invite, users, codes, revoke, revoke-code, suspend, unsuspend scripts

Key decisions:

  • Single-use codes, optional TTL (default: no expiry)
  • Server stores invite_code → JID mapping permanently (enables re-link on browser clear)
  • Store invited_by from day one for future delegated invites
  • Operator-only invite generation to start (Model A)
  • Audit log: logs/invite-audit.log

Step 3 — Web UI channel

Files to create:

  • src/channels/web.ts — implements Channel, WebSocket server, JID prefix web:
  • Frontend: single HTML/CSS/JS chat page
  • setup/web-ui.ts — deploy frontend to CMS jail nginx

Files to modify:

  • src/index.ts — start web channel on WEB_UI_PORT if WEB_UI_ENABLED=true
  • .envWEB_UI_ENABLED=false, WEB_UI_PORT=3001
  • setup/index.ts — add web-ui step
  • setup/pf.ts — allow/restrict WEB_UI_PORT as appropriate

User identity on Web UI: invite code entered on first visit, stored in localStorage. Same invite system as Step 2 — no new concepts for the user.

Step 4 — Version bump and docs

  • Bump to v0.9.0
  • Update docs/internal/sessions/ with a session log for this work
  • Update install docs to mention Web UI and invite codes

Part A — Decisions

Decision 1 — End user registration model ✓ DECIDED: Invite code

Operator runs npm run invite → gets a short code (e.g. CLAWDIE-A3X7), shares it out of band. User sends it as their first message. Bot registers them automatically.


Decision 2 — Invite code behaviour ✓ DECIDED: Single-use, optional TTL

One code per person, works until used. Store invited_by: jid | 'operator' on every registered user from day one — enables upgrade to delegated invites later without a migration.

Security requirements:

  • Optional TTL: npm run invite --ttl 7d (default: no expiry). TTL stored in invite_codes.expires_at (nullable). Expired codes rejected silently.
  • Audit log: every code creation and consumption appended to logs/invite-audit.log with timestamp, code, JID (on consume), and source.
  • Revoke unused codes: npm run revoke-code <code> hard-deletes an unused code. Used codes cannot be revoked (JID is already registered — use npm run revoke <jid> instead).
  • List codes: npm run codes shows all active codes with status:
CODE          CREATED     EXPIRES    STATUS    JID
CLAWDIE-A3X7  2026-03-15  —          used      web:a3x7
CLAWDIE-B9K2  2026-03-17  2026-03-24 active    —

Schema change: add expires_at TIMESTAMP NULL and revoked_at TIMESTAMP NULL to invite_codes table. Rollback migration: DROP COLUMN expires_at; DROP COLUMN revoked_at;


Decision 3 — Web UI user identity ✓ DECIDED: Invite code login

User enters their invite code on first Web UI visit. Code becomes their persistent identity — survives browser clears, works on any device. Reuses the same invite system as Decision 1. No OAuth, no Google dependency.

Identity recovery: localStorage is used for convenience (avoids re-entry on repeat visits) but is NOT the authoritative identity store. The server maintains a permanent invite_code → jid mapping in the invite_codes table. If a user clears browser data, entering the same invite code re-links them to their existing JID — no lockout. tryConsumeInviteCode must check for an existing mapping before creating a new JID:

async function tryConsumeInviteCode(code: string): Promise<{ jid: string; isNew: boolean }> {
  const existing = await db.getJidByInviteCode(code);
  if (existing) return { jid: existing, isNew: false };  // re-link, not new
  // ... create new JID, store mapping
}

Decision 4 — Signal ✓ DEFERRED

signal-cli requires Java. Java in a jail adds attack surface. No mature non-Java Signal client exists. Revisit only when real users ask for it.


Decision 5 — WhatsApp ✓ DEFERRED

Meta-owned, metadata collected, unofficial library only. Revisit only when real users ask for it. If added: use Baileys (pure Node.js, no browser/Puppeteer dependency).


Part B — Implementation steps

Step 1 — Onboarding refactor (prerequisite for everything)

What changes:

  • setup/onboarding.ts — replace hardcoded Telegram token prompt with channel selection
  • setup/telegram.ts — Telegram becomes one option, not the default assumption
  • src/db.ts — add invite_codes table, add invited_by field to registered users
  • src/index.ts — handle first-message invite code registration logic
  • npm run invite — new CLI command to generate invite codes
  • npm run users — list registered users with channel and invited_by
  • npm run revoke <jid> — remove a registered user

What does not change:

  • Channel interface — already correct
  • Message pipeline — already channel-agnostic
  • JID storage — already channel-prefixed

Step 2 — Web UI (Tier 1)

What changes:

  • src/channels/web.ts — implements Channel, WebSocket server, JID prefix web:
  • src/index.ts — start web channel on WEB_UI_PORT
  • Frontend: single HTML/CSS/JS chat page served from CMS jail via nginx
  • setup/web-ui.ts — deploy frontend, configure nginx route
  • .envWEB_UI_ENABLED, WEB_UI_PORT
  • Invite code entered on first visit → stored in localStorage for convenience
  • localStorage is not authoritative — server re-links on re-entry (see Decision 3)

Rate limiting (WebSocket):

  • Per-IP: max 10 messages/minute; excess connections dropped with 429
  • Per-JID: suspended users rejected at WebSocket handshake (no session created)
  • Implementation: in-memory Map<ip, { count, resetAt }> in src/channels/web.ts

Operator user management:

  • npm run suspend <jid> — sets suspended_at, blocks all channels for that JID
  • npm run unsuspend <jid> — clears suspended_at, restores access
  • Suspension does not delete data — full history preserved

Access options:

  • Private: Tailscale only (no public exposure)
  • Public: chat.yourdomain.com via nginx, invite code is the only gate

Step 3 — Signal (deferred)

Add only when real users request it. Java/JRE in a jail is the accepted tradeoff at that point.


Step 4 — WhatsApp (deferred)

Add only when real users request it. Tier 2, opt-in, informed-consent warning in setup.


Step 5 — WeChat (future / community)

Requires Chinese business registration. Not part of main Clawdie install. JID prefix reserved: wc:


Summary

Step Target version Priority
1. Onboarding refactor v0.9.0 Now — blocks everything else
2. Web UI v0.9.0 Now — solves phone dependency
3. Signal Future Only on user demand
4. WhatsApp Future Only on user demand
5. WeChat Community Not in scope