Ship-PBX ↔ Nautilus Bus Integration
Spec version: 0.1 (draft) Status: Tier-C interface specification License (when published): spec text under CC BY 4.0; reference stubs + conformance test harness under Apache 2.0 Scope: this spec describes how an existing on-vessel PBX (Avaya, Mitel, Cisco UCM, Alcatel, Grandstream UCM, FreeSWITCH, Asterisk, a legacy proprietary system, or any other) integrates with Nautilus / Quanta so AI agents, telemetry, safety workflows, and downstream Nautilus domains consume PBX events without the PBX being replaced. Non-goals: this is not a PBX implementation guide. It does not specify SIP dialplans, codecs, or vendor-specific provisioning. It specifies only the bus contract between the PBX and Nautilus.
1. Purpose & audience
Most cruise vessels arrive in a Nautilus deployment with a pre-existing PBX that is not going to be ripped out on day one. ConnectOne may land gradually (new hulls, major refits) but many deployments are retrofits.
This spec is for three audiences:
- Ship integrators writing a bridge between a legacy PBX and the Nautilus / Quanta bus.
- Cruise operators evaluating how their existing telephony investment integrates with Nautilus.
- ConnectOne implementers — the closed NT Connect PBX service — who honor the same bus contract (so ConnectOne and a legacy PBX are interchangeable from Nautilus's perspective).
A valid ship-PBX integration consists of:
- A PBX bridge service (provided by the integrator, or by NT Connect as a reference implementation).
- A JetStream account issued by NT Connect (or delegated IdP) for the vessel + tenant.
- A JWT credential identifying the bridge as a ship-PBX integration.
- Publish/subscribe on the subjects defined in §4.
- Observance of the SLAs in §6, the priority semantics in §7, and the retention policies in §9.
2. Integration model
┌────────────────────┐ ┌────────────────────────────┐
│ Existing ship PBX │ │ Nautilus / Quanta bus │
│ (SIP, analog, or │ │ (NATS JetStream) │
│ hybrid; any brand)│ │ │
│ │ │ │
│ │ publish │ pbx.{tenant}.{vessel}.* │
│ ┌─►┌───────┐ │ ─────────► │ │
│ │ │ PBX │ │ │ │
│ │ │bridge │ │ ◄───────── │ nautilus.{tenant}.{vessel}.│
│ │ └───────┘ │ command │ pbx.command.* │
│ └─┘ │ │ │
└────────┬───────────┘ └─────────┬──────────────┬────┘
│ │ │
│ ┌───────────▼─┐ ┌───────▼─────────┐
│ │ CallCraft │ │ Nautilus domain │
│ │ AI agents │ │ services │
│ │ (closed) │ │ (AGPL) │
│ └─────────────┘ └─────────────────┘
│
└── SIP phones, cabin handsets, PA/GA, etc. (unchanged)
The bridge is a thin adapter. It translates PBX events (SIP INVITE / BYE / re-INVITE, cabin-call origination, PA announcement triggered on a designated handset, E911 dial, etc.) into NATS JetStream publishes on well-defined subjects, and translates bus commands (transfer, mute, disconnect, broadcast) into PBX actions.
The PBX itself is not replaced, reconfigured in its core, or required to understand NATS.
3. Authentication & authorization
3.1 Transport
- mTLS between the bridge and the NATS cluster (onboard NATS leaf node or shore cluster).
- NATS connection uses JWT + NKey authentication (NATS Auth Callout or decentralized JWT model).
3.2 Bridge JWT
Issued by NT Connect IdP (or delegated issuer).
| Claim | Value |
|---|---|
iss |
NT Connect IdP URL |
sub |
Bridge NKey public key |
type |
"pbx-bridge" |
tenant_id |
Tenant UUID |
vessel_id |
Vessel UUID (single vessel per JWT) |
capabilities |
Array subset of ["call.publish", "pa.publish", "ga.publish", "e911.publish", "recording.publish", "command.consume"] |
account |
NATS JetStream account name (for cross-account sourcing) |
exp |
Short — ≤ 24 h recommended; rotate automatically |
iat |
Issuance time |
jti |
Unique token ID for revocation |
Rotation: the bridge refreshes its JWT before exp via an OIDC-style refresh flow with the IdP. Compromised tokens revoke via jti blocklist.
Tenant isolation: the account claim binds the bridge to a specific JetStream account. Cross-account JetStream sourcing (Quanta's multi-tenant pattern) ensures PBX events from tenant A are not visible to tenant B even though they share infrastructure.
3.3 Capability model
| Capability | Grants publish on | Grants consume on |
|---|---|---|
call.publish |
pbx.{tenant}.{vessel}.call.* |
— |
pa.publish |
pbx.{tenant}.{vessel}.pa.* |
— |
ga.publish |
pbx.{tenant}.{vessel}.ga.* |
— |
e911.publish |
pbx.{tenant}.{vessel}.e911.* |
— |
recording.publish |
pbx.{tenant}.{vessel}.recording.*, pbx.{tenant}.{vessel}.transcript.* |
— |
command.consume |
— | nautilus.{tenant}.{vessel}.pbx.command.* |
A minimal bridge has call.publish + command.consume. A maritime-spec bridge adds PA/GA/E911/recording.
4. Bus subjects & streams
All subjects are rooted at pbx.{tenant_id}.{vessel_id}.* for PBX→Nautilus, and nautilus.{tenant_id}.{vessel_id}.pbx.command.* for Nautilus→PBX.
4.1 PBX → Nautilus (published by the bridge)
| Subject | Stream | Delivery | Purpose |
|---|---|---|---|
pbx.{t}.{v}.call.started |
PBX_CALLS |
at-least-once, ordered per call | New call initiated (inbound, outbound, or internal) |
pbx.{t}.{v}.call.answered |
PBX_CALLS |
at-least-once, ordered per call | Call picked up |
pbx.{t}.{v}.call.held |
PBX_CALLS |
at-least-once, ordered per call | Call placed on hold |
pbx.{t}.{v}.call.resumed |
PBX_CALLS |
at-least-once, ordered per call | Hold released |
pbx.{t}.{v}.call.transferred |
PBX_CALLS |
at-least-once, ordered per call | Transfer initiated (attended or blind) |
pbx.{t}.{v}.call.ended |
PBX_CALLS |
at-least-once, ordered per call | Call torn down |
pbx.{t}.{v}.call.dtmf |
PBX_DTMF |
at-least-once | DTMF digit (optional; only if integrator opts in) |
pbx.{t}.{v}.pa.broadcast |
PBX_PA |
at-least-once | PA announcement initiated (routine or informational tier) |
pbx.{t}.{v}.ga.alert |
PBX_GA |
exactly-once, priority | General Alarm — safety-critical preempting event |
pbx.{t}.{v}.e911.dialed |
PBX_E911 |
exactly-once, priority | E911 / emergency number dialed |
pbx.{t}.{v}.recording.available |
PBX_MEDIA |
at-least-once | Call recording stored (includes URL + SHA-256) |
pbx.{t}.{v}.transcript.available |
PBX_MEDIA |
at-least-once | Transcript ready (if bridge or downstream did STT) |
pbx.{t}.{v}.dlq.> |
PBX_DLQ |
— | Dead-letter subject for events the bridge couldn't serialize / publish cleanly |
4.2 Nautilus → PBX (consumed by the bridge)
| Subject | Purpose |
|---|---|
nautilus.{t}.{v}.pbx.command.transfer |
Request a call be transferred to another extension |
nautilus.{t}.{v}.pbx.command.mute / .unmute |
Mute/unmute a party in an active call |
nautilus.{t}.{v}.pbx.command.disconnect |
Tear down an active call (with reason) |
nautilus.{t}.{v}.pbx.command.broadcast |
Initiate a PA broadcast (Nautilus-originated emergency or scheduled announcement) |
nautilus.{t}.{v}.pbx.command.set-dnd |
Set / clear DND on an extension |
nautilus.{t}.{v}.pbx.command.wake-up |
Schedule a wake-up call |
nautilus.{t}.{v}.pbx.command.emergency-override |
Preempt non-priority traffic (muster event — see §7.2) |
4.3 Stream configuration (reference)
STREAM: PBX_CALLS
subjects: pbx.*.*.call.*
retention: limits
max_age: 30d (configurable per tenant; longer for fleets with audit needs)
storage: file
replicas: 3 (shore), 1 (onboard leaf)
STREAM: PBX_GA
subjects: pbx.*.*.ga.*
retention: limits
max_age: 1y
storage: file
replicas: 3 (shore) + 1 (onboard leaf, mirrored-on-reconnect)
discard: new (never drop GA events)
STREAM: PBX_E911
subjects: pbx.*.*.e911.*
retention: limits
max_age: 7y (regulatory retention)
storage: file
replicas: 3 (shore), with immediate onboard→shore mirror
STREAM: PBX_MEDIA
subjects: pbx.*.*.recording.*, pbx.*.*.transcript.*
retention: limits
max_age: tenant-configurable
storage: file — payloads are metadata + URLs; media objects live in S3-compatible object store
5. Event schemas
JSON payloads. All events carry a common envelope plus event-specific fields.
5.1 Envelope (every event)
{
"event_id": "01J9ABCDE1234567", // ULID
"event_type": "pbx.call.started",
"occurred_at": "2026-04-20T14:33:21.123Z",
"tenant_id": "t-uuid",
"vessel_id": "v-uuid",
"bridge_id": "nkey-public-key",
"schema_version": "1.0",
"data": { ... event-specific ... }
}
5.2 pbx.call.started
{
"event_type": "pbx.call.started",
"data": {
"call_id": "01J9CALLIDULID", // bridge-generated ULID, stable for the call
"direction": "inbound" | "outbound" | "internal",
"from": {
"extension": "5201", // cabin number where applicable
"display_name": "Cabin 5201",
"identity": "guest:g-uuid" | "crew:c-uuid" | null,
"role": "guest" | "crew" | "visitor" | "bridge" | "external" | null
},
"to": {
"extension": "0", // operator/concierge
"display_name": "Concierge",
"identity": "service:concierge" | null,
"role": "..."
},
"priority": "routine" | "informational" | "bridge" | "ga",
"codec": "opus" | "g711" | "g729" | "other",
"pbx_native_id": "opaque-pbx-specific-call-id", // for correlation with PBX logs
"sip_call_id": "sip-call-id@domain" // if available
}
}
5.3 pbx.call.ended
{
"event_type": "pbx.call.ended",
"data": {
"call_id": "01J9CALLIDULID",
"reason": "hangup" | "transfer" | "disconnect-by-command" | "network-loss" | "error",
"duration_sec": 127,
"quality": {
"mos": 4.2, // mean opinion score if PBX measures it
"jitter_ms": 12,
"loss_pct": 0.1
}
}
}
5.4 pbx.pa.broadcast
{
"event_type": "pbx.pa.broadcast",
"data": {
"broadcast_id": "01J9PAULID",
"tier": "routine" | "informational",
"origin_extension": "9001", // e.g., bridge, XO stateroom, mess room
"origin_identity": "crew:c-uuid" | "service:scheduler",
"zones": ["deck-5", "pool-deck", "crew-mess"],
"duration_sec": 30,
"recording_url": "s3://bucket/.../pa-01J9PAULID.opus" // if archived
}
}
5.5 pbx.ga.alert
{
"event_type": "pbx.ga.alert",
"data": {
"alert_id": "01J9GAULID",
"alert_type": "muster" | "fire" | "abandon-ship" | "man-overboard" | "medical" | "security" | "drill",
"origin_extension": "1" | "bridge",
"origin_identity": "crew:c-uuid",
"preempts": true,
"zones": ["all"] | ["..."],
"pre_recorded_message_id": "msg-uuid" | null,
"live_audio_active": true | false
}
}
5.6 pbx.e911.dialed
{
"event_type": "pbx.e911.dialed",
"data": {
"call_id": "01J9CALLIDULID",
"origin_extension": "5201",
"origin_cabin": "5201",
"location_attestation": {
"type": "static" | "dynamic",
"cabin": "5201",
"deck": 5,
"coordinates": { "lat": 25.77, "lon": -80.19 },
"attested_by": "pbx" | "bridge"
},
"dispatched_to": "onboard-medical" | "bridge" | "external-agency"
}
}
5.7 pbx.recording.available / pbx.transcript.available
{
"event_type": "pbx.recording.available",
"data": {
"call_id": "01J9CALLIDULID",
"recording_id": "r-uuid",
"url": "s3://bucket/path/recording.opus",
"sha256": "...",
"duration_sec": 127,
"codec": "opus",
"retention_until": "2027-04-20T00:00:00Z"
}
}
{
"event_type": "pbx.transcript.available",
"data": {
"call_id": "01J9CALLIDULID",
"transcript_id": "tr-uuid",
"url": "s3://bucket/path/transcript.json",
"engine": "whisper" | "other",
"language": "en",
"sentiment_score": null // filled by a Heimdall consumer; not by the bridge
}
}
5.8 Commands (nautilus.*.pbx.command.*)
Each command message carries a request_id for correlation. The bridge replies on pbx.{t}.{v}.command.ack.{request_id} with success/failure.
{
"request_id": "01J9REQULID",
"command": "transfer",
"target_call_id": "01J9CALLIDULID",
"target_extension": "2005"
}
6. SLAs and delivery semantics
| Guarantee | Target |
|---|---|
Bridge → bus call.started publish latency (nominal network) |
p95 ≤ 200 ms |
Bridge → bus ga.alert publish latency (onboard leaf to onboard consumers) |
p95 ≤ 100 ms |
| Command ack from bridge to bus | p95 ≤ 500 ms |
| Ship→shore propagation under nominal VSAT / Starlink | p95 ≤ 10 s |
| Replay on reconnect after WAN loss | automatic via JetStream source replication; no manual intervention required |
The bridge MUST NOT block PBX call handling on bus connectivity. If the bus is unreachable, the bridge buffers locally (JetStream leaf node) and resumes on reconnect. Primary PBX function continues regardless of bus state.
7. Priority & emergency signaling
7.1 Priority tiers
Four tiers, top = highest:
ga— General Alarm (muster, fire, abandon-ship, man-overboard). Preempts all other traffic fleet-wide. Exactly-once delivery. Never dropped.bridge— Bridge / officer priority. Preempts routine and informational traffic.informational— PA announcements, program broadcasts.routine— Normal calls, room service, concierge, crew coordination.
The priority field is present in every call.started and pa.broadcast event. Consumers (CallCraft agents, Nautilus domain services, signage, iTV) respect tiering per their own policy.
7.2 Muster / emergency-override flow
When Domain 6 (Safety & Muster) declares a muster event:
- Nautilus publishes
nautilus.{t}.{v}.pbx.command.emergency-overridewithalert_type, duration, and override policy. - Bridge enables emergency-only routing in the PBX: non-priority calls blocked, bridge/GA calls permitted, all iTV / signage / mobile endpoints receive the override.
- Bridge publishes
pbx.ga.alertevents as each PA zone fires. - On clear, Nautilus publishes
nautilus.{t}.{v}.pbx.command.emergency-overridewithclear=true; bridge restores normal routing.
GA and E911 streams use discard: new = false — the bus MUST NOT drop these events under any load condition. If the onboard leaf is full, the bridge backpressures the PBX notifications layer (e.g., buffers in bridge memory) rather than dropping.
8. Tenant isolation
Each ship-PBX integration authenticates into a dedicated JetStream account (Quanta's cross-account sourcing pattern). A bridge holding a JWT for tenant A / vessel V1 cannot publish to or consume from tenant B / vessel V2 subjects — the NATS server enforces the boundary at the account layer, not at application layer.
Multi-vessel tenants (cruise operators with fleets) obtain one account per vessel, with an aggregator account at the tenant level that sources streams from all vessel accounts for fleet-wide dashboards and HQ consumers.
9. Retention & data handling
| Stream | Default retention | Rationale |
|---|---|---|
PBX_CALLS |
30 days | Operational; tenant-configurable longer for audit needs |
PBX_DTMF |
7 days | Sensitive (may include PINs); short retention strongly recommended |
PBX_PA |
90 days | Operational history |
PBX_GA |
1 year | Safety audit |
PBX_E911 |
7 years | Regulatory |
PBX_MEDIA |
Tenant-configurable | Recordings are tenant-sensitive; default align with tenant's retention policy |
PBX_DLQ |
14 days | Operational debugging |
Recording and transcript payloads live in S3-compatible object storage; bus events carry URLs + SHA-256s, not media bytes.
10. Conformance testing
A reference conformance harness (Apache 2.0) will be published alongside this spec. It exercises:
- All 4.1 subject publishes with schema validation.
- All 4.2 command consume + ack flows.
- JWT authentication with synthetic tokens.
- Priority / preemption behavior under simulated GA load.
- Offline buffering with simulated WAN loss.
- Tenant-isolation enforcement (publishing to the wrong tenant fails).
A bridge is Nautilus-compatible if it passes the harness against the current spec version. Integrators publish conformance reports for each major PBX vendor they bridge.
11. Security considerations
- Bus events are not end-to-end encrypted. Call metadata, participant identities, PA announcements, GA alerts, DTMF digits (if opted in), recording URLs — all are visible to any authorized bus consumer within the tenant boundary. Tenants MUST understand this before deploying a bridge that publishes DTMF, or when recording + transcript retention is long.
- DTMF exposure. Many PBXs transport DTMF during financial-transaction IVR flows. Publishing DTMF to the bus exposes PINs / card-numbers to any authorized consumer. Strongly recommend: either never publish DTMF (
call.dtmfopt-in is off), or filter known sensitive windows (e.g., between prompt-played and tone-received events). - Recording retention + passport/biometric NFR-25. If calls include passport / biometric / medical content, recording retention must honor Nautilus NFR-25's jurisdiction-aware retention.
- Tenant isolation relies on cross-account JetStream sourcing. A flaw in that mechanism is a cross-tenant disclosure. Nautilus NFR-23 (Heimdall anomaly detection) SHOULD monitor cross-account access patterns for misuse.
- JWT rotation is mandatory. A compromised bridge JWT grants access to the tenant+vessel subject space until
exp. Default 24-hour lifetime. Emergency revocation viajtiblocklist. - GA / E911 cannot be back-pressured invisibly. If the bridge's local buffer fills during a network partition, it MUST surface a local alarm to the PBX operator console rather than silently drop events.
12. Known unknowns / TBD
- Exact subject namespace for
ship-to-shipcalling when Nautilus federates fleet-wide (needs spec extension). - Media codec negotiation when the bridge is in-line (vs passthrough). Current assumption: passthrough.
- Accessibility handset signaling (TTY, amplified, visual-alarm). Likely an extension subject
pbx.{t}.{v}.accessibility.*— draft separately. - iTV / cabin-GRMS interaction when call state changes (e.g., iTV mute on incoming call). Handled in a separate Tier-C spec for the iTV GRMS adapter (OQ-16).
- Multi-bridge scenarios (two PBXs on one vessel during migration). Likely resolvable by separate
bridge_ids and vessel-internal routing rules; not yet specified.
13. Relationship to Nautilus requirements
This spec satisfies the integration contracts implied by:
- FR-10 (voice/video transport via ConnectOne) — when the tenant runs a legacy PBX instead of ConnectOne, this spec is the replacement contract.
- F-D14-80 through F-D14-98 (Domain 14.F Maritime PBX Specialization) — the feature set Nautilus expects is the feature set the bridge must expose on the bus.
- F-D6-09 (AI-guided emergency announcements) — consumed from
pbx.ga.alertevents. - FR-15 (BLE location services) — location attestation in
pbx.e911.dialedinteroperates with BLE location data. - NFR-25 (passport/biometric retention) — honored by
PBX_MEDIAretention policy. - OQ-21 (Heimdall onboard/offline strategy) — Heimdall consumes
pbx.transcript.availableevents for sentiment; bridge behavior during offline windows must not break this consumer.
14. Licensing
When published, this spec is released under Creative Commons Attribution 4.0 (CC BY 4.0). The accompanying conformance test harness and reference stub are released under Apache License 2.0. Implementers are not required to use any NT Connect code to build a compliant bridge; implementing against this spec alone is sufficient.
"Nautilus", "Quanta", "ConnectOne", and "CallCraft" are trademarks of NT Connect Holdings, Inc. A compliant bridge MAY reference these marks when describing its integration with the corresponding products; the spec text itself is reusable under CC BY 4.0.
Draft. Review cycle open. Contact: nautilus-interfaces@ntconnect.example.