Building a Hive Plugin

A complete walkthrough — from an empty directory to a production-safe plugin with models, migrations, validators, triggers, and scheduled jobs. All examples use real Hive API types from include/hive-api/ and include/hive-model/.

Module Boundaries — What Belongs Where

Hive enforces strict module boundaries. Violating them causes build failures (dependency order enforced by CMake). Before writing any code, understand which module your code belongs in.

ModuleInclude this forDo NOT include from
hive-api Plugin base class, IValidator, IRepository, Trigger, Job, JobConfig, OperationResult — everything a plugin author needs Never include hive-http, hive-essential, or hive-db-sqlite from plugin code
hive-model ModelDefinition, ColumnDefinition, ColumnDefinitionFlag, Crudl, TriggerPhase, AccessMode enums Does not depend on any other Hive module — pure metadata types
hive-orm BaseModel entity class, column constant structs (BaseColumns-style typed access) Should not include hive-db-sqlite or hive-http
hive-plugin-* Include hive-api and hive-model only. Never directly call hive-db-sqlite or hive-http. All database access must go through IRepository. All HTTP routing through ModelDefinition registration.

ModelDefinition — Fluent Builder API

Every entity you want Hive to manage must be described by a ModelDefinition. This single struct drives REST routing, database persistence, frontend rendering, caching, and validation — simultaneously.

Complete ModelDefinition example

#include <hive/model/ModelDefinition.hpp>
#include <hive/model/ColumnDefinition.hpp>

// All ColumnDefinitionFlag values (bitmask integers)
using namespace hive::model::flags;
// MANDATORY=1   UNIQUE=2        FOREIGN_KEY=4    AUTO=8
// HIDDEN=16     READONLY=32     MUTABLE=64       INTERNAL=128
// TEXT=256      TEXTAREA=512    INTEGER=1024      REAL=2048
// BLOB=4096     BOOL=8192       DATETIME=16384

static hive::ModelDefinition book_definition() {
    return hive::ModelDefinition("book")
        // Grouping in the frontend navigation sidebar
        .set_group("Library")

        // Which column provides the human-readable name for this entity
        // Used by FK dropdowns in other models that reference "book"
        .set_title_column("title")

        // Enable all 5 CRUD operations + List
        .set_all_rest_operations()
        // Or selectively: .set_rest_operations({Crudl::Read, Crudl::List})

        // Column definitions
        .set_columns({

            // Primary key — always INTEGER | AUTO | HIDDEN
            ColumnDefinition("id")
                .set_flags(INTEGER | AUTO | HIDDEN),

            // Mandatory text field — shown in list and forms
            ColumnDefinition("title")
                .set_flags(TEXT | MANDATORY | MUTABLE),

            // Optional multi-line text (Markdown editor in frontend)
            ColumnDefinition("description")
                .set_flags(TEXTAREA | MUTABLE),

            // Integer field — optional
            ColumnDefinition("page_count")
                .set_flags(INTEGER | MUTABLE),

            // Real (floating point)
            ColumnDefinition("rating")
                .set_flags(REAL | MUTABLE),

            // Boolean checkbox
            ColumnDefinition("is_read")
                .set_flags(BOOL | MUTABLE),

            // Foreign key — renders as <select> dropdown in frontend
            // Populated by list_all_entities("author") call
            ColumnDefinition("author_id")
                .set_flags(INTEGER | FOREIGN_KEY | MANDATORY)
                .set_foreign_key_model("author"),

            // UNIQUE constraint in the DB
            ColumnDefinition("isbn")
                .set_flags(TEXT | UNIQUE | MUTABLE),

            // Auto-set server-side — shown read-only, excluded from forms
            ColumnDefinition("created_at")
                .set_flags(DATETIME | AUTO | READONLY),

            ColumnDefinition("updated_at")
                .set_flags(DATETIME | AUTO | READONLY),

            // INTERNAL — never sent to client at all
            ColumnDefinition("internal_score")
                .set_flags(INTEGER | INTERNAL),

            // HIDDEN — stored and used server-side but not shown in UI
            ColumnDefinition("user_id")
                .set_flags(INTEGER | FOREIGN_KEY | HIDDEN)
                .set_foreign_key_model("user")
        })

        // Enable LRU+TTL cache for read and list operations
        .set_cache_enabled(true)

        // Also cache immediately after create (pre-populate cache)
        .set_cached_after_create(true)

        // Custom action buttons shown in the read view
        .add_custom_read_action("export_pdf",  "Export as PDF")
        .add_custom_read_action("send_email",  "Email to user")

        // Custom action for the list view
        .add_custom_list_action("import_csv", "Import from CSV")

        // Mark entire model as read-only (no create/update/delete by anyone)
        // .set_readonly(true)

        // Allow users with role Reader (normally read-only) to also write
        // .allow_reader_write(true)

        // Virtual table — no DB table, data comes from InsteadOf triggers
        // .set_virtual_table(true)

        // No table at all — metadata-only model (used for custom API surfaces)
        // .set_no_table(true)
    ;
}

ColumnDefinition flag auto-inference rules

When you set a type flag, related flags are inferred automatically. You can always override by explicitly setting or clearing flags:

If flag is setAuto-inferredReason
AUTOAlso READONLYAuto-generated fields (PK, timestamps) should never appear in input forms
FOREIGN_KEYAlso INTEGERFKs are always integer references to other model IDs
MANDATORY (without AUTO)Also MUTABLEA mandatory field that's not auto-set must be editable
UNIQUEAlso MUTABLE (if not AUTO)Unique user-provided fields need to be editable
INTERNALAlso HIDDENInternal fields are never sent to the client — HIDDEN is the weaker subset

IValidator — Security and Invariant Gate

Validators are the security and consistency gate of Hive. They run before any trigger or persistence call. A validator can inspect the AccessTokenContext (who is calling, with what role) and the request payload, and either pass or reject the operation.

OperationResult — the return type

#include <hive/api/OperationResult.hpp>

struct OperationResult {
    int         status;    // 0 or 200 = ok; any other = HTTP error code
    std::string error;     // human-readable message returned to client

    bool ok() const { return status == 0 || status == 200; }
};

// Built-in result macros (use these instead of raw constructors)
ok_result                    // {0, ""}
status_403_forbidden         // {403, "Forbidden"}
status_405_unsupported_operation  // {405, "Unsupported operation"}

Complete IValidator implementation

#include <hive/api/IValidator.hpp>
#include <hive/api/OperationResult.hpp>
#include <hive/api/AccessTokenContext.hpp>

class BookOwnerValidator : public hive::IValidator {
public:
    // Called before every Create operation on the "book" model
    OperationResult can_create(
        hive::AccessTokenContext& ctx,
        hive::Fields&             fields
    ) override {
        // Guest users cannot create books
        if (ctx.role == hive::UserRole::Guest) {
            return status_403_forbidden;
        }

        // Inject the current user's ID as owner
        fields["user_id"] = std::to_string(ctx.user_id);

        return ok_result;
    }

    // Called before every Read operation
    OperationResult can_read(
        hive::AccessTokenContext& ctx,
        int                       id
    ) override {
        // All authenticated users can read — Guests cannot
        if (ctx.role == hive::UserRole::Guest) {
            return {403, "Login required to read books."};
        }
        return ok_result;
    }

    // Called before every Update operation
    OperationResult can_update(
        hive::AccessTokenContext& ctx,
        int                       id,
        hive::Fields&             fields
    ) override {
        // Admin can update anything
        if (ctx.role >= hive::UserRole::Admin) {
            return ok_result;
        }

        // Other users can only update their own books
        // Use a query to check ownership
        auto book = book_query_.find_by_id(id);
        if (!book || book->user_id != ctx.user_id) {
            return {403, "You can only edit your own books."};
        }

        return ok_result;
    }

    // Called before every Delete operation
    OperationResult can_delete(
        hive::AccessTokenContext& ctx,
        int                       id
    ) override {
        if (ctx.role < hive::UserRole::Editor) {
            return status_403_forbidden;
        }
        return ok_result;
    }

    // Called before every List operation
    OperationResult can_list(
        hive::AccessTokenContext& ctx,
        hive::QueryParams&        qp
    ) override {
        // For non-admin users, silently filter list to their own books
        if (ctx.role < hive::UserRole::Admin) {
            qp.add_filter("user_id", std::to_string(ctx.user_id));
        }
        return ok_result;
    }

private:
    BookQueryHelper book_query_;  // injected via constructor in real code
};

Registering validators in the plugin

// In MyPlugin::initialize(PluginContext& ctx)
register_validator("book", std::make_shared<BookOwnerValidator>());

// Multiple validators can be registered for the same model
// They execute in registration order — first rejection wins
register_validator("book", std::make_shared<BookMandatoryFieldsValidator>());
register_validator("book", std::make_shared<BookIsbnFormatValidator>());

Trigger — Before, After, and InsteadOf

Triggers hook into the CRUD pipeline at three phases. Unlike validators (which only accept/reject), triggers can modify data, replace persistence entirely, or execute side effects after persistence.

Trigger phases and their contract

TriggerPhaseValueWhen it firesCan modify data?Can abort?
Before0After validators, before repository callYes — return modified FieldsYes — return nullopt
After1After repository call, post-persistenceNo (entity already written)No (cannot undo)
InsteadOf2Replaces the repository call entirelyYes — full controlBy returning nullopt
Around3Reserved for future use

Trigger constructor

#include <hive/api/Trigger.hpp>

// Trigger(name, priority, operations, phase, table)
// table = "*"    means all registered models
// table = "book" means only the "book" model
// Lower priority integer = earlier execution

// Example: a global After-trigger that logs all creates to "history"
class GlobalHistoryTrigger : public hive::Trigger {
public:
    GlobalHistoryTrigger() : hive::Trigger(
        "global_history",         // name
        100,                      // priority (high = late = after model triggers)
        {hive::Crudl::Create,
         hive::Crudl::Update,
         hive::Crudl::Delete},   // operations to fire for
        hive::TriggerPhase::After,
        "*"                       // all models
    ) {}

    // After phase: run_before_or_after is called for Before AND After
    std::optional<hive::Fields> run_before_or_after(
        hive::AccessTokenContext& ctx,
        hive::Fields&             fields
    ) override {
        auto history_repo = get_repository("history");
        hive::Fields hist;
        hist["model_name"]  = fields["_model_name"];
        hist["entity_id"]   = fields["id"];
        hist["operation"]   = fields["_operation"];
        hist["user_id"]     = std::to_string(ctx.user_id);
        hist["after_data"]  = fields_to_json(fields);
        std::string err;
        history_repo->create(hist, err);

        return fields;  // return original fields unmodified
    }
};

Before trigger — modifying data before save

class BookTimestampBeforeTrigger : public hive::Trigger {
public:
    BookTimestampBeforeTrigger() : hive::Trigger(
        "book_timestamp_before",
        10,
        {hive::Crudl::Create, hive::Crudl::Update},
        hive::TriggerPhase::Before,
        "book"
    ) {}

    std::optional<hive::Fields> run_before_or_after(
        hive::AccessTokenContext& ctx,
        hive::Fields&             fields
    ) override {
        auto now = current_iso8601_timestamp();

        if (fields["_operation"] == "create") {
            fields["created_at"] = now;
        }
        fields["updated_at"] = now;

        return fields;  // modified fields get written to DB

        // To abort the operation: return std::nullopt;
    }
};

InsteadOf trigger — replacing the repository call

class BookSearchInsteadOfTrigger : public hive::Trigger {
public:
    BookSearchInsteadOfTrigger() : hive::Trigger(
        "book_search",
        1,
        {hive::Crudl::List},
        hive::TriggerPhase::InsteadOf,
        "book"
    ) {}

    // InsteadOf List — fired when "q" query param is present
    std::optional<hive::Entities> run_instead_of_list(
        hive::AccessTokenContext& ctx,
        hive::QueryParams&        qp
    ) override {
        if (!qp.has("q")) {
            return std::nullopt;  // Fall through to standard list
        }

        std::string query = qp.get("q");
        auto repo = get_repository("book");
        std::string err;

        hive::QueryParams search_qp;
        search_qp.set_raw_sql(
            "WHERE title LIKE ? OR description LIKE ?",
            {"%" + query + "%", "%" + query + "%"}
        );
        return repo->list(search_qp, err);
    }
};

Registering triggers

// In MyPlugin::initialize(PluginContext& ctx)
register_trigger(std::make_shared<BookTimestampBeforeTrigger>());
register_trigger(std::make_shared<BookSearchInsteadOfTrigger>());
register_trigger(std::make_shared<GlobalHistoryTrigger>());

// Trigger execution order within a phase is determined by priority:
// Lower integer = earlier execution
// Priority 1   → fires first
// Priority 100 → fires last (after model-specific triggers)

Job — Cron-Scheduled Background Tasks

Jobs run on a fixed schedule managed by CronScheduler (4 worker threads). Each job run is persisted to the job_run table. Jobs can access repositories and queries via JobConfig.

Complete Job implementation

#include <hive/api/Job.hpp>
#include <hive/api/JobConfig.hpp>

class BookWeeklyReportJob : public hive::Job {
public:
    BookWeeklyReportJob() : hive::Job(
        "book_weekly_report",    // unique name (stored in job_entry table)
        "Weekly reading report", // human-readable description
        "0 9 * * 1",             // cron: every Monday at 09:00
        true,                    // enabled_by_default
        true                     // run_once_when_missed
    ) {}

    // run() is called by a CronScheduler worker thread
    // Return value: string written to job_run.output
    std::string run(hive::JobConfig& config) override {
        auto book_repo = config.get_repository("book");
        std::string err;

        hive::QueryParams qp;
        qp.set_filter("created_at", last_7_days_filter());
        auto recent_books = book_repo->list(qp, err);

        if (!err.empty()) {
            return "ERROR: " + err;
        }

        auto stats = config.call_query("book_stats", {});

        std::ostringstream report;
        report << "Weekly book report:\n";
        report << "  New books this week: " << recent_books.size() << "\n";
        report << "  Total books: " << stats["total_count"] << "\n";
        report << "  Avg rating: "  << stats["avg_rating"] << "\n";

        return report.str();
    }
};

JobConfig API

class JobConfig {
public:
    std::shared_ptr<IRepository> get_repository(const std::string& model_name);

    hive::QueryResult call_query(
        const std::string&              name,
        const std::vector<std::string>& args
    );

    const PluginRegistry& get_plugin_registry() const;
};

Cron expression format (standard 5-field)

"0 2 * * 0"    // Every Sunday at 02:00 (weekly vacuum)
"0 3 * * *"    // Every day at 03:00 (daily cleanup)
"*/15 * * * *" // Every 15 minutes
"0 9 * * 1"    // Every Monday at 09:00
"0 0 1 * *"    // First day of every month at midnight

Database Migrations — Naming and Integrity

Hive uses versioned SQL migration files with SHA-256 chain-hash integrity. The integrity system guarantees that applied migrations are never silently modified after deployment.

File naming convention (validated by regex at startup)

V{sequence_number}__{descriptive_name}.sql
Regex: V(\d+)__([a-zA-Z0-9_]+).sql

# Valid:
V1__initial_schema.sql
V2__add_user_id_to_book.sql
V3__add_isbn_index.sql
V10__rename_description_to_summary.sql

# INVALID (will fail validation):
v1_initial_schema.sql     # lowercase v
V1_initial_schema.sql     # single underscore
V1__initial schema.sql    # space in name

Example migration files

-- V1__initial_schema.sql
CREATE TABLE IF NOT EXISTS book (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    title       TEXT    NOT NULL,
    description TEXT,
    rating      REAL,
    is_read     INTEGER NOT NULL DEFAULT 0,
    isbn        TEXT    UNIQUE,
    created_at  TEXT    NOT NULL,
    updated_at  TEXT,
    user_id     INTEGER REFERENCES user(id)
);

-- V2__add_isbn_index.sql
CREATE INDEX IF NOT EXISTS idx_book_isbn ON book(isbn);

-- V3__add_genre_column.sql
ALTER TABLE book ADD COLUMN genre TEXT;

Chain-hash integrity mechanism

// For each migration file in sequence order:
//   file_hash  = SHA-256(file_content)
//   chain_hash = SHA-256(previous_chain_hash || file_hash)
//   Store: (filename, file_hash, chain_hash, applied_at)
//
// On subsequent startups:
//   Recompute file_hash from current file
//   If file_hash != stored: FATAL — migration file was tampered
//   Recompute chain_hash: if differs: FATAL — ordering attack detected
//
// Golden rule: NEVER edit a migration after it has been applied.
//              Add a new migration file instead.

Registering migrations in the plugin

// In MyPlugin::initialize(PluginContext& ctx)
register_migrations({
    hive::Migration("V1__initial_schema.sql",
        R"SQL(
            CREATE TABLE IF NOT EXISTS book (
                id          INTEGER PRIMARY KEY AUTOINCREMENT,
                title       TEXT    NOT NULL,
                created_at  TEXT    NOT NULL
            );
        )SQL"
    ),
    hive::Migration("V2__add_description.sql",
        "ALTER TABLE book ADD COLUMN description TEXT;"
    ),
});

Complete Plugin — MyPlugin.hpp / MyPlugin.cpp

MyPlugin.hpp

#pragma once
#include <hive/api/Plugin.hpp>

class MyPlugin : public hive::Plugin {
public:
    MyPlugin();
    std::string get_name() const override { return "my_plugin"; }
    void initialize(hive::PluginContext& ctx) override;
};

MyPlugin.cpp

#include "MyPlugin.hpp"
#include "BookValidator.hpp"
#include "BookTimestampTrigger.hpp"
#include "BookSearchTrigger.hpp"
#include "BookStatsQuery.hpp"
#include "BookWeeklyReportJob.hpp"
#include <hive/model/ModelDefinition.hpp>
#include <hive/model/ColumnDefinition.hpp>

using namespace hive::model::flags;

MyPlugin::MyPlugin() {
    add_dependency("core");   // required — always load core first
}

void MyPlugin::initialize(hive::PluginContext& ctx) {
    // 1. Migrations — applied first, in order, with chain-hash integrity
    register_migrations({
        hive::Migration("V1__initial_schema.sql",
            R"SQL(
                CREATE TABLE IF NOT EXISTS book (
                    id          INTEGER PRIMARY KEY AUTOINCREMENT,
                    title       TEXT    NOT NULL,
                    description TEXT,
                    rating      REAL,
                    is_read     INTEGER NOT NULL DEFAULT 0,
                    created_at  TEXT    NOT NULL,
                    updated_at  TEXT,
                    user_id     INTEGER REFERENCES user(id)
                );
            )SQL"),
        hive::Migration("V2__add_isbn.sql",
            "ALTER TABLE book ADD COLUMN isbn TEXT UNIQUE;"),
    });

    // 2. Model definitions — drive REST routing and frontend
    register_model(
        hive::ModelDefinition("book")
            .set_group("Library")
            .set_title_column("title")
            .set_all_rest_operations()
            .set_columns({
                ColumnDefinition("id")
                    .set_flags(INTEGER | AUTO | HIDDEN),
                ColumnDefinition("title")
                    .set_flags(TEXT | MANDATORY | MUTABLE),
                ColumnDefinition("description")
                    .set_flags(TEXTAREA | MUTABLE),
                ColumnDefinition("rating")
                    .set_flags(REAL | MUTABLE),
                ColumnDefinition("is_read")
                    .set_flags(BOOL | MUTABLE),
                ColumnDefinition("isbn")
                    .set_flags(TEXT | UNIQUE | MUTABLE),
                ColumnDefinition("created_at")
                    .set_flags(DATETIME | AUTO | READONLY),
                ColumnDefinition("updated_at")
                    .set_flags(DATETIME | AUTO | READONLY),
                ColumnDefinition("user_id")
                    .set_flags(INTEGER | FOREIGN_KEY | HIDDEN)
                    .set_foreign_key_model("user"),
            })
            .set_cache_enabled(true)
            .add_custom_list_action("export_csv", "Export CSV")
    );

    // 3. Validators
    register_validator("book", std::make_shared<BookOwnerValidator>());

    // 4. Triggers
    register_trigger(std::make_shared<BookTimestampBeforeTrigger>());
    register_trigger(std::make_shared<BookSearchInsteadOfTrigger>());

    // 5. Custom queries
    register_query("book_stats", std::make_shared<BookStatsQuery>());

    // 6. Jobs
    register_job(std::make_shared<BookWeeklyReportJob>());

    // close_for_changes() is called automatically after initialize()
    // — the plugin registry becomes read-only at runtime
}

CMakeLists.txt for the plugin

add_library(hive_plugin_my_plugin
    src/MyPlugin.cpp
    src/BookValidator.cpp
    src/BookTimestampTrigger.cpp
    src/BookSearchTrigger.cpp
    src/BookStatsQuery.cpp
    src/BookWeeklyReportJob.cpp
)

target_include_directories(hive_plugin_my_plugin PUBLIC
    ${CMAKE_CURRENT_SOURCE_DIR}/include
)

target_link_libraries(hive_plugin_my_plugin
    hive_api      # Plugin, IValidator, Trigger, Job, OperationResult
    hive_model    # ModelDefinition, ColumnDefinition, enums
)

Main.cpp factory registration

#include <hive/plugin/myplugin/MyPlugin.hpp>

// In main():
plugin_registry.register_factory("my_plugin",
    []() -> std::unique_ptr<hive::Plugin> {
        return std::make_unique<MyPlugin>();
    }
);

Plugin Debugging Checklist

When a plugin isn't working as expected, work through this checklist in order:

1

Plugin loads at all?

Check server startup logs for: [INFO] Plugin loaded: my_plugin
If missing: verify allowed_plugins=my_plugin in hive.properties, factory registration in Main.cpp, and get_name() returns exactly "my_plugin".

2

Migrations applied?

Check startup logs for migration output. If integrity error: revert the edited file or recreate the DB. If migration not found: verify the V{n}__name.sql naming — double underscore required.

sqlite3 hive.db \
  "SELECT * FROM hive_migrations WHERE plugin='my_plugin';"
3

Model appears in model_definition?

curl -s http://localhost:9000/api/v1/model_definition | \
  jq '.[] | select(.name == "book")'

If missing: check register_model() call in initialize(). Verify set_all_rest_operations() or explicit ops are set.

4

CRUD endpoints accessible?

TOKEN="eyJhbG..."  # from login response
curl -s http://localhost:9000/api/v1/book \
  -H "Authorization: Bearer $TOKEN"

curl -s -X POST http://localhost:9000/api/v1/book \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"title":"Test Book"}'

403 = validator rejecting. 405 = operation not enabled in ModelDefinition. 500 = server error — check logs.

5

Triggers firing correctly?

After a create: check the history table. Check created_at / updated_at fields. If Before trigger not modifying fields: verify it returns fields (not std::nullopt).

sqlite3 hive.db \
  "SELECT * FROM history WHERE model_name='book' ORDER BY id DESC LIMIT 5;"
6

Jobs running?

sqlite3 hive.db \
  "SELECT * FROM job_entry WHERE name='book_weekly_report';"
sqlite3 hive.db \
  "SELECT * FROM job_run ORDER BY id DESC LIMIT 5;"

If job not in job_entry: check register_job(). If runs fail: check job_run.output for the error string. If never runs: validate the cron expression.

7

Enable verbose logging

# configuration/hive.properties
max_log_level=DEBUG

# Or on command line:
./hive_app start --log-level DEBUG ...

Debug output shows every request step: auth context resolution, validator name + result, trigger name + phase + outcome, SQL queries, and cache hit/miss statistics.


Public API Header Quick-Reference

All types a plugin author needs are in include/hive-api/ and include/hive-model/.

HeaderKey types / constants
hive/api/Plugin.hppPluginregister_model, register_validator, register_trigger, register_job, register_query, register_migrations, add_dependency, close_for_changes
hive/api/IValidator.hppIValidatorcan_create, can_read, can_update, can_delete, can_list
hive/api/IRepository.hppIRepositorycreate, read, update, remove, list, list_in_ids, list_ids, request_to_entity_fields
hive/api/Trigger.hppTriggerrun_before_or_after, run_instead_of_create/read/update/delete/list
hive/api/Job.hppJobrun(JobConfig&) → string
hive/api/JobConfig.hppJobConfigget_repository, call_query, get_plugin_registry
hive/api/OperationResult.hppOperationResult {status, error}, macros ok_result, status_403_forbidden, status_405_unsupported_operation
hive/model/ModelDefinition.hppModelDefinition — full fluent builder API including all set_*() and add_custom_*_action() methods
hive/model/ColumnDefinition.hppColumnDefinitionset_flags, set_foreign_key_model; all ColumnDefinitionFlag constants
hive/model/Crudl.hppCrudl enum: Undefined=0 Create=1 Read=2 Update=3 Delete=4 List=5
hive/model/TriggerPhase.hppTriggerPhase: Before=0 After=1 InsteadOf=2 Around=3
hive/model/AccessMode.hppAccessMode (0–8): MaintenanceMode … PublicFullAccess
hive/model/UserRole.hppUserRole: Guest=0 Reader=1 Editor=2 Reviewer=3 Admin=4 SuperAdmin=5 System=100

Ready to build?

Use the existing plugins (src/hive-plugin-slip-box/, src/hive-plugin-dictionary/) as authoritative structural templates — they demonstrate every extension point in production use.