Skip to content

fix(googlechat): supported keyless ADC and reliable send-once - #1513

Open
chaodu-obk[bot] wants to merge 5 commits into
mainfrom
fix/pr-1512-review-f15-f28
Open

fix(googlechat): supported keyless ADC and reliable send-once#1513
chaodu-obk[bot] wants to merge 5 commits into
mainfrom
fix/pr-1512-review-f15-f28

Conversation

@chaodu-obk

@chaodu-obk chaodu-obk Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

What this PR does

Carries #1512 plus focused maintainer fixes. The final design uses two distinct service accounts: the attached runtime SA obtains its metadata credential and impersonates a dedicated Google Chat SA for chat.bot. Google prohibits access-token self-impersonation, so runtime/target equality is rejected before a base token is requested. Google Chat remains send-once, but normal delivery acknowledgement is independent from cosmetic streaming so API/auth failures reach core.

Review Contract

Goal

Deliver keyless Google Chat auth without SA keys using Google's supported runtime-SA -> distinct-Chat-SA impersonation flow; keep send-once rendering while correlating delivery success/failure; and resolve the complete review set (timeouts, token validation, precedence, command guards, docs, logging, backoff, and regression coverage).

Non-goals

Accepted Residual Risks

  • IAM error classification is best-effort; unrecognized bodies remain unclassified with the raw body truncated to 400 chars.
  • Token-request timeout (10s), acknowledged reply timeout (35s), and failed-refresh cooldown (30s) are fixed constants.
  • A configured static fallback is opaque and may represent another identity; degradation logs an explicit possible identity switch.
  • Google Chat delete remains an intentional no-op.

Acceptance Criteria

  • GOOGLE_CHAT_ADC_TARGET_SERVICE_ACCOUNT / adc_target_service_account / Helm adcTargetServiceAccount is required for ADC and propagated end-to-end.
  • Runtime and target service accounts must differ; equality fails before metadata token/IAM calls.
  • Runtime SA calls generateAccessToken for the configured distinct Chat SA; both identities are logged.
  • Google Chat send-once normal replies carry/await a request ID; success=false, promised-channel closure, and promised timeout return Err to core.
  • Blank/whitespace tokens are rejected across all OAuth boundaries.
  • Refresh failure serves a still-valid token and records a 30s cooldown so queued senders do not repeat the failed refresh.
  • Exact-head CI (check, Review Contract, platform-schema, Helm, standard + unified Docker smoke) is green at the final SHA.

Follow-ups

Commits

  • bd62ee49 - resolves F15-F28 (timeouts, boundary validation, precedence/docs/readability/operability).
  • 904f626c - makes delete an explicit no-op.
  • fa9d58a9 - locks shared token and edit-resource boundaries.
  • 3a1ce860 - replaces prohibited self-impersonation with distinct-target ADC, decouples delivery acknowledgement from streaming, clarifies fallback identity, and adds refresh-failure cooldown.

Validation

Exact-head CI is the executable gate. The prior head passed all 45 checks; the final SHA is re-running them. Independent correctness, security, platform-spec, testing, architecture, readability, docs/UX, operability, and simplicity audits will be re-run against the final delta.

sebastian-hsu and others added 2 commits August 31, 2026 08:44
Keyless ADC (MetadataTokenSource): mint a chat.bot-scoped token from the
workload's own GCP identity — GCE metadata (SA email + base token) -> IAM
Credentials generateAccessToken (self-impersonation). No SA key file.
Config [googlechat].use_adc / GOOGLE_CHAT_USE_ADC; auth precedence SA key >
ADC > static token; cache under the IAM-granted expireTime (fallback 3600s).

Send-once for Google Chat: its write rate limit is 1/sec/space
(create+patch+delete combined) so per-token streaming edits 429, and the
unified adapter returns a synthetic message id that patch can't target (404).
googlechat added to NON_STREAMING_PLATFORMS (renamed from
NON_EDITABLE_PLATFORMS); resolve_streaming forces send-once on both the
embedded dispatch (stream_prompt_blocks) and WebSocket gateway paths.

Also: Dockerfile.claude OPENAB_BUILD_FEATURES arg, Helm googleChat.useAdc
value, docs + config-first conformance entry + googlechat.toml schema record.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- F15: bound every token-mint request (SA-key exchange, metadata, IAM
  Credentials) with a 10s TOKEN_REQUEST_TIMEOUT so a hung connection
  cannot stall senders behind the cache write lock or defeat the
  ADC -> static token degradation path
- F16: reject empty/whitespace minted tokens at all three extraction
  sites (SA-key exchange, metadata base token, generateAccessToken)
  so a malformed response follows the degradation path instead of
  being cached as valid
- F17: correct the shorthand precedence wording in config.toml.example,
  config.rs, config-reference.md, google-chat.md env table, and
  values.yaml to name the configured-but-unloadable-key -> ADC fallback
- F19: refuse edit_message for non-resource-name (synthetic unified_)
  ids locally instead of sending a doomed patch (400 INVALID_ARGUMENT)
- F20: cross-reference the two sibling streaming gates
  (resolve_streaming / platform_supports_streaming) in both docs
- F21: document get_token precedence and its asymmetric failure
  behavior at the function
- F22: replace from_parts' five positional args with a named
  GoogleChatParts struct; all call sites and tests name their fields
- F23: install metadata_source only when no SA key loaded, so the
  code encodes the precedence it documents
- F24: drop private review-numbering labels (F1/F2/F4/F5) from
  source comments and test comments
- F25: fix the self-contradictory 'immutable after creation' GCE
  scope wording in docs/google-chat.md Option C
- F26: identify the orphaned Secret (agentFullname convention +
  discovery commands) in the key-to-ADC migration note
- F27: log the resolved service-account identity on successful mint
- F28: classify generateAccessToken failures (insufficient_scope /
  missing_role / api_not_enabled) in the error string

New regression tests: loaded-key-suppresses-ADC-source, blank-minted-
token rejection (wiremock), synthetic-id edit_message no-op (wiremock,
expect(0)), and error-classification table.
chaodu-obk Bot added 2 commits August 31, 2026 20:40
Route delete_message with the other unsupported Google Chat commands so it
returns before token resolution, logging, or network work instead of falling
through to the empty-send response path. Add a regression test that
distinguishes the old fallthrough behavior and update the platform schema
feature/quirk notes to document the explicit no-op.
Share one non-whitespace token validator across the SA-key, metadata, and
IAM response paths and cover empty/whitespace/valid values in a table test.
Require exact spaces/{space}/messages/{message} edit targets, cover malformed
resource shapes, and assert the valid edit path issues exactly one PATCH.
@chaodu-obk chaodu-obk Bot changed the title fix(googlechat): address PR #1512 review findings F15-F28 fix(googlechat): resolve PR #1512 review findings Aug 31, 2026
@chaodu-obk

This comment has been minimized.

@chaodu-obk chaodu-obk Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Important

CHANGES REQUESTED ⚠️ - The keyless ADC flow violates Google's self-impersonation contract, and send-once mode suppresses Google Chat delivery failures.

Consolidated review: #1513 (comment)

GitHub event: COMMENT - self-review delivery only; this is not an approval.

Comment thread crates/openab-gateway/src/adapters/googlechat.rs Outdated
Comment thread crates/openab-core/src/gateway.rs
Comment thread crates/openab-gateway/src/adapters/googlechat.rs Outdated
Comment thread crates/openab-gateway/src/adapters/googlechat.rs
- require a distinct adc_target_service_account for keyless ADC; reject
  runtime/target equality before requesting the metadata base token because
  Google prohibits access-token self-impersonation
- plumb GOOGLE_CHAT_ADC_TARGET_SERVICE_ACCOUNT through config, env, Helm,
  docs, schema, and config-first conformance; log both runtime and target SAs
- classify documented FAILED_PRECONDITION self-impersonation errors
- decouple normal-reply acknowledgements from cosmetic streaming so Google
  Chat remains send-once but carries/awaits request_id; promised ack failures,
  channel closure, and timeout now fail closed instead of reporting gw_sent
- remove unverifiable ADC/static-token identity-equivalence claims and log
  static fallback as a possible identity switch
- add 30s failed-refresh cooldown so queued senders reuse a still-valid token
  instead of serially repeating metadata/IAM timeouts
- add regression tests for distinct-target enforcement, ack error propagation,
  and refresh retry suppression
@chaodu-obk chaodu-obk Bot changed the title fix(googlechat): resolve PR #1512 review findings fix(googlechat): supported keyless ADC and reliable send-once Aug 31, 2026
@chaodu-obk

chaodu-obk Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

Note

LGTM ✅ - All prior findings are resolved at exact head 3a1ce8601cb9092893642a4537b8c17badc7bf67, and exact-head CI is green.

What This PR Does

This PR delivers supported keyless Google Chat authentication through a runtime service account impersonating a distinct Chat-app service account. It also keeps Google Chat send-once while preserving correlated delivery success and failure, and hardens token validation, fallback diagnostics, command routing, and refresh behavior.

How It Works

The gateway reads the attached runtime service account identity and metadata credential, rejects runtime/target equality before requesting the metadata token, and calls IAM Credentials generateAccessToken for the configured distinct target with chat.bot. Core now treats cosmetic streaming and delivery acknowledgement as separate capabilities. The Google Chat adapter explicitly handles edit/delete commands, logs possible fallback identity changes, and suppresses repeated failed refreshes with a bounded cooldown while a cached token remains valid.

Findings

# Severity Finding Location
F1 🟢 Resolved The ADC flow now requires distinct runtime and target service accounts and rejects prohibited self-impersonation before the metadata token request. crates/openab-gateway/src/adapters/googlechat.rs:1241
F2 🟢 Resolved Send-once rendering is decoupled from delivery acknowledgement; Google Chat failures, channel closure, and timeout now propagate to core. crates/openab-core/src/gateway.rs:48
F3 🟢 Resolved Static-token degradation is documented and logged as a possible identity switch instead of asserting identity equivalence. crates/openab-gateway/src/adapters/googlechat.rs:411
F4 🟢 Resolved Failed near-expiry refreshes record a 30-second cooldown so queued callers reuse the still-valid token without a retry storm. crates/openab-gateway/src/adapters/googlechat.rs:1138
F5 🟢 Praise Token boundaries, request timeouts, auth precedence, edit/delete guards, docs, and focused regression coverage are materially stronger. crates/openab-gateway/src/adapters/googlechat.rs
Finding Details

F1: Supported two-identity ADC flow

Resolved in 3a1ce860. MetadataTokenSource is constructed with a configured target service account. Refresh reads the runtime identity first, rejects equal identities case-insensitively, and only then requests the runtime metadata token and mints a chat.bot token for the distinct target. The regression test proves equality is rejected before the metadata token endpoint is called.

F2: Send-once with reliable delivery acknowledgement

Resolved in 3a1ce860. REPLY_RESPONSE_PLATFORMS and reply_requires_ack preserve a request ID for Google Chat independently of NON_STREAMING_PLATFORMS. A negative GatewayResponse, a closed acknowledgement channel, and the 35-second promised acknowledgement timeout return errors to core. Tests cover send-once acknowledgement and success=false propagation.

F3: Explicit fallback identity semantics

Resolved in 3a1ce860. The static access token remains an explicitly configured fallback, but code and documentation now state that it is opaque and may represent another principal. ADC mint failure emits an explicit possible identity switch error before degradation.

F4: Shared refresh failure cooldown

Resolved in 3a1ce860. When refresh fails but the cached token is still valid, the source records refresh_retry_after for 30 seconds. Callers waiting on the write lock immediately reuse the cached token during that window. The regression test executes two calls and verifies only one failed metadata refresh.

Addressing Inline Review Feedback

  • F1 - Prohibited self-impersonation: Addressed. The implementation now uses a distinct configured target and rejects runtime/target equality before retrieving the base token.
  • F2 - Delivery failures hidden by send-once: Addressed. Delivery acknowledgement is now independent of cosmetic streaming and failures reach core.
  • F3 - Unverified identity-safe fallback claim: Addressed. The claim was removed; fallback is documented and logged as a possible identity switch.
  • F4 - Repeated locked refresh attempts: Addressed. A 30-second retry cooldown prevents queued and subsequent callers from repeating the failed refresh.

All four inline threads are resolved by the current exact head. No reviewer names or internal coordination details are included here.

Baseline Check
  • PR opened: 2026-08-31
  • Declared base: main at d4f376f670982b2c93d5f58fddc2422fb19dd924
  • Reviewed head: 3a1ce8601cb9092893642a4537b8c17badc7bf67
  • Merge base: d4f376f670982b2c93d5f58fddc2422fb19dd924
  • Diff: 14 files, 1,274 additions, 84 deletions
  • Main already has: the Google Chat adapter and static/service-account-key authentication.
  • Net-new value: supported two-identity keyless ADC, send-once delivery acknowledgement, bounded refresh failure behavior, configuration propagation, documentation, and regression tests.
Validation
  • git diff --check origin/main...origin/pr-1513-review: passed.
  • Local PR ref, GitHub head, and reviewed head all matched 3a1ce8601cb9092893642a4537b8c17badc7bf67 before publication.
  • Exact-head CI: 44 check runs completed; all 43 non-skipped checks succeeded and the operator job was intentionally skipped.
  • Review Contract, platform-schema conformance, Helm tests, standard Docker smoke tests, and unified Docker smoke tests succeeded.
  • Exact-ref static inspection verified each prior finding and its focused regression coverage.
What's Good
  • The auth flow follows Google's distinct-principal impersonation contract and fails before token retrieval on invalid identity configuration.
  • Delivery semantics are explicit: send-once affects presentation, not reliability reporting.
  • Failure paths are bounded by request timeouts, acknowledgement timeout, and refresh retry cooldown.
  • Tests pin the highest-risk boundaries, including zero-call self-impersonation rejection, failure propagation, retry-storm prevention, and explicit delete no-op behavior.

5. Three Reasons We Might Not Need This PR

  1. Existing key-based authentication already works - operators without a keyless requirement can avoid the additional metadata and IAM path.
  2. The auth implementation still carries parallel caches - the follow-up in Consolidate googlechat token-source cache machinery (review F29 follow-up) #1514 may provide a cleaner shared abstraction.
  3. This is a companion to feat(googlechat): keyless ADC auth + send-once for the unified adapter #1512 - the net-new fixes could instead be applied directly to the original contributor branch.

These trade-offs do not introduce a correctness blocker for the current implementation.

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.

1 participant