Markdown-Reader
BEAMREACTOR_CMDB
BeamReactor – CMDB (Configuration Management & Message Bus) #
> Version: 1.0
> Date: 2026-03-24
> Audience: Core developers, plugin developers, system integrators
> Scope: Inter-component communication, component registry, owner management
---
1. Overview #
The CMDB is BeamReactor's backbone for inter-component communication. It provides:
- A message bus for plugins, LLMs, load balancers, and services to exchange data
- A component registry to declare and track what's running
- An owner system for multi-tenant and federated deployments
- Polling (flag-based) and push (handler callback) delivery modes
| File | Role |
|------|------|
| `lib/cmdb/CMDB.class.php` | Core class — all static methods |
| `lib/cmdb/cmdb.sql` | Schema: 4 tables |
Namespace: `Beamreactor\CMDB\CMDB`
---
2. Tables #
| Table | Purpose |
|-------|---------|
| `cmdb_owners` | Owners: sites, clients, organisations |
| `cmdb_registry` | Declared components with dual ownership |
| `cmdb_messages` | Message bus (the queue) |
| `cmdb_subscriptions` | Who listens to what |
Installation #
use Beamreactor\CMDB\CMDB;
CMDB::install(); // Runs cmdb.sql — idempotent (CREATE IF NOT EXISTS)
---
3. Owners #
Owners represent the entities that control components. A component has two optional owners:
| Role | Field | Meaning |
|------|-------|---------|
| Business | `business_owner_id` | Client, contract holder, project sponsor |
| Technical | `technical_owner_id` | Ops team, sysadmin, DevOps |
Owner Types #
| Type | Usage |
|------|-------|
| `local` | This BeamReactor instance |
| `remote` | External site / federated node |
| `client` | End customer / tenant |
| `org` | Organisation / department |
| `system` | System-level (cron, scheduler) |
Creating Owners #
// Business owner (a client)
$client_id = CMDB::createOwner('client', 'acme-corp', 'ACME Corporation',
'https://acme.example.com/api', // remote endpoint
'admin@acme.com',
['contract' => 'PRO-2026', 'sla' => '99.9%'], // metadata
3 // trust level (high)
);
// Technical owner (ops team)
$ops_id = CMDB::createOwner('local', 'devops-team', 'DevOps Team');
Owner Methods #
| Method | Returns | Description |
|--------|---------|-------------|
| `createOwner($type, $name, ...)` | `int\|false` | Create or update an owner (upsert) |
| `getOwner(int $id)` | `array\|false` | Get owner by ID (decodes metadata JSON) |
| `findOwner($type, $name)` | `array\|false` | Find owner by type + name |
| `listOwners(?$type, ?$status)` | `array` | List owners with optional filters |
| `setOwnerStatus($id, $status)` | `int` | Change status: active, suspended, revoked, pending |
| `getOwnedComponents($id, $role)` | `array` | List components for an owner (business/technical/any) |
Inter-Site Authentication #
Owners can hold an API key for remote authentication:
// Generate and set (store the plain key, only the hash is saved)
$api_key = bin2hex(random_bytes(32));
CMDB::setOwnerApiKey($owner_id, $api_key);
// Verify later
if (CMDB::verifyOwnerApiKey($owner_id, $incoming_key))
{
// Authenticated
}
Keys are hashed with Argon2ID. The plain key is never stored.
Trust Levels #
| Level | Meaning |
|-------|---------|
| 1 | Maximum trust (internal core) |
| 3 | High trust (known partner) |
| 5 | Standard (default) |
| 7 | Low trust (new/unverified) |
| 9 | Minimal trust (sandboxed) |
---
4. Registry #
Every component (plugin, LLM, service, load balancer) should register itself on startup.
Registering a Component #
CMDB::register(
'plugin', // type
'xchange', // name
'1.0.0', // version
'Marketplace plugin', // description
['trading', 'rating', 'messaging'],// capabilities
['max_photos' => 5], // config
$client_id, // business owner
$ops_id // technical owner
);
Component Types #
| Type | Usage |
|------|-------|
| `plugin` | BeamReactor plugin |
| `llm` | LLM connector (Aegis, GPT, Claude...) |
| `lb` | Load balancer instance |
| `service` | Background service, daemon |
| `core` | BeamReactor core component |
Registry Methods #
| Method | Returns | Description |
|--------|---------|-------------|
| `register($type, $name, ...)` | `int\|false` | Register component (upsert) |
| `getComponent($type, $name)` | `array\|false` | Get component with owner info (JOIN) |
| `listComponents(?$type, ?$status)` | `array` | List with optional filters |
| `hasCapability($type, $name, $cap)` | `bool` | Check if component declares a capability |
| `setStatus($type, $name, $status)` | `int` | Update status |
| `heartbeat($type, $name)` | `int` | Update last_heartbeat timestamp |
| `getStaleComponents($minutes)` | `array` | Find components with no heartbeat since N minutes |
Component Identifier Format #
Components are referenced as `type:name` strings throughout the message bus:
$id = CMDB::componentId('plugin', 'xchange'); // "plugin:xchange"
$parts = CMDB::parseComponentId($id); // ['type' => 'plugin', 'name' => 'xchange']
---
5. Messages #
The message bus uses a simple queue with flags and priorities.
Sending a Message #
CMDB::send(
'plugin:xchange', // source
'plugin:messaging', // target
'xchange.first_contact',// channel
[ // payload (JSON)
'buyer_id' => 42,
'seller_id' => 7,
'product_id' => 123,
'message' => 'Interested in your item'
],
'notify', // action (optional)
CMDB::PRIORITY_NORMAL, // priority (optional, default 5)
3600, // TTL in seconds (optional, null = permanent)
42 // user_id (optional)
);
Request/Response Pattern #
For tracked exchanges with automatic correlation:
// Sender: create a request
$req = CMDB::request('plugin:xchange', 'plugin:messaging', 'xchange.first_contact', [
'buyer_id' => 42,
'seller_id' => 7,
]);
// $req = ['message_id' => 15, 'correlation_id' => 'a3f8...']
// Receiver: reply to it
CMDB::reply($req['message_id'], ['status' => 'conversation_opened']);
// Sender: check for responses
$responses = CMDB::getResponses($req['correlation_id'], 'plugin:xchange');
Polling (Receiver Side) #
// Get all pending messages
$messages = CMDB::poll('plugin:messaging');
// Filter by channel pattern
$messages = CMDB::poll('plugin:messaging', 'xchange.%');
// Quick checks
if (CMDB::hasPending('plugin:messaging'))
{
$count = CMDB::countPending('plugin:messaging');
}
`poll()` automatically marks messages as READ and sets status to `delivered`.
Priority Constants #
| Constant | Value | Use |
|----------|-------|-----|
| `PRIORITY_CRITICAL` | 1 | System failure, security alert |
| `PRIORITY_HIGH` | 3 | Needs prompt attention |
| `PRIORITY_NORMAL` | 5 | Default |
| `PRIORITY_LOW` | 7 | Background / informational |
| `PRIORITY_IDLE` | 9 | Cleanup, analytics |
Messages are delivered in priority order (1 first), then by creation date.
---
6. Flags #
Old-school bitfield system for message state tracking.
Flag Constants #
| Constant | Value | Meaning |
|----------|-------|---------|
| `FLAG_READ` | 1 | Message has been seen |
| `FLAG_ACK` | 2 | Receiver acknowledged |
| `FLAG_PROCESSED` | 4 | Action completed |
| `FLAG_ERROR` | 8 | Processing failed |
| `FLAG_EXPIRED` | 16 | TTL exceeded |
Usage #
// Set a flag
CMDB::setFlag($message_id, CMDB::FLAG_READ);
// Set multiple flags at once
CMDB::setFlag($message_id, CMDB::FLAG_ACK | CMDB::FLAG_PROCESSED);
// Clear a flag
CMDB::clearFlag($message_id, CMDB::FLAG_ERROR);
// Check flags (on a fetched message)
if (CMDB::hasFlag($msg['flags'], CMDB::FLAG_ACK))
{
// Already acknowledged
}
// Shorthand: acknowledge = ACK + PROCESSED + status update
CMDB::acknowledge($message_id);
// Shorthand: mark as failed with error info
CMDB::fail($message_id, 'Connection timeout to remote API');
---
7. Subscriptions #
Components can subscribe to message channels for push or poll delivery.
Subscribing #
// Poll mode (default) — component fetches messages with poll()
CMDB::subscribe('plugin:messaging', 'xchange.*');
// Push mode — handler is called immediately when a matching message arrives
CMDB::subscribe('plugin:messaging', 'xchange.first_contact', 'push',
'plugins/messaging/handlers/cmdb_push.handler.php'
);
// Both modes
CMDB::subscribe('plugin:monitoring', 'llm.*', 'both',
'plugins/monitoring/handlers/cmdb_push.handler.php',
null, // source filter (null = all sources)
3 // priority_min: only priority 1-3
);
Push Handlers #
Push handler files are included when a matching message is sent. They receive:
| Variable | Type | Description |
|----------|------|-------------|
| `$cmdb_push_message_id` | `int` | ID of the message that triggered the push |
| `$cmdb_push_subscriber` | `string` | Subscriber component ID |
<?php
// plugins/messaging/handlers/cmdb_push.handler.php
use Beamreactor\CMDB\CMDB;
$msg = CMDB::getMessage($cmdb_push_message_id);
if ($msg && $msg['channel'] === 'xchange.first_contact')
{
// Create internal conversation between buyer and seller
$payload = $msg['payload'];
// ... create_conversation($payload['buyer_id'], $payload['seller_id']);
CMDB::acknowledge($cmdb_push_message_id);
}
?>
Channel Patterns #
Subscriptions support wildcard matching:
| Pattern | Matches |
|---------|---------|
| `xchange.*` | `xchange.first_contact`, `xchange.rating`, etc. |
| `llm.health` | Only `llm.health` |
| `*.error` | `xchange.error`, `llm.error`, etc. |
Subscription Methods #
| Method | Returns | Description |
|--------|---------|-------------|
| `subscribe($subscriber, $pattern, ...)` | `int\|false` | Subscribe (upsert) |
| `unsubscribe($subscriber, $pattern)` | `int` | Remove subscription |
| `getSubscriptions($subscriber)` | `array` | List active subscriptions |
---
8. Maintenance #
TTL Expiration #
Messages with a `ttl` have an `expires_at` computed at creation. Expired messages must be swept:
// Run periodically (cron / scheduler)
$expired = CMDB::expireMessages();
Purge Old Messages #
// Delete processed/expired/failed messages older than 30 days
$deleted = CMDB::purge(30);
Stale Component Detection #
// Components that haven't sent a heartbeat in 30 minutes
$stale = CMDB::getStaleComponents(30);
foreach ($stale as $component)
{
CMDB::setStatus($component['component_type'], $component['component_name'], 'degraded');
}
---
9. Typical Plugin Integration #
A plugin registers itself and subscribes during its initialization (`conf/{name}.conf.php` or autoload):
use Beamreactor\CMDB\CMDB;
// Register
CMDB::register('plugin', 'xchange', '1.0.0', 'Marketplace', ['trading', 'rating']);
// Subscribe to responses
CMDB::subscribe('plugin:xchange', 'xchange.*', 'push',
'plugins/xchange/handlers/cmdb_push.handler.php'
);
// Heartbeat (if the plugin runs as a service)
CMDB::heartbeat('plugin', 'xchange');
Sending Inter-Plugin Messages #
// xchange triggers a messaging conversation
CMDB::send('plugin:xchange', 'plugin:messaging', 'xchange.first_contact', [
'buyer_id' => $buyer_id,
'seller_id' => $seller_id,
'product_id' => $product_id,
'subject' => 'Interested in: ' . $product_title,
]);
Receiving Messages #
// In a handler or cron job
$messages = CMDB::poll('plugin:xchange', 'messaging.%');
foreach ($messages as $msg)
{
// Process
CMDB::acknowledge($msg['id']);
}
---
10. Migrations #
The CMDB includes a full SQL migration system. Each component (plugin, lib, core) can have versioned SQL files that are tracked, checksummed, and never replayed.
File Convention #
plugins/xchange/sql/migrations/001_initial.sql
plugins/xchange/sql/migrations/002_add_trades_table.sql
plugins/xchange/sql/migrations/003_add_rating_index.sql
lib/cmdb/sql/migrations/001_initial.sql
Files must be named with a sortable prefix. They are executed in alphabetical order.
Optional Description Header #
Place a description in the first SQL comment line:
-- Description: Add trades table and buyer/seller indexes
CREATE TABLE IF NOT EXISTS xchange_trades ( ... );
Running Migrations #
// Single component
$result = CMDB::migrate(
'plugin', // component type
'xchange', // component name
'plugins/xchange/sql/migrations', // migrations directory
'1.2.0', // current version (optional)
'admin', // who runs this
'production' // environment tag
);
// $result = ['applied' => 2, 'skipped' => 1, 'failed' => 0, 'errors' => []]
// All components at once (scans lib/*, plugins/*)
$results = CMDB::migrateAll('deploy-server', 'production');
// $results = ['plugin:xchange' => [...], 'lib:cmdb' => [...], ...]
Checking Status #
// What migrations have been applied?
$status = CMDB::getMigrationStatus('plugin', 'xchange');
// Returns: migration_id, version, status, description, statements_count,
// execution_time_ms, applied_by, environment, batch, applied_at
// What's pending?
$pending = CMDB::getPendingMigrations('plugin', 'xchange', 'plugins/xchange/sql/migrations');
// Returns: ['003_add_rating_index.sql']
Integrity Check #
Detects if migration files were modified after being applied (checksum mismatch) or deleted:
$tampered = CMDB::verifyMigrationIntegrity();
foreach ($tampered as $t)
{
echo $t['component'] . ' / ' . $t['file'] . ': ' . $t['issue'];
// "plugin:xchange / 001_initial.sql: checksum_mismatch"
}
Behavior #
| Scenario | Behavior |
|----------|----------|
| File already applied | Skipped (checksum verified) |
| New file found | Executed in transaction, recorded |
| Statement fails | Transaction rolled back, status = `failed`, migration stops |
| File modified after apply | Warning in `verifyMigrationIntegrity()` |
| File deleted after apply | Reported as `file_missing` |
Batch Numbers #
All migrations executed in a single `migrate()` or `migrateAll()` call share the same batch number. This allows easy rollback identification.
Integration with Deployment #
On `devarea.beamreactor.com`, after syncing PHP files:
// Auto-run all pending migrations
$results = CMDB::migrateAll('devarea-deploy', 'production');
foreach ($results as $component => $r)
{
if ($r['failed'] > 0)
{
// Alert: migration failed for $component
error_log("Migration failed: $component — " . implode(', ', $r['errors']));
}
}
---
11. Architecture Diagram #
┌─────────────┐
│ cmdb_owners │
│ (sites, │
│ clients) │
└──────┬──────┘
business_owner_id │ technical_owner_id
│
┌──────────────┐ ┌──────┴──────┐ ┌───────────────────┐
│ Plugin A │──send─▶│ cmdb_messages│◀─poll──│ Plugin B │
│ (xchange) │ │ (the bus) │ │ (messaging) │
└──────────────┘ └──────┬──────┘ └───────────────────┘
│ │ ▲
│ ┌──────┴──────┐ │
└──register────▶│cmdb_registry│ ┌────────────┴──────┐
│ (who exists)│ │cmdb_subscriptions │
└─────────────┘ │ (who listens) │
└───────────────────┘
---
11. Quick Reference #
Most Used Methods #
// Owners
CMDB::createOwner($type, $name, $display, $url, $email, $meta, $trust);
CMDB::getOwnedComponents($owner_id, 'business');
// Registry
CMDB::register($type, $name, $version, $desc, $caps, $config, $biz_owner, $tech_owner);
CMDB::getComponent($type, $name);
CMDB::hasCapability($type, $name, 'rating');
// Messages
CMDB::send($source, $target, $channel, $payload, $action, $priority, $ttl);
CMDB::poll($target, $channel_filter);
CMDB::request($source, $target, $channel, $payload);
CMDB::reply($message_id, $payload);
CMDB::acknowledge($message_id);
CMDB::fail($message_id, $error);
// Subscriptions
CMDB::subscribe($subscriber, $pattern, 'push', $handler);
CMDB::unsubscribe($subscriber, $pattern);
// Maintenance
CMDB::expireMessages();
CMDB::purge(30);
CMDB::getStaleComponents(30);
// Migrations
CMDB::migrate($type, $name, $dir, $version, $who, $env);
CMDB::migrateAll($who, $env);
CMDB::getMigrationStatus($type, $name);
CMDB::getPendingMigrations($type, $name, $dir);
CMDB::verifyMigrationIntegrity();