Close decision assistant H18 F19 G18 D20
This commit is contained in:
+18
-3
@@ -2,7 +2,7 @@
|
||||
|
||||
## Current Goal
|
||||
|
||||
Automation execution-layer PXX remains closed and its known scope stays frozen. Under explicit user direction, the active product phase is a dense `Decision Assistant` operator workspace with grouped recall, broader replay baselines, note templates, quick-save shortcuts, and concrete OKX announcements/system-status read-only sources. This run closed H17/F18/G17/D19 by adding handoff resume history / stale-persistence cleanup, digest proof-resolution SLA / repeated-stale suppression, template recovery recommendations / audit filtering, and setup-family review-lane SLA / promotion backlog prioritization. Local validation passed, the current working tree was deployed, and remote smoke confirmed the latest Decision Assistant fields on the live host.
|
||||
Automation execution-layer PXX remains closed and its known scope stays frozen. Under explicit user direction, the active product phase is a dense `Decision Assistant` operator workspace with grouped recall, broader replay baselines, note templates, quick-save shortcuts, and concrete OKX announcements/system-status read-only sources. This run closed H18/F19/G18/D20 by adding handoff cleanup-action readiness / resume backlog grouping, digest proof-SLA backlog routing / freshness-debt summary, template recovery shortcuts / audit lane grouping, and setup-family promotion evidence queue / backlog trend handoff. Local validation passed, the current working tree was deployed, and remote smoke confirmed the latest Decision Assistant fields on the live host.
|
||||
|
||||
## Current Branch
|
||||
|
||||
@@ -96,6 +96,10 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
|
||||
- latest_decision_assistant_digest_proof_sla_slice: proof_resolution_sla_repeated_stale_suppression
|
||||
- latest_decision_assistant_template_recovery_recommendation_slice: recovery_recommendations_audit_filtering
|
||||
- latest_decision_assistant_replay_backlog_slice: review_lane_sla_promotion_backlog_priority
|
||||
- latest_decision_assistant_handoff_backlog_slice: cleanup_action_readiness_resume_backlog_grouping
|
||||
- latest_decision_assistant_digest_backlog_slice: proof_sla_backlog_freshness_debt_summary
|
||||
- latest_decision_assistant_template_audit_lane_slice: recovery_shortcuts_audit_lane_grouping
|
||||
- latest_decision_assistant_replay_evidence_backlog_slice: promotion_evidence_queue_backlog_trend_handoff
|
||||
- latest_decision_assistant_summary_status: implemented
|
||||
- latest_decision_assistant_change_summary_status: implemented
|
||||
- latest_decision_assistant_evidence_status: implemented
|
||||
@@ -176,10 +180,14 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
|
||||
- latest_decision_assistant_digest_proof_sla_status: implemented
|
||||
- latest_decision_assistant_template_recovery_recommendation_status: implemented
|
||||
- latest_decision_assistant_replay_backlog_status: implemented
|
||||
- latest_decision_assistant_handoff_backlog_status: implemented
|
||||
- latest_decision_assistant_digest_backlog_status: implemented
|
||||
- latest_decision_assistant_template_audit_lane_status: implemented
|
||||
- latest_decision_assistant_replay_evidence_backlog_status: implemented
|
||||
- latest_decision_assistant_workbench_status: visible_local_smoke
|
||||
- latest_remote_decision_assistant_deploy_status: deployed_current_head
|
||||
- latest_remote_decision_assistant_smoke_status: passed
|
||||
- latest_remote_decision_assistant_review_packet_status: resume_history_proof_sla_template_filter_review_backlog_visible
|
||||
- latest_remote_decision_assistant_review_packet_status: cleanup_backlog_proof_debt_template_lanes_promotion_evidence_visible
|
||||
- latest_p7_selector_choice: continue_periodic_analysis_decision_monitoring_with_maturity_rollup
|
||||
- latest_p14_slice: webhook_transport_retry
|
||||
- latest_p15_pxx_tasklist_status: ready
|
||||
@@ -258,6 +266,12 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
|
||||
|
||||
## Completed In This Run
|
||||
|
||||
- Added handoff cleanup-action readiness / resume backlog grouping fields (`cleanup_action_readiness`, `cleanup_action_ready_count`, `resume_backlog_group`, `resume_backlog_groups`, and `next_cleanup_action_prompt`) plus cockpit visibility.
|
||||
- Added digest proof-SLA backlog routing / freshness-debt summary fields (`proof_sla_backlog_route`, `proof_sla_backlog_priority`, `freshness_debt_score`, `proof_sla_backlog_queue`, and `freshness_debt_summary`) plus cockpit visibility.
|
||||
- Added template recovery shortcuts / audit lane grouping fields (`recovery_action_shortcuts`, `audit_lane_group`, `audit_lane_rank`, `template_audit_lane_groups`, and `next_recovery_action_shortcut`) plus cockpit visibility.
|
||||
- Added setup-family promotion evidence queue / backlog trend handoff fields (`promotion_evidence_queue`, `promotion_evidence_queue_count`, `promotion_backlog_trend_status`, and `promotion_backlog_trend_handoff`) plus cockpit visibility.
|
||||
- Passed focused validation: `unittest tests.unit.test_operator_workspace_service`, `node --check src/web/app.js`, `unittest tests.unit.test_operator_workspace_service tests.unit.test_api_static_server tests.unit.test_api_server`, full `unittest discover -s tests/unit` (713 tests), and `scripts/run_script_static_checks.py`.
|
||||
- Deployed the current working tree to `http://192.168.3.90:8000`; deploy smoke passed 11 endpoints, `run_remote_ops_smoke.py` passed 9 checks, and remote field smoke confirmed cleanup backlog, proof debt, template audit lanes, and promotion evidence fields.
|
||||
- Added handoff resume-history / stale-persistence cleanup fields (`resume_history`, `resume_history_count`, `stale_persistence_cleanup_queue`, `stale_persistence_cleanup_count`, and `next_stale_persistence_cleanup_prompt`) plus cockpit visibility.
|
||||
- Added digest proof-resolution SLA / repeated-stale suppression fields (`proof_resolution_sla_status`, `proof_resolution_sla_seconds`, `proof_resolution_sla_queue`, `proof_resolution_sla_breach_count`, and `repeated_stale_proof_suppression_count`).
|
||||
- Added template recovery recommendation / audit-filter fields (`preference_recovery_recommendation`, `preference_recovery_recommendations`, `guardrail_audit_filter`, `guardrail_audit_filter_counts`, and `guardrail_audit_filtered_trail`).
|
||||
@@ -560,12 +574,13 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
|
||||
- P24 remote ops smoke passed on `http://192.168.3.90:8000` with 9 passed checks, warnings=[], blockers=[].
|
||||
- PXX final validation passed: 709 unit tests, 73 script static checks, `node --check src/web/app.js`, P24 remote ops smoke passed, and closure gate passed.
|
||||
- Latest H16/F17/G16/D18 validation passed: 713 unit tests, 73 script static checks, `node --check src/web/app.js`, deploy smoke passed 11 endpoints, remote ops smoke passed 9 checks, and remote field smoke returned `status=passed`.
|
||||
- Latest H18/F19/G18/D20 validation passed: 713 unit tests, 73 script static checks, `node --check src/web/app.js`, deploy smoke passed 11 endpoints, remote ops smoke passed 9 checks, and remote field smoke returned `status=passed`.
|
||||
- Latest H17/F18/G17/D19 validation passed: 713 unit tests, 73 script static checks, `node --check src/web/app.js`, deploy smoke passed 11 endpoints, remote ops smoke passed 9 checks, and remote field smoke returned `status=passed`.
|
||||
- Real-live order submission remains intentionally disabled because the OKX order adapter is still not a real-live submitter.
|
||||
|
||||
## Exact Next Step
|
||||
|
||||
Continue the Decision Assistant dense operator workspace with the next bounded slice: H18 handoff cleanup action readiness / resume backlog grouping, F19 digest proof SLA backlog routing / freshness-debt summary, G18 template recovery action shortcuts / audit lane grouping, and D20 setup-family promotion evidence queue / backlog trend handoff. PXX remains closed; do not create P25/P26 automatically, and do not enable real trading.
|
||||
Continue the Decision Assistant dense operator workspace with the next bounded slice: H19 handoff cleanup execution log / grouped resume handoff summary, F20 digest freshness-debt trend / SLA recovery recommendations, G19 template audit-lane recommendations / shortcut outcome telemetry, and D21 setup-family promotion evidence aging / backlog comparison summary. PXX remains closed; do not create P25/P26 automatically, and do not enable real trading.
|
||||
|
||||
## Validation Commands Used Recently
|
||||
|
||||
|
||||
@@ -96,6 +96,10 @@
|
||||
- [x] Decision Assistant F18 digest proof resolution SLA / repeated-stale proof suppression slice complete
|
||||
- [x] Decision Assistant G17 template recovery recommendations / audit filtering slice complete
|
||||
- [x] Decision Assistant D19 setup-family review-lane SLA / promotion backlog prioritization slice complete
|
||||
- [x] Decision Assistant H18 handoff cleanup action readiness / resume backlog grouping slice complete
|
||||
- [x] Decision Assistant F19 digest proof SLA backlog routing / freshness-debt summary slice complete
|
||||
- [x] Decision Assistant G18 template recovery action shortcuts / audit lane grouping slice complete
|
||||
- [x] Decision Assistant D20 setup-family promotion evidence queue / backlog trend handoff slice complete
|
||||
|
||||
## Next Up
|
||||
|
||||
@@ -123,10 +127,14 @@
|
||||
- [x] F18 deepen digest recall with proof resolution SLA / repeated-stale proof suppression
|
||||
- [x] G17 continue review template ergonomics with template recovery recommendations / audit filtering
|
||||
- [x] D19 extend replay comparison with setup-family review-lane SLA / promotion backlog prioritization
|
||||
- [ ] H18 continue compact operator workspace polish with handoff cleanup action readiness / resume backlog grouping
|
||||
- [ ] F19 deepen digest recall with proof SLA backlog routing / freshness-debt summary
|
||||
- [ ] G18 continue review template ergonomics with template recovery action shortcuts / audit lane grouping
|
||||
- [ ] D20 extend replay comparison with setup-family promotion evidence queue / backlog trend handoff
|
||||
- [x] H18 continue compact operator workspace polish with handoff cleanup action readiness / resume backlog grouping
|
||||
- [x] F19 deepen digest recall with proof SLA backlog routing / freshness-debt summary
|
||||
- [x] G18 continue review template ergonomics with template recovery action shortcuts / audit lane grouping
|
||||
- [x] D20 extend replay comparison with setup-family promotion evidence queue / backlog trend handoff
|
||||
- [ ] H19 continue compact operator workspace polish with handoff cleanup execution log / grouped resume handoff summary
|
||||
- [ ] F20 deepen digest recall with freshness-debt trend / SLA recovery recommendations
|
||||
- [ ] G19 continue review template ergonomics with template audit-lane recommendations / shortcut outcome telemetry
|
||||
- [ ] D21 extend replay comparison with setup-family promotion evidence aging / backlog comparison summary
|
||||
- [x] P8 local closure complete
|
||||
- [x] P9 local closure complete
|
||||
- [x] P10 local closure complete
|
||||
@@ -230,6 +238,10 @@
|
||||
- [x] latest_decision_assistant_digest_proof_sla_slice: proof_resolution_sla_repeated_stale_suppression
|
||||
- [x] latest_decision_assistant_template_recovery_recommendation_slice: recovery_recommendations_audit_filtering
|
||||
- [x] latest_decision_assistant_replay_backlog_slice: review_lane_sla_promotion_backlog_priority
|
||||
- [x] latest_decision_assistant_handoff_backlog_slice: cleanup_action_readiness_resume_backlog_grouping
|
||||
- [x] latest_decision_assistant_digest_backlog_slice: proof_sla_backlog_freshness_debt_summary
|
||||
- [x] latest_decision_assistant_template_audit_lane_slice: recovery_shortcuts_audit_lane_grouping
|
||||
- [x] latest_decision_assistant_replay_evidence_backlog_slice: promotion_evidence_queue_backlog_trend_handoff
|
||||
- [x] latest_decision_assistant_summary_status: implemented
|
||||
- [x] latest_decision_assistant_change_summary_status: implemented
|
||||
- [x] latest_decision_assistant_evidence_status: implemented
|
||||
@@ -307,10 +319,14 @@
|
||||
- [x] latest_decision_assistant_digest_proof_sla_status: implemented
|
||||
- [x] latest_decision_assistant_template_recovery_recommendation_status: implemented
|
||||
- [x] latest_decision_assistant_replay_backlog_status: implemented
|
||||
- [x] latest_decision_assistant_handoff_backlog_status: implemented
|
||||
- [x] latest_decision_assistant_digest_backlog_status: implemented
|
||||
- [x] latest_decision_assistant_template_audit_lane_status: implemented
|
||||
- [x] latest_decision_assistant_replay_evidence_backlog_status: implemented
|
||||
- [x] latest_decision_assistant_workbench_status: visible_local_smoke
|
||||
- [x] latest_remote_decision_assistant_deploy_status: deployed_current_head
|
||||
- [x] latest_remote_decision_assistant_smoke_status: passed
|
||||
- [x] latest_remote_decision_assistant_review_packet_status: resume_history_proof_sla_template_filter_review_backlog_visible
|
||||
- [x] latest_remote_decision_assistant_review_packet_status: cleanup_backlog_proof_debt_template_lanes_promotion_evidence_visible
|
||||
- [x] latest_p7_selector_choice: continue_periodic_analysis_decision_monitoring_with_maturity_rollup
|
||||
- [x] latest_p14_slice: webhook_transport_retry
|
||||
- [x] latest_p15_pxx_tasklist_status: ready
|
||||
@@ -388,6 +404,11 @@
|
||||
|
||||
## Newly Done
|
||||
|
||||
- [x] Added handoff cleanup-action readiness / resume backlog grouping fields and cockpit visibility
|
||||
- [x] Added digest proof-SLA backlog routing / freshness-debt summary fields and cockpit visibility
|
||||
- [x] Added template recovery action shortcuts / audit lane grouping fields and cockpit visibility
|
||||
- [x] Added setup-family promotion evidence queue / backlog trend handoff fields and cockpit visibility
|
||||
- [x] Passed focused validation, full unit discovery, script static checks, JS syntax check, deploy smoke, remote ops smoke, and remote field smoke for H18/F19/G18/D20
|
||||
- [x] Added handoff lifecycle / operator resume queue fields and cockpit visibility
|
||||
- [x] Added digest proof follow-up queue, resolved proof state counts, and group follow-up actions
|
||||
- [x] Added template guardrail resolution and preferred-template decay fields
|
||||
|
||||
@@ -1095,6 +1095,9 @@ def _build_decision_assistant_review_entrypoints(
|
||||
item["guardrail_audit_trail"] = outcome.get("guardrail_audit_trail") or []
|
||||
item["guardrail_audit_trail_count"] = outcome.get("guardrail_audit_trail_count") or 0
|
||||
item["preference_recovery_recommendation"] = outcome.get("preference_recovery_recommendation")
|
||||
item["recovery_action_shortcuts"] = outcome.get("recovery_action_shortcuts") or []
|
||||
item["audit_lane_group"] = outcome.get("audit_lane_group")
|
||||
item["audit_lane_rank"] = outcome.get("audit_lane_rank")
|
||||
item["guardrail_audit_filter"] = outcome.get("guardrail_audit_filter")
|
||||
item["guardrail_audit_filter_reason"] = outcome.get("guardrail_audit_filter_reason")
|
||||
item["guardrail_audit_filtered_trail"] = outcome.get("guardrail_audit_filtered_trail") or []
|
||||
@@ -1429,6 +1432,18 @@ def _score_template_outcome(*, template_id: str, usage: dict, notes: list[dict])
|
||||
guardrail_audit_trail=guardrail_audit_trail,
|
||||
guardrail_audit_filter=guardrail_audit_filter,
|
||||
)
|
||||
preference_recovery_recommendation = _template_preference_recovery_recommendation(
|
||||
template_id=template_id,
|
||||
preference_recovery_status=preference_recovery_status,
|
||||
negative_count=negative_count,
|
||||
positive_count=positive_count,
|
||||
actionable_count=action_count,
|
||||
)
|
||||
audit_lane_group = _template_audit_lane_group(
|
||||
guardrail_audit_filter=guardrail_audit_filter,
|
||||
preference_recovery_status=preference_recovery_status,
|
||||
outcome_status=outcome_status,
|
||||
)
|
||||
return {
|
||||
"template_id": template_id,
|
||||
"reuse_count": reuse_count,
|
||||
@@ -1470,13 +1485,14 @@ def _score_template_outcome(*, template_id: str, usage: dict, notes: list[dict])
|
||||
),
|
||||
"guardrail_audit_trail": guardrail_audit_trail[:5],
|
||||
"guardrail_audit_trail_count": len(guardrail_audit_trail),
|
||||
"preference_recovery_recommendation": _template_preference_recovery_recommendation(
|
||||
"preference_recovery_recommendation": preference_recovery_recommendation,
|
||||
"recovery_action_shortcuts": _template_recovery_action_shortcuts(
|
||||
template_id=template_id,
|
||||
preference_recovery_status=preference_recovery_status,
|
||||
negative_count=negative_count,
|
||||
positive_count=positive_count,
|
||||
actionable_count=action_count,
|
||||
recommendation=preference_recovery_recommendation,
|
||||
audit_lane_group=audit_lane_group,
|
||||
),
|
||||
"audit_lane_group": audit_lane_group,
|
||||
"audit_lane_rank": _template_audit_lane_rank(audit_lane_group),
|
||||
"guardrail_audit_filter": guardrail_audit_filter,
|
||||
"guardrail_audit_filter_reason": _template_guardrail_audit_filter_reason(guardrail_audit_filter),
|
||||
"guardrail_audit_filtered_trail": guardrail_audit_filtered_trail[:5],
|
||||
@@ -1650,6 +1666,77 @@ def _template_guardrail_audit_filtered_trail(
|
||||
return guardrail_audit_trail[:2]
|
||||
|
||||
|
||||
def _template_audit_lane_group(
|
||||
*,
|
||||
guardrail_audit_filter: str,
|
||||
preference_recovery_status: str,
|
||||
outcome_status: str,
|
||||
) -> str:
|
||||
if guardrail_audit_filter == "negative_guardrail_only" or preference_recovery_status == "recovery_required":
|
||||
return "guardrail"
|
||||
if preference_recovery_status == "recovery_in_progress":
|
||||
return "recovery"
|
||||
if outcome_status in {"needs_outcome_feedback", "unscored"} or preference_recovery_status == "awaiting_feedback":
|
||||
return "capture"
|
||||
return "stable"
|
||||
|
||||
|
||||
def _template_audit_lane_rank(audit_lane_group: str) -> int:
|
||||
return {
|
||||
"guardrail": 1,
|
||||
"recovery": 2,
|
||||
"capture": 3,
|
||||
"stable": 4,
|
||||
}.get(str(audit_lane_group or "stable"), 4)
|
||||
|
||||
|
||||
def _template_recovery_action_shortcuts(*, template_id: str, recommendation: dict, audit_lane_group: str) -> list[dict]:
|
||||
action = str(recommendation.get("action") or "capture_first_outcome")
|
||||
prompt = str(recommendation.get("prompt") or "")
|
||||
priority = str(recommendation.get("priority") or "low")
|
||||
template_label = template_id.replace("_", " ") or "template"
|
||||
shortcuts = [
|
||||
{
|
||||
"action_id": f"{template_id}:{action}",
|
||||
"label": _template_recovery_shortcut_label(action),
|
||||
"priority": priority,
|
||||
"audit_lane_group": audit_lane_group,
|
||||
"prompt": prompt or f"{template_label}: capture recovery evidence.",
|
||||
}
|
||||
]
|
||||
if audit_lane_group in {"guardrail", "recovery"}:
|
||||
shortcuts.append(
|
||||
{
|
||||
"action_id": f"{template_id}:open_audit_lane",
|
||||
"label": "Open Audit Lane",
|
||||
"priority": "medium" if audit_lane_group == "recovery" else "high",
|
||||
"audit_lane_group": audit_lane_group,
|
||||
"prompt": f"Review {template_label} audit evidence before restoring preference.",
|
||||
}
|
||||
)
|
||||
if audit_lane_group == "capture":
|
||||
shortcuts.append(
|
||||
{
|
||||
"action_id": f"{template_id}:capture_outcome",
|
||||
"label": "Capture Outcome",
|
||||
"priority": "low",
|
||||
"audit_lane_group": audit_lane_group,
|
||||
"prompt": f"Save one outcome note for {template_label} before ranking it again.",
|
||||
}
|
||||
)
|
||||
return shortcuts[:3]
|
||||
|
||||
|
||||
def _template_recovery_shortcut_label(action: str) -> str:
|
||||
if action == "collect_recovery_note":
|
||||
return "Collect Recovery"
|
||||
if action == "review_recovery_evidence":
|
||||
return "Review Recovery"
|
||||
if action == "capture_first_outcome":
|
||||
return "Capture First Outcome"
|
||||
return "Keep Stable"
|
||||
|
||||
|
||||
def _build_template_feedback_loop(*, template_id: str, outcome_status: str, reuse_count: int) -> dict:
|
||||
template_label = template_id.replace("_", " ") or "template"
|
||||
if outcome_status == "review_before_reuse":
|
||||
@@ -1757,10 +1844,23 @@ def _build_template_feedback_aggregation(
|
||||
for item in template_outcomes.values()
|
||||
if dict(item.get("preference_recovery_recommendation") or {}).get("action") != "keep_preferred_candidate"
|
||||
]
|
||||
recovery_action_shortcuts = [
|
||||
dict(shortcut)
|
||||
for item in template_outcomes.values()
|
||||
for shortcut in list(item.get("recovery_action_shortcuts") or [])
|
||||
]
|
||||
recovery_action_shortcuts.sort(
|
||||
key=lambda row: (
|
||||
_template_audit_lane_rank(str(row.get("audit_lane_group") or "stable")),
|
||||
{"high": 1, "medium": 2, "low": 3}.get(str(row.get("priority") or "low"), 3),
|
||||
str(row.get("label") or ""),
|
||||
)
|
||||
)
|
||||
guardrail_audit_filter_counts = {}
|
||||
for item in template_outcomes.values():
|
||||
audit_filter = str(item.get("guardrail_audit_filter") or "unknown")
|
||||
guardrail_audit_filter_counts[audit_filter] = int(guardrail_audit_filter_counts.get(audit_filter) or 0) + 1
|
||||
template_audit_lane_groups = _build_template_audit_lane_groups(template_outcomes)
|
||||
rated_template_count = sum(
|
||||
1
|
||||
for item in template_outcomes.values()
|
||||
@@ -1835,9 +1935,47 @@ def _build_template_feedback_aggregation(
|
||||
"guardrail_audit_filter_counts": guardrail_audit_filter_counts,
|
||||
"guardrail_audit_filtered_trail": guardrail_audit_filtered_trail[:5],
|
||||
"guardrail_audit_filtered_count": len(guardrail_audit_filtered_trail),
|
||||
"template_audit_lane_groups": template_audit_lane_groups,
|
||||
"recovery_action_shortcuts": recovery_action_shortcuts[:6],
|
||||
"recovery_action_shortcut_count": len(recovery_action_shortcuts),
|
||||
"next_recovery_action_shortcut": recovery_action_shortcuts[0] if recovery_action_shortcuts else None,
|
||||
}
|
||||
|
||||
|
||||
def _build_template_audit_lane_groups(template_outcomes: dict[str, dict]) -> list[dict]:
|
||||
lanes: dict[str, list[dict]] = {}
|
||||
for item in template_outcomes.values():
|
||||
lane = str(item.get("audit_lane_group") or "stable")
|
||||
lanes.setdefault(lane, []).append(dict(item))
|
||||
groups = []
|
||||
for lane in ("guardrail", "recovery", "capture", "stable"):
|
||||
items = lanes.get(lane) or []
|
||||
if not items:
|
||||
continue
|
||||
ranked = sorted(items, key=lambda row: (int(row.get("audit_lane_rank") or 9), str(row.get("template_id") or "")))
|
||||
groups.append(
|
||||
{
|
||||
"audit_lane_group": lane,
|
||||
"label": _template_audit_lane_label(lane),
|
||||
"count": len(items),
|
||||
"template_ids": [str(item.get("template_id") or "") for item in ranked[:5]],
|
||||
"next_template_id": str((ranked[0] or {}).get("template_id") or ""),
|
||||
"next_prompt": (ranked[0] or {}).get("preference_recovery_prompt"),
|
||||
}
|
||||
)
|
||||
return groups
|
||||
|
||||
|
||||
def _template_audit_lane_label(lane: str) -> str:
|
||||
if lane == "guardrail":
|
||||
return "Guardrail"
|
||||
if lane == "recovery":
|
||||
return "Recovery"
|
||||
if lane == "capture":
|
||||
return "Capture"
|
||||
return "Stable"
|
||||
|
||||
|
||||
def _pick_best_template_outcome(template_outcomes: dict[str, dict]) -> str | None:
|
||||
if not template_outcomes:
|
||||
return None
|
||||
@@ -2115,10 +2253,38 @@ def _build_decision_assistant_digest_history_summary(history: list[dict]) -> dic
|
||||
"prompt": group.get("proof_resolution_sla_prompt"),
|
||||
"age_seconds": group.get("proof_follow_up_age_seconds"),
|
||||
"suppression": group.get("repeated_stale_proof_suppression"),
|
||||
"backlog_route": group.get("proof_sla_backlog_route"),
|
||||
"backlog_priority": group.get("proof_sla_backlog_priority"),
|
||||
"freshness_debt_score": group.get("freshness_debt_score"),
|
||||
}
|
||||
for group in groups
|
||||
if group.get("proof_resolution_sla_status") in {"breached", "due_soon", "missing_timestamp_sla"}
|
||||
]
|
||||
proof_sla_backlog_routes = {}
|
||||
for group in groups:
|
||||
route = str(group.get("proof_sla_backlog_route") or "unknown")
|
||||
proof_sla_backlog_routes[route] = int(proof_sla_backlog_routes.get(route) or 0) + 1
|
||||
proof_sla_backlog_queue = [
|
||||
{
|
||||
"kind": str(group.get("kind") or "history"),
|
||||
"route": group.get("proof_sla_backlog_route"),
|
||||
"priority": group.get("proof_sla_backlog_priority"),
|
||||
"freshness_debt_score": group.get("freshness_debt_score"),
|
||||
"sla_status": group.get("proof_resolution_sla_status"),
|
||||
"prompt": group.get("proof_resolution_sla_prompt"),
|
||||
"evidence_anchor": group.get("refresh_evidence_anchor"),
|
||||
}
|
||||
for group in groups
|
||||
if group.get("proof_sla_backlog_route") not in {"resolved"}
|
||||
]
|
||||
proof_sla_backlog_queue.sort(
|
||||
key=lambda row: (
|
||||
{"high": 1, "medium": 2, "low": 3, "none": 4}.get(str(row.get("priority") or "none"), 4),
|
||||
-int(row.get("freshness_debt_score") or 0),
|
||||
str(row.get("kind") or ""),
|
||||
)
|
||||
)
|
||||
freshness_debt_summary = _build_digest_history_freshness_debt_summary(groups)
|
||||
repeated_stale_suppression_count = sum(
|
||||
1
|
||||
for group in groups
|
||||
@@ -2195,6 +2361,11 @@ def _build_decision_assistant_digest_history_summary(history: list[dict]) -> dic
|
||||
"repeated_stale_proof_suppression_count": repeated_stale_suppression_count,
|
||||
"next_proof_resolution_sla_kind": proof_resolution_sla_queue[0].get("kind") if proof_resolution_sla_queue else None,
|
||||
"next_proof_resolution_sla_prompt": proof_resolution_sla_queue[0].get("prompt") if proof_resolution_sla_queue else None,
|
||||
"proof_sla_backlog_routes": proof_sla_backlog_routes,
|
||||
"proof_sla_backlog_queue": proof_sla_backlog_queue[:5],
|
||||
"proof_sla_backlog_count": len(proof_sla_backlog_queue),
|
||||
"freshness_debt_summary": freshness_debt_summary,
|
||||
"next_freshness_debt_prompt": freshness_debt_summary.get("next_prompt"),
|
||||
}
|
||||
|
||||
|
||||
@@ -2255,6 +2426,21 @@ def _build_decision_assistant_digest_history_groups(history: list[dict], *, reco
|
||||
proof_follow_up_state=proof_follow_up_state,
|
||||
proof_resolution_sla_seconds=proof_resolution_sla_seconds,
|
||||
)
|
||||
proof_sla_backlog_route = _digest_history_proof_sla_backlog_route(
|
||||
proof_resolution_sla_status=proof_resolution_sla_status,
|
||||
proof_follow_up_state=proof_follow_up_state,
|
||||
freshness_proof_status=freshness_proof_status,
|
||||
)
|
||||
freshness_debt_score = _digest_history_freshness_debt_score(
|
||||
age=age,
|
||||
proof_resolution_sla_status=proof_resolution_sla_status,
|
||||
proof_follow_up_state=proof_follow_up_state,
|
||||
)
|
||||
proof_sla_backlog_priority = _digest_history_proof_sla_backlog_priority(
|
||||
route=proof_sla_backlog_route,
|
||||
proof_resolution_sla_status=proof_resolution_sla_status,
|
||||
freshness_debt_score=freshness_debt_score,
|
||||
)
|
||||
repeated_stale_proof_suppression = _digest_history_repeated_stale_proof_suppression(
|
||||
item_count=len(items),
|
||||
proof_follow_up_stale_queue=proof_follow_up_stale_queue,
|
||||
@@ -2317,6 +2503,9 @@ def _build_decision_assistant_digest_history_groups(history: list[dict], *, reco
|
||||
proof_resolution_sla_status=proof_resolution_sla_status,
|
||||
proof_resolution_sla_seconds=proof_resolution_sla_seconds,
|
||||
),
|
||||
"proof_sla_backlog_route": proof_sla_backlog_route,
|
||||
"proof_sla_backlog_priority": proof_sla_backlog_priority,
|
||||
"freshness_debt_score": freshness_debt_score,
|
||||
"repeated_stale_proof_suppression": repeated_stale_proof_suppression,
|
||||
"refresh_confirmation_trail": refresh_confirmation_trail,
|
||||
"latest_confirmation_trail_item": refresh_confirmation_trail[0] if refresh_confirmation_trail else None,
|
||||
@@ -2648,6 +2837,124 @@ def _digest_history_proof_resolution_sla_prompt(
|
||||
return f"{kind} proof is resolved; no proof SLA action is pending."
|
||||
|
||||
|
||||
def _digest_history_proof_sla_backlog_route(
|
||||
*,
|
||||
proof_resolution_sla_status: str,
|
||||
proof_follow_up_state: str,
|
||||
freshness_proof_status: str,
|
||||
) -> str:
|
||||
if proof_resolution_sla_status == "breached":
|
||||
return "resolve_now"
|
||||
if proof_resolution_sla_status == "missing_timestamp_sla" or freshness_proof_status == "missing_timestamp_proof":
|
||||
return "timestamp_proof"
|
||||
if proof_resolution_sla_status in {"due_soon", "within_sla"} or proof_follow_up_state == "watch":
|
||||
return "watch_queue"
|
||||
return "resolved"
|
||||
|
||||
|
||||
def _digest_history_proof_sla_backlog_priority(
|
||||
*,
|
||||
route: str,
|
||||
proof_resolution_sla_status: str,
|
||||
freshness_debt_score: int,
|
||||
) -> str:
|
||||
if route == "resolve_now" or proof_resolution_sla_status == "breached":
|
||||
return "high"
|
||||
if route == "timestamp_proof":
|
||||
return "high" if int(freshness_debt_score or 0) >= 60 else "medium"
|
||||
if route == "watch_queue":
|
||||
return "medium" if int(freshness_debt_score or 0) >= 45 else "low"
|
||||
return "none"
|
||||
|
||||
|
||||
def _digest_history_freshness_debt_score(
|
||||
*,
|
||||
age: dict,
|
||||
proof_resolution_sla_status: str,
|
||||
proof_follow_up_state: str,
|
||||
) -> int:
|
||||
age_band = str(age.get("age_band") or "unknown_age")
|
||||
base = {
|
||||
"fresh_now": 0,
|
||||
"monitor_window": 10,
|
||||
"same_session": 35,
|
||||
"stale": 75,
|
||||
"untimed": 60,
|
||||
"unknown_age": 60,
|
||||
}.get(age_band, 40)
|
||||
if proof_resolution_sla_status == "breached":
|
||||
base += 25
|
||||
elif proof_resolution_sla_status == "due_soon":
|
||||
base += 15
|
||||
elif proof_resolution_sla_status == "missing_timestamp_sla":
|
||||
base += 20
|
||||
elif proof_resolution_sla_status == "within_sla":
|
||||
base += 10
|
||||
if proof_follow_up_state == "follow_up_required":
|
||||
base += 10
|
||||
return max(0, min(100, int(base)))
|
||||
|
||||
|
||||
def _build_digest_history_freshness_debt_summary(groups: list[dict]) -> dict:
|
||||
if not groups:
|
||||
return {
|
||||
"status": "idle",
|
||||
"debt_score_total": 0,
|
||||
"high_debt_count": 0,
|
||||
"route_counts": {},
|
||||
"next_kind": None,
|
||||
"next_prompt": None,
|
||||
}
|
||||
debt_score_total = sum(int(group.get("freshness_debt_score") or 0) for group in groups)
|
||||
high_debt_groups = [
|
||||
group
|
||||
for group in groups
|
||||
if int(group.get("freshness_debt_score") or 0) >= 70
|
||||
]
|
||||
route_counts = {}
|
||||
for group in groups:
|
||||
route = str(group.get("proof_sla_backlog_route") or "unknown")
|
||||
route_counts[route] = int(route_counts.get(route) or 0) + 1
|
||||
next_group = dict(
|
||||
sorted(
|
||||
groups,
|
||||
key=lambda row: (
|
||||
int(row.get("freshness_debt_score") or 0),
|
||||
str(row.get("kind") or ""),
|
||||
),
|
||||
reverse=True,
|
||||
)[0]
|
||||
)
|
||||
if high_debt_groups:
|
||||
status = "high_debt"
|
||||
elif any(group.get("proof_sla_backlog_route") in {"timestamp_proof", "watch_queue"} for group in groups):
|
||||
status = "watch_debt"
|
||||
else:
|
||||
status = "clear"
|
||||
return {
|
||||
"status": status,
|
||||
"debt_score_total": debt_score_total,
|
||||
"average_debt_score": debt_score_total / len(groups) if groups else 0.0,
|
||||
"high_debt_count": len(high_debt_groups),
|
||||
"route_counts": route_counts,
|
||||
"next_kind": next_group.get("kind"),
|
||||
"next_debt_score": next_group.get("freshness_debt_score"),
|
||||
"next_prompt": _digest_history_freshness_debt_prompt(next_group),
|
||||
}
|
||||
|
||||
|
||||
def _digest_history_freshness_debt_prompt(group: dict) -> str:
|
||||
kind = str(group.get("kind") or "history")
|
||||
route = str(group.get("proof_sla_backlog_route") or "resolved")
|
||||
if route == "resolve_now":
|
||||
return f"Resolve {kind} freshness debt now before reusing this digest group."
|
||||
if route == "timestamp_proof":
|
||||
return f"Attach timestamp proof for {kind} so the digest backlog can leave the debt queue."
|
||||
if route == "watch_queue":
|
||||
return f"Keep {kind} in the watch queue and refresh it before it drives the next note."
|
||||
return f"{kind} freshness debt is clear."
|
||||
|
||||
|
||||
def _digest_history_repeated_stale_proof_suppression(
|
||||
*,
|
||||
item_count: int,
|
||||
@@ -3006,6 +3313,13 @@ def _build_decision_assistant_scan_handoff_summary(cards: list[dict]) -> dict:
|
||||
title=str(item.get("title") or item.get("target_section") or "handoff"),
|
||||
handoff_status=str(item.get("handoff_status") or ""),
|
||||
),
|
||||
"cleanup_action_readiness": _scan_handoff_cleanup_action_readiness(str(item.get("handoff_status") or "")),
|
||||
"cleanup_action_readiness_reason": _scan_handoff_cleanup_action_readiness_reason(
|
||||
title=str(item.get("title") or item.get("target_section") or "handoff"),
|
||||
handoff_status=str(item.get("handoff_status") or ""),
|
||||
),
|
||||
"resume_backlog_group": _scan_handoff_resume_backlog_group(str(item.get("handoff_status") or "")),
|
||||
"resume_backlog_priority": _scan_handoff_resume_backlog_priority(str(item.get("handoff_status") or "")),
|
||||
}
|
||||
for item in cards
|
||||
if item.get("handoff_marker")
|
||||
@@ -3025,6 +3339,20 @@ def _build_decision_assistant_scan_handoff_summary(cards: list[dict]) -> dict:
|
||||
for row in list(item.get("resume_history") or [])
|
||||
]
|
||||
stale_persistence_cleanup_queue = _build_scan_handoff_stale_persistence_cleanup_queue(markers)
|
||||
resume_backlog_groups = _build_scan_handoff_resume_backlog_groups(markers)
|
||||
cleanup_action_queue = [
|
||||
item
|
||||
for item in sorted(
|
||||
markers,
|
||||
key=lambda row: (
|
||||
int(row.get("resume_backlog_priority") or 99),
|
||||
int(row.get("cleanup_priority") or 0) * -1,
|
||||
str(row.get("title") or ""),
|
||||
),
|
||||
)
|
||||
if item.get("cleanup_action_readiness") in {"ready_now", "ready_after_refresh", "ready_after_blocker", "monitor"}
|
||||
]
|
||||
next_cleanup_action = dict(cleanup_action_queue[0] or {}) if cleanup_action_queue else {}
|
||||
next_unresolved_marker = next(
|
||||
(
|
||||
item
|
||||
@@ -3084,6 +3412,10 @@ def _build_decision_assistant_scan_handoff_summary(cards: list[dict]) -> dict:
|
||||
"next_stale_persistence_cleanup_prompt": (
|
||||
stale_persistence_cleanup_queue[0].get("stale_persistence_cleanup_prompt") if stale_persistence_cleanup_queue else None
|
||||
),
|
||||
"resume_backlog_groups": resume_backlog_groups,
|
||||
"cleanup_action_ready_count": len(cleanup_action_queue),
|
||||
"next_cleanup_action_label": next_cleanup_action.get("cleanup_action_label"),
|
||||
"next_cleanup_action_prompt": next_cleanup_action.get("cleanup_action_readiness_reason"),
|
||||
"handoff_stale_policy": "Persisted handoffs older than their stale window should be reopened and refreshed before reuse.",
|
||||
"handoff_cleanup_recommendation": _scan_handoff_cleanup_recommendation(markers),
|
||||
"snooze_options": [
|
||||
@@ -3273,6 +3605,103 @@ def _scan_handoff_stale_persistence_cleanup_prompt(*, title: str, handoff_status
|
||||
return f"{title} does not need stale persistence cleanup."
|
||||
|
||||
|
||||
def _scan_handoff_cleanup_action_readiness(handoff_status: str) -> str:
|
||||
if handoff_status == "ready_handoff":
|
||||
return "ready_now"
|
||||
if handoff_status == "refresh_handoff":
|
||||
return "ready_after_refresh"
|
||||
if handoff_status == "blocked_handoff":
|
||||
return "ready_after_blocker"
|
||||
if handoff_status == "next_handoff":
|
||||
return "monitor"
|
||||
return "not_needed"
|
||||
|
||||
|
||||
def _scan_handoff_cleanup_action_readiness_reason(*, title: str, handoff_status: str) -> str:
|
||||
readiness = _scan_handoff_cleanup_action_readiness(handoff_status)
|
||||
if readiness == "ready_now":
|
||||
return f"{title} can be reopened and cleared from the persisted resume backlog."
|
||||
if readiness == "ready_after_refresh":
|
||||
return f"Refresh {title}, then clear the stale persisted handoff."
|
||||
if readiness == "ready_after_blocker":
|
||||
return f"Keep {title} in the cleanup backlog until its blocker is resolved."
|
||||
if readiness == "monitor":
|
||||
return f"{title} is the next handoff; monitor the stale window before cleanup."
|
||||
return f"{title} does not need a cleanup action."
|
||||
|
||||
|
||||
def _scan_handoff_resume_backlog_group(handoff_status: str) -> str:
|
||||
if handoff_status == "next_handoff":
|
||||
return "active_resume"
|
||||
if handoff_status == "blocked_handoff":
|
||||
return "blocked_cleanup"
|
||||
if handoff_status == "refresh_handoff":
|
||||
return "refresh_cleanup"
|
||||
if handoff_status == "ready_handoff":
|
||||
return "ready_cleanup"
|
||||
return "carry_forward"
|
||||
|
||||
|
||||
def _scan_handoff_resume_backlog_priority(handoff_status: str) -> int:
|
||||
return {
|
||||
"blocked_handoff": 1,
|
||||
"next_handoff": 2,
|
||||
"refresh_handoff": 3,
|
||||
"ready_handoff": 4,
|
||||
"context_ready": 5,
|
||||
}.get(str(handoff_status or "context_ready"), 5)
|
||||
|
||||
|
||||
def _build_scan_handoff_resume_backlog_groups(markers: list[dict]) -> list[dict]:
|
||||
groups_by_name: dict[str, list[dict]] = {}
|
||||
for marker in markers:
|
||||
group = str(marker.get("resume_backlog_group") or "carry_forward")
|
||||
groups_by_name.setdefault(group, []).append(marker)
|
||||
ranked_groups = ["blocked_cleanup", "active_resume", "refresh_cleanup", "ready_cleanup", "carry_forward"]
|
||||
groups = []
|
||||
for group in ranked_groups:
|
||||
items = groups_by_name.get(group) or []
|
||||
if not items:
|
||||
continue
|
||||
ranked_items = sorted(
|
||||
items,
|
||||
key=lambda row: (
|
||||
int(row.get("resume_backlog_priority") or 99),
|
||||
str(row.get("title") or ""),
|
||||
),
|
||||
)
|
||||
cleanup_ready_items = [
|
||||
item
|
||||
for item in ranked_items
|
||||
if item.get("cleanup_action_readiness") in {"ready_now", "ready_after_refresh", "ready_after_blocker", "monitor"}
|
||||
]
|
||||
groups.append(
|
||||
{
|
||||
"resume_backlog_group": group,
|
||||
"label": _scan_handoff_resume_backlog_group_label(group),
|
||||
"count": len(ranked_items),
|
||||
"cleanup_ready_count": len(cleanup_ready_items),
|
||||
"titles": [str(item.get("title") or "handoff") for item in ranked_items[:4]],
|
||||
"next_marker": (ranked_items[0] or {}).get("handoff_marker"),
|
||||
"next_cleanup_action": (cleanup_ready_items[0] or {}).get("cleanup_action_label") if cleanup_ready_items else None,
|
||||
"next_prompt": (ranked_items[0] or {}).get("cleanup_action_readiness_reason"),
|
||||
}
|
||||
)
|
||||
return groups
|
||||
|
||||
|
||||
def _scan_handoff_resume_backlog_group_label(group: str) -> str:
|
||||
if group == "blocked_cleanup":
|
||||
return "Blocked Cleanup"
|
||||
if group == "active_resume":
|
||||
return "Active Resume"
|
||||
if group == "refresh_cleanup":
|
||||
return "Refresh Cleanup"
|
||||
if group == "ready_cleanup":
|
||||
return "Ready Cleanup"
|
||||
return "Carry Forward"
|
||||
|
||||
|
||||
def _build_scan_handoff_stale_persistence_cleanup_queue(markers: list[dict]) -> list[dict]:
|
||||
cleanup_status_rank = {
|
||||
"cleanup_required": 1,
|
||||
@@ -3287,6 +3716,10 @@ def _build_scan_handoff_stale_persistence_cleanup_queue(markers: list[dict]) ->
|
||||
"stale_persistence_cleanup_status": item.get("stale_persistence_cleanup_status"),
|
||||
"stale_persistence_cleanup_prompt": item.get("stale_persistence_cleanup_prompt"),
|
||||
"resume_queue_rank": item.get("resume_queue_rank"),
|
||||
"cleanup_action_readiness": item.get("cleanup_action_readiness"),
|
||||
"cleanup_action_readiness_reason": item.get("cleanup_action_readiness_reason"),
|
||||
"resume_backlog_group": item.get("resume_backlog_group"),
|
||||
"resume_backlog_priority": item.get("resume_backlog_priority"),
|
||||
}
|
||||
for item in markers
|
||||
if item.get("stale_persistence_cleanup_status") in cleanup_status_rank
|
||||
@@ -3321,6 +3754,10 @@ def _build_scan_handoff_resume_queue(markers: list[dict]) -> list[dict]:
|
||||
"resume_history_count": item.get("resume_history_count") or 0,
|
||||
"stale_persistence_cleanup_status": item.get("stale_persistence_cleanup_status"),
|
||||
"stale_persistence_cleanup_prompt": item.get("stale_persistence_cleanup_prompt"),
|
||||
"cleanup_action_readiness": item.get("cleanup_action_readiness"),
|
||||
"cleanup_action_readiness_reason": item.get("cleanup_action_readiness_reason"),
|
||||
"resume_backlog_group": item.get("resume_backlog_group"),
|
||||
"resume_backlog_priority": item.get("resume_backlog_priority"),
|
||||
}
|
||||
for item in markers
|
||||
if item.get("lifecycle_state") != "carry_forward"
|
||||
@@ -3691,6 +4128,19 @@ def _build_setup_replay_broader_baseline_summary(
|
||||
"reason": "insufficient_samples",
|
||||
"action": "Collect replay samples before promotion backlog review.",
|
||||
},
|
||||
"promotion_evidence_queue": [
|
||||
{
|
||||
"evidence_type": "sample_gap",
|
||||
"status": "collecting",
|
||||
"value": 5,
|
||||
"summary": f"{setup_family} needs replay samples before promotion evidence can be reviewed.",
|
||||
}
|
||||
],
|
||||
"promotion_evidence_queue_count": 1,
|
||||
"promotion_backlog_trend_status": "stable_collecting",
|
||||
"promotion_backlog_trend_handoff": (
|
||||
f"Keep {setup_family} in analysis-only collection until replay evidence reaches the sample threshold."
|
||||
),
|
||||
"demotion_history": [],
|
||||
"demotion_history_count": 0,
|
||||
"focus_recommendation": f"Collect samples before changing focus for {setup_family}.",
|
||||
@@ -3924,6 +4374,19 @@ def _build_setup_family_trend_delta(
|
||||
"reason": "insufficient_samples",
|
||||
"action": "Collect replay samples before promotion backlog review.",
|
||||
},
|
||||
"promotion_evidence_queue": [
|
||||
{
|
||||
"evidence_type": "sample_gap",
|
||||
"status": "collecting",
|
||||
"value": max(0, sample_threshold - sample_count),
|
||||
"summary": f"{setup_family} needs more replay samples before promotion evidence can be reviewed.",
|
||||
}
|
||||
],
|
||||
"promotion_evidence_queue_count": 1,
|
||||
"promotion_backlog_trend_status": "stable_collecting",
|
||||
"promotion_backlog_trend_handoff": (
|
||||
f"Keep {setup_family} in analysis-only collection until replay evidence reaches the sample threshold."
|
||||
),
|
||||
"demotion_history": [],
|
||||
"demotion_history_count": 0,
|
||||
"focus_recommendation": f"Collect samples before changing focus for {setup_family}.",
|
||||
@@ -4059,6 +4522,24 @@ def _build_setup_family_trend_delta(
|
||||
promotion_backlog_rank = _setup_family_promotion_backlog_rank(
|
||||
promotion_backlog_priority=promotion_backlog_priority,
|
||||
)
|
||||
promotion_evidence_queue = _setup_family_promotion_evidence_queue(
|
||||
setup_family=setup_family,
|
||||
trend_status=trend_status,
|
||||
sample_count=sample_count,
|
||||
sample_gap=sample_gap,
|
||||
framework_delta=framework_delta,
|
||||
avg_r_delta=avg_r_delta,
|
||||
risk_delta=risk_delta,
|
||||
promotion_blockers=promotion_blockers,
|
||||
review_lane_sla_status=review_lane_sla_status,
|
||||
promotion_backlog_priority=promotion_backlog_priority,
|
||||
)
|
||||
promotion_backlog_trend_status = _setup_family_promotion_backlog_trend_status(
|
||||
promotion_ready=promotion_ready,
|
||||
sample_gap=sample_gap,
|
||||
review_lane_sla_status=review_lane_sla_status,
|
||||
promotion_backlog_priority=promotion_backlog_priority,
|
||||
)
|
||||
return {
|
||||
"status": "active",
|
||||
"trend_status": trend_status,
|
||||
@@ -4109,6 +4590,14 @@ def _build_setup_family_trend_delta(
|
||||
promotion_backlog_reason=promotion_backlog_reason,
|
||||
promotion_next_action=promotion_next_action,
|
||||
),
|
||||
"promotion_evidence_queue": promotion_evidence_queue[:6],
|
||||
"promotion_evidence_queue_count": len(promotion_evidence_queue),
|
||||
"promotion_backlog_trend_status": promotion_backlog_trend_status,
|
||||
"promotion_backlog_trend_handoff": _setup_family_promotion_backlog_trend_handoff(
|
||||
setup_family=setup_family,
|
||||
promotion_backlog_trend_status=promotion_backlog_trend_status,
|
||||
promotion_backlog_reason=promotion_backlog_reason,
|
||||
),
|
||||
"demotion_history": demotion_history[:5],
|
||||
"demotion_history_count": len(demotion_history),
|
||||
"focus_recommendation": focus_recommendation,
|
||||
@@ -4402,6 +4891,89 @@ def _setup_family_promotion_backlog_item(
|
||||
}
|
||||
|
||||
|
||||
def _setup_family_promotion_evidence_queue(
|
||||
*,
|
||||
setup_family: str,
|
||||
trend_status: str,
|
||||
sample_count: int,
|
||||
sample_gap: int,
|
||||
framework_delta: float,
|
||||
avg_r_delta: float,
|
||||
risk_delta: float,
|
||||
promotion_blockers: list[str],
|
||||
review_lane_sla_status: str,
|
||||
promotion_backlog_priority: str,
|
||||
) -> list[dict]:
|
||||
queue = [
|
||||
{
|
||||
"evidence_type": "sample_window",
|
||||
"status": "ready" if sample_gap <= 0 else "collecting",
|
||||
"value": sample_count,
|
||||
"summary": f"{setup_family} has {sample_count} replay sample(s); sample gap is {sample_gap}.",
|
||||
},
|
||||
{
|
||||
"evidence_type": "family_delta",
|
||||
"status": trend_status,
|
||||
"value": round(float(framework_delta or 0.0), 4),
|
||||
"summary": (
|
||||
f"Framework delta {framework_delta:.2f}, avg-R delta {avg_r_delta:.2f}, "
|
||||
f"risk delta {risk_delta:.2f}."
|
||||
),
|
||||
},
|
||||
{
|
||||
"evidence_type": "review_lane_sla",
|
||||
"status": review_lane_sla_status,
|
||||
"value": promotion_backlog_priority,
|
||||
"summary": f"Review lane SLA is {review_lane_sla_status}; backlog priority is {promotion_backlog_priority}.",
|
||||
},
|
||||
]
|
||||
for blocker in promotion_blockers[:3]:
|
||||
queue.append(
|
||||
{
|
||||
"evidence_type": "promotion_blocker",
|
||||
"status": "blocking",
|
||||
"value": blocker,
|
||||
"summary": f"{setup_family} promotion evidence is blocked by {blocker}.",
|
||||
}
|
||||
)
|
||||
return queue
|
||||
|
||||
|
||||
def _setup_family_promotion_backlog_trend_status(
|
||||
*,
|
||||
promotion_ready: bool,
|
||||
sample_gap: int,
|
||||
review_lane_sla_status: str,
|
||||
promotion_backlog_priority: str,
|
||||
) -> str:
|
||||
if promotion_ready:
|
||||
return "ready_for_review"
|
||||
if review_lane_sla_status == "breached" or promotion_backlog_priority == "urgent":
|
||||
return "backlog_breached"
|
||||
if review_lane_sla_status in {"due_now", "due_soon"} or promotion_backlog_priority in {"high", "medium"}:
|
||||
return "backlog_due"
|
||||
if sample_gap > 0:
|
||||
return "stable_collecting"
|
||||
return "evidence_watch"
|
||||
|
||||
|
||||
def _setup_family_promotion_backlog_trend_handoff(
|
||||
*,
|
||||
setup_family: str,
|
||||
promotion_backlog_trend_status: str,
|
||||
promotion_backlog_reason: str,
|
||||
) -> str:
|
||||
if promotion_backlog_trend_status == "ready_for_review":
|
||||
return f"Hand off {setup_family} for analysis-lane promotion review; keep execution permissions unchanged."
|
||||
if promotion_backlog_trend_status == "backlog_breached":
|
||||
return f"Review {setup_family} backlog immediately because {promotion_backlog_reason} is breached."
|
||||
if promotion_backlog_trend_status == "backlog_due":
|
||||
return f"Queue {setup_family} for the next operator review because {promotion_backlog_reason} is due."
|
||||
if promotion_backlog_trend_status == "stable_collecting":
|
||||
return f"Keep collecting {setup_family} replay evidence before promotion handoff."
|
||||
return f"Keep {setup_family} in evidence watch until promotion backlog signals change."
|
||||
|
||||
|
||||
def _rank_session_composite(rows: list[dict], *, session_code: str) -> int | None:
|
||||
if not session_code:
|
||||
return None
|
||||
|
||||
@@ -587,6 +587,10 @@ function renderDecisionAssistant(summary) {
|
||||
['stale persistence cleanup', String(scanHandoffSummary.stale_persistence_cleanup_count ?? 0)],
|
||||
['next persistence cleanup', scanHandoffSummary.next_stale_persistence_cleanup_key || 'n/a'],
|
||||
['persistence cleanup prompt', scanHandoffSummary.next_stale_persistence_cleanup_prompt || 'n/a'],
|
||||
['resume backlog groups', formatList((scanHandoffSummary.resume_backlog_groups || []).map((item) => `${item.label || item.resume_backlog_group}: ${item.count ?? 0}`))],
|
||||
['cleanup action ready', String(scanHandoffSummary.cleanup_action_ready_count ?? 0)],
|
||||
['next cleanup action', scanHandoffSummary.next_cleanup_action_label || 'n/a'],
|
||||
['next cleanup action prompt', scanHandoffSummary.next_cleanup_action_prompt || 'n/a'],
|
||||
['next resume action', scanHandoffSummary.next_resume_action_label || 'n/a'],
|
||||
['next resume prompt', scanHandoffSummary.next_resume_prompt || 'n/a'],
|
||||
['handoff cleanup recommendation', scanHandoffSummary.handoff_cleanup_recommendation || 'n/a'],
|
||||
@@ -729,6 +733,11 @@ function renderDecisionAssistant(summary) {
|
||||
['stale proof suppressions', String(digestSurface.history_summary?.repeated_stale_proof_suppression_count ?? 0)],
|
||||
['next proof SLA kind', digestSurface.history_summary?.next_proof_resolution_sla_kind || 'n/a'],
|
||||
['next proof SLA prompt', digestSurface.history_summary?.next_proof_resolution_sla_prompt || 'n/a'],
|
||||
['proof SLA backlog', String(digestSurface.history_summary?.proof_sla_backlog_count ?? 0)],
|
||||
['proof SLA routes', formatJsonInline(digestSurface.history_summary?.proof_sla_backlog_routes || {})],
|
||||
['freshness debt', digestSurface.history_summary?.freshness_debt_summary?.status || 'n/a'],
|
||||
['freshness debt score', String(digestSurface.history_summary?.freshness_debt_summary?.debt_score_total ?? 0)],
|
||||
['next freshness debt', digestSurface.history_summary?.next_freshness_debt_prompt || 'n/a'],
|
||||
['next proof follow-up', digestSurface.history_summary?.next_proof_follow_up_prompt || 'n/a'],
|
||||
['confirmation trail items', String(digestSurface.history_summary?.refresh_confirmation_trail_count ?? 0)],
|
||||
['confirmation trail', formatList((digestSurface.history_summary?.refresh_confirmation_trail || []).map((item) => `${item.kind}:${item.proof_label}`))],
|
||||
@@ -826,6 +835,9 @@ function renderDecisionAssistant(summary) {
|
||||
['proof SLA status', item.proof_resolution_sla_status || 'n/a'],
|
||||
['proof SLA', formatDurationSeconds(item.proof_resolution_sla_seconds)],
|
||||
['proof SLA prompt', item.proof_resolution_sla_prompt || 'n/a'],
|
||||
['SLA backlog route', item.proof_sla_backlog_route || 'n/a'],
|
||||
['SLA backlog priority', item.proof_sla_backlog_priority || 'n/a'],
|
||||
['freshness debt', String(item.freshness_debt_score ?? 0)],
|
||||
['stale suppression', item.repeated_stale_proof_suppression || 'n/a'],
|
||||
['latest proof trail', item.latest_confirmation_trail_item?.proof_label || 'n/a'],
|
||||
['proof trail items', formatList((item.refresh_confirmation_trail || []).map((row) => `${row.title || row.kind}: ${row.proof_label}`))],
|
||||
@@ -894,6 +906,9 @@ function renderDecisionAssistant(summary) {
|
||||
['next recovery prompt', reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.next_preference_recovery_prompt || 'n/a'],
|
||||
['audit filters', formatJsonInline(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.guardrail_audit_filter_counts || {})],
|
||||
['filtered audit trail', String(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.guardrail_audit_filtered_count ?? 0)],
|
||||
['audit lane groups', formatList((reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.template_audit_lane_groups || []).map((item) => `${item.label || item.audit_lane_group}: ${item.count ?? 0}`))],
|
||||
['recovery shortcuts', String(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.recovery_action_shortcut_count ?? 0)],
|
||||
['next recovery shortcut', reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.next_recovery_action_shortcut?.label || 'n/a'],
|
||||
['preferred template decay', reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.preferred_template_decay_status || 'n/a'],
|
||||
['preferred decay templates', formatList(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.preferred_template_decay_template_ids || [])],
|
||||
['max preference decay', formatPercent(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.preferred_template_decay_max)],
|
||||
@@ -959,6 +974,9 @@ function renderDecisionAssistant(summary) {
|
||||
['preference recovery', item.preference_recovery_status || 'n/a'],
|
||||
['recovery prompt', item.preference_recovery_prompt || 'n/a'],
|
||||
['recovery recommendation', item.preference_recovery_recommendation?.action || 'n/a'],
|
||||
['audit lane', item.audit_lane_group || 'n/a'],
|
||||
['audit lane rank', String(item.audit_lane_rank ?? 'n/a')],
|
||||
['recovery shortcuts', formatList((item.recovery_action_shortcuts || []).map((row) => row.label || row.action_id))],
|
||||
['audit filter', item.guardrail_audit_filter || 'n/a'],
|
||||
['audit filter reason', item.guardrail_audit_filter_reason || 'n/a'],
|
||||
['guardrail audit trail', formatList((item.guardrail_audit_trail || []).map((row) => `${row.label || row.signal}: ${row.resolution_status}`))],
|
||||
@@ -1275,6 +1293,10 @@ function renderDecisionAssistant(summary) {
|
||||
['promotion backlog priority', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_backlog_priority || 'n/a'],
|
||||
['promotion backlog rank', String(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_backlog_rank ?? 'n/a')],
|
||||
['promotion backlog reason', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_backlog_reason || 'n/a'],
|
||||
['promotion evidence queue', String(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_evidence_queue_count ?? 0)],
|
||||
['promotion evidence', formatList((setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_evidence_queue || []).map((item) => `${item.evidence_type}: ${item.status}`))],
|
||||
['backlog trend', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_backlog_trend_status || 'n/a'],
|
||||
['backlog handoff', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_backlog_trend_handoff || 'n/a'],
|
||||
['demotion history', String(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.demotion_history_count ?? 0)],
|
||||
['demotion reasons', formatList((setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.demotion_history || []).map((item) => item.reason || item.event))],
|
||||
['family sample threshold', String(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.sample_threshold ?? 'n/a')],
|
||||
|
||||
@@ -214,6 +214,10 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("guardrail_audit_filter_counts", summary["review_entrypoints"]["template_reuse_metrics"]["template_feedback_aggregation"])
|
||||
self.assertIn("guardrail_audit_filtered_trail", summary["review_entrypoints"]["template_reuse_metrics"]["template_feedback_aggregation"])
|
||||
self.assertIn("guardrail_audit_filtered_count", summary["review_entrypoints"]["template_reuse_metrics"]["template_feedback_aggregation"])
|
||||
self.assertIn("template_audit_lane_groups", summary["review_entrypoints"]["template_reuse_metrics"]["template_feedback_aggregation"])
|
||||
self.assertIn("recovery_action_shortcuts", summary["review_entrypoints"]["template_reuse_metrics"]["template_feedback_aggregation"])
|
||||
self.assertIn("recovery_action_shortcut_count", summary["review_entrypoints"]["template_reuse_metrics"]["template_feedback_aggregation"])
|
||||
self.assertIn("next_recovery_action_shortcut", summary["review_entrypoints"]["template_reuse_metrics"]["template_feedback_aggregation"])
|
||||
self.assertEqual(summary["review_entrypoints"]["note_templates"][0]["reuse_count"], 1)
|
||||
self.assertEqual(summary["review_entrypoints"]["note_templates"][0]["reuse_status"], "saved_reuse")
|
||||
self.assertEqual(summary["review_entrypoints"]["note_templates"][0]["outcome_status"], "useful_signal")
|
||||
@@ -232,6 +236,9 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("guardrail_audit_trail", summary["review_entrypoints"]["note_templates"][0])
|
||||
self.assertIn("guardrail_audit_trail_count", summary["review_entrypoints"]["note_templates"][0])
|
||||
self.assertIn("preference_recovery_recommendation", summary["review_entrypoints"]["note_templates"][0])
|
||||
self.assertIn("recovery_action_shortcuts", summary["review_entrypoints"]["note_templates"][0])
|
||||
self.assertIn("audit_lane_group", summary["review_entrypoints"]["note_templates"][0])
|
||||
self.assertIn("audit_lane_rank", summary["review_entrypoints"]["note_templates"][0])
|
||||
self.assertIn("guardrail_audit_filter", summary["review_entrypoints"]["note_templates"][0])
|
||||
self.assertIn("guardrail_audit_filter_reason", summary["review_entrypoints"]["note_templates"][0])
|
||||
self.assertIn("guardrail_audit_filtered_trail", summary["review_entrypoints"]["note_templates"][0])
|
||||
@@ -272,6 +279,11 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("proof_resolution_sla_queue_count", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("repeated_stale_proof_suppression_count", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("next_proof_resolution_sla_prompt", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("proof_sla_backlog_routes", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("proof_sla_backlog_queue", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("proof_sla_backlog_count", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("freshness_debt_summary", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("next_freshness_debt_prompt", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("refresh_prompts", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("digest_history_groups", summary)
|
||||
self.assertIn("group_status", summary["digest_history_groups"][0])
|
||||
@@ -300,6 +312,9 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("proof_resolution_sla_seconds", summary["digest_history_groups"][0])
|
||||
self.assertIn("proof_resolution_sla_status", summary["digest_history_groups"][0])
|
||||
self.assertIn("proof_resolution_sla_prompt", summary["digest_history_groups"][0])
|
||||
self.assertIn("proof_sla_backlog_route", summary["digest_history_groups"][0])
|
||||
self.assertIn("proof_sla_backlog_priority", summary["digest_history_groups"][0])
|
||||
self.assertIn("freshness_debt_score", summary["digest_history_groups"][0])
|
||||
self.assertIn("repeated_stale_proof_suppression", summary["digest_history_groups"][0])
|
||||
self.assertIn("refresh_confirmation_trail", summary["digest_history_groups"][0])
|
||||
self.assertIn("latest_confirmation_trail_item", summary["digest_history_groups"][0])
|
||||
@@ -355,6 +370,10 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("promotion_backlog_rank", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("promotion_backlog_reason", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("promotion_backlog_item", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("promotion_evidence_queue", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("promotion_evidence_queue_count", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("promotion_backlog_trend_status", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("promotion_backlog_trend_handoff", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("demotion_history", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("demotion_history_count", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("sample_threshold", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
@@ -407,6 +426,10 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("stale_persistence_cleanup_count", summary["scan_handoff_summary"])
|
||||
self.assertIn("next_stale_persistence_cleanup_key", summary["scan_handoff_summary"])
|
||||
self.assertIn("next_stale_persistence_cleanup_prompt", summary["scan_handoff_summary"])
|
||||
self.assertIn("resume_backlog_groups", summary["scan_handoff_summary"])
|
||||
self.assertIn("cleanup_action_ready_count", summary["scan_handoff_summary"])
|
||||
self.assertIn("next_cleanup_action_label", summary["scan_handoff_summary"])
|
||||
self.assertIn("next_cleanup_action_prompt", summary["scan_handoff_summary"])
|
||||
self.assertIn("handoff_cleanup_recommendation", summary["scan_handoff_summary"])
|
||||
self.assertTrue(summary["scan_handoff_summary"]["snooze_options"])
|
||||
self.assertIn("snooze_seconds", summary["scan_handoff_summary"]["handoff_markers"][0])
|
||||
@@ -424,6 +447,10 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("resume_history_count", summary["scan_handoff_summary"]["handoff_markers"][0])
|
||||
self.assertIn("stale_persistence_cleanup_status", summary["scan_handoff_summary"]["handoff_markers"][0])
|
||||
self.assertIn("stale_persistence_cleanup_prompt", summary["scan_handoff_summary"]["handoff_markers"][0])
|
||||
self.assertIn("cleanup_action_readiness", summary["scan_handoff_summary"]["handoff_markers"][0])
|
||||
self.assertIn("cleanup_action_readiness_reason", summary["scan_handoff_summary"]["handoff_markers"][0])
|
||||
self.assertIn("resume_backlog_group", summary["scan_handoff_summary"]["handoff_markers"][0])
|
||||
self.assertIn("resume_backlog_priority", summary["scan_handoff_summary"]["handoff_markers"][0])
|
||||
self.assertTrue(summary["external_context_summary"]["operator_questions"])
|
||||
self.assertTrue(summary["external_context_summary"]["source_cards"])
|
||||
self.assertEqual(summary["external_context_summary"]["exchange_status"]["status"], "active")
|
||||
|
||||
Reference in New Issue
Block a user