• Multi-Tenant Model

    Tenant isolation is part of every authorization identity. Roles, bindings, menus, APIs, revisions, audit state, cache keys, and data operations all live inside a normalized scope.

    Relationship Model

    This section explains the operation in plain terms, including when to use it, which values must come from trusted server state, and which return fields are safe to read.

    Text equivalent.A tenant contains one or more complete scopes. Each scope independently owns roles, user-role sets, menu nodes, and API bindings. Users bind to roles through a scoped assignment set, and roles hold allow or deny rules plus menu grants. Reusing the same userId or roleId in another scope does not share authorization state.

    ## Same Identifiers, Isolated State

    This section explains the operation in plain terms, including when to use it, which values must come from trusted server state, and which return fields are safe to read.

    const scopeA = { tenantId: 'tenant-a', appId: 'admin' };
    const scopeB = { tenantId: 'tenant-b', appId: 'admin' };
    const tenantA = pc.scope(scopeA);
    const tenantB = pc.scope(scopeB);
    
    await tenantA.roles.create({ id: 'manager', label: 'A manager' });
    await tenantA.roles.allow('manager', {
      action: 'read', resource: 'ui:page:tenant-a-dashboard',
    });
    await tenantA.userRoles.assign('same-user', 'manager');
    
    await tenantB.roles.create({ id: 'manager', label: 'B manager' });
    await tenantB.roles.allow('manager', {
      action: 'read', resource: 'ui:page:tenant-b-dashboard',
    });
    await tenantB.userRoles.assign('same-user', 'manager');
    
    const subjectA = pc.forSubject({ userId: 'same-user', scope: scopeA });
    const subjectB = pc.forSubject({ userId: 'same-user', scope: scopeB });
    const tenantAOwnResource = await subjectA.can('read', 'ui:page:tenant-a-dashboard');
    const tenantACrossResource = await subjectA.can('read', 'ui:page:tenant-b-dashboard');
    const tenantBOwnResource = await subjectB.can('read', 'ui:page:tenant-b-dashboard');
    const tenantBCrossResource = await subjectB.can('read', 'ui:page:tenant-a-dashboard');
    {
      "tenantAOwnResource": true,
      "tenantACrossResource": false,
      "tenantBOwnResource": true,
      "tenantBCrossResource": false
    }

    Construct a Trusted Subject

    This section explains the operation in plain terms, including when to use it, which values must come from trusted server state, and which return fields are safe to read.

    const subject = pc.forSubject({
      userId: session.userId,
      scope: {
        tenantId: session.tenantId,
        appId: 'admin',
      },
      claims: { merchantId: session.merchantId },
    });

    Enforce Scope in Business Data

    This section explains the operation in plain terms, including when to use it, which values must come from trusted server state, and which return fields are safe to read.

    const orders = subject.data.collection('orders', {
      resource: 'db:orders',
      scopeFields: {
        tenantId: 'tenantId',
        appId: 'applicationId',
      },
    });

    Persistence, Cache, and Audit Isolation

    This section explains the operation in plain terms, including when to use it, which values must come from trusted server state, and which return fields are safe to read.

    Operational Checks

    This section explains the operation in plain terms, including when to use it, which values must come from trusted server state, and which return fields are safe to read.

    Continue with Cache.