Part of #3896.
Summary
Move messaging credential conflict detection out of the legacy channel metadata path and make it manifest/plan driven.
src/lib/messaging-conflict.ts previously derived provider suffixes and token keys from hard-coded channel metadata. Phase 4a replaces that path with compiled SandboxMessagingPlan credential bindings and manifest-declared provider metadata.
Scope
- Plan data is the credential-hash source of truth. Do not read, migrate, preserve, or fall back to legacy
providerCredentialHashes.
- No fallback is needed for the onboarding conflict gate; it reads the fresh compiled plan for the current sandbox.
- Model single-consumer messaging credentials in
ChannelManifest or the compiled channel plan.
- Resolve token comparison keys and bridge provider names from manifest credential metadata instead of
src/lib/sandbox/channels.ts constants.
- Keep comparisons hash-only; raw token values must not be persisted, logged, or returned from plan objects.
- Cover Telegram, Discord, Slack, and WeChat token-backed bridges; WhatsApp remains a no-op because it has no host-side token provider.
Source-of-Truth Decisions
- Pre-plan registry entries: the only legacy state Phase 4a honors is channel presence (
messagingChannels / disabledChannels). The boundary is the local registry plus OpenShell provider names. The source fix is channel-presence backfill from manifest provider metadata; credential hashes are not recovered. Regression coverage lives in conflict-detection-legacy.test.ts. Remove this path once pre-plan registry entries are no longer supported.
- Plan bindings without
credentialHash: a token-backed binding with credentialAvailable: true but no hash is treated as unverifiable metadata and classified conservatively as unknown-token for conflict detection. Phase 4a must not read raw tokens or legacy hashes to fill this gap. Regression coverage lives in conflict-detection-plan.test.ts and conflict-detection-entry.test.ts. Remove this state only when every credential source used by the planner can provide a non-secret hash at compile time.
channels add conflict checks: malformed conflict metadata is not repaired from legacy sources in Phase 4a. Conflict detection is best-effort for this mutation path unless an explicit same-token or unknown-token conflict is detected; --force remains the user override for detected conflicts. Future hardening can fail closed, but that is outside this phase. Regression coverage lives in policy-channel-conflict.test.ts.
- Legacy provider backfill: gateway probes may infer only which manifest channels are present for pre-plan registry rows. Probe errors skip writes so transient gateway failures do not persist false channel state. Regression coverage lives in
conflict-detection-legacy.test.ts and status-command-deps.test.ts. Remove this path with the pre-plan registry support removal.
Explicitly Out of Scope
- A compatibility adapter for
src/lib/messaging-conflict.ts.
- Legacy
providerCredentialHashes matching-token or distinct-token behavior for pre-plan registry entries.
- Runtime migration from legacy
providerCredentialHashes into messaging.plan.credentialBindings.
- Fallback logic that rebuilds conflict requests from legacy channel constants or legacy hash fields.
Acceptance Criteria
findChannelConflicts, findAllOverlaps, and backfillMessagingChannels are driven by manifest/plan credential metadata.
- Legacy registry backfill may infer channel presence from OpenShell providers, but it must not recover or preserve legacy credential hashes.
- Stopped channels still do not block another sandbox from claiming a token.
- Tests prove plan-driven hash-only comparisons, legacy channel-presence backfill, disabled-channel handling, and WhatsApp no-op behavior.
- Production conflict detection no longer imports legacy channel metadata and no longer exposes the legacy
messaging-conflict.ts adapter.
Part of #3896.
Summary
Move messaging credential conflict detection out of the legacy channel metadata path and make it manifest/plan driven.
src/lib/messaging-conflict.tspreviously derived provider suffixes and token keys from hard-coded channel metadata. Phase 4a replaces that path with compiledSandboxMessagingPlancredential bindings and manifest-declared provider metadata.Scope
providerCredentialHashes.ChannelManifestor the compiled channel plan.src/lib/sandbox/channels.tsconstants.Source-of-Truth Decisions
messagingChannels/disabledChannels). The boundary is the local registry plus OpenShell provider names. The source fix is channel-presence backfill from manifest provider metadata; credential hashes are not recovered. Regression coverage lives inconflict-detection-legacy.test.ts. Remove this path once pre-plan registry entries are no longer supported.credentialHash: a token-backed binding withcredentialAvailable: truebut no hash is treated as unverifiable metadata and classified conservatively as unknown-token for conflict detection. Phase 4a must not read raw tokens or legacy hashes to fill this gap. Regression coverage lives inconflict-detection-plan.test.tsandconflict-detection-entry.test.ts. Remove this state only when every credential source used by the planner can provide a non-secret hash at compile time.channels addconflict checks: malformed conflict metadata is not repaired from legacy sources in Phase 4a. Conflict detection is best-effort for this mutation path unless an explicit same-token or unknown-token conflict is detected;--forceremains the user override for detected conflicts. Future hardening can fail closed, but that is outside this phase. Regression coverage lives inpolicy-channel-conflict.test.ts.conflict-detection-legacy.test.tsandstatus-command-deps.test.ts. Remove this path with the pre-plan registry support removal.Explicitly Out of Scope
src/lib/messaging-conflict.ts.providerCredentialHashesmatching-token or distinct-token behavior for pre-plan registry entries.providerCredentialHashesintomessaging.plan.credentialBindings.Acceptance Criteria
findChannelConflicts,findAllOverlaps, andbackfillMessagingChannelsare driven by manifest/plan credential metadata.messaging-conflict.tsadapter.