Skip to content

docs: channel setup guides with compile-tested TOML examples#82

Closed
bglusman wants to merge 6 commits into
mainfrom
docs/channel-setup-guides
Closed

docs: channel setup guides with compile-tested TOML examples#82
bglusman wants to merge 6 commits into
mainfrom
docs/channel-setup-guides

Conversation

@bglusman
Copy link
Copy Markdown
Owner

Summary

Adds per-channel setup guides to docs/channels/ (Telegram, Matrix, Signal, WhatsApp), published to calciforge.org/channels/.

Each guide has: architecture diagram, prerequisites, config TOML, identity/routing wiring, verify step.

The key thing: TOML blocks are compile-tested

config.rs grows a test suite that loads each markdown file via include_str!, extracts every fenced [[channels]] TOML block, and parses it against the live CalciforgeConfig schema:

test config::tests::test_channel_docs_telegram_toml_blocks_valid ... ok
test config::tests::test_channel_docs_matrix_toml_blocks_valid   ... ok
test config::tests::test_channel_docs_signal_toml_blocks_valid   ... ok
test config::tests::test_channel_docs_whatsapp_toml_blocks_valid ... ok

If a field is renamed and the doc isn't updated, cargo test -p calciforge fails. The docs can't silently go stale.

Also adds test_channel_config_<channel>_inline unit tests as schema-correct reference examples.

AGENTS.md

Documents the standard and process so future channels follow the same pattern automatically.

Files

  • docs/channels/telegram.md — new
  • docs/channels/matrix.md — new (includes E2EE caveat)
  • docs/channels/signal.md — new (replaces config-example-only coverage)
  • docs/channels/whatsapp.md — new (supersedes crates/calciforge/WHATSAPP-SETUP.md; HMAC section corrected — it is implemented, not a TODO)
  • docs/index.md — links to channel pages
  • AGENTS.md — doc standard and process
  • crates/calciforge/src/config.rs — 8 new tests

Generated with Claude Code

Adds per-channel setup docs (Telegram, Matrix, Signal, WhatsApp) to
docs/channels/ as part of the public docs site (calciforge.org/channels/).

Each guide covers: architecture diagram, prerequisites, config TOML,
identity/routing wiring, and a verify step.

The TOML config blocks in each doc are compile-tested: config.rs grows
extract_toml_blocks() + test_channel_docs_<channel>_toml_blocks_valid
tests that load each markdown via include_str!, extract every fenced
[[channels]] block, and parse it against the live CalciforgeConfig schema.
If a field is renamed and the doc isn't updated, cargo test fails.

Also adds test_channel_config_<channel>_inline unit tests as
schema-correct reference examples for each channel kind.

AGENTS.md documents the standard and process: every channel needs a
docs/channels/ entry, tests in config.rs, and both must be updated
together when the schema changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 29, 2026 19:09
@qodo-code-review
Copy link
Copy Markdown

ⓘ You've reached your Qodo monthly free-tier limit. Reviews pause until next month — upgrade your plan to continue now, or link your paid account if you already have one.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 29, 2026

Greptile Summary

Adds four per-channel setup guides (telegram.md, matrix.md, signal.md, whatsapp.md) with architecture diagrams, config references, and identity-wiring examples, plus a documentation standard in AGENTS.md. The standout element is the compile-tested TOML strategy in config.rs: include_str!-loaded markdown is parsed at test time against the live schema, so field renames break CI immediately. Both gaps flagged in previous review rounds — silent discard of unclosed fences and vacuously-passing empty-block loops — are closed with assert!(!in_block, …) and assert!(!blocks.is_empty(), …) respectively.

Confidence Score: 5/5

Safe to merge — all previously flagged issues are resolved; the only remaining finding is a minor documentation wording inconsistency.

All P0/P1 concerns from prior review rounds have been addressed. The single remaining comment is a P2 doc wording nit ("ZeroClaw/OpenClaw" in index.md vs. ZeroClaw-only in the linked docs) that does not affect correctness or safety.

No files require special attention.

Important Files Changed

Filename Overview
crates/calciforge/src/config.rs Adds 8 tests: 4 inline schema-correctness tests and 4 doc-block tests that load markdown via include_str! and parse every [[channels]] TOML fence. Both previously flagged gaps (unclosed-fence assertion and non-empty assertion) are addressed.
docs/channels/telegram.md New setup guide: architecture diagram, prerequisites, config TOML, identity wiring, verify step. TOML block is compile-tested.
docs/channels/matrix.md New setup guide with E2EE caveat, HTTP long-poll architecture, TOML config block, and identity wiring. TOML block is compile-tested.
docs/channels/signal.md New setup guide for ZeroClaw-backed webhook receiver. Clearly separates Calciforge config (compile-tested) from ZeroClaw config (not tested, correctly excluded by [[channels]] filter).
docs/channels/whatsapp.md New setup guide superseding WHATSAPP-SETUP.md; includes HMAC-SHA256 verification details (corrected from TODO to implemented). TOML block is compile-tested.
AGENTS.md Adds the channel documentation standard and CI/test process for future contributors; clear and actionable.
docs/index.md Adds links to the four new channel guides. Minor: "ZeroClaw/OpenClaw" in the Signal/WhatsApp blurbs is not referenced in the linked docs.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["cargo test -p calciforge"] --> B["test_channel_docs_*_toml_blocks_valid"]
    B --> C["include_str! loads docs/channels/*.md"]
    C --> D["extract_toml_blocks()"]
    D --> E{"assert! unclosed fence"}
    E -- fail --> F["❌ compile error: unclosed toml block"]
    E -- pass --> G["channel_blocks_from_doc():\nfilter blocks containing [[channels]]"]
    G --> H{"assert! non-empty"}
    H -- fail --> I["❌ test failure: no [[channels]] blocks found"]
    H -- pass --> J["parse_channel_block() per block\nwrap in CalciforgeConfig envelope"]
    J --> K{"toml::from_str succeeds?"}
    K -- no --> L["❌ panic with block + schema error"]
    K -- yes --> M["assert cfg.channels[0].kind == expected"]
    M --> N["✅ test passes"]
Loading

Reviews (6): Last reviewed commit: "Update docs/index.md" | Re-trigger Greptile

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds per-channel setup documentation (Telegram, Matrix, Signal, WhatsApp) and introduces a test harness in calciforge to ensure the TOML config examples embedded in those docs continue to parse against the live CalciforgeConfig schema.

Changes:

  • Added new channel setup guides under docs/channels/ and linked them from docs/index.md.
  • Added calciforge unit tests that include_str! the markdown docs, extract fenced toml blocks containing [[channels]], and parse them as config.
  • Documented the “compile-tested TOML examples” documentation process in AGENTS.md.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
docs/index.md Links to new per-channel setup guides.
docs/channels/telegram.md Telegram long-poll setup guide + TOML example.
docs/channels/matrix.md Matrix long-poll setup guide + TOML example and E2EE caveat.
docs/channels/signal.md Signal webhook setup guide + TOML example.
docs/channels/whatsapp.md WhatsApp webhook setup guide + TOML example and HMAC verification notes.
crates/calciforge/src/config.rs Added tests to parse inline examples + doc-extracted TOML blocks.
AGENTS.md Documents the standard/process for channel docs and schema-checked examples.

current.push_str(line);
current.push('\n');
}
}
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extract_toml_blocks() silently drops an unterminated toml fence: if the markdown ends while `in_block` is still true, the current block is never pushed (and the doc tests can still pass). Consider detecting this and failing the test (e.g., `assert!(!in_block, "unterminated toml block")`) or pushing the final block so it gets parsed and fails appropriately.

Suggested change
}
}
assert!(!in_block, "unterminated ```toml block");

Copilot uses AI. Check for mistakes.
Comment on lines +1686 to +1691
fn test_channel_docs_telegram_toml_blocks_valid() {
let doc = include_str!("../../../docs/channels/telegram.md");
for block in channel_blocks_from_doc(doc) {
let cfg = parse_channel_block(&block);
assert_eq!(cfg.channels[0].kind, "telegram", "unexpected kind in block:\n{block}");
}
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These doc tests will pass if channel_blocks_from_doc(doc) returns an empty Vec (e.g., if the [[channels]] example block is accidentally removed or extract_toml_blocks fails to detect it). Recommend capturing let blocks = channel_blocks_from_doc(doc); then assert!(!blocks.is_empty(), "no [[channels]] TOML blocks found") before iterating (same applies to the other test_channel_docs_* tests below).

Copilot uses AI. Check for mistakes.
Comment thread crates/calciforge/src/config.rs
Comment thread crates/calciforge/src/config.rs
- assert!(!in_block) at end of extract_toml_blocks catches unclosed
  ```toml fences instead of silently dropping the partial block
- assert!(!blocks.is_empty()) before each test_channel_docs_* loop
  so the test fails visibly if the [[channels]] marker is ever
  accidentally removed from a doc file

Addresses PR #82 review feedback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 29, 2026 19:59
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.

Comment thread crates/calciforge/src/config.rs Outdated
cfg.channels[0].homeserver.as_deref(),
Some("https://matrix.example.com")
);
assert_eq!(cfg.channels[0].allowed_users, ["@operator:example.com"]);
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assertion won’t compile because ChannelConfig.allowed_users is a Vec<String> (see struct definition), but you’re comparing it to an array of &str. Compare against a Vec<String> (or a slice of String) instead.

Suggested change
assert_eq!(cfg.channels[0].allowed_users, ["@operator:example.com"]);
assert_eq!(
cfg.channels[0].allowed_users,
vec!["@operator:example.com".to_string()]
);

Copilot uses AI. Check for mistakes.
Comment thread crates/calciforge/src/config.rs Outdated
cfg.channels[0].webhook_listen.as_deref(),
Some("0.0.0.0:18796")
);
assert_eq!(cfg.channels[0].allowed_numbers, ["+15555550001"]);
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assertion won’t compile because ChannelConfig.allowed_numbers is a Vec<String>, but the RHS is an array of &str. Compare against a Vec<String> (or a &[String]) instead.

Suggested change
assert_eq!(cfg.channels[0].allowed_numbers, ["+15555550001"]);
assert_eq!(cfg.channels[0].allowed_numbers, vec!["+15555550001".to_string()]);

Copilot uses AI. Check for mistakes.
Comment thread docs/channels/whatsapp.md
Comment on lines +56 to +65
| Field | Required | Default | Description |
|---|---|---|---|
| `zeroclaw_endpoint` | yes | — | URL of the ZeroClaw/OpenClaw gateway |
| `zeroclaw_auth_token` | yes | — | Bearer token for the gateway |
| `webhook_listen` | no | `0.0.0.0:18795` | Address Calciforge listens on for incoming WhatsApp webhooks |
| `webhook_path` | no | `/webhooks/whatsapp` | URL path for incoming webhooks |
| `webhook_secret` | no | — | HMAC-SHA256 secret; when set, Calciforge rejects requests with invalid or missing `X-Hub-Signature-256` headers |
| `allowed_numbers` | yes | `[]` | E.164 phone numbers allowed to interact |
| `scan_messages` | no | `false` | Enable inbound adversarial content scanning |

Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The table header starts with ||, which breaks standard Markdown table rendering in many parsers. Use a single leading | for the header/separator rows.

Copilot uses AI. Check for mistakes.
Comment thread docs/channels/signal.md
Comment on lines +56 to +65
| Field | Required | Default | Description |
|---|---|---|---|
| `zeroclaw_endpoint` | yes | — | URL of the ZeroClaw/OpenClaw gateway |
| `zeroclaw_auth_token` | yes | — | Bearer token for the gateway |
| `webhook_listen` | no | `0.0.0.0:18796` | Address Calciforge listens on for incoming Signal webhooks |
| `webhook_path` | no | `/webhooks/signal` | URL path for incoming webhooks |
| `webhook_secret` | no | — | HMAC-SHA256 secret; when set, Calciforge rejects unsigned requests |
| `allowed_numbers` | yes | `[]` | E.164 phone numbers allowed to interact |
| `scan_messages` | no | `false` | Enable inbound adversarial content scanning |

Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The table header starts with ||, which breaks standard Markdown table rendering in many parsers. Use a single leading | for the header/separator rows.

Copilot uses AI. Check for mistakes.
Comment thread docs/channels/matrix.md
Comment on lines +72 to +80
| Field | Required | Description |
|---|---|---|
| `homeserver` | yes | Full URL of the Matrix homeserver |
| `access_token_file` | yes | Path to file containing the bot's access token |
| `room_id` | yes | Internal room ID (starts with `!`) |
| `allowed_users` | yes | Matrix user IDs permitted to send commands; empty list allows all room members (not recommended) |
| `scan_messages` | no (`false`) | Enable inbound adversarial content scanning |
| `allow_chat_secret_set` | no (`false`) | Allow `!secure set` via Matrix (not recommended) |

Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The table header starts with ||, which breaks standard Markdown table rendering in many parsers. Use a single leading | for the header/separator rows (and ensure the column count matches the header).

Copilot uses AI. Check for mistakes.
- matrix.md: correct allowed_users description — empty list errors at
  startup; "* " is the wildcard for all room members
- telegram.md: fix log message grep hint to match actual tracing output
  ("unknown Telegram sender — dropping silently sender_id=<id>")
- config.rs: update comment to say [[channels]]-filtered blocks (not
  "every fenced block"); change Vec<String> assertions to explicit
  .to_string() form for clarity

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 29, 2026 21:01
@bglusman bglusman force-pushed the docs/channel-setup-guides branch from 216e14f to 9968e17 Compare April 29, 2026 21:01
Calciforge is meant to be agent-agnostic; the prior phrasing made it sound
like ZeroClaw (or OpenClaw) was a required component of the system. The
actual contract is a webhook wire format — incoming POST to /webhooks/<channel>
and outbound POST to {gateway}/tools/invoke. Any gateway implementing those
endpoints works.

Channel docs now lead with the wire-format contract and call out ZeroClaw
as the known-working reference implementation. Future-work notes describe
embedded paths via zeroclawlabs (the Rust library) — distinct from running
ZeroClaw or OpenClaw as products.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@bglusman bglusman force-pushed the docs/channel-setup-guides branch from 9968e17 to 3d545f9 Compare April 29, 2026 21:02
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Comment on lines +1701 to +1707
for block in blocks {
let cfg = parse_channel_block(&block);
assert_eq!(
cfg.channels[0].kind, "telegram",
"unexpected kind in block:\n{block}"
);
}
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These doc-validation tests index cfg.channels[0], which produces a less-informative panic if a fenced TOML block contains the string [[channels]] (e.g. in a comment) but doesn’t actually deserialize into any channel entries. Also, if a single fenced block contains multiple [[channels]] entries, only the first one is checked. Consider asserting cfg.channels.len() == 1 (or at least !cfg.channels.is_empty()) and validating the kind for all parsed channel entries; same applies to the other test_channel_docs_*_toml_blocks_valid loops.

Copilot uses AI. Check for mistakes.
Comment thread docs/index.md Outdated
Comment thread docs/channels/whatsapp.md
Comment on lines +149 to +150
"entry": [{
"changes": [{
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sentence says the synthetic webhook message will be dropped unless 15555550001 is in an identity alias, but the WhatsApp channel normalizes from to E.164 with a leading + before identity lookup. Update the example text to reference +15555550001 (or otherwise clarify that aliases must include the +).

Copilot uses AI. Check for mistakes.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 29, 2026 22:42
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Comment thread docs/channels/telegram.md
Comment on lines +24 to +26
1. **Create a bot** via [@BotFather](https://t.me/BotFather): send `/newbot`, follow the
prompts, copy the token it returns (format: `1234567890:ABCDEFghijklmnopqrstuvwxyz01234567`)
2. **Find your Telegram user ID** (numeric, not your username):
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Telegram token example uses a real token-shaped value (digits + ':' + long base64-ish suffix). Since this repo extends gitleaks default rules and docs/channels/** is not allowlisted, this kind of string is likely to be flagged as a leaked credential. Consider switching to an obviously-non-token placeholder (e.g., REPLACE_WITH_TELEGRAM_BOT_TOKEN) and avoid embedding a token-shaped value in both the prose and shell snippet.

Copilot uses AI. Check for mistakes.
Comment on lines +1568 to +1571
if line.trim() == "```toml" {
in_block = true;
current.clear();
} else if line.trim() == "```" && in_block {
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extract_toml_blocks only enters TOML mode when a line trims to exactly ```toml. This misses valid Markdown fences like ```toml with trailing spaces or extra info-string attributes (which would silently skip blocks and reduce the effectiveness of the doc/schema sync). Consider loosening the check to starts_with("```toml") (after trimming) so the extraction matches common Markdown variations.

Suggested change
if line.trim() == "```toml" {
in_block = true;
current.clear();
} else if line.trim() == "```" && in_block {
let trimmed = line.trim();
if trimmed.starts_with("```toml") {
in_block = true;
current.clear();
} else if trimmed == "```" && in_block {

Copilot uses AI. Check for mistakes.
@bglusman
Copy link
Copy Markdown
Owner Author

Closing as superseded. The channel setup guide/schema-test work has been carried forward on top of current main in #89, which also updates the WhatsApp docs for the embedded client and adds Text/iMessage channel docs.

@bglusman bglusman closed this Apr 30, 2026
@bglusman bglusman deleted the docs/channel-setup-guides branch May 1, 2026 17:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants