Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/omnibase_infra/hooks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# SPDX-FileCopyrightText: 2025 OmniNode.ai Inc.
# SPDX-License-Identifier: MIT
32 changes: 32 additions & 0 deletions src/omnibase_infra/hooks/hook_activation_resolver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# SPDX-FileCopyrightText: 2025 OmniNode.ai Inc.
# SPDX-License-Identifier: MIT

import logging

from omnibase_core.models.contracts.subcontracts.model_hook_activation import (
ModelHookActivation,
)

_log = logging.getLogger(__name__)


class HookActivationResolver:
"""Resolves package hook_activations into a composite bitmask."""

def __init__(self, activations: list[ModelHookActivation]) -> None:
self._activations = activations

def compute_mask(self) -> int:
"""Return bitmask with bits set for every enabled_by_default activation."""
mask = 0
for activation in self._activations:
try:
if activation.enabled_by_default:
mask |= int(activation.hook_bit)
except Exception as exc: # noqa: BLE001 — lenient rollout-safety policy: log and skip, never crash runtime
_log.warning(
"Skipping unresolvable hook activation %s: %s",
activation.hook_bit,
exc,
)
return mask
40 changes: 40 additions & 0 deletions tests/integration/test_hook_activation_resolver_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# SPDX-FileCopyrightText: 2025 OmniNode.ai Inc.
# SPDX-License-Identifier: MIT
"""Integration coverage for hook activation mask resolution."""

from __future__ import annotations

import pytest

from omnibase_core.enums.enum_hook_bit import EnumHookBit, hook_enabled
from omnibase_core.models.contracts.subcontracts.model_hook_activation import (
ModelHookActivation,
)
from omnibase_infra.hooks.hook_activation_resolver import HookActivationResolver

pytestmark = pytest.mark.integration


def test_hook_activations_resolve_to_runtime_hook_mask() -> None:
"""Validate hook activation contracts resolve into a runtime hook mask."""
activations = [
ModelHookActivation.model_validate(
{
"hook_bit": "CI_REMINDER",
"enabled_by_default": True,
"description": "Enable CI reminder hook by default.",
}
),
ModelHookActivation.model_validate(
{
"hook_bit": "AUTO_HOSTILE_REVIEW",
"enabled_by_default": False,
"description": "Keep hostile review opt-in for this package.",
}
),
]

mask = HookActivationResolver(activations=activations).compute_mask()

assert hook_enabled(EnumHookBit.CI_REMINDER, mask=mask)
assert not hook_enabled(EnumHookBit.AUTO_HOSTILE_REVIEW, mask=mask)
2 changes: 2 additions & 0 deletions tests/unit/hooks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# SPDX-FileCopyrightText: 2025 OmniNode.ai Inc.
# SPDX-License-Identifier: MIT
54 changes: 54 additions & 0 deletions tests/unit/hooks/test_hook_activation_resolver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# SPDX-FileCopyrightText: 2025 OmniNode.ai Inc.
# SPDX-License-Identifier: MIT

import pytest

from omnibase_core.enums.enum_hook_bit import EnumHookBit
from omnibase_core.models.contracts.subcontracts.model_hook_activation import (
ModelHookActivation,
)
from omnibase_infra.hooks.hook_activation_resolver import HookActivationResolver


@pytest.mark.unit
def test_resolver_returns_activated_bits_for_installed_packages() -> None:
activations = [
ModelHookActivation(hook_bit=EnumHookBit.CI_REMINDER, enabled_by_default=True),
ModelHookActivation(
hook_bit=EnumHookBit.AUTO_HOSTILE_REVIEW, enabled_by_default=False
),
]
resolver = HookActivationResolver(activations=activations)
mask = resolver.compute_mask()
assert mask & int(EnumHookBit.CI_REMINDER) != 0
assert mask & int(EnumHookBit.AUTO_HOSTILE_REVIEW) == 0


@pytest.mark.unit
def test_resolver_empty_activations_returns_zero_mask() -> None:
resolver = HookActivationResolver(activations=[])
mask = resolver.compute_mask()
assert mask == 0


@pytest.mark.unit
def test_resolver_all_enabled_by_default_sets_all_bits() -> None:
activations = [
ModelHookActivation(hook_bit=EnumHookBit.CI_REMINDER, enabled_by_default=True),
ModelHookActivation(hook_bit=EnumHookBit.RUFF_FIX, enabled_by_default=True),
]
resolver = HookActivationResolver(activations=activations)
mask = resolver.compute_mask()
assert mask & int(EnumHookBit.CI_REMINDER) != 0
assert mask & int(EnumHookBit.RUFF_FIX) != 0


@pytest.mark.unit
def test_resolver_none_enabled_by_default_returns_zero_mask() -> None:
activations = [
ModelHookActivation(hook_bit=EnumHookBit.CI_REMINDER, enabled_by_default=False),
ModelHookActivation(hook_bit=EnumHookBit.RUFF_FIX, enabled_by_default=False),
]
resolver = HookActivationResolver(activations=activations)
mask = resolver.compute_mask()
assert mask == 0
Loading