Implement ACTIVITY-WP-0021 production automation reliability
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:
parent
1209ff6973
commit
98e8aa83bd
15 changed files with 638 additions and 63 deletions
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue