Skip to content

ModuleConfig

The ModuleConfig table is a global singleton that stores the owner_identity — the cryptographic identity of the entity that deployed the QS-Bridge module to SpacetimeDB. This single value serves as the root of trust for the entire platform, powering the is_gateway() authorization guard.


Scope

🌍 Global — Singleton. This table always contains exactly one row, inserted during module initialization and never modified thereafter.


Schema

Column Type Constraints Description
owner_identity Identity The SpacetimeDB Identity of the module deployer

Rust Definition

#[spacetimedb::table(public, name = module_config)]
pub struct ModuleConfig {
    pub owner_identity: Identity,
}

Note: ModuleConfig has no explicit primary key column. As a singleton table with exactly one row, it is accessed via filter_iter().next() rather than keyed lookups.


Initialization

The ModuleConfig row is created by the init reducer, which fires exactly once when the module is first published:

#[spacetimedb::reducer(init)]
pub fn init(ctx: &ReducerContext) {
    ModuleConfig::insert(ModuleConfig {
        owner_identity: ctx.sender(),
    });
}

At this moment, ctx.sender() is the Identity of the CLI client or automation system that ran spacetimedb publish. This identity is cryptographic — derived from the caller's private key — and cannot be spoofed.


Usage Patterns

The is_gateway() Guard

The sole consumer of ModuleConfig is the is_gateway() function, which compares the caller's identity against the stored owner_identity:

fn is_gateway(ctx: &ReducerContext) -> bool {
    let config = ModuleConfig::filter_iter()
        .next()
        .expect("ModuleConfig must exist");
    ctx.sender() == config.owner_identity
}

This function is called at the top of every administrative and panel reducer. If the caller is not the gateway (module owner), the reducer returns early without performing any mutation.

Trust Chain

The owner_identity establishes a trust chain for the entire platform:

graph TD
    Owner["owner_identity (in ModuleConfig)"]
    Owner --> GWTrue["is_gateway() returns true"]
    Owner --> GWFalse["is_gateway() returns false"]

    GWTrue --> AdminR["Admin reducers execute"]
    GWTrue --> PanelR["Panel reducers execute"]
    PanelR --> FineAuth["Panel session + AdminRole provide fine-grained authorization"]

    GWFalse --> Reject["Reducer rejects the call"]

Singleton Invariant

The table must always contain exactly one row. The init reducer enforces this by running only once. No other reducer inserts into ModuleConfig. The .expect("ModuleConfig must exist") call in is_gateway() will panic if the singleton is somehow missing — this is intentional, as a missing ModuleConfig represents a broken module state that should halt execution.

Persistence Across Upgrades

When the module is upgraded via spacetimedb publish, the init reducer does not re-fire. The existing ModuleConfig row is preserved, maintaining the original owner_identity. This ensures continuity of the trust chain across module updates.


Reducer Operation
init Inserts the singleton row on first module deployment

No other reducer reads from or writes to ModuleConfig directly. The is_gateway() guard function reads the table, and it is called by all admin and panel reducers.


ModuleConfig is not typically subscribed to by external clients. Its data is consumed internally by the module's guard logic. However, as a public table, it can be queried for diagnostic purposes:

SELECT * FROM module_config