Implement ACTIVITY-WP-0021 production automation reliability
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Build and Publish Container Image / build-and-push (push) Successful in 47s

Root-cause IssueSink 503 (dead Forgejo PAT on issue-core), add state-hub
task sink path B, log runs before emit, harden sync_schedules, deterministic
SBOM/triage reports, DB probe thrash fix, and prod automation-status helper.
This commit is contained in:
tegwick 2026-07-21 04:21:55 +02:00
parent 1209ff6973
commit 98e8aa83bd
15 changed files with 638 additions and 63 deletions

View file

@ -124,6 +124,20 @@ def execute_instruction_with_audit(
return _execute(instr, event, context, llm_client)
except UntrustedFieldError as exc:
logger.warning("instruction %r rejected — %s", instr.id, exc)
# ACTIVITY-WP-0021-T05: still leave a durable report when sinks are
# configured so daily triage is never silent after a policy rejection.
failure_report = _execution_failure_report(instr, str(exc))
if failure_report is not None:
return InstructionResult(
tasks=[],
report=failure_report,
prompt_hash=None,
model=getattr(instr, "model", None),
output_validated=False,
review_required=True,
condition_matched=getattr(instr, "condition", "") or None,
validation_error=str(exc),
)
return _empty_result(instr)
except Exception as exc:
logger.warning("instruction %r failed — %s", instr.id, exc)
@ -156,6 +170,14 @@ def _execute(
logger.warning("instruction %r condition is unsafe — %s", instr.id, exc)
return _empty_result(instr)
# ACTIVITY-WP-0021: deterministic report-only instructions skip the LLM.
# Use model in {none, deterministic, unused} with report_sinks configured.
model_name = str(getattr(instr, "model", "") or "").strip().lower()
if model_name in {"none", "deterministic", "unused"} and getattr(
instr, "report_sinks", None
):
return _deterministic_context_report(instr, context)
# Step 2 — render prompt (raises UntrustedFieldError on policy violation)
rendered = _render_prompt(instr.prompt, instr.trusted_fields, event, context)
prompt_hash = hashlib.sha256(rendered.encode()).hexdigest()
@ -668,6 +690,62 @@ def _execution_failure_report(instr: Any, error: str) -> dict[str, Any] | None:
}
def _deterministic_context_report(instr: Any, context: dict) -> InstructionResult:
"""Build a report from resolved context without calling an LLM."""
repos = context.get("repos") if isinstance(context, dict) else None
if isinstance(repos, dict):
repo_list = repos.get("repos") if isinstance(repos.get("repos"), list) else []
stale = [
r
for r in repo_list
if isinstance(r, dict) and isinstance(r.get("sbom_age_days"), (int, float)) and r["sbom_age_days"] > 30
]
summary = (
f"SBOM staleness: {len(stale)} stale of {len(repo_list)} repos "
f"(threshold 30d)."
)
report: dict[str, Any] = {
"summary": summary,
"status": "deterministic",
"deterministic": True,
"stale_count": len(stale),
"repo_count": len(repo_list),
"stale_repos": [
{
"repo_slug": r.get("repo_slug"),
"sbom_age_days": r.get("sbom_age_days"),
"has_sbom": r.get("has_sbom"),
"last_sbom_at": r.get("last_sbom_at"),
}
for r in stale[:100]
],
}
else:
digest = context.get("daily_triage_digest") if isinstance(context, dict) else None
if isinstance(digest, str) and digest.strip():
report = {
"summary": f"Deterministic report for instruction {instr.id}.",
"status": "deterministic",
"deterministic": True,
"digest_preview": digest[:4000],
}
else:
report = {
"summary": f"Deterministic report for instruction {instr.id} (empty context).",
"status": "deterministic",
"deterministic": True,
}
return InstructionResult(
tasks=[],
report=report,
prompt_hash=None,
model=getattr(instr, "model", None),
output_validated=True,
review_required=bool(getattr(instr, "review_required", False)),
condition_matched=getattr(instr, "condition", "") or None,
)
def _validate_output(
raw_output: Any,
instr: Any,