Nautilus

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:

  1. Ship integrators writing a bridge between a legacy PBX and the Nautilus / Quanta bus.
  2. Cruise operators evaluating how their existing telephony investment integrates with Nautilus.
  3. 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:

  1. ga — General Alarm (muster, fire, abandon-ship, man-overboard). Preempts all other traffic fleet-wide. Exactly-once delivery. Never dropped.
  2. bridge — Bridge / officer priority. Preempts routine and informational traffic.
  3. informational — PA announcements, program broadcasts.
  4. 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:

  1. Nautilus publishes nautilus.{t}.{v}.pbx.command.emergency-override with alert_type, duration, and override policy.
  2. Bridge enables emergency-only routing in the PBX: non-priority calls blocked, bridge/GA calls permitted, all iTV / signage / mobile endpoints receive the override.
  3. Bridge publishes pbx.ga.alert events as each PA zone fires.
  4. On clear, Nautilus publishes nautilus.{t}.{v}.pbx.command.emergency-override with clear=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

  1. 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.
  2. 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.dtmf opt-in is off), or filter known sensitive windows (e.g., between prompt-played and tone-received events).
  3. Recording retention + passport/biometric NFR-25. If calls include passport / biometric / medical content, recording retention must honor Nautilus NFR-25's jurisdiction-aware retention.
  4. 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.
  5. JWT rotation is mandatory. A compromised bridge JWT grants access to the tenant+vessel subject space until exp. Default 24-hour lifetime. Emergency revocation via jti blocklist.
  6. 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-ship calling 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.alert events.
  • FR-15 (BLE location services) — location attestation in pbx.e911.dialed interoperates with BLE location data.
  • NFR-25 (passport/biometric retention) — honored by PBX_MEDIA retention policy.
  • OQ-21 (Heimdall onboard/offline strategy) — Heimdall consumes pbx.transcript.available events 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.