It is difficult to measure how similar two sequences of actions are when those actions are tied to complex, nested permissions.
It compares two sequences of actions by looking at how much they overlap while accounting for the roles and permissions assigned to those actions.
It provides a way to accurately measure similarity between tasks while respecting the hierarchy of user permissions.
It was run inside an isolated container with no network access. This is the exact command and the real output it produced — captured process output, not written by a model.
$ python3 rbac_jaccard.py Hierarchical Path-Step Jaccard Similarity: 0.7500
A screenshot of that run.
A clean run proves this does what is shown above, in a CPU-only sandbox. It is a small research demo — not a production tool, and nothing here was published anywhere.
All of it — 68 lines, one file, standard library only.
# Hierarchical Path-Step Jaccard Similarity with RBAC and Action Importance Weighting
from collections import defaultdict
def get_ancestor_permissions(role, roles):
""" Returns all inherited permissions for a role """
permissions = set(roles[role].get('permissions', []))
parent = roles[role].get('parent')
if parent and parent in roles:
permissions.update(get_ancestor_permissions(parent, roles))
return permissions
def calculate_jaccard(seq1, seq2):
""" Calculates Jaccard similarity between two sequences """
set1 = set(seq1)
set2 = set(seq2)
intersection = set1 & set2
union = set1 | set2
return len(intersection) / len(union) if union else 0
def calculate_weighted_jaccard(seq1, seq2, weights):
""" Calculates weighted Jaccard with action importance """
set1 = set(seq1)
set2 = set(seq2)
intersection = set1 & set2
union = set1 | set2
weighted_intersection = sum(weights.get(a, 1) for a in intersection)
weighted_union = sum(weights.get(a, 1) for a in union)
return weighted_intersection / weighted_union if weighted_union else 0.0
def hierarchical_jaccard(roles, seq1, seq2, weights=None):
""" Main function with optional weighting """
allowed_actions = set()
for role in roles:
allowed_actions.update(get_ancestor_permissions(role, roles))
filtered1 = [a for a in seq1 if a in allowed_actions]
filtered2 = [a for a in seq2 if a in allowed_actions]
if weights:
return calculate_weighted_jaccard(filtered1, filtered2, weights)
return calculate_jaccard(filtered1, filtered2)
if __name__ == "__main__":
# RBAC Hierarchy
rbac_roles = {
'admin': {
'parent': None,
'permissions': ['create', 'read', 'update', 'delete']
},
'editor': {
'parent': 'admin',
'permissions': ['read', 'update']
},
'viewer': {
'parent': 'editor',
'permissions': ['read']
}
}
# Test sequences
seq1 = ['create', 'read', 'update', 'delete']
seq2 = ['read', 'update', 'delete', 'publish']
# Standard similarity
sim = hierarchical_jaccard(rbac_roles, seq1, seq2)
print(f'Hierarchical Jaccard Similarity: {sim:.4f}')
# Weighted similarity
weights = {'create':3, 'delete':3, 'update':2, 'read':1, 'publish':2}
wsim = hierarchical_jaccard(rbac_roles, seq1, seq2, weights)
print(f'Hierarchical Weighted Similarity: {wsim:.4f}')