Skip to content

Dashboard: Spaces

Status: target state. No cross-principal Space list UI exists. The data is in <host-home>/state/auth-state.json and accessible per-principal via system.auth ops.

Target view

A platform-wide Space list with per-row:

  • spaceId, spacePath, publicNamespace, authorityRoot.
  • Owner (ownerType + ownerPrincipalId or ownerOrganizationId).
  • Status (active / suspended).
  • Created at, updated at.
  • Linked Device count (joined from system.devices).
  • Public namespace claim type and verification level.

Filterable by:

  • Status.
  • Owner type.
  • Verification level.
  • Created-at range.
  • Free-text on spacePath.

Per-row actions:

  • Inspect (deep view: claims, Host Links, runtimes).
  • Suspend / unsuspend (target).
  • Transfer ownership (target).

Today's data

Per-principal listing

bash
matrix invoke system.auth auth.namespace.list '{"principalId":"p_xxx"}'

Returns the namespace claims for one principal, each carrying spaceId, spacePath/routeKey, publicNamespace, canonical, status. Cross-reference with auth.space.get for the full Space record.

Cross-principal listing (no op today)

There is no auth.space.list op. To enumerate every Space:

bash
# Read the state file directly (platform Host)
sudo jq '.spaces' <host-home>/state/auth-state.json
sudo jq '.publicNamespaces' <host-home>/state/auth-state.json
sudo jq '.principalDefaultSpaces' <host-home>/state/auth-state.json

Each spaces[] row matches the IHostSpaceRecord schema. Each publicNamespaces[] row is an IHostPublicNamespaceClaim.

For programmatic use, write a script that joins these arrays:

ts
const state = JSON.parse(fs.readFileSync(path));
const spacesByOwner = new Map();
for (const space of state.spaces) {
  const list = spacesByOwner.get(space.ownerPrincipalId) ?? [];
  list.push(space);
  spacesByOwner.set(space.ownerPrincipalId, list);
}

What the target requires

To implement the dashboard:

  1. auth.space.list op with optional filters (status, owner type, owner id, page cursor).
  2. auth.space.suspend / unsuspend ops to wire up actions.
  3. A join with system.devices to compute linked Device count per Space.
  4. A UI in matrix-web rendering the table + actions.

None of those steps is large; the admin dashboard is gated more by prioritization than by complexity.

Schema recap

ts
interface IHostSpaceRecord {
  id: string;             // spaceId
  authorityRoot: string;
  ownerPrincipalId?: string;
  ownerType?: 'principal' | 'organization' | 'system' | 'service';
  status: 'active' | 'suspended';
  createdAt: string;
  updatedAt: string;
}

The schema does not carry displayName for a Space — the user-facing label is the spacePath from the canonical namespace claim. Spaces without a public namespace claim (rare) would need synthesized display logic.

Edge cases for the target UI

  • Spaces with no public namespace claim. Display by spaceId or (deferred).
  • Spaces owned by ownerType: 'organization' when no organization records exist (today). Show as (unowned org).
  • Suspended Spaces. Highlight visually.
  • Spaces whose authorityRoot does not match any principal's address roots (legacy data). Flag.

Operator workaround for common tasks

"How many Spaces does this principal have?"

bash
matrix invoke system.auth auth.namespace.list '{"principalId":"p_xxx"}'
# Count distinct spaceId values.

"Who owns this Space?"

bash
matrix invoke system.auth auth.space.get '{"spaceId":"spc_xxx"}'
# Returns ownerPrincipalId, then auth.principal.get on it.

"Suspend this Space"

Hand-edit auth-state.json (under hivecast stop to avoid concurrent writes), set status: 'suspended', restart. Until auth.space.suspend ships.

See also

Source: projects/matrix-3/packages/system-auth/src/host-auth.ts:61-85 for the schema. index.ts:655-717 for current ops.