From 031241e118294e8ede9dc931083f30fe7a79f394 Mon Sep 17 00:00:00 2001 From: Jared Atkinson Date: Wed, 9 Sep 2026 22:05:11 -0700 Subject: [PATCH 1/2] BED-9719 bump OpenHound to 0.4.0 --- pyproject.toml | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index dcc7c65..e9134ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ local_scheme = "no-local-version" [dependency-groups] dev = [ - "openhound>=0.2.7", + "openhound>=0.4.0", "pre-commit>=4.5.1", "pytest>=9.0.1", "ruff>=0.15.5", diff --git a/uv.lock b/uv.lock index 0703224..3f2ca6e 100644 --- a/uv.lock +++ b/uv.lock @@ -847,7 +847,7 @@ wheels = [ [[package]] name = "openhound" -version = "0.2.7" +version = "0.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alive-progress" }, @@ -865,9 +865,9 @@ dependencies = [ { name = "typer" }, { name = "types-requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/79/bb/94e8278a628c92d5eb65fbaa284f2936cf4db69428bb275550f474133d69/openhound-0.2.7.tar.gz", hash = "sha256:a8e87e938024ea0dbd3611818aea99315e87e012a84dffcc1cc3580195fa44d0", size = 3542656, upload-time = "2026-06-24T17:04:48.169Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/03/95cf2dc3d2ea8cc8d2245c894f37a97b53b52dd9fda2f20b5f3757854f5d/openhound-0.4.0.tar.gz", hash = "sha256:7ba9f295f073eea4980aef9776a5cdf09c375d58fac39e6d71c2200d4029af34", size = 3580578, upload-time = "2026-09-08T14:54:20.215Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/5b/2d226a995e797142bcda11053fb6e9c8b18db11e73097f3411cdbbb83999/openhound-0.2.7-py3-none-any.whl", hash = "sha256:f758e10e89d3b30b16cd3f5c50bc19f2183f2735296cd50c5c03b2ac3074188a", size = 66881, upload-time = "2026-06-24T17:04:47.041Z" }, + { url = "https://files.pythonhosted.org/packages/da/17/c1d6b7f39a3d08c16982c167f717be63c0fd87b0274e245ccf8cc0a53be2/openhound-0.4.0-py3-none-any.whl", hash = "sha256:f547d34eda0a48e271a316197304dc4c04214e42b9d00a685cea3982d0137567", size = 74608, upload-time = "2026-09-08T14:54:18.895Z" }, ] [[package]] @@ -897,7 +897,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "mypy", specifier = ">=1.19.1" }, - { name = "openhound", specifier = ">=0.2.7" }, + { name = "openhound", specifier = ">=0.4.0" }, { name = "pre-commit", specifier = ">=4.5.1" }, { name = "pytest", specifier = ">=9.0.1" }, { name = "ruff", specifier = ">=0.15.5" }, From 69fbcb72bb27e20b11caa8af12a8d43e65402133 Mon Sep 17 00:00:00 2001 From: Jared Atkinson Date: Wed, 9 Sep 2026 22:26:11 -0700 Subject: [PATCH 2/2] BED-9720 assign source kinds by node family --- src/openhound_github/graph.py | 10 ++++++++++ src/openhound_github/main.py | 2 +- src/openhound_github/models/scim_user.py | 9 ++++----- tests/test_runner_models.py | 14 +++++++++----- tests/test_saml_models.py | 6 ++++++ tests/test_scim_models.py | 5 +++-- 6 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/openhound_github/graph.py b/src/openhound_github/graph.py index a973c8e..1fe0295 100644 --- a/src/openhound_github/graph.py +++ b/src/openhound_github/graph.py @@ -4,6 +4,9 @@ from openhound.core.models.entries_dataclass import Node as BaseNode from openhound.core.models.entries_dataclass import NodeProperties as BaseProperties +GITHUB_SOURCE_KIND = "GitHub" +SAML_SOURCE_KIND = "SAML" + @dataclass class GHNodeProperties(BaseProperties): @@ -22,6 +25,13 @@ def __post_init__(self): # Use GitHub's native node_id as the OpenGraph node id so edges can # reference nodes by the same identifier used during collection. self.id = self.properties.node_id + source_kind = ( + SAML_SOURCE_KIND + if any(kind.startswith("SAML_") for kind in self.kinds) + else GITHUB_SOURCE_KIND + ) + if source_kind not in self.kinds: + self.kinds.append(source_kind) @dataclass diff --git a/src/openhound_github/main.py b/src/openhound_github/main.py index 63d3b41..ab243b5 100644 --- a/src/openhound_github/main.py +++ b/src/openhound_github/main.py @@ -8,7 +8,7 @@ from .lookup import GithubLookup from .transforms import transforms -app = OpenHound("github", source_kind="GitHub", help="OpenGraph collector for GitHub") +app = OpenHound("github", help="OpenGraph collector for GitHub") @app.collect() diff --git a/src/openhound_github/models/scim_user.py b/src/openhound_github/models/scim_user.py index fc9914b..0043bbe 100644 --- a/src/openhound_github/models/scim_user.py +++ b/src/openhound_github/models/scim_user.py @@ -23,6 +23,8 @@ from openhound_github.main import app from openhound_github.models.saml_helpers import detect_foreign_idp +SCIM_SOURCE_KIND = "SCIM" + def scim_organization_id(scope_node_id: str) -> str: return f"SCIM_Organization_{scope_node_id}" @@ -40,7 +42,6 @@ class ScimNodeProperties(NodeProperties): profile_url: str | None = None enterprise: str | None = None organization: str | None = None - source_kind: str | None = None @dataclass @@ -48,7 +49,8 @@ class ScimNode(Node): id: str def __post_init__(self): - return None + if SCIM_SOURCE_KIND not in self.kinds: + self.kinds.append(SCIM_SOURCE_KIND) class Name(BaseModel): @@ -128,7 +130,6 @@ def as_node(self) -> ScimNode: environmentid=self.scope_node_id, enterprise=self.enterprise_slug, organization=self.org_login, - source_kind="GitHub", ), ) @@ -210,7 +211,6 @@ def as_node(self) -> ScimNode: profile_url=self.meta.location if self.meta else None, enterprise=self.enterprise_slug, organization=self.org_login, - source_kind="GitHub", ), ) @@ -306,7 +306,6 @@ def as_node(self) -> ScimNode: external_id=self.external_id, enterprise=self.enterprise_slug, organization=self.org_login, - source_kind="GitHub", ), ) diff --git a/tests/test_runner_models.py b/tests/test_runner_models.py index 92c6bdb..496373e 100644 --- a/tests/test_runner_models.py +++ b/tests/test_runner_models.py @@ -197,7 +197,7 @@ def test_org_runner_group_keeps_generic_runner_group_label() -> None: node = group.as_node - assert node.kinds == [nk.ORG_RUNNER_GROUP, nk.RUNNER_GROUP] + assert node.kinds == [nk.ORG_RUNNER_GROUP, nk.RUNNER_GROUP, "GitHub"] assert node.properties.scope == "organization" assert node.id == "ORG_1_runner_group_1" @@ -267,22 +267,26 @@ def test_runner_groups_and_runners_use_scope_owner_prefixes_with_generic_suffixe ) repo_runner._lookup = SimpleNamespace(org_id_for_login=lambda _login: "ORG_1") - assert org_runner.as_node.kinds == [nk.ORG_RUNNER, nk.RUNNER] + assert org_runner.as_node.kinds == [nk.ORG_RUNNER, nk.RUNNER, "GitHub"] assert org_runner.as_node.properties.scope == "organization" assert org_runner.as_node.id == "ORG_1_runner_8" assert org_runner.as_node.properties.name == "acme/org-runner-1" assert org_runner.as_node.properties.displayname == "org-runner-1" - assert group.as_node.kinds == [nk.ENTERPRISE_RUNNER_GROUP, nk.RUNNER_GROUP] + assert group.as_node.kinds == [ + nk.ENTERPRISE_RUNNER_GROUP, + nk.RUNNER_GROUP, + "GitHub", + ] assert group.as_node.properties.scope == "enterprise" assert group.as_node.id == "ENT_1_runner_group_2" - assert runner.as_node.kinds == [nk.ENTERPRISE_RUNNER, nk.RUNNER] + assert runner.as_node.kinds == [nk.ENTERPRISE_RUNNER, nk.RUNNER, "GitHub"] assert runner.as_node.properties.scope == "enterprise" assert runner.as_node.id == "ENT_1_runner_9" assert runner.as_node.properties.name == "acme-enterprise/enterprise-runner-1" assert runner.as_node.properties.displayname == "enterprise-runner-1" - assert repo_runner.as_node.kinds == [nk.REPO_RUNNER, nk.RUNNER] + assert repo_runner.as_node.kinds == [nk.REPO_RUNNER, nk.RUNNER, "GitHub"] assert repo_runner.as_node.properties.scope == "repository" assert repo_runner.as_node.id == "REPO_1_runner_10" assert repo_runner.as_node.properties.name == "acme/repo/repo-runner-1" diff --git a/tests/test_saml_models.py b/tests/test_saml_models.py index 1a79af3..c063cbb 100644 --- a/tests/test_saml_models.py +++ b/tests/test_saml_models.py @@ -1,6 +1,7 @@ from types import SimpleNamespace from openhound_github.kinds import edges as ek +from openhound_github.kinds import nodes as nk from openhound_github.graphql import ENTERPRISE_SAML_QUERY, SAML_IDENTITIES_QUERY from openhound_github.models.external_identity import ExternalIdentity from openhound_github.models.saml_assertion_consumer_service import ( @@ -58,6 +59,7 @@ def _saml_account_edge(identity: ExternalIdentity): def test_external_identity_prefers_saml_username_for_display_name() -> None: identity = _identity_with_lookup() + assert identity.as_node.kinds == [nk.EXTERNAL_IDENTITY, "GitHub"] assert identity.as_node.properties.name == "Alice@example.com" assert identity.as_node.properties.displayname == "Alice@example.com" @@ -106,6 +108,9 @@ def test_normalized_saml_nodes_expose_contract_metadata() -> None: assert service_provider.as_node.id == "github:saml:sp:org:acme" assert issuer.as_node.id == "github:saml:trusted-issuer:org:acme" assert acs.as_node.id == "github:saml:acs:org:acme" + assert service_provider.as_node.kinds == [nk.SAML_SERVICE_PROVIDER, "SAML"] + assert issuer.as_node.kinds == [nk.SAML_ISSUER, "SAML"] + assert acs.as_node.kinds == [nk.SAML_ASSERTION_CONSUMER_SERVICE, "SAML"] assert sp_properties.github_deployment_id == DEFAULT_GITHUB_DEPLOYMENT_ID assert sp_properties.github_web_origin == DEFAULT_GITHUB_WEB_ORIGIN assert sp_properties.schema_contract_version == SAML_CONTRACT_VERSION @@ -228,6 +233,7 @@ def test_saml_provider_replays_snake_case_fields_and_deployment_metadata() -> No assert provider.sso_url == "https://issuer.example.com/sso" assert provider.signature_method == "rsa-sha256" assert provider.idp_certificate == "certificate-data" + assert provider.as_node.kinds == [nk.SAML_IDENTITY_PROVIDER, "GitHub"] assert provider.as_node.properties.github_deployment_id == "github.example.com" assert provider.as_node.properties.github_web_origin == "https://github.example.com" diff --git a/tests/test_scim_models.py b/tests/test_scim_models.py index 0db8f8d..42c5609 100644 --- a/tests/test_scim_models.py +++ b/tests/test_scim_models.py @@ -90,7 +90,7 @@ def test_scim_user_emits_normalized_edges_without_legacy_correlation_by_default( edges = list(user.edges) unmatched_edges = list(unmatched_user.edges) - assert node.kinds == [nk.SCIM_USER] + assert node.kinds == [nk.SCIM_USER, "SCIM"] assert node.properties.environmentid == "ENT_NODE_1" assert node.properties.external_id == "00u-okta-1" assert node.properties.name == "alice@example.test" @@ -136,6 +136,7 @@ def test_scim_group_emits_membership_and_tenant_scoped_legacy_correlation() -> N edges = list(group.edges) + assert group.as_node.kinds == [nk.SCIM_GROUP, "SCIM"] assert [edge.kind for edge in edges] == [ ek.SCIM_CONTAINS, ek.SCIM_MEMBER_OF, @@ -180,7 +181,7 @@ def test_scim_organization_stays_within_github_environment_root() -> None: node = organization.as_node assert node.id == "SCIM_Organization_ENT_NODE_1" - assert node.kinds == [nk.SCIM_ORGANIZATION] + assert node.kinds == [nk.SCIM_ORGANIZATION, "SCIM"] assert node.properties.environmentid == "ENT_NODE_1"