Skip to content

Implement swimlane Assignment Using Groups instead of the realm roles(bpm specific roles)#69

Merged
andrepestana-aot merged 6 commits into
AOT-Technologies:mainfrom
andrepestana-aot:M8F-268-implement-swimlane-assignment-using-groups-instead-of-the-realm-roles-bpm-specific-roles
May 15, 2026
Merged

Implement swimlane Assignment Using Groups instead of the realm roles(bpm specific roles)#69
andrepestana-aot merged 6 commits into
AOT-Technologies:mainfrom
andrepestana-aot:M8F-268-implement-swimlane-assignment-using-groups-instead-of-the-realm-roles-bpm-specific-roles

Conversation

@andrepestana-aot
Copy link
Copy Markdown
Collaborator

@andrepestana-aot andrepestana-aot commented May 13, 2026

JIRA Ticket

https://aottech.atlassian.net/browse/M8F-268

Description

This PR moves swimlane assignment to tenant-scoped organizational groups and separates Keycloak permission roles from organizational groups in tokens. It also adds fail-fast task submission errors for unresolved lane assignment, tenant-aware group syncing on sign-in, and Keycloak bootstrap/template updates for the new token shape and default groups.

Key changes

  • BPMN lane resolution now prefers explicit lane_owners from workflow/script data for the current lane, and otherwise resolves the lane against tenant-qualified local groups such as tenant-a:/Finance.
  • Script and form data are rehydrated into workflow.data["data_objects"], so lane_owners and other script outputs survive async execution and remain available during later gateway/task evaluation.
  • If a queued form submission would create follow-up work that cannot resolve a lane group, the API now returns task_lane_assignment_error immediately instead of failing later in background execution.
  • Keycloak organizational groups and permission roles are now separated. Organizational membership is emitted in groups, while M8Flow permission roles are emitted in a top-level roles claim.
  • The backend now syncs separated claims into tenant-qualified local groups, for example tenant-a:/Finance for org groups and tenant-a:reviewer for permission roles.
  • Tenant realm provisioning and local bootstrap now reconcile the m8flow-backend client mappers, seed default groups, and create the requested default memberships.
  • Default seeded groups are /Approvers, /Designers, /Administrators, and /Support, with reviewer, editor, admin, and integrator assigned respectively.
image - For BPMN lane assignment that relies on lane/group matching, M8Flow no longer fails when the tenant-local group record is missing.Instead, M8Flow now creates a placeholder local group using the canonical tenant-qualified lane identifier and keeps the task assigned to that lane group.The task is saved with: - `lane_assignment_id` pointing to the created group - an empty `potential_owners` list when the group has no users yet When a user from that Keycloak group logs into M8Flow later, the existing assignment backfill logic can attach the open task to that user. - Explicit `lane_owners` defined by script tasks remain strict. If a script task sets `lane_owners` for a lane and the listed users do not exist in the M8Flow DB, M8Flow still fails with a user-facing lane assignment error.

Assigning roles and groups

  • A permission role remains a Keycloak realm role such as editor, reviewer, integrator, viewer, or tenant-admin.
  • A permission role can be assigned directly to a user or assigned to a Keycloak group and inherited by users in that group. In both cases M8Flow reads the effective role from the token’s roles claim.
  • Swimlane routing should use Keycloak organizational groups that match the BPMN lane, for example /Manager or /Finance. M8Flow reads these from the token’s groups claim and stores them locally as tenant-qualified identifiers such as tenant-a:/Manager.
  • If a task needs explicit user overrides instead of group routing, a script task can populate lane_owners with usernames for the target lane.

Token layout

Legacy mixed token shape:

{
  "groups": ["/Finance", "reviewer"]
}

New token shape:

{
  "groups": ["Finance"],
  "roles": ["reviewer"]
}
  • The groups claim now carries normalized organizational group paths without the leading /.
  • The roles claim now carries M8Flow permission roles.
  • For master-realm/admin-cli style tokens that do not include the top-level roles claim, the backend still falls back to realm_access.roles.

Type

  • Feature
  • Bug fix
  • Documentation
  • Other

Changes

  • Backend
  • Frontend
  • Documentation

Testing

Automated coverage in this branch

  • test_apply_promotes_submitted_form_data_into_workflow_data_objects and test_evaluate_exposes_rehydrated_data_objects_without_external_context cover persistence and rehydration of script-produced lane_owners.
  • test_get_potential_owners_from_task_lane_owners_win_over_group_assignment covers explicit lane_owners overriding lane group lookup.
  • test_get_potential_owners_from_task_resolves_bare_lane_to_existing_org_group and test_get_potential_owners_from_task_resolves_full_path_lane_to_existing_org_group cover lane assignment through Keycloak/M8Flow groups when no lane_owners are present.
  • test_get_potential_owners_from_task_keeps_lane_assignment_for_existing_empty_group covers the empty-group case where no error is raised if the local M8Flow group exists.
  • test_get_potential_owners_from_task_raises_when_no_matching_group_exists, test_validate_queued_follow_up_work_turns_missing_lane_assignment_into_api_error, and test_apply_preflights_queued_form_submissions_before_returning cover fail-fast errors when lane assignment cannot resolve locally.
  • test_create_user_from_sign_in_syncs_roles_and_org_groups_separately covers separate syncing of organizational groups and permission roles from the new token layout.
  • test_tenant_user_access_token_separates_groups_and_roles_claims covers the new Keycloak token shape.
  • test_apply_waiting_group_assignments_only_applies_current_tenant_groups covers tenant-safe delayed assignment when a matching local group already exists and a user logs in later.

Recommended regression scenarios

  1. Lane assignment with a script task defining lane_owners for all lanes: verify every lane resolves to the explicit usernames and bypasses group lookup.
  2. Lane assignment with a script task defining lane_owners for only some lanes: verify lanes present in lane_owners use explicit users and omitted lanes fall back to group-based resolution.
  3. Lane assignment with no script task or lane_owners: verify assignment resolves from Keycloak organizational groups synced into M8Flow local groups.
  4. Error when a script task defines a lane_owner that does not exist in M8Flow DB: verify task processing fails with No users found in task data lane owner list for lane ....
  5. No error when a task is assigned to an empty group that already exists or not in M8Flow DB: verify the task remains lane-assigned and becomes visible after a user is added to the group and logs in.
  6. When a user leaves a group, verify the next sign-in sync removes the old tenant-local group membership and unassigns lane-assignment task rows tied to that removed group.

Related Issues

Closes #

@andrepestana-aot andrepestana-aot self-assigned this May 13, 2026
@andrepestana-aot andrepestana-aot marked this pull request as ready for review May 13, 2026 22:55
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 13, 2026

PR Agent Review

Blocking issues

  1. resolve_named_resource_id in ensure_keycloak_master_super_admin.sh can resolve wrong IDs
    • The parser walks line-by-line and stores the last seen "id" in current_id, then matches the "name"/"clientId" field later. In JSON, field order is not guaranteed and pretty-printing may put fields on separate lines unpredictably.
    • This can return an unrelated ID (or empty) and cause updates/deletes against the wrong Keycloak resource.
    • Action: Replace this sed/line-based parser with jq filtering (as already done in start_keycloak.sh) or parse whole JSON objects reliably.
  2. _reconcile_backend_client_claim_mappers may create duplicate roles mappers after deleting legacy groups mapper
    • Function loads mappers once, deletes legacy mapper(s), then checks if any(_is_roles_claim_mapper(mapper) for mapper in mappers): return.
    • If a roles mapper is added concurrently or mapper state changes during reconciliation, this stale snapshot can lead to duplicate creation attempts and noisy failures.
    • Action: Re-fetch mappers after deletion before deciding to create, or handle 409 idempotently as success.

Non-blocking suggestions

  1. Potential mismatch in claim config key formatting in shell scripts
    • Scripts use -s 'config.multivalued=true' while other keys use config."...". Depending on kcadm parsing/version, this can behave inconsistently.
    • Action: Standardize to -s 'config."multivalued"=true' for consistency.
  2. Integration test decodes JWT without asserting claim type
    • test_tenant_user_access_token_separates_groups_and_roles_claims checks content but not that groups/roles are arrays.
    • Action: Add isinstance(decoded_access_token["groups"], list) and same for roles to catch mapper regressions.

Recommended tests

  1. Script-level regression tests for resource ID resolution
    • Add unit/fixture tests for ensure_keycloak_master_super_admin.sh helper resolution logic with:
      • pretty-printed JSON
      • compact JSON
      • multiple resources with similar names
    • Goal: verify exact ID resolution and prevent accidental cross-resource updates.
  2. Idempotency test for mapper reconciliation
    • Add test that runs mapper reconciliation twice and asserts:
      • exactly one normalized groups mapper
      • exactly one roles realm-role mapper
      • no legacy groups <- realm-role mapper remains.
  3. End-to-end token claim shape tests
    • Extend integration coverage to assert:
      • groups claim is list of normalized paths (no leading slash)
      • roles claim is list of role names
      • no role leakage into groups
      • fallback to realm_access.roles when top-level roles absent.

Generated by model gpt-5.3-codex on PR updates via OpenAI Responses API.

Copy link
Copy Markdown
Collaborator

@auslin-aot auslin-aot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The newly created tenant is missing the group claim in the token. Could you please check?

Copy link
Copy Markdown
Collaborator

@auslin-aot auslin-aot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When having script-based lane owners. Throws the following error on the second user task(in lane2) action

workflow
Image
Lane 2 action as admin user(Lane2 owner- Manger)
Image
Error
Image

This issue is caused by process_instance_processor_patch.py lines 120-123

image

Comment thread m8flow-backend/src/m8flow_backend/services/process_instance_processor_patch.py Outdated
@andrepestana-aot
Copy link
Copy Markdown
Collaborator Author

The newly created tenant is missing the group claim in the token. Could you please check?

Fix the problem.
image

image

@andrepestana-aot
Copy link
Copy Markdown
Collaborator Author

When having script-based lane owners. Throws the following error on the second user task(in lane2) action

workflow Image Lane 2 action as admin user(Lane2 owner- Manger) Image Error Image

This issue is caused by process_instance_processor_patch.py lines 120-123

image

There was an issue that I fixed. Although if the user was never logged in (no register exists in m8flow DB), the error will occur as expected.

Copy link
Copy Markdown
Collaborator

@auslin-aot auslin-aot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please resolve conflicts

…groups-instead-of-the-realm-roles-bpm-specific-roles
@andrepestana-aot andrepestana-aot merged commit 0fd1a76 into AOT-Technologies:main May 15, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants