Fail closed on invalid Glas profiles
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Container Image / build-and-push (push) Successful in 22s

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a028de-e2c8-7732-8521-46a7fc5db82f
This commit is contained in:
tegwick 2026-08-22 23:02:41 +02:00
parent 561538c95e
commit b933cf52c8
12 changed files with 455 additions and 9 deletions

View file

@ -503,6 +503,9 @@ async def evaluate_instructions(payload: dict) -> dict:
"model": result.model,
"output_validated": result.output_validated,
"review_required": result.review_required,
"approach_hint": instruction.approach_hint,
"harness_profile_ref": instruction.harness_profile_ref,
"execution_refs": instruction.execution_refs,
})
return {"task_specs": task_specs, "reports": reports}
@ -538,6 +541,41 @@ async def emit_tasks(payload: dict) -> list[str]:
activity_id = payload.get("activity_id", "")
triggering_event_id = payload.get("triggering_event_id", "")
# Profile errors are policy failures, not best-effort queue failures. Check
# the whole batch before opening a DB transaction or touching IssueSink so a
# later malformed item cannot leave an earlier item partially emitted.
from activity_core.glas_profile import (
ProfileRefError,
normalise_execution_refs,
resolve_execution_selector,
)
validated_specs: list[dict] = []
for index, raw_spec in enumerate(task_specs_raw):
if not isinstance(raw_spec, dict):
raise ApplicationError(
f"task spec {index} is not a mapping",
non_retryable=True,
)
spec_dict = dict(raw_spec)
try:
profile_ref, approach_hint = resolve_execution_selector(
spec_dict.get("harness_profile_ref"),
spec_dict.get("approach_hint"),
)
except ProfileRefError as exc:
source = f"{spec_dict.get('source_type', 'rule')}:{spec_dict.get('source_id', '')}"
raise ApplicationError(
f"execution profile refused for {source}: {exc}",
non_retryable=True,
) from exc
spec_dict["harness_profile_ref"] = profile_ref
spec_dict["approach_hint"] = approach_hint
spec_dict["execution_refs"] = normalise_execution_refs(
spec_dict.get("execution_refs")
)
validated_specs.append(spec_dict)
sink = get_issue_sink()
Session = _get_session_factory()
@ -545,7 +583,7 @@ async def emit_tasks(payload: dict) -> list[str]:
errors: list[str] = []
async with Session() as session:
async with session.begin():
for spec_dict in task_specs_raw:
for spec_dict in validated_specs:
spec = TaskSpec(
title=spec_dict.get("title", ""),
description=spec_dict.get("description", ""),