Worum es geht
Mehrere Plugins sollen sich einen Trunk teilen. Ein Contact Center betreibt einen Outbound-Dialer und
eine Support-Hotline, dazu vielleicht Telefon-Einwahl in Konferenzen und ein Softphone im Browser —
alle über dieselbe SIP-Registrierung, weil ein Trunk mit Rufnummernblock der Normalfall ist und nicht
die Ausnahme.
Die Fähigkeiten dafür sind da: ICommunicationChannelRegistry ist workspace-scoped, jedes Plugin kommt
an die Channels, hängt sich an IncomingCall und kann über PlaceCallAsync selbst wählen. Was fehlt,
sind die zwei Regeln, die aus „alle können alles" ein geordnetes Nebeneinander machen.
Beide gehören hierher, weil kein Plugin sie für sich entscheiden kann: Keines kann festlegen, dass ein
eingehender Anruf ihm gehört, und keines kann sich selbst ein faires Kapazitätsbudget geben.
Teil A — Eingehende Anrufe zuordnen
Heute
IncomingCallObserver meldet jeden eingehenden Anruf, und jeder Konsument, der sich an
ICommunicationChannel.IncomingCall gehängt hat, bekommt ihn. Wer zuerst AcceptAsync ruft, gewinnt.
Das ist ein Rennen, kein Routing — und bei zwei Konsumenten im selben Workspace ist es nicht
entscheidbar, wer gewinnen sollte.
Erschwerend: ICall.Target ist der Anrufer. Die gerufene Nummer gibt es im Vertrag nicht. Ein
Plugin könnte also nicht einmal dann korrekt filtern, wenn es wollte.
Was fehlt
Rufnummern am Account. SipAccount trägt heute Id, Workspace, Verbindung,
MaxConcurrentCalls und Enabled — aber keine Rufnummern. Ein Workspace kann gar nicht ausdrücken,
welche DIDs er hat.
Die gerufene Nummer im Vertrag. Auf IncomingCallEventArgs oder ICall, damit überhaupt
entscheidbar ist, wem ein Anruf gilt.
Eine Zuordnung Nummer → Konsument, pro Workspace konfiguriert. Im Kern eine kleine
PBX-Routingtabelle: Diese Durchwahl gehört der Support-Hotline, jene der Konferenz-Einwahl.
Beobachten ist nicht Besitzen
Die wichtigste Unterscheidung, und sie sollte im Vertrag stehen, nicht in einer Konvention:
- Ein Besitzer bekommt die Anrufe seiner Nummern und entscheidet über Annehmen und Abweisen. Genau
einer pro Nummer.
- Ein Beobachter — Aufzeichnung, Analyse, Wallboard, Compliance — sieht alle Anrufe und
entscheidet nichts. Beliebig viele.
Heute sind beide dasselbe Event, und deshalb kann ein Beobachter versehentlich einen Anruf annehmen.
Zu entscheiden
- Keine Zuordnung trifft zu. Ablehnen ist ehrlicher als von allen ignorieren zu lassen, und der
Ablehnungscode sollte derselbe Gedanke sein wie bei IQuiescableChannel: 503 schickt den Carrier
zur nächsten Route, 486 gibt dem Anrufer ein Besetztzeichen.
- Besitzer ist nicht aktiv (Plugin deaktiviert, im Workspace nicht verfügbar). Nicht stillschweigend
an den Nächstbesten fallen lassen — das wäre dieselbe Klasse Fehler wie ein Identitätsanbieter, der
bei Deinstallation auf anonym zurückfällt.
- Zwei Nummern, ein Besitzer muss gehen (eine Hotline mit mehreren Durchwahlen). Umgekehrt nicht.
Teil B — Kapazität teilen
Heute
MaxConcurrentCalls steht auf SipAccount, wird beim Anlegen validiert und liegt in der Datenbank —
und wird beim Wählen nirgends geprüft. CallControlService liest es nicht. Gebremst wird heute nur
vom Carrier.
Solange ein Plugin telefoniert, fällt das nicht auf. Sobald mehrere es tun, ist es ein Betriebsunfall
mit Ansage: Ein Outbound-Dialer belegt alle dreißig Leitungen und die Support-Hotline ist
unerreichbar.
Kontingente statt getrennter Accounts
Der naheliegende Reflex — jedes Plugin bekommt seinen eigenen Account — löst es schlechter. Bei einem
Trunk mit Rufnummernblock ist das technisch kaum sinnvoll teilbar, und die Kapazität wird starr
halbiert: Der Dialer bekommt fünfzehn und lässt sie brachliegen, während die Hotline an ihren fünfzehn
ansteht.
Besser ist ein Account mit Kontingenten: „Der Dialer darf höchstens zwanzig der dreißig belegen, zehn
bleiben eingehend reserviert." Getrennte Accounts bleiben richtig, wo der Unterschied wirklich außen
liegt — anderer Carrier, andere Marke, anderer Vertrag.
Zu entscheiden
- Reservieren, nicht zählen. Zwei gleichzeitige Wählversuche dürfen nicht beide gewinnen, weil
beide vor dem Hochzählen geprüft haben.
- Eingehende Anrufe zählen mit. Sie belegen dieselben Leitungen. Ein Kontingent, das nur Ausgehendes
begrenzt, schützt die Hotline nicht.
- Verhalten bei Erschöpfung. Ausgehend: benannter Fehler, kein stiller Fehlschlag. Eingehend: 503,
aus demselben Grund wie oben.
- Reservierung überlebt den Anrufer nicht. Ein Plugin, das abstürzt oder deaktiviert wird, darf
keine Leitungen dauerhaft binden.
Akzeptanzkriterien
Teil A
Teil B
Referenzen
custom/static-plugins/Communication/src/Application/Calls/IncomingCallObserver.cs — meldet, entscheidet
ausdrücklich nicht („that is a consumer plugin's (e.g. a PBX's) decision")
custom/static-plugins/Communication/src/Domain/Accounts/SipAccount.cs — MaxConcurrentCalls ohne Durchsetzung
custom/static-plugins/Communication/src/Infrastructure/Sdk/SdkVoiceChannel.cs — 503-statt-486-Begründung
- Künftige Konsumenten: Telefon-Einwahl, Softphone im Surface, Dialer, CRM
Worum es geht
Mehrere Plugins sollen sich einen Trunk teilen. Ein Contact Center betreibt einen Outbound-Dialer und
eine Support-Hotline, dazu vielleicht Telefon-Einwahl in Konferenzen und ein Softphone im Browser —
alle über dieselbe SIP-Registrierung, weil ein Trunk mit Rufnummernblock der Normalfall ist und nicht
die Ausnahme.
Die Fähigkeiten dafür sind da:
ICommunicationChannelRegistryist workspace-scoped, jedes Plugin kommtan die Channels, hängt sich an
IncomingCallund kann überPlaceCallAsyncselbst wählen. Was fehlt,sind die zwei Regeln, die aus „alle können alles" ein geordnetes Nebeneinander machen.
Beide gehören hierher, weil kein Plugin sie für sich entscheiden kann: Keines kann festlegen, dass ein
eingehender Anruf ihm gehört, und keines kann sich selbst ein faires Kapazitätsbudget geben.
Teil A — Eingehende Anrufe zuordnen
Heute
IncomingCallObservermeldet jeden eingehenden Anruf, und jeder Konsument, der sich anICommunicationChannel.IncomingCallgehängt hat, bekommt ihn. Wer zuerstAcceptAsyncruft, gewinnt.Das ist ein Rennen, kein Routing — und bei zwei Konsumenten im selben Workspace ist es nicht
entscheidbar, wer gewinnen sollte.
Erschwerend:
ICall.Targetist der Anrufer. Die gerufene Nummer gibt es im Vertrag nicht. EinPlugin könnte also nicht einmal dann korrekt filtern, wenn es wollte.
Was fehlt
Rufnummern am Account.
SipAccountträgt heute Id, Workspace, Verbindung,MaxConcurrentCallsundEnabled— aber keine Rufnummern. Ein Workspace kann gar nicht ausdrücken,welche DIDs er hat.
Die gerufene Nummer im Vertrag. Auf
IncomingCallEventArgsoderICall, damit überhauptentscheidbar ist, wem ein Anruf gilt.
Eine Zuordnung Nummer → Konsument, pro Workspace konfiguriert. Im Kern eine kleine
PBX-Routingtabelle: Diese Durchwahl gehört der Support-Hotline, jene der Konferenz-Einwahl.
Beobachten ist nicht Besitzen
Die wichtigste Unterscheidung, und sie sollte im Vertrag stehen, nicht in einer Konvention:
einer pro Nummer.
entscheidet nichts. Beliebig viele.
Heute sind beide dasselbe Event, und deshalb kann ein Beobachter versehentlich einen Anruf annehmen.
Zu entscheiden
Ablehnungscode sollte derselbe Gedanke sein wie bei
IQuiescableChannel: 503 schickt den Carrierzur nächsten Route, 486 gibt dem Anrufer ein Besetztzeichen.
an den Nächstbesten fallen lassen — das wäre dieselbe Klasse Fehler wie ein Identitätsanbieter, der
bei Deinstallation auf anonym zurückfällt.
Teil B — Kapazität teilen
Heute
MaxConcurrentCallssteht aufSipAccount, wird beim Anlegen validiert und liegt in der Datenbank —und wird beim Wählen nirgends geprüft.
CallControlServiceliest es nicht. Gebremst wird heute nurvom Carrier.
Solange ein Plugin telefoniert, fällt das nicht auf. Sobald mehrere es tun, ist es ein Betriebsunfall
mit Ansage: Ein Outbound-Dialer belegt alle dreißig Leitungen und die Support-Hotline ist
unerreichbar.
Kontingente statt getrennter Accounts
Der naheliegende Reflex — jedes Plugin bekommt seinen eigenen Account — löst es schlechter. Bei einem
Trunk mit Rufnummernblock ist das technisch kaum sinnvoll teilbar, und die Kapazität wird starr
halbiert: Der Dialer bekommt fünfzehn und lässt sie brachliegen, während die Hotline an ihren fünfzehn
ansteht.
Besser ist ein Account mit Kontingenten: „Der Dialer darf höchstens zwanzig der dreißig belegen, zehn
bleiben eingehend reserviert." Getrennte Accounts bleiben richtig, wo der Unterschied wirklich außen
liegt — anderer Carrier, andere Marke, anderer Vertrag.
Zu entscheiden
beide vor dem Hochzählen geprüft haben.
begrenzt, schützt die Hotline nicht.
aus demselben Grund wie oben.
keine Leitungen dauerhaft binden.
Akzeptanzkriterien
Teil A
Teil B
MaxConcurrentCallswird beim Wählen durchgesetztReferenzen
custom/static-plugins/Communication/src/Application/Calls/IncomingCallObserver.cs— meldet, entscheidetausdrücklich nicht („that is a consumer plugin's (e.g. a PBX's) decision")
custom/static-plugins/Communication/src/Domain/Accounts/SipAccount.cs—MaxConcurrentCallsohne Durchsetzungcustom/static-plugins/Communication/src/Infrastructure/Sdk/SdkVoiceChannel.cs— 503-statt-486-Begründung