Summary
Core (src/config/zod-schema.providers-core.ts:1425-1492) defines its own BlueBubblesAccountSchemaBase / BlueBubblesAccountSchema / BlueBubblesConfigSchema that duplicates the plugin-owned schema in extensions/bluebubbles/src/config-schema.ts. Schema additions need to be made in two places to land cleanly, and adding a single optional field requires touching three coupled artifacts (plugin schema, core schema, generated bundled-channel-config-metadata.generated.ts).
This was discovered while landing #69193 (sendTimeoutMs): the PR added the field to the plugin schema only. openclaw config set channels.bluebubbles.sendTimeoutMs 45000 failed with must NOT have additional properties because:
- Core Zod schema (
BlueBubblesAccountSchemaBase.strict()) rejected the unknown field at OpenClawSchema.safeParse.
- Generated Ajv metadata (
src/config/bundled-channel-config-metadata.generated.ts) was stale, so the secondary JSON-Schema validation (validation.ts:932) rejected it again.
Both are downstream of the plugin-owned Zod schema and shouldn't exist as independent sources of truth.
Boundary impact
Per CLAUDE.md:
Boundary rule: core must not know extension implementation details. Extensions hook into core through manifests, registries, capabilities, and public openclaw/plugin-sdk/* contracts. If you find core production code naming a specific extension... call it out and prefer moving coverage/logic to the owning extension or a generic contract test.
The duplicate violates this rule by name (BlueBubbles* symbols in core).
Proposed direction
- Delete
BlueBubblesAccountSchemaBase / BlueBubblesAccountSchema / BlueBubblesConfigSchema from src/config/zod-schema.providers-core.ts.
- Migrate the few core consumers (
config.allowlist-requires-allowfrom.test.ts, config.schema-regressions.test.ts, etc.) to either:
- the plugin-owned schema via
openclaw/plugin-sdk/bluebubbles / the test-utils bundled-plugin-public-surface helper, or
- generic channel-contract assertions instead of BlueBubbles-specific ones.
- Confirm
OpenClawSchema validation still flows through normalizeBundledChannelConfigs → getDirectChannelRuntimeSchema → plugin Zod schema; remove any path that double-validates against a separately-maintained core copy.
- Keep the generated
bundled-channel-config-metadata.generated.ts regen step (pnpm config:channels:gen) — it's the right pattern, just needs to be the only secondary surface.
Out of scope
- iMessage/Matrix/Discord/etc. likely have similar duplicates — file separately if confirmed; this issue is BlueBubbles only so the change set stays reviewable.
Context
Discovered: 2026-04-19, while testing #69193.
PR #69193 follow-up commits added belt-and-suspenders fixes to the duplicate surfaces (27bdf2d, 606f5e4) so the field works today; this issue tracks removing the duplication itself.
Summary
Core (
src/config/zod-schema.providers-core.ts:1425-1492) defines its ownBlueBubblesAccountSchemaBase/BlueBubblesAccountSchema/BlueBubblesConfigSchemathat duplicates the plugin-owned schema inextensions/bluebubbles/src/config-schema.ts. Schema additions need to be made in two places to land cleanly, and adding a single optional field requires touching three coupled artifacts (plugin schema, core schema, generatedbundled-channel-config-metadata.generated.ts).This was discovered while landing #69193 (
sendTimeoutMs): the PR added the field to the plugin schema only.openclaw config set channels.bluebubbles.sendTimeoutMs 45000failed withmust NOT have additional propertiesbecause:BlueBubblesAccountSchemaBase.strict()) rejected the unknown field atOpenClawSchema.safeParse.src/config/bundled-channel-config-metadata.generated.ts) was stale, so the secondary JSON-Schema validation (validation.ts:932) rejected it again.Both are downstream of the plugin-owned Zod schema and shouldn't exist as independent sources of truth.
Boundary impact
Per
CLAUDE.md:The duplicate violates this rule by name (
BlueBubbles*symbols in core).Proposed direction
BlueBubblesAccountSchemaBase/BlueBubblesAccountSchema/BlueBubblesConfigSchemafromsrc/config/zod-schema.providers-core.ts.config.allowlist-requires-allowfrom.test.ts,config.schema-regressions.test.ts, etc.) to either:openclaw/plugin-sdk/bluebubbles/ the test-utils bundled-plugin-public-surface helper, orOpenClawSchemavalidation still flows throughnormalizeBundledChannelConfigs→getDirectChannelRuntimeSchema→ plugin Zod schema; remove any path that double-validates against a separately-maintained core copy.bundled-channel-config-metadata.generated.tsregen step (pnpm config:channels:gen) — it's the right pattern, just needs to be the only secondary surface.Out of scope
Context
Discovered: 2026-04-19, while testing #69193.
PR #69193 follow-up commits added belt-and-suspenders fixes to the duplicate surfaces (27bdf2d, 606f5e4) so the field works today; this issue tracks removing the duplication itself.