feat(detection-core): bound model execution handoff

This commit is contained in:
Marius Ciepluch
2026-11-01 12:18:30 +01:00
parent 292d4b5557
commit 64e44341ee
3 changed files with 263 additions and 1 deletions
+1 -1
View File
@@ -310,7 +310,7 @@
- _Requirements: 3.5, 14.3_
- _Boundary: Authorization Adapter_
- _Depends: 3.3, 7.2_
- [ ] 9.2 (P) Translate hunt-owned model execution through the bounded handoff
- [x] 9.2 (P) Translate hunt-owned model execution through the bounded handoff
- Convert structured executor inputs and outputs to the canonical request/result contracts.
- Keep prompt creation, provider calls, caching, parsing, and redaction in the hunt subsystem.
- Reject mismatched declared results through core validation without importing DSPy into core.
+145
View File
@@ -0,0 +1,145 @@
"""Translate injected hunt executor I/O into reproducible core model records.
Prompt rendering, provider execution, caching, parsing, storage, and redaction stay
outside this module. The adapter only freezes the structured handoff and asks the
dependency-free core to validate the returned identities and outcome.
"""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from openultrasast.core import canonical, model
from openultrasast.core.evidence import EvidenceRecord
from openultrasast.core.family import FamilyContract, ResidualQuestion
from openultrasast.core.provenance import ModelCallIdentity
from openultrasast.core.semantics import FieldValue
@dataclass(frozen=True)
class ExecutorModelInput:
"""Frozen structured input from which hunt-owned code may render a prompt."""
request_digest: str
packet_digest: str
family_contract_digest: str
residual_question_id: str
completeness_digest: str
program_declaration_digest: str
record: EvidenceRecord
contract: FamilyContract
residual_question: ResidualQuestion
completeness: tuple[model.CompletenessItem, ...]
@dataclass(frozen=True)
class ExecutorModelOutput:
"""Untrusted structured output supplied by the hunt-owned executor."""
request_digest: str
packet_digest: str
provider: str
model: str
program_digest: str
prompt_digest: str
configuration_digest: str
adapter_revision: str
status: str
normalized_fields: tuple[FieldValue, ...]
inline_redacted_text: str | None
artifact: canonical.ContentRef | None
redaction_policy_digest: str | None
family_decision: str | None
decision_mapping_digest: str | None
failure_reason: str | None
ModelExecutor = Callable[[ExecutorModelInput], ExecutorModelOutput]
def model_executor_input(request: model.ModelRequest) -> ExecutorModelInput:
"""Validate and freeze a core request for hunt-owned prompt/execution code."""
model.model_request_bytes(request)
return ExecutorModelInput(
request.request_id,
request.packet_digest,
request.family_contract_digest,
request.residual_question_id,
request.completeness_digest,
request.program.declaration_digest,
request.record,
request.contract,
request.residual_question,
request.completeness,
)
def _response(output: ExecutorModelOutput) -> model.PreservedModelResponse | None:
absent = (
not output.normalized_fields
and output.inline_redacted_text is None
and output.artifact is None
and output.redaction_policy_digest is None
)
if absent:
return None
if output.redaction_policy_digest is None:
raise model.ModelContractError("preserved executor response requires redaction_policy_digest")
return model.make_preserved_model_response(
normalized_fields=output.normalized_fields,
inline_redacted_text=output.inline_redacted_text,
artifact=output.artifact,
redaction_policy_digest=output.redaction_policy_digest,
)
def _status(value: str) -> model.ModelStatus:
try:
return model.ModelStatus(value)
except (TypeError, ValueError) as exc:
raise model.ModelContractError("executor status must be succeeded or failed") from exc
def _result(output: ExecutorModelOutput) -> model.ModelResult:
if type(output) is not ExecutorModelOutput:
raise model.ModelContractError("executor must return an exact ExecutorModelOutput")
return model.ModelResult(
output.request_digest,
output.packet_digest,
ModelCallIdentity(
output.provider,
output.model,
output.program_digest,
output.prompt_digest,
output.configuration_digest,
output.adapter_revision,
),
_status(output.status),
_response(output),
output.family_decision,
output.decision_mapping_digest,
output.failure_reason,
"",
)
def execute_model_contract(
request: model.ModelRequest,
executor: ModelExecutor,
resolver: canonical.ContentResolver,
) -> model.ModelResult:
"""Execute once through an injected hunt boundary and validate the exact result."""
frozen = model_executor_input(request)
result = _result(executor(frozen))
model.validate_model_result(request, result, resolver)
return result
__all__ = [
"ExecutorModelInput",
"ExecutorModelOutput",
"ModelExecutor",
"execute_model_contract",
"model_executor_input",
]
+117
View File
@@ -0,0 +1,117 @@
"""Hunt-owned execution crosses the dependency-free core model boundary."""
from __future__ import annotations
import ast
from dataclasses import replace
from pathlib import Path
import pytest
from test_core_model import _case, _digest
from openultrasast.core import model
from openultrasast.hunt.model_contract import (
ExecutorModelOutput,
execute_model_contract,
model_executor_input,
)
def _output(result: model.ModelResult) -> ExecutorModelOutput:
identity = result.identity
return ExecutorModelOutput(
request_digest=result.request_digest,
packet_digest=result.packet_digest,
provider=identity.provider,
model=identity.model,
program_digest=identity.program_digest,
prompt_digest=identity.prompt_digest,
configuration_digest=identity.configuration_digest,
adapter_revision=identity.adapter_revision,
status=result.status.value,
normalized_fields=() if result.response is None else result.response.normalized_fields,
inline_redacted_text=None if result.response is None else result.response.inline_redacted_text,
artifact=None if result.response is None else result.response.artifact,
redaction_policy_digest=None if result.response is None else result.response.redaction_policy_digest,
family_decision=result.family_decision,
decision_mapping_digest=result.decision_mapping_digest,
failure_reason=result.failure_reason,
)
@pytest.mark.parametrize("failed", [False, True])
def test_fake_executor_success_and_failure_cross_canonical_boundary(failed: bool) -> None:
case = _case(failed=failed)
seen = []
def executor(value):
seen.append(value)
return _output(case.result)
result = execute_model_contract(case.request, executor, case.resolver)
assert seen == [model_executor_input(case.request)]
assert result == case.result
model.validate_model_result(case.request, result, case.resolver)
@pytest.mark.parametrize(
("field", "value", "message"),
[
("request_digest", _digest("wrong-request"), "request"),
("packet_digest", _digest("wrong-packet"), "packet"),
("program_digest", _digest("wrong-program"), "program"),
("decision_mapping_digest", _digest("wrong-mapping"), "mapping"),
],
)
def test_executor_cannot_substitute_declared_result_identity(field: str, value: str, message: str) -> None:
case = _case()
output = replace(_output(case.result), **{field: value})
with pytest.raises(model.ModelContractError, match=message):
execute_model_contract(case.request, lambda _: output, case.resolver)
def test_failure_shape_and_success_decision_are_core_validated() -> None:
case = _case()
failure = replace(
_output(case.result),
status="failed",
family_decision=None,
decision_mapping_digest=None,
failure_reason=None,
)
with pytest.raises(model.ModelContractError, match="failure_reason"):
execute_model_contract(case.request, lambda _: failure, case.resolver)
forbidden = replace(_output(case.result), family_decision="not-declared")
with pytest.raises(model.ModelContractError, match="allowed"):
execute_model_contract(case.request, lambda _: forbidden, case.resolver)
def test_hunt_adapter_owns_no_provider_or_dspy_execution() -> None:
import openultrasast.hunt.model_contract as adapter
with open(adapter.__file__, encoding="utf-8") as source_file:
source = source_file.read()
assert "import dspy" not in source
assert "litellm" not in source
assert "openai" not in source
def test_core_import_census_has_no_model_runtime_dependency() -> None:
core = Path(__file__).parents[1] / "src" / "openultrasast" / "core"
forbidden = {"dspy", "litellm", "openai"}
found = []
for path in sorted(core.glob("*.py")):
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
for node in ast.walk(tree):
if isinstance(node, ast.Import):
names = {alias.name.split(".", 1)[0] for alias in node.names}
elif isinstance(node, ast.ImportFrom) and node.module:
names = {node.module.split(".", 1)[0]}
else:
continue
if names & forbidden:
found.append((path.name, node.lineno, tuple(sorted(names & forbidden))))
assert found == []