Skip to content

feat(googlechat): full [googlechat] section — credentials + connection config-first - #1383

Merged
thepagent merged 2 commits into
mainfrom
feat/1379-googlechat-config-first
Jul 13, 2026
Merged

feat(googlechat): full [googlechat] section — credentials + connection config-first#1383
thepagent merged 2 commits into
mainfrom
feat/1379-googlechat-config-first

Conversation

@chaodu-agent

Copy link
Copy Markdown
Collaborator

Note

Stacked on #1382 (→ #1381 → main). Net-new diff = the last commit; will re-target as parents merge.

What problem does this solve?

Third platform slice of #1375: Google Chat's credentials/connection were env-only. [googlechat] graduates from the shared trust-only struct to a dedicated GoogleChatConfig (6 fields + trust), resolving each field config → GOOGLE_CHAT_* env → default.

How It Works

Layer Change
core GoogleChatConfig/ResolvedGoogleChat + resolve(); trust_config() view keeps the registry path unchanged
gateway DRY bonus: adapter construction was copy-pasted between from_env and standalone run() — deduplicated into GoogleChatAdapter::from_parts; GatewayGoogleChatConfig bridge + apply_googlechat_config(); new googlechat_webhook_path state honored by both binaries
unified Applies [googlechat] before warn_unenforceable_l1 — config-supplied audience builds the JWT verifier and satisfies the #1373 L1 check
docs example, config-reference (dedicated section), google-chat.md

Tests

  • googlechat_resolve_all_scenarios — defaults, config-wins (incl. explicit enabled=false beating env true), empty-string fallthrough, trust_config()
  • apply_googlechat_config_builds_adapter_and_feeds_l1_warning — pins the L1 integration: enabled-without-audience flagged → config audience clears → disabled removes adapter

Validation

  • clippy -D warnings clean (all-features + gateway no-features); unified + default builds pass
  • core 662 passed (1 known pre-existing macOS secrets failure); gateway l1_audit 6/6
  • rustfmt isolated: 0 new drift (googlechat.rs 45→45 pre-existing)

Closes #1379. Refs #1375, #1373.

@chaodu-agent
chaodu-agent requested a review from thepagent as a code owner July 13, 2026 00:51
@chaodu-agent

This comment has been minimized.

chaodu-agent added a commit that referenced this pull request Jul 13, 2026
…irst

Fourth platform slice of the config-first parity umbrella (#1375):
[teams] graduates from the shared PlatformTrustConfig to a dedicated
TeamsConfig carrying app_id, app_secret, allowed_tenants,
oauth_endpoint, openid_metadata, webhook_path alongside the trust
fields. Each field resolves config → TEAMS_* env → default.

- core: TeamsConfig/ResolvedTeams + resolve(); trust_config() view
- gateway: TeamsConfig::from_env refactored onto a from_reader shared
  with the new AppState::apply_teams_config (app_id + app_secret
  mandatory — incomplete section disables the adapter, matching
  env-only semantics); new teams_webhook_path state honored by both
  binaries' route mounts
- unified: applies [teams] before warn_unenforceable_l1
- docs: config.toml.example, config-reference.md (full field table),
  msteams-selfhosted.md

Closes #1380. Refs #1375. Stacked on #1383.
@chaodu-agent
chaodu-agent force-pushed the feat/1379-googlechat-config-first branch from 6f8ce39 to bcf35d8 Compare July 13, 2026 01:01
@chaodu-agent
chaodu-agent force-pushed the feat/1378-wecom-config-first branch from ec468f1 to 094c876 Compare July 13, 2026 01:01
@chaodu-agent

This comment has been minimized.

chaodu-agent added a commit that referenced this pull request Jul 13, 2026
…irst

Fourth platform slice of the config-first parity umbrella (#1375):
[teams] graduates from the shared PlatformTrustConfig to a dedicated
TeamsConfig carrying app_id, app_secret, allowed_tenants,
oauth_endpoint, openid_metadata, webhook_path alongside the trust
fields. Each field resolves config → TEAMS_* env → default.

- core: TeamsConfig/ResolvedTeams + resolve(); trust_config() view
- gateway: TeamsConfig::from_env refactored onto a from_reader shared
  with the new AppState::apply_teams_config (app_id + app_secret
  mandatory — incomplete section disables the adapter, matching
  env-only semantics); new teams_webhook_path state honored by both
  binaries' route mounts
- unified: applies [teams] before warn_unenforceable_l1
- docs: config.toml.example, config-reference.md (full field table),
  msteams-selfhosted.md

Closes #1380. Refs #1375. Stacked on #1383.
@chaodu-agent
chaodu-agent force-pushed the feat/1379-googlechat-config-first branch from bcf35d8 to 21f5f8a Compare July 13, 2026 01:05
chaodu-agent added a commit that referenced this pull request Jul 13, 2026
…irst

Fourth platform slice of the config-first parity umbrella (#1375):
[teams] graduates from the shared PlatformTrustConfig to a dedicated
TeamsConfig carrying app_id, app_secret, allowed_tenants,
oauth_endpoint, openid_metadata, webhook_path alongside the trust
fields. Each field resolves config → TEAMS_* env → default.

- core: TeamsConfig/ResolvedTeams + resolve(); trust_config() view
- gateway: TeamsConfig::from_env refactored onto a from_reader shared
  with the new AppState::apply_teams_config (app_id + app_secret
  mandatory — incomplete section disables the adapter, matching
  env-only semantics); new teams_webhook_path state honored by both
  binaries' route mounts
- unified: applies [teams] before warn_unenforceable_l1
- docs: config.toml.example, config-reference.md (full field table),
  msteams-selfhosted.md

Closes #1380. Refs #1375. Stacked on #1383.
@chaodu-agent

Copy link
Copy Markdown
Collaborator Author

Note

LGTM ✅ — Clean config-first graduation for [googlechat]: DRY adapter construction, correct resolve-order semantics, solid test coverage.

What This PR Does

Graduates [googlechat] from the shared PlatformTrustConfig (trust-only L3 fields) into a full first-class GoogleChatConfig struct carrying credentials + connection + trust, resolving each field via the project-wide config → env → default cascade (#1379).

How It Works

  • core: GoogleChatConfig (8 fields) + ResolvedGoogleChat + resolve(); trust_config() adapter preserves backward compat with the trust registry.
  • gateway: GoogleChatAdapter::from_parts deduplicates the token-cache/JWT-verifier construction that was copy-pasted between from_env and serve(); GatewayGoogleChatConfig bridge carries resolved values across crate boundary; googlechat_webhook_path now lives on AppState.
  • unified (main.rs): Applies config before warn_unenforceable_l1 so a config-supplied audience builds the JWT verifier and is never falsely flagged.
  • docs: config-reference gets a dedicated [googlechat] table; google-chat.md updated; example TOML annotated.

Findings

# Severity Finding Location
1 🟢 DRY win: from_parts eliminates ~40 lines of duplicated adapter construction googlechat.rs:273
2 🟢 Test pins config-wins-env, empty-string fallthrough, and trust_config() roundtrip in one test config.rs:2408
3 🟢 L1 integration test covers the full lifecycle: enabled-without-audience → flagged → audience-supplied → cleared → disabled lib.rs:933
4 🟢 Doc comment on PlatformTrustConfig accurately updated to reflect Teams-only scope config.rs:978
What's Good (🟢)
  • Consistent pattern: follows the exact same structure as the LINE and WeCom config-first slices — easy for future reviewers to understand.
  • Backward-compatible: env-only deployments flow through from_parts identically; trust_config() view means the registry override path is untouched.
  • ${}-aware empty-string filtering: opt_str properly treats "" (from unexpanded ${}) as unset, falling through to env — a subtle but correct design choice.
  • Feature-gate hygiene: #[cfg(not(feature = "googlechat"))] fallback for googlechat_webhook_path prevents compilation errors in minimal builds.
Baseline Check
  • PR opened: 2026-07-13
  • Main already has: PlatformTrustConfig with allow_all_users/allowed_users only; adapter construction via inline env-var logic in from_env() and serve().
  • Net-new value: Full credential/connection config (sa_key_json, sa_key_file, access_token, audience, webhook_path, enabled) with config-first resolution, DRY adapter construction, dedicated docs section.
5️⃣ Three Reasons We Might Not Need This PR
  1. Env vars work fine today — Google Chat deployments already configure everything via GOOGLE_CHAT_* env vars; the config file is optional. Counter: config-first is the project-wide direction (umbrella: config-first parity — every platform section resolves config → env → default for ALL fields #1375) and enables unified validation/docs.
  2. Stacked PR risk — depends on feat(wecom): full [wecom] section — credentials + connection config-first #1382feat(line): full [line] section — credentials + connection config-first #1381; merge conflicts if parents change. Counter: stacking is the agreed workflow; net-new diff is isolated to googlechat.
  3. Standalone gateway still env-onlyserve() doesn't call apply_googlechat_config, so config-first only works in unified mode. Counter: this is by design (documented in Phase 1c consolidation plan); standalone gateway is the legacy path.

@chaodu-agent

Copy link
Copy Markdown
Collaborator Author

Note

LGTM ✅ — Clean config-first graduation of [googlechat] from trust-only to full platform section.

What This PR Does

Google Chat's credentials and connection settings were env-only; this graduates [googlechat] from the shared PlatformTrustConfig to a dedicated GoogleChatConfig (6 fields + trust), resolving each field config → GOOGLE_CHAT_* env → default.

How It Works

  • Core: GoogleChatConfig / ResolvedGoogleChat + resolve() method; trust_config() view preserves the registry path unchanged.
  • Gateway: Adapter construction deduplicated into GoogleChatAdapter::from_parts (was copy-pasted between from_env and serve()); GatewayGoogleChatConfig bridge + apply_googlechat_config(); googlechat_webhook_path state honored by both binaries.
  • Unified: Applies [googlechat] before warn_unenforceable_l1 so config-supplied audience satisfies L1 without false warnings.
  • Docs: config-reference (dedicated section), google-chat.md updated, config.toml.example annotated.

Findings

# Severity Finding Location
1 🟢 Follows the established LINE/WeCom graduation pattern exactly — consistent layering config.rs, lib.rs, main.rs
2 🟢 DRY deduplication of adapter construction removes ~30 lines of copy-paste googlechat.rs:273
3 🟢 Empty-string fallthrough for ${} expansion correctly handled config.rs opt_str closure
4 🟢 Ordering discipline: apply_googlechat_config before L1 audit prevents false warnings main.rs
5 🟢 Comprehensive test: defaults, config-wins, empty-string fallthrough, trust_config(), L1 integration config.rs, lib.rs
What's Good (🟢)
  • Resolution pattern (config → env → default) is identical to LINE and WeCom — makes the codebase predictable.
  • trust_config() view method lets the shared registry path continue working unchanged during the transition.
  • GatewayGoogleChatConfig bridge type keeps the gateway crate free of openab-core dependency.
  • Test env cleanup is thorough — no global state leakage between scenarios.
  • #[cfg(not(feature = "googlechat"))] fallback for googlechat_webhook_path ensures compilation without the feature flag.
Baseline Check

5️⃣ Three Reasons We Might Not Need This PR

  1. Standalone gateway mode unchanged — The serve() path still reads all Google Chat settings from env only; config-first benefits only apply to unified mode today. However, this is by design (gateway has no config.toml) and consistent with LINE/WeCom.
  2. Google Chat is likely the least-deployed adapter — Investment in config ergonomics might not match usage. Counter: the config-first contract (umbrella: config-first parity — every platform section resolves config → env → default for ALL fields #1375) applies uniformly to all platforms.
  3. Stacked PR complexity — Depends on feat(wecom): full [wecom] section — credentials + connection config-first #1382/feat(line): full [line] section — credentials + connection config-first #1381; merge conflicts possible if parents change. Counter: the net-new diff is isolated to the last commit and the rebase plan is documented.

…n config-first

Third platform slice of the config-first parity umbrella (#1375):
[googlechat] graduates from the shared PlatformTrustConfig to a
dedicated GoogleChatConfig carrying enabled, sa_key_json, sa_key_file,
access_token, audience, webhook_path alongside the trust fields. Each
field resolves config → GOOGLE_CHAT_* env → default.

- core: GoogleChatConfig/ResolvedGoogleChat + resolve(); trust_config()
  view keeps the registry override path unchanged
- gateway: adapter construction deduplicated into
  GoogleChatAdapter::from_parts (was copy-pasted between from_env and
  the standalone run()); GatewayGoogleChatConfig bridge +
  AppState::apply_googlechat_config; new googlechat_webhook_path state
  honored by both binaries' route mounts
- unified: applies [googlechat] before warn_unenforceable_l1 — a
  config-supplied audience builds the JWT verifier and satisfies the
  #1373 L1 startup check (test pins the whole flagged→cleared flow)
- docs: config.toml.example, config-reference.md ([googlechat] gets its
  own full section), google-chat.md

Backward compatible: env-only deployments construct identically via the
shared from_parts path; trust-only [googlechat] sections keep parsing.

Closes #1379. Refs #1375. Stacked on #1382.
chaodu-agent added a commit that referenced this pull request Jul 13, 2026
…irst

Fourth platform slice of the config-first parity umbrella (#1375):
[teams] graduates from the shared PlatformTrustConfig to a dedicated
TeamsConfig carrying app_id, app_secret, allowed_tenants,
oauth_endpoint, openid_metadata, webhook_path alongside the trust
fields. Each field resolves config → TEAMS_* env → default.

- core: TeamsConfig/ResolvedTeams + resolve(); trust_config() view
- gateway: TeamsConfig::from_env refactored onto a from_reader shared
  with the new AppState::apply_teams_config (app_id + app_secret
  mandatory — incomplete section disables the adapter, matching
  env-only semantics); new teams_webhook_path state honored by both
  binaries' route mounts
- unified: applies [teams] before warn_unenforceable_l1
- docs: config.toml.example, config-reference.md (full field table),
  msteams-selfhosted.md

Closes #1380. Refs #1375. Stacked on #1383.
@chaodu-agent
chaodu-agent changed the base branch from feat/1378-wecom-config-first to main July 13, 2026 01:46
@chaodu-agent
chaodu-agent force-pushed the feat/1379-googlechat-config-first branch from c71528c to 3c6a924 Compare July 13, 2026 01:46
@thepagent
thepagent merged commit 51ff4d2 into main Jul 13, 2026
39 of 59 checks passed
chaodu-agent added a commit that referenced this pull request Jul 13, 2026
…irst

Fourth platform slice of the config-first parity umbrella (#1375):
[teams] graduates from the shared PlatformTrustConfig to a dedicated
TeamsConfig carrying app_id, app_secret, allowed_tenants,
oauth_endpoint, openid_metadata, webhook_path alongside the trust
fields. Each field resolves config → TEAMS_* env → default.

- core: TeamsConfig/ResolvedTeams + resolve(); trust_config() view
- gateway: TeamsConfig::from_env refactored onto a from_reader shared
  with the new AppState::apply_teams_config (app_id + app_secret
  mandatory — incomplete section disables the adapter, matching
  env-only semantics); new teams_webhook_path state honored by both
  binaries' route mounts
- unified: applies [teams] before warn_unenforceable_l1
- docs: config.toml.example, config-reference.md (full field table),
  msteams-selfhosted.md

Closes #1380. Refs #1375. Stacked on #1383.
thepagent pushed a commit that referenced this pull request Jul 13, 2026
…irst (#1384)

* feat(teams): full [teams] section — credentials + connection config-first

Fourth platform slice of the config-first parity umbrella (#1375):
[teams] graduates from the shared PlatformTrustConfig to a dedicated
TeamsConfig carrying app_id, app_secret, allowed_tenants,
oauth_endpoint, openid_metadata, webhook_path alongside the trust
fields. Each field resolves config → TEAMS_* env → default.

- core: TeamsConfig/ResolvedTeams + resolve(); trust_config() view
- gateway: TeamsConfig::from_env refactored onto a from_reader shared
  with the new AppState::apply_teams_config (app_id + app_secret
  mandatory — incomplete section disables the adapter, matching
  env-only semantics); new teams_webhook_path state honored by both
  binaries' route mounts
- unified: applies [teams] before warn_unenforceable_l1
- docs: config.toml.example, config-reference.md (full field table),
  msteams-selfhosted.md

Closes #1380. Refs #1375. Stacked on #1383.

* docs(config): PlatformTrustConfig is now the shared trust-view type

---------

Co-authored-by: chaodu-agent <chaodu-agent@users.noreply.github.com>
thepagent pushed a commit that referenced this pull request Jul 13, 2026
The platform-configured check read only the GOOGLE_CHAT_ENABLED env
var, so a deployment enabling the platform via `[googlechat] enabled =
true` (first-class section, #1383) would have its cron jobs rejected
at startup by validate_cronjobs. Use GoogleChatConfig::resolve()
(config → env → default) — same semantics the adapter itself uses.
Align the cronjob.md platform table and the schema note wording.

Verified: all-features clippy clean, cron tests 74 pass, bin tests 15
pass, schema conformance green. (Single-feature --features googlechat
clippy failure is pre-existing on main, unrelated.)
thepagent pushed a commit that referenced this pull request Jul 13, 2026
* feat(cron): support Google Chat platform for scheduled messages

* fix(cron): googlechat enablement check is config-first

The platform-configured check read only the GOOGLE_CHAT_ENABLED env
var, so a deployment enabling the platform via `[googlechat] enabled =
true` (first-class section, #1383) would have its cron jobs rejected
at startup by validate_cronjobs. Use GoogleChatConfig::resolve()
(config → env → default) — same semantics the adapter itself uses.
Align the cronjob.md platform table and the schema note wording.

Verified: all-features clippy clean, cron tests 74 pass, bin tests 15
pass, schema conformance green. (Single-feature --features googlechat
clippy failure is pre-existing on main, unrelated.)

---------

Co-authored-by: shaun-agent <265093149+shaun-agent@users.noreply.github.com>
Co-authored-by: chaodu-agent <chaodu-agent@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(googlechat): full [googlechat] section — credentials + connection config-first

2 participants