The Honesty Rule
Every Aeion integration with a third-party device, API, or provider — POS bump bar, badge printer, BLE lock, ONVIF camera, OBD-II adapter, social DM, CRM sync, payment processor, transcription engine — goes through a typed adapter registry with one non-negotiable contract: **when no real call is made, return `transmittedToProvider:false` with a machine-readable reason. Never fabricate success.** 18 live adapter registries in production, none of them lying about whether the thing actually happened.
Why This Rule Exists
A platform that wires up hundreds of third-party integrations has a structural temptation: when no credentials are configured, when no device is connected, when an integration partner is down — return a fake "success" and move on. Dashboards stay green; downstream code stays simple.
The cost is invisible until something goes wrong. A customer thinks their printer queue is sending labels. Their POS receipts are "printing". Their CRM lead is "synced". Their door is "locked". And one day they discover the printer's been unplugged for a week, the CRM sync was a no-op the whole time, the door was never actually locked.
Aeion's adapter contract refuses this temptation in the type system. The honest-no-op pattern is enforced at the platform layer — every adapter declares:
```typescript interface AdapterCallResult { // The honest signal: did this call actually reach the external system? transmittedToProvider: boolean;
// When transmittedToProvider is false: machine-readable reason // so dashboards can show the gap and operators can investigate reason?: | "no_adapter" // No adapter is configured for this domain | "no_credentials" // Adapter configured but credentials missing | "device_offline" // Device known but unreachable | "feature_not_enabled" // Tenant doesn't have this extension | "managed_externally" // Customer handles this themselves | string; // Domain-specific reasons (per-adapter) } ```
Downstream code branches honestly on transmittedToProvider. Dashboards surface the reason. Compliance audits get an unambiguous answer to "did the thing actually happen?"
Two Registry Shapes
import { AdapterRegistry, SingleSlotRegistry, type AdapterCallResult,} from "@aeion/core/adapters";```
### AdapterRegistry<T> — Multiple adapters keyed by platform / protocol / provider
For domains where the system speaks to many different external systems at the same time — your tenant might use Salesforce _and_ HubSpot _and_ Pipedrive simultaneously, each via its own adapter:
```typescriptexport interface PrintResult extends AdapterCallResult { jobId?: string; // Inherits transmittedToProvider + reason from AdapterCallResult}
class NoTransportPrinter implements PrinterAdapter { readonly name = "none"; async print(): Promise<PrintResult> { return { transmittedToProvider: false, reason: "no_printer_adapter" }; }}
export const printerRegistry = new AdapterRegistry<PrinterAdapter>({ domain: "events-badge-printer", fallback: new NoTransportPrinter(),});
// Register real adapters at module setup:printerRegistry.register("brother-ql820", new BrotherQL820Adapter());printerRegistry.register("zebra-zd420", new ZebraZD420Adapter());printerRegistry.register("dymo-labelwriter", new DymoAdapter());
// Use it at runtime:const adapter = printerRegistry.get(tenant.config.printerKind);const result = await adapter.print(label);if (!result.transmittedToProvider) { logger.warn(`Print no-op: ${result.reason}`); // Surface "no printer configured" in admin UI; do not pretend the label printed}```
### SingleSlotRegistry<T> — Service has exactly one global adapter slot
For domains where only one external connection is meaningful at a time — PTZ camera control, an access-control lock controller, an NDI video pipeline. One adapter active; replacing it swaps the implementation atomically:
```typescriptexport const ptzRegistry = new SingleSlotRegistry<PtzAdapter>({ domain: "sentinel-ptz", fallback: new NoPtzAdapter(),});
// Module setup wires in the real adapter when available:if (env.ONVIF_PTZ_URL) { ptzRegistry.set(new OnvifPtzAdapter(env.ONVIF_PTZ_URL));}
// Use it:const result = await ptzRegistry.get().preset(camera, 3);if (!result.transmittedToDevice) { logger.warn(`PTZ no-op: ${result.reason}`); // e.g. "no_ptz_adapter"}18 Live Adapter Registries — A Cross-Module Catalog
POS hardware
A keyed adapter registry routing to bump bar / receipt printer / cash drawer / customer display / scale, over Aeion Bridge serial + USB.
Badge printers
A keyed adapter registry routing to Brother QL-820, Zebra ZD420, Dymo LabelWriter, or manual mode.
CRM sync
A keyed adapter registry routing to Salesforce / HubSpot / Pipedrive / Zoho / Copper for event-lead sync.
Social DMs
A keyed adapter registry routing to Twitter / X, LinkedIn, Facebook, and Instagram.
IoT control
A keyed adapter registry routing to MQTT · Modbus · BACnet · KNX · Z-Wave · Zigbee · Matter.
IoT ping
A keyed adapter registry handling device-online probes and polling fallbacks for non-MQTT devices.
Streaming protocols
A single-slot registry per protocol routing to NewTek NDI, SRT (Haivision / OBS), and OBS-NDI virtual camera.
FTP delivery
A keyed adapter registry routing to SFTP · FTPS · classic FTP · S3-as-FTP for dailies handoff.
Compliance providers
A keyed adapter registry routing to A2P 10DLC registrars and STIR/SHAKEN attestation providers.
Triage analysis
A single-slot registry routing to symptom-checker engines (Isabel, Mediktor, MedAware).
Geo-IP
A single-slot registry routing to MaxMind / IPInfo / IP2Location / IPGeolocation.
Recruit screening
A keyed adapter registry routing to Checkr · GoodHire · HireRight · Sterling background checks.
PITR (Aegis)
A single-slot registry routing to pgBackRest, an externally customer-managed Postgres, or a no-op.
Sentinel access control
A single-slot registry routing to Wiegand · OSDP · BACnet · KNX badge readers.
Sentinel PTZ
A single-slot registry routing to ONVIF / Pelco-D camera control.
Smart Space control
A keyed adapter registry over HTTP / MQTT / BACnet routing to HVAC · Lighting (Lutron / Crestron) · Shades · AV-over-IP.
Smart Space ping
A keyed adapter registry over HTTP / MQTT / BACnet handling building-IoT device-health probes.
Core (platform)
The base registry implementation every domain above is built on.
What This Looks Like in a Customer Dashboard
The honest no-op contract shows up directly in the admin UI — not buried in logs. Examples from live screens:
``` Events / Badge Print Queue ──────────────────────────────────────── 12 badges queued for John Smith's check-in ↳ Status: NOT TRANSMITTED — reason: no_printer_adapter "Configure a printer in Events Settings → Badge Printers to start fulfilling the queue."
Smart Spaces / Climate Zone 3 ──────────────────────────────────────── HVAC setpoint command sent: 72°F ↳ Status: NOT TRANSMITTED — reason: device_offline "Last seen 14 min ago. Override applied locally; will retry every 60s until device responds."
Connect / CRM Sync — Salesforce ──────────────────────────────────────── 342 leads pending sync ↳ Status: NOT TRANSMITTED — reason: no_credentials "OAuth token expired on 2026-05-15. Reconnect in Connect → Integrations → Salesforce."
Sentinel / Access Control — Front Door ──────────────────────────────────────── Lock command issued at 14:23:11 ↳ Status: TRANSMITTED — jobId: lck_abc123 "Door locked successfully. Confirmed by relay state." ```
Operators see precisely what's happening. They don't get blindsided by "everything's green" until something fails at the worst possible moment.
Operator Override — When Customers Manage Externally
For tenants who don't run a given integration through Aeion at all (custom in-house POS, dedicated PMS, self-managed Postgres), the adapter contract handles them honestly too:
typescript
class ExternalManagedAdapter implements PitrAdapter {
readonly name = "external";
async collectStatus(): Promise<PitrStatusSnapshot> {
return {
enabled: false,
reason: "managed_externally",
customerClaimedRpo: this.config.customerClaimedRpo,
};
}
}
Compliance reports surface the customer-claimed RPO verbatim alongside a clear indicator that _Aeion did not measure this number, the customer attested to it_. SOC2 / ISO27001 auditors get an honest answer instead of a fabricated platform claim.
Per-Key Manager Pattern — The Adjacent Discipline
The honesty rule is one half of the platform's "be honest about external systems" discipline. The other half is the per-key manager pattern — for services that hold a _per-resource subprocess or connection_ (FFmpeg pipeline per stream, RTSP relay per camera, encoder per destination):
```typescript // Wrong: module-level singleton — throws "already running" on second start let activeEncoder: EncoderInstance | null = null;
// Right: per-key manager class CompositorManager { private instances = new Map<string, CompositorInstance>(); async start(streamId: string, opts: CompositorOpts) { / ... / } async stop(streamId: string) { / ... / } async shutdown() { / await all stops / } } ```
Live in production: one FFmpeg relay per camera, a multi-source compositor per stream, and a multi-destination encoder per stream — each managed the same way.
The old singleton-with-throw pattern was an early-days shortcut that broke the moment a second stream started; the per-key manager replaced it platform-wide. Same discipline applies going forward — new services get the per-key pattern from day one. See the Architecture Bible for the full discipline.
Frequently Asked Questions
Throwing turns "no integration configured" into a runtime crash. Most consumers don't want a crash — they want to queue the work, surface the gap in the admin UI, and let the operator decide. The honest-no-op return shape says "this didn't actually happen, here's why" without breaking the calling code's flow. Consumers that *do* want strict mode wrap the call: `if (!result.transmittedToProvider) throw new GapDetected(result.reason)`.
Every adapter contract ships with a dedicated honesty-test suite that exercises the no-credentials / no-device / no-adapter paths and asserts `transmittedToProvider:false` plus a non-null `reason`. New adapters can't ship without passing it — enforced automatically, every time.
`AdapterRegistry<T>` is a keyed map — you can have multiple adapters registered at once and look one up by key (`get("salesforce")` vs `get("hubspot")`). `SingleSlotRegistry<T>` enforces "exactly one active adapter at a time" — used for domains where multiple-at-once would be semantically wrong (one PTZ controller per camera, one PITR engine per Postgres).
The admin UI translates `reason: "no_credentials"` → "Salesforce OAuth token expired. Reconnect in Settings." with a fix link. Operators get the friendly version; logs / API responses get the machine-readable code so downstream automation can branch.
The adapter normalizes their behavior into the contract. If the partner returns 200 OK with a fake success on missing credentials, the adapter checks the response body for a known failure signature and translates to `transmittedToProvider:false`. Per-adapter logic; we wrap their dishonesty so downstream Aeion code can stay honest.
Yes — implement the relevant interface (`PrinterAdapter` / `CrmSyncAdapter` / etc.) and register it via the appropriate registry at module setup. Every registry has a typed interface in `@aeion/core/adapters` plus a working reference implementation (the no-op fallback).
When a real adapter call fails (network error, 5xx from the partner), the adapter returns `transmittedToProvider:false` with a transient reason (`network_error`, `provider_5xx`). The bus consumer treats those as retryable and re-attempts with backoff. When the reason is non-transient (`no_credentials`, `feature_not_enabled`), retries are suppressed and the job goes to the DLQ for operator review.