Skip to main content
Wildo.ai Coming soon

Organisations and members

Keep team access with the team’s records

Use unit roles to grant access to the relevant records while preserving authorised organisation-wide access.

A role reaches records in the customer's North unit while South remains separate.

Keep team access with the team’s records

A resource can identify the unit each record belongs to. People whose authority comes from a unit role then reach the records of the units that actually grant them that authority.

Organisation-wide roles remain organisation-wide. This adds a narrower way to grant access; it does not silently reduce the access leadership already holds.

Example — Let a team manage its own goals

A Sales manager works with Sales goals through a unit role. An organisation manager can still review goals across teams through their broader organisation role.

For engineers

Give the resource a real unit field

Wonder Todos’ TeamGoals_Schema contains both the customer and team identifiers. These selected fields are from shared-lib/src/modules/tasks-manager/resources/team-goals/team-goals.schemas.ts, after initZodDecorators(z) initializes the decorators:

organizationId: z.string().min(1).isDBIndexed().isForeignKey().isSummaryField().excludeFromUpdate(),
organizationUnitId: z.string().min(1).isDBIndexed().isForeignKey().isSummaryField(),

These are fields inside z.object, not a standalone schema. The organization relationship establishes tenant ownership; organizationUnitId identifies the team within it. The required field prevents unit-less goals. It remains editable so an organization-wide manager can move a goal, while a confined manager cannot move it outside their permitted units.

Require a role that the team grant can supply

The resource’s operationsConfiguration requires CORE_ORG_ROLES.ORG_MANAGER. Here is its READ entry; Wonder Todos makes the same role choice for LIST, CREATE, UPDATE and DELETE:

[CoreResourceOperation.READ]: {
  variants: [{
    variantType: ResourceOperationVariantType.API_CALL,
    isDefault: true,
    roles: [CORE_ORG_ROLES.ORG_MANAGER],
    riskLevel: ResourceOperationRiskLevel.LOW,
  }],
},

The operation enums and CORE_ORG_ROLES are exported by @wildo-ai/saas-models. This entry belongs in the resource configuration passed to createResourceConfiguration_Initialization, alongside mainSchema: TeamGoals_Schema, its resource identifiers, relationships and declared core operations.

The role choice is decisive. With ORG_MEMBER on an operation, an ordinary member already qualifies organization-wide; adding a team grant would not confine that access. With ORG_MANAGER, a person who is only an organization member can qualify through their manager role in Sales, while an organization-wide manager retains broader access.

Register the resource and its narrowing policy

Wonder Todos opts its team-goals resource into narrowing in backend-api/src/bootstrap-config.ts:

const APPLICATION_ORGANIZATION_UNIT_NARROWING: Readonly<Partial<Record<string, { unitFieldName: string }>>> = {
  [TasksManager_ResourceType.TEAM_GOALS]: { unitFieldName: 'organizationUnitId' },
};

const initializationConfig = buildApplicationInitializationConfig({
  additionalServiceImplementations: userSelfCustomImplementations,
  organizationUnitNarrowing: APPLICATION_ORGANIZATION_UNIT_NARROWING,
});

This excerpt keeps the narrowing registration and omits unrelated bootstrap options. The resource factory is separately registered in the task module’s moduleResourcesConfigurationsFactoryMap:

[TasksManager_ResourceType.TEAM_GOALS]: teamGoals_ResourceConfiguration_InitializationFactory,

That is an entry in the existing module map, not a replacement for it. Keep the task module and its relationship declarations in the application’s shared module registration, and pass the backend initialization configuration through the existing startup path. Registering the narrowing field alone does not publish a resource or its routes.

Startup rejects forbidden authorization resources and empty field names. This validator does not receive the resource schema and does not check that the named field exists: matching unitFieldName to a real field remains an authoring obligation. The declaration is backend policy, not a request parameter a caller may use to choose authority.

Assign a team role and call the resource

This request sequence follows Wonder Todos’ organization-unit-narrowing.e2e.ts. Start with two sibling root units, Sales and Legal, and one goal in each. MEMBERSHIP_ID is the person’s existing organization-membership row ID, not their user ID. ADMIN_TOKEN belongs to an organization administrator allowed to assign the role; MEMBER_TOKEN belongs to a person holding only ORG_MEMBER organization-wide.

curl "$BACKEND_URL/organizations/$ORG_ID/organization-unit-members" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H 'Content-Type: application/json' \
  --data "{\"organizationUnitId\":\"$SALES_UNIT_ID\",\"organizationMemberId\":\"$MEMBERSHIP_ID\",\"role\":\"ORG_MANAGER\"}"

curl "$BACKEND_URL/organizations/$ORG_ID/team-goals" \
  -H "Authorization: Bearer $MEMBER_TOKEN"

The first request grants the Sales unit role; it does not promote the person’s organization membership. The second lists goals using that person’s own authority. In this setup, the list includes Sales goals and excludes Legal goals. First confirm that an organization-wide administrator can reach the same registered collection: a missing route is not evidence of confinement.

Apply the same answer at the gate and the records

resolveOrganizationUnitNarrowingDecision first checks whether an organisation-wide grant satisfies the operation. Otherwise it selects qualifying unit grants and returns the unit IDs that may be used by the row filter and write validation.

Caller’s authorityResult
Sufficient organisation-wide roleOrganisation-wide access
Only a qualifying unit roleAccess confined to qualifying units
Unit membership with an insufficient roleNo grant from that unit
Neither kind of qualifying roleRefusal

Test the boundaries, not only the list

Check a list, an addressed read and a write naming another unit. Also check a member assigned different roles in two units: only the unit whose role satisfies the operation should qualify. Context stamps can tighten when combined, not overwrite a narrower decision with a wider one later in the same request.

Building a B2B product or an internal tool?

Wildo is not self-service yet. Tell us what you have in mind and we will say plainly whether it fits, and what happens next.