gabriel / musehub public
feat patch graph task/identity-graph #1 / 1
AI Agent gabriel · 156 days ago · Apr 21, 2026 · Diff

feat(graph): identity DAG with acyclicity, root distance, and quorum soundness

I1 — CycleDetector: DFS from proposed target; rejects if it reaches source. I2 — RootDistanceIndex: multi-source BFS from all human nodes; None = no human ancestor. I3 — QuorumEngine: recursive descent; sub-org vote counts only if its quorum independently met. IdentityGraphService: unified surface enforcing all three invariants atomically. 82 TDD tests — all passing.

sha256:1e4ec712d7328e36c0b657b7020aa4eb14627a6ffdf89a2f36dab10ae1a755aa sha
+220 symbols
sha256:f1faf1a2de652a27f7c0d60b37ba8f887f55599b83a4c440f0d5614b7e106121 snapshot
+220
symbols added
0
dead code introduced
Semantic Changes 220 symbols
~ musehub/graph/cycle.py .py 7 symbols added
+ CycleDetector class class CycleDetector L16–45
+ __init__ method method __init__ L17–18
+ assert_no_cycle method method assert_no_cycle L20–45
+ CycleError class class CycleError L12–13
+ EdgeType import import EdgeType L9–9
+ IdentityDAG import import IdentityDAG L9–9
+ annotations import import annotations L7–7
~ musehub/graph/dag.py .py 20 symbols added
+ EdgeType class class EdgeType L15–17
+ GraphEdge class class GraphEdge L20–26
+ __init__ method method __init__ L23–26
+ IdentityDAG class class IdentityDAG L29–104
+ __init__ method method __init__ L36–44
+ add_edge method method add_edge L79–81
+ add_node method method add_node L76–77
+ all_handles method method all_handles L99–104
+ empty method method empty L49–50
+ from_edges method method from_edges L53–59
+ from_nodes_and_edges method method from_nodes_and_edges L62–72
+ nodes method method nodes L90–91
+ predecessors method method predecessors L96–97
+ remove_edge method method remove_edge L83–85
+ successors method method successors L93–94
+ NodeType class class NodeType L9–12
+ Enum import import Enum L5–5
+ Iterable import import Iterable L6–6
+ annotations import import annotations L2–2
+ defaultdict import import defaultdict L4–4
~ musehub/graph/depth.py .py 9 symbols added
+ RootDistanceIndex class class RootDistanceIndex L14–64
+ __init__ method method __init__ L15–21
+ build method method build L24–54
+ distance method method distance L56–59
+ human_ancestors method method human_ancestors L61–64
+ IdentityDAG import import IdentityDAG L11–11
+ NodeType import import NodeType L11–11
+ annotations import import annotations L7–7
+ deque import import deque L9–9
~ musehub/graph/quorum.py .py 10 symbols added
+ OrgSpec class class OrgSpec L14–17
+ QuorumEngine class class QuorumEngine L26–79
+ __init__ method method __init__ L27–28
+ _tally method method _tally L56–79
+ effective_weight method method effective_weight L36–52
+ is_quorum_met method method is_quorum_met L30–34
+ VoteRecord class class VoteRecord L21–23
+ Decimal import import Decimal L10–10
+ annotations import import annotations L7–7
+ dataclass class import dataclass L9–9
~ musehub/graph/service.py .py 24 symbols added
+ IdentityGraphService class class IdentityGraphService L13–109
+ __init__ method method __init__ L14–19
+ _build_quorum_engine method method _build_quorum_engine L100–109
+ _index method method _index L95–98
+ add_identity method method add_identity L23–35
+ add_membership method method add_membership L46–61
+ add_spawn method method add_spawn L37–44
+ human_ancestors method method human_ancestors L73–74
+ is_quorum_met method method is_quorum_met L76–91
+ node_type method method node_type L65–68
+ root_distance method method root_distance L70–71
+ CycleDetector import import CycleDetector L7–7
+ CycleError import import CycleError L7–7
+ Decimal import import Decimal L5–5
+ EdgeType import import EdgeType L8–8
+ GraphEdge import import GraphEdge L8–8
+ IdentityDAG import import IdentityDAG L8–8
+ NodeType import import NodeType L8–8
+ OrgSpec import import OrgSpec L10–10
+ QuorumEngine import import QuorumEngine L10–10
+ RootDistanceIndex import import RootDistanceIndex L9–9
+ VoteRecord import import VoteRecord L10–10
+ annotations import import annotations L2–2
+ defaultdict import import defaultdict L4–4
~ tests/test_graph_cycle.py .py 38 symbols added
+ M variable variable M L24–24
+ S variable variable S L23–23
+ TestCrossEdgeCycle class class TestCrossEdgeCycle L161–182
+ test_complex_cross_edge_valid method method test_complex_cross_edge_valid L174–182
+ test_member_of_then_spawns_cycle method method test_member_of_then_spawns_cycle L168–172
+ test_spawns_then_member_of_cycle method method test_spawns_then_member_of_cycle L162–166
+ TestMemberOfCycle class class TestMemberOfCycle L131–156
+ test_direct_org_cycle method method test_direct_org_cycle L137–141
+ test_indirect_org_cycle_five method method test_indirect_org_cycle_five L148–156
+ test_indirect_org_cycle_three method method test_indirect_org_cycle_three L143–146
+ test_self_membership_rejected method method test_self_membership_rejected L132–135
+ TestMemberOfValid class class TestMemberOfValid L99–126
+ test_agent_joins_org method method test_agent_joins_org L104–106
+ test_deep_org_nesting_valid method method test_deep_org_nesting_valid L112–118
+ test_human_joins_org method method test_human_joins_org L100–102
+ test_multiple_members_in_org_valid method method test_multiple_members_in_org_valid L120–126
+ test_org_joins_parent_org method method test_org_joins_parent_org L108–110
+ TestSpawnsCycle class class TestSpawnsCycle L68–94
+ test_direct_spawn_cycle method method test_direct_spawn_cycle L74–78
+ test_indirect_spawn_cycle_five_nodes method method test_indirect_spawn_cycle_five_nodes L86–94
+ test_indirect_spawn_cycle_three_nodes method method test_indirect_spawn_cycle_three_nodes L80–84
+ test_self_spawn_rejected method method test_self_spawn_rejected L69–72
+ TestSpawnsValid class class TestSpawnsValid L29–63
+ test_agent_spawns_agent method method test_agent_spawns_agent L38–40
+ test_deep_spawn_chain_valid method method test_deep_spawn_chain_valid L46–53
+ test_diamond_dag_no_cycle method method test_diamond_dag_no_cycle L55–63
+ test_empty_graph_accepts_any_spawn method method test_empty_graph_accepts_any_spawn L30–32
+ test_human_spawns_agent method method test_human_spawns_agent L34–36
+ test_org_spawns_agent method method test_org_spawns_agent L42–44
+ dag function function dag L16–20
+ CycleDetector import import CycleDetector L11–11
+ CycleError import import CycleError L11–11
+ EdgeType import import EdgeType L10–10
+ GraphEdge import import GraphEdge L10–10
+ IdentityDAG import import IdentityDAG L10–10
+ NodeType import import NodeType L10–10
+ annotations import import annotations L6–6
+ pytest import import pytest L8–8
~ tests/test_graph_depth.py .py 35 symbols added
+ M variable variable M L15–15
+ S variable variable S L14–14
+ TestAgentSpawnDepth class class TestAgentSpawnDepth L42–75
+ test_agent_chain_with_no_human_root_is_none method method test_agent_chain_with_no_human_root_is_none L69–75
+ test_agent_spawned_by_agent_is_two method method test_agent_spawned_by_agent_is_two L50–56
+ test_agent_spawned_by_human_is_one method method test_agent_spawned_by_human_is_one L43–48
+ test_agent_with_no_spawner_is_none method method test_agent_with_no_spawner_is_none L65–67
+ test_deep_spawn_chain method method test_deep_spawn_chain L58–63
+ TestHumanAncestors class class TestHumanAncestors L180–206
+ test_agent_inherits_spawners_ancestors method method test_agent_inherits_spawners_ancestors L185–190
+ test_human_is_own_ancestor method method test_human_is_own_ancestor L181–183
+ test_no_ancestors_returns_empty method method test_no_ancestors_returns_empty L204–206
+ test_org_inherits_all_reachable_humans method method test_org_inherits_all_reachable_humans L192–202
+ TestHumanDepth class class TestHumanDepth L29–37
+ test_lone_human_is_zero method method test_lone_human_is_zero L30–32
+ test_multiple_humans_all_zero method method test_multiple_humans_all_zero L34–37
+ TestOrgMemberDepth class class TestOrgMemberDepth L80–110
+ test_nested_orgs method method test_nested_orgs L96–103
+ test_org_with_agent_member_depth_two method method test_org_with_agent_member_depth_two L88–94
+ test_org_with_human_member_is_one method method test_org_with_human_member_is_one L81–86
+ test_org_with_no_human_reachable_is_none method method test_org_with_no_human_reachable_is_none L105–110
+ TestShortestPath class class TestShortestPath L115–166
+ test_deeply_nested_finds_shortest method method test_deeply_nested_finds_shortest L149–166
+ test_diamond_takes_shorter_path method method test_diamond_takes_shorter_path L116–131
+ test_two_human_paths_takes_shorter method method test_two_human_paths_takes_shorter L133–147
+ TestUnknownHandle class class TestUnknownHandle L171–175
+ test_unknown_handle_raises method method test_unknown_handle_raises L172–175
+ build function function build L18–24
+ EdgeType import import EdgeType L10–10
+ GraphEdge import import GraphEdge L10–10
+ IdentityDAG import import IdentityDAG L10–10
+ NodeType import import NodeType L10–10
+ RootDistanceIndex import import RootDistanceIndex L11–11
+ annotations import import annotations L6–6
+ pytest import import pytest L8–8
~ tests/test_graph_quorum.py .py 38 symbols added
+ M variable variable M L18–18
+ S variable variable S L17–17
+ TestEffectiveWeight class class TestEffectiveWeight L202–223
+ test_human_effective_weight_is_direct method method test_human_effective_weight_is_direct L203–207
+ test_org_effective_weight_is_direct_when_quorum_met method method test_org_effective_weight_is_direct_when_quorum_met L217–223
+ test_org_effective_weight_zero_when_quorum_not_met method method test_org_effective_weight_zero_when_quorum_not_met L209–215
+ TestFlatQuorum class class TestFlatQuorum L32–84
+ test_non_member_vote_does_not_count method method test_non_member_vote_does_not_count L79–84
+ test_quorum_1_met_by_single_vote method method test_quorum_1_met_by_single_vote L62–66
+ test_quorum_met_exact_threshold method method test_quorum_met_exact_threshold L33–43
+ test_quorum_not_met_no_votes method method test_quorum_not_met_no_votes L56–60
+ test_quorum_not_met_one_short method method test_quorum_not_met_one_short L45–54
+ test_unanimous_quorum method method test_unanimous_quorum L68–77
+ TestNestedQuorum class class TestNestedQuorum L124–197
+ _two_level_engine method method _two_level_engine L125–137
+ test_org_vote_counts_when_its_quorum_met method method test_org_vote_counts_when_its_quorum_met L139–148
+ test_org_vote_does_not_count_when_its_quorum_not_met method method test_org_vote_does_not_count_when_its_quorum_not_met L150–157
+ test_parent_quorum_not_met_even_if_sub_quorum_met method method test_parent_quorum_not_met_even_if_sub_quorum_met L159–166
+ test_three_level_nesting_all_quorums_met method method test_three_level_nesting_all_quorums_met L168–184
+ test_three_level_nesting_middle_quorum_not_met method method test_three_level_nesting_middle_quorum_not_met L186–197
+ TestUnknownOrg class class TestUnknownOrg L228–232
+ test_unknown_org_raises method method test_unknown_org_raises L229–232
+ TestWeightedQuorum class class TestWeightedQuorum L89–119
+ test_fractional_weights_sum_to_quorum method method test_fractional_weights_sum_to_quorum L111–119
+ test_low_weight_vote_does_not_reach_quorum method method test_low_weight_vote_does_not_reach_quorum L100–109
+ test_weighted_vote_reaches_quorum method method test_weighted_vote_reaches_quorum L90–98
+ Decimal import import Decimal L9–9
+ EdgeType import import EdgeType L13–13
+ GraphEdge import import GraphEdge L13–13
+ IdentityDAG import import IdentityDAG L13–13
+ NodeType import import NodeType L13–13
+ OrgSpec import import OrgSpec L14–14
+ QuorumEngine import import QuorumEngine L14–14
+ VoteRecord import import VoteRecord L14–14
+ annotations import import annotations L7–7
+ pytest import import pytest L11–11
+ spec function function spec L21–23
+ vote function function vote L26–27
~ tests/test_graph_service.py .py 39 symbols added
+ TestAddIdentity class class TestAddIdentity L43–63
+ test_add_agent method method test_add_agent L48–50
+ test_add_human method method test_add_human L44–46
+ test_add_org method method test_add_org L52–54
+ test_duplicate_handle_raises method method test_duplicate_handle_raises L56–59
+ test_unknown_handle_raises_on_lookup method method test_unknown_handle_raises_on_lookup L61–63
+ TestAddMembership class class TestAddMembership L99–121
+ test_human_joins_org method method test_human_joins_org L100–104
+ test_membership_cycle_rejected_atomically method method test_membership_cycle_rejected_atomically L110–115
+ test_non_org_target_raises method method test_non_org_target_raises L117–121
+ test_org_joins_parent_org method method test_org_joins_parent_org L106–108
+ TestAddSpawn class class TestAddSpawn L68–94
+ test_agent_spawns_agent method method test_agent_spawns_agent L75–78
+ test_human_spawns_agent method method test_human_spawns_agent L69–73
+ test_spawn_cycle_rejected_atomically method method test_spawn_cycle_rejected_atomically L80–84
+ test_unknown_spawnee_raises method method test_unknown_spawnee_raises L91–94
+ test_unknown_spawner_raises method method test_unknown_spawner_raises L86–89
+ TestRootDistance class class TestRootDistance L126–157
+ test_depth_updates_after_spawn method method test_depth_updates_after_spawn L135–140
+ test_depth_updates_cascade method method test_depth_updates_cascade L142–157
+ test_human_is_zero method method test_human_is_zero L127–129
+ test_orphan_agent_is_none method method test_orphan_agent_is_none L131–133
+ TestServiceAncestors class class TestServiceAncestors L201–218
+ test_agent_inherits_human_spawner method method test_agent_inherits_human_spawner L202–206
+ test_no_ancestors_is_empty_set method method test_no_ancestors_is_empty_set L216–218
+ test_org_inherits_all_human_members method method test_org_inherits_all_human_members L208–214
+ TestServiceQuorum class class TestServiceQuorum L162–196
+ test_nested_quorum_via_service method method test_nested_quorum_via_service L173–196
+ test_simple_quorum_via_service method method test_simple_quorum_via_service L163–171
+ human_and_agent function function human_and_agent L25–29
+ CycleError import import CycleError L13–13
+ Decimal import import Decimal L8–8
+ EdgeType import import EdgeType L12–12
+ IdentityGraphService import import IdentityGraphService L14–14
+ NodeType import import NodeType L12–12
+ annotations import import annotations L6–6
+ pytest import import pytest L10–10
+ svc function function svc L20–21
+ two_orgs function function two_orgs L33–38
← Older Oldest on task/identity-graph
All commits
Newer → Latest on task/identity-graph

0 comments

No comments yet. Be the first to start the discussion.

To add a comment, use the Muse CLI: muse hub commit comment sha256:1e4ec712d7328e36c0b657b7020aa4eb14627a6ffdf89a2f36dab10ae1a755aa --body "your comment"