Close decision assistant H17 F18 G17 D19
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 H16/F17/G16/D18 by adding handoff resume persistence / resolved-lane state, digest proof follow-up resolution history / stale queue aging, template preference recovery / guardrail audit trails, and setup-family promotion decision history / review-lane aging. 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 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.
|
||||
|
||||
## Current Branch
|
||||
|
||||
@@ -92,6 +92,10 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
|
||||
- latest_decision_assistant_digest_resolution_history_slice: proof_followup_resolution_history_queue_aging
|
||||
- latest_decision_assistant_template_recovery_slice: preference_recovery_guardrail_audit
|
||||
- latest_decision_assistant_replay_review_lane_slice: promotion_decision_history_review_lane_aging
|
||||
- latest_decision_assistant_handoff_resume_history_slice: resume_history_stale_persistence_cleanup
|
||||
- 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_summary_status: implemented
|
||||
- latest_decision_assistant_change_summary_status: implemented
|
||||
- latest_decision_assistant_evidence_status: implemented
|
||||
@@ -168,10 +172,14 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
|
||||
- latest_decision_assistant_digest_resolution_history_status: implemented
|
||||
- latest_decision_assistant_template_recovery_status: implemented
|
||||
- latest_decision_assistant_replay_review_lane_status: implemented
|
||||
- latest_decision_assistant_handoff_resume_history_status: implemented
|
||||
- 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_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_persistence_proof_resolution_template_recovery_review_lane_visible
|
||||
- latest_remote_decision_assistant_review_packet_status: resume_history_proof_sla_template_filter_review_backlog_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
|
||||
@@ -250,6 +258,12 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
|
||||
|
||||
## Completed In This Run
|
||||
|
||||
- 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`).
|
||||
- Added setup-family review-lane SLA / promotion backlog fields (`review_lane_sla_seconds`, `review_lane_sla_status`, `promotion_backlog_priority`, `promotion_backlog_rank`, and `promotion_backlog_item`).
|
||||
- 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 resume history, proof SLA, recovery audit filtering, and review-lane backlog fields.
|
||||
- Added handoff resume persistence / resolved-lane fields (`resume_persistence_key`, `resume_persistence_status`, `resolved_lane_state`, `resolved_lane_prompt`, `resume_queue_persistence_status`, and `resolved_lane_state_counts`) plus cockpit visibility.
|
||||
- Added digest proof follow-up resolution history / stale queue aging (`proof_follow_up_age_band`, `proof_follow_up_stale_queue`, `proof_resolution_history`, `stale_proof_follow_up_count`, and `proof_follow_up_age_counts`).
|
||||
- Added template preference recovery / guardrail audit fields (`preference_recovery_status`, `preference_recovery_prompt`, `guardrail_audit_trail`, `preference_recovery_queue`, and `guardrail_audit_trail_count`).
|
||||
@@ -546,11 +560,12 @@ 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 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: H17 handoff resume history / stale persistence cleanup, F18 digest proof resolution SLA / repeated-stale proof suppression, G17 template recovery recommendations / audit filtering, and D19 setup-family review-lane SLA / promotion backlog prioritization. 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: 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.
|
||||
|
||||
## Validation Commands Used Recently
|
||||
|
||||
|
||||
@@ -92,6 +92,10 @@
|
||||
- [x] Decision Assistant F17 digest proof follow-up resolution history / stale queue aging slice complete
|
||||
- [x] Decision Assistant G16 template preference recovery / guardrail audit trail slice complete
|
||||
- [x] Decision Assistant D18 setup-family promotion decision history / review lane aging slice complete
|
||||
- [x] Decision Assistant H17 handoff resume history / stale persistence cleanup slice complete
|
||||
- [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
|
||||
|
||||
## Next Up
|
||||
|
||||
@@ -115,10 +119,14 @@
|
||||
- [x] F17 deepen digest recall with proof follow-up resolution history / stale queue aging
|
||||
- [x] G16 continue review template ergonomics with template preference recovery / guardrail audit trail
|
||||
- [x] D18 extend replay comparison with setup-family promotion decision history / review lane aging
|
||||
- [ ] H17 continue compact operator workspace polish with handoff resume history / stale persistence cleanup
|
||||
- [ ] F18 deepen digest recall with proof resolution SLA / repeated-stale proof suppression
|
||||
- [ ] G17 continue review template ergonomics with template recovery recommendations / audit filtering
|
||||
- [ ] D19 extend replay comparison with setup-family review-lane SLA / promotion backlog prioritization
|
||||
- [x] H17 continue compact operator workspace polish with handoff resume history / stale persistence cleanup
|
||||
- [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] P8 local closure complete
|
||||
- [x] P9 local closure complete
|
||||
- [x] P10 local closure complete
|
||||
@@ -218,6 +226,10 @@
|
||||
- [x] latest_decision_assistant_digest_resolution_history_slice: proof_followup_resolution_history_queue_aging
|
||||
- [x] latest_decision_assistant_template_recovery_slice: preference_recovery_guardrail_audit
|
||||
- [x] latest_decision_assistant_replay_review_lane_slice: promotion_decision_history_review_lane_aging
|
||||
- [x] latest_decision_assistant_handoff_resume_history_slice: resume_history_stale_persistence_cleanup
|
||||
- [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_summary_status: implemented
|
||||
- [x] latest_decision_assistant_change_summary_status: implemented
|
||||
- [x] latest_decision_assistant_evidence_status: implemented
|
||||
@@ -291,10 +303,14 @@
|
||||
- [x] latest_decision_assistant_digest_resolution_history_status: implemented
|
||||
- [x] latest_decision_assistant_template_recovery_status: implemented
|
||||
- [x] latest_decision_assistant_replay_review_lane_status: implemented
|
||||
- [x] latest_decision_assistant_handoff_resume_history_status: implemented
|
||||
- [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_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_persistence_proof_resolution_template_recovery_review_lane_visible
|
||||
- [x] latest_remote_decision_assistant_review_packet_status: resume_history_proof_sla_template_filter_review_backlog_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
|
||||
|
||||
@@ -1094,6 +1094,11 @@ def _build_decision_assistant_review_entrypoints(
|
||||
item["preference_recovery_prompt"] = outcome.get("preference_recovery_prompt")
|
||||
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["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 []
|
||||
item["guardrail_audit_filtered_count"] = outcome.get("guardrail_audit_filtered_count") or 0
|
||||
return {
|
||||
"status": "active",
|
||||
"note_count": len(recent_notes),
|
||||
@@ -1415,6 +1420,15 @@ def _score_template_outcome(*, template_id: str, usage: dict, notes: list[dict])
|
||||
feedback_trend_history=feedback_trend_history,
|
||||
negative_count=negative_count,
|
||||
)
|
||||
guardrail_audit_filter = _template_guardrail_audit_filter(
|
||||
negative_count=negative_count,
|
||||
positive_count=positive_count,
|
||||
actionable_count=action_count,
|
||||
)
|
||||
guardrail_audit_filtered_trail = _template_guardrail_audit_filtered_trail(
|
||||
guardrail_audit_trail=guardrail_audit_trail,
|
||||
guardrail_audit_filter=guardrail_audit_filter,
|
||||
)
|
||||
return {
|
||||
"template_id": template_id,
|
||||
"reuse_count": reuse_count,
|
||||
@@ -1456,6 +1470,17 @@ 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(
|
||||
template_id=template_id,
|
||||
preference_recovery_status=preference_recovery_status,
|
||||
negative_count=negative_count,
|
||||
positive_count=positive_count,
|
||||
actionable_count=action_count,
|
||||
),
|
||||
"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],
|
||||
"guardrail_audit_filtered_count": len(guardrail_audit_filtered_trail),
|
||||
}
|
||||
|
||||
|
||||
@@ -1557,6 +1582,74 @@ def _template_guardrail_audit_trail(*, template_id: str, feedback_trend_history:
|
||||
return trail
|
||||
|
||||
|
||||
def _template_preference_recovery_recommendation(
|
||||
*,
|
||||
template_id: str,
|
||||
preference_recovery_status: str,
|
||||
negative_count: int,
|
||||
positive_count: int,
|
||||
actionable_count: int,
|
||||
) -> dict:
|
||||
template_label = template_id.replace("_", " ") or "template"
|
||||
if preference_recovery_status == "recovery_required":
|
||||
action = "collect_recovery_note"
|
||||
priority = "high"
|
||||
reason = f"{negative_count} misleading feedback signal(s) still need recovery evidence."
|
||||
elif preference_recovery_status == "recovery_in_progress":
|
||||
action = "review_recovery_evidence"
|
||||
priority = "medium"
|
||||
reason = f"{positive_count + actionable_count} useful/actionable signal(s) may offset the guardrail."
|
||||
elif preference_recovery_status == "awaiting_feedback":
|
||||
action = "capture_first_outcome"
|
||||
priority = "low"
|
||||
reason = "No preference evidence has been captured yet."
|
||||
else:
|
||||
action = "keep_preferred_candidate"
|
||||
priority = "low"
|
||||
reason = "Preference evidence is stable."
|
||||
return {
|
||||
"template_id": template_id,
|
||||
"action": action,
|
||||
"priority": priority,
|
||||
"reason": reason,
|
||||
"prompt": f"{template_label}: {reason}",
|
||||
}
|
||||
|
||||
|
||||
def _template_guardrail_audit_filter(*, negative_count: int, positive_count: int, actionable_count: int) -> str:
|
||||
if negative_count:
|
||||
return "negative_guardrail_only"
|
||||
if positive_count or actionable_count:
|
||||
return "recovery_evidence"
|
||||
return "awaiting_feedback"
|
||||
|
||||
|
||||
def _template_guardrail_audit_filter_reason(guardrail_audit_filter: str) -> str:
|
||||
if guardrail_audit_filter == "negative_guardrail_only":
|
||||
return "show unresolved or misleading feedback before reuse"
|
||||
if guardrail_audit_filter == "recovery_evidence":
|
||||
return "show useful/actionable evidence supporting stable preference"
|
||||
return "show templates that still need first outcome feedback"
|
||||
|
||||
|
||||
def _template_guardrail_audit_filtered_trail(
|
||||
*,
|
||||
guardrail_audit_trail: list[dict],
|
||||
guardrail_audit_filter: str,
|
||||
) -> list[dict]:
|
||||
if guardrail_audit_filter == "negative_guardrail_only":
|
||||
filtered = [item for item in guardrail_audit_trail if item.get("event") == "negative_guardrail"]
|
||||
return filtered or guardrail_audit_trail[:1]
|
||||
if guardrail_audit_filter == "recovery_evidence":
|
||||
filtered = [
|
||||
item
|
||||
for item in guardrail_audit_trail
|
||||
if item.get("signal") in {"positive_actionable", "positive", "actionable"}
|
||||
]
|
||||
return filtered or guardrail_audit_trail[:2]
|
||||
return guardrail_audit_trail[:2]
|
||||
|
||||
|
||||
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":
|
||||
@@ -1654,6 +1747,20 @@ def _build_template_feedback_aggregation(
|
||||
for item in template_outcomes.values()
|
||||
for row in list(item.get("guardrail_audit_trail") or [])
|
||||
]
|
||||
guardrail_audit_filtered_trail = [
|
||||
row
|
||||
for item in template_outcomes.values()
|
||||
for row in list(item.get("guardrail_audit_filtered_trail") or [])
|
||||
]
|
||||
preference_recovery_recommendations = [
|
||||
dict(item.get("preference_recovery_recommendation") or {})
|
||||
for item in template_outcomes.values()
|
||||
if dict(item.get("preference_recovery_recommendation") or {}).get("action") != "keep_preferred_candidate"
|
||||
]
|
||||
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
|
||||
rated_template_count = sum(
|
||||
1
|
||||
for item in template_outcomes.values()
|
||||
@@ -1717,6 +1824,17 @@ def _build_template_feedback_aggregation(
|
||||
),
|
||||
"guardrail_audit_trail": guardrail_audit_trail[:5],
|
||||
"guardrail_audit_trail_count": len(guardrail_audit_trail),
|
||||
"preference_recovery_recommendations": preference_recovery_recommendations[:5],
|
||||
"preference_recovery_recommendation_count": len(preference_recovery_recommendations),
|
||||
"next_preference_recovery_action": (
|
||||
preference_recovery_recommendations[0].get("action") if preference_recovery_recommendations else None
|
||||
),
|
||||
"next_preference_recovery_prompt": (
|
||||
preference_recovery_recommendations[0].get("prompt") if preference_recovery_recommendations else None
|
||||
),
|
||||
"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),
|
||||
}
|
||||
|
||||
|
||||
@@ -1963,6 +2081,10 @@ def _build_decision_assistant_digest_history_summary(history: list[dict]) -> dic
|
||||
"age_seconds": group.get("proof_follow_up_age_seconds"),
|
||||
"age_band": group.get("proof_follow_up_age_band"),
|
||||
"stale_queue": group.get("proof_follow_up_stale_queue"),
|
||||
"sla_status": group.get("proof_resolution_sla_status"),
|
||||
"sla_seconds": group.get("proof_resolution_sla_seconds"),
|
||||
"sla_prompt": group.get("proof_resolution_sla_prompt"),
|
||||
"repeated_stale_suppression": group.get("repeated_stale_proof_suppression"),
|
||||
}
|
||||
for group in groups
|
||||
if group.get("proof_follow_up_state") in {"follow_up_required", "watch"}
|
||||
@@ -1981,6 +2103,27 @@ def _build_decision_assistant_digest_history_summary(history: list[dict]) -> dic
|
||||
for group in groups
|
||||
for row in list(group.get("proof_resolution_history") or [])
|
||||
]
|
||||
proof_resolution_sla_status_counts = {}
|
||||
for group in groups:
|
||||
sla_status = str(group.get("proof_resolution_sla_status") or "unknown")
|
||||
proof_resolution_sla_status_counts[sla_status] = int(proof_resolution_sla_status_counts.get(sla_status) or 0) + 1
|
||||
proof_resolution_sla_queue = [
|
||||
{
|
||||
"kind": str(group.get("kind") or "history"),
|
||||
"sla_status": group.get("proof_resolution_sla_status"),
|
||||
"sla_seconds": group.get("proof_resolution_sla_seconds"),
|
||||
"prompt": group.get("proof_resolution_sla_prompt"),
|
||||
"age_seconds": group.get("proof_follow_up_age_seconds"),
|
||||
"suppression": group.get("repeated_stale_proof_suppression"),
|
||||
}
|
||||
for group in groups
|
||||
if group.get("proof_resolution_sla_status") in {"breached", "due_soon", "missing_timestamp_sla"}
|
||||
]
|
||||
repeated_stale_suppression_count = sum(
|
||||
1
|
||||
for group in groups
|
||||
if group.get("repeated_stale_proof_suppression") == "suppress_repeated_stale_prompt"
|
||||
)
|
||||
proof_follow_up_with_age = [
|
||||
group
|
||||
for group in groups
|
||||
@@ -2045,6 +2188,13 @@ def _build_decision_assistant_digest_history_summary(history: list[dict]) -> dic
|
||||
"proof_resolution_history_count": len(proof_resolution_history),
|
||||
"oldest_proof_follow_up_kind": oldest_proof_follow_up.get("kind"),
|
||||
"oldest_proof_follow_up_age_seconds": oldest_proof_follow_up.get("proof_follow_up_age_seconds"),
|
||||
"proof_resolution_sla_status_counts": proof_resolution_sla_status_counts,
|
||||
"proof_resolution_sla_breach_count": int(proof_resolution_sla_status_counts.get("breached") or 0),
|
||||
"proof_resolution_sla_queue": proof_resolution_sla_queue[:5],
|
||||
"proof_resolution_sla_queue_count": len(proof_resolution_sla_queue),
|
||||
"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,
|
||||
}
|
||||
|
||||
|
||||
@@ -2097,6 +2247,19 @@ def _build_decision_assistant_digest_history_groups(history: list[dict], *, reco
|
||||
proof_resolved_state=proof_resolved_state,
|
||||
refresh_confirmation_trail=refresh_confirmation_trail,
|
||||
)
|
||||
proof_resolution_sla_seconds = _digest_history_proof_resolution_sla_seconds(
|
||||
proof_follow_up_state=proof_follow_up_state,
|
||||
)
|
||||
proof_resolution_sla_status = _digest_history_proof_resolution_sla_status(
|
||||
age=age,
|
||||
proof_follow_up_state=proof_follow_up_state,
|
||||
proof_resolution_sla_seconds=proof_resolution_sla_seconds,
|
||||
)
|
||||
repeated_stale_proof_suppression = _digest_history_repeated_stale_proof_suppression(
|
||||
item_count=len(items),
|
||||
proof_follow_up_stale_queue=proof_follow_up_stale_queue,
|
||||
proof_resolution_sla_status=proof_resolution_sla_status,
|
||||
)
|
||||
groups.append(
|
||||
{
|
||||
"kind": kind,
|
||||
@@ -2147,6 +2310,14 @@ def _build_decision_assistant_digest_history_groups(history: list[dict], *, reco
|
||||
"proof_follow_up_age_band": proof_follow_up_age_band,
|
||||
"proof_follow_up_stale_queue": proof_follow_up_stale_queue,
|
||||
"proof_resolution_history": proof_resolution_history,
|
||||
"proof_resolution_sla_seconds": proof_resolution_sla_seconds,
|
||||
"proof_resolution_sla_status": proof_resolution_sla_status,
|
||||
"proof_resolution_sla_prompt": _digest_history_proof_resolution_sla_prompt(
|
||||
kind=kind,
|
||||
proof_resolution_sla_status=proof_resolution_sla_status,
|
||||
proof_resolution_sla_seconds=proof_resolution_sla_seconds,
|
||||
),
|
||||
"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,
|
||||
"items": items[:3],
|
||||
@@ -2432,6 +2603,64 @@ def _digest_history_proof_resolution_history(
|
||||
return history
|
||||
|
||||
|
||||
def _digest_history_proof_resolution_sla_seconds(*, proof_follow_up_state: str) -> int:
|
||||
if proof_follow_up_state == "follow_up_required":
|
||||
return 3600
|
||||
if proof_follow_up_state == "watch":
|
||||
return 7200
|
||||
return 0
|
||||
|
||||
|
||||
def _digest_history_proof_resolution_sla_status(
|
||||
*,
|
||||
age: dict,
|
||||
proof_follow_up_state: str,
|
||||
proof_resolution_sla_seconds: int,
|
||||
) -> str:
|
||||
if proof_follow_up_state == "resolved":
|
||||
return "resolved"
|
||||
age_seconds = age.get("age_seconds")
|
||||
if age_seconds is None:
|
||||
return "missing_timestamp_sla"
|
||||
if proof_resolution_sla_seconds <= 0:
|
||||
return "resolved"
|
||||
if int(age_seconds or 0) >= proof_resolution_sla_seconds:
|
||||
return "breached"
|
||||
if int(age_seconds or 0) >= max(1, int(proof_resolution_sla_seconds * 0.75)):
|
||||
return "due_soon"
|
||||
return "within_sla"
|
||||
|
||||
|
||||
def _digest_history_proof_resolution_sla_prompt(
|
||||
*,
|
||||
kind: str,
|
||||
proof_resolution_sla_status: str,
|
||||
proof_resolution_sla_seconds: int,
|
||||
) -> str:
|
||||
if proof_resolution_sla_status == "breached":
|
||||
return f"Resolve or refresh {kind} proof now; it has exceeded the {proof_resolution_sla_seconds}s proof SLA."
|
||||
if proof_resolution_sla_status == "due_soon":
|
||||
return f"Resolve {kind} proof before the {proof_resolution_sla_seconds}s SLA expires."
|
||||
if proof_resolution_sla_status == "missing_timestamp_sla":
|
||||
return f"Add timestamp proof for {kind} so the SLA can be measured."
|
||||
if proof_resolution_sla_status == "within_sla":
|
||||
return f"{kind} proof is inside SLA but still needs follow-up."
|
||||
return f"{kind} proof is resolved; no proof SLA action is pending."
|
||||
|
||||
|
||||
def _digest_history_repeated_stale_proof_suppression(
|
||||
*,
|
||||
item_count: int,
|
||||
proof_follow_up_stale_queue: bool,
|
||||
proof_resolution_sla_status: str,
|
||||
) -> str:
|
||||
if proof_follow_up_stale_queue and item_count > 1 and proof_resolution_sla_status in {"breached", "missing_timestamp_sla"}:
|
||||
return "suppress_repeated_stale_prompt"
|
||||
if proof_follow_up_stale_queue:
|
||||
return "allow_single_stale_prompt"
|
||||
return "not_stale"
|
||||
|
||||
|
||||
def _digest_history_refresh_evidence_anchor(*, kind: str, items: list[dict], latest_ts) -> dict:
|
||||
latest = dict(items[0] or {}) if items else {}
|
||||
title = str(latest.get("title") or kind or "history")
|
||||
@@ -2756,6 +2985,27 @@ 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 ""),
|
||||
),
|
||||
"resume_history": _scan_handoff_resume_history(
|
||||
title=str(item.get("title") or item.get("target_section") or "handoff"),
|
||||
handoff_status=str(item.get("handoff_status") or ""),
|
||||
persistence_key=str(item.get("handoff_persistence_key") or item.get("handoff_marker") or ""),
|
||||
stale_after_seconds=int(item.get("handoff_stale_after_seconds") or 0),
|
||||
),
|
||||
"resume_history_count": len(
|
||||
_scan_handoff_resume_history(
|
||||
title=str(item.get("title") or item.get("target_section") or "handoff"),
|
||||
handoff_status=str(item.get("handoff_status") or ""),
|
||||
persistence_key=str(item.get("handoff_persistence_key") or item.get("handoff_marker") or ""),
|
||||
stale_after_seconds=int(item.get("handoff_stale_after_seconds") or 0),
|
||||
)
|
||||
),
|
||||
"stale_persistence_cleanup_status": _scan_handoff_stale_persistence_cleanup_status(
|
||||
str(item.get("handoff_status") or "")
|
||||
),
|
||||
"stale_persistence_cleanup_prompt": _scan_handoff_stale_persistence_cleanup_prompt(
|
||||
title=str(item.get("title") or item.get("target_section") or "handoff"),
|
||||
handoff_status=str(item.get("handoff_status") or ""),
|
||||
),
|
||||
}
|
||||
for item in cards
|
||||
if item.get("handoff_marker")
|
||||
@@ -2769,6 +3019,12 @@ def _build_decision_assistant_scan_handoff_summary(cards: list[dict]) -> dict:
|
||||
next_cleanup_marker = dict(cleanup_markers[0] or {}) if cleanup_markers else {}
|
||||
operator_resume_queue = _build_scan_handoff_resume_queue(markers)
|
||||
resolved_lane_state_counts = _build_scan_handoff_resolved_lane_state_counts(markers)
|
||||
resume_history = [
|
||||
row
|
||||
for item in markers
|
||||
for row in list(item.get("resume_history") or [])
|
||||
]
|
||||
stale_persistence_cleanup_queue = _build_scan_handoff_stale_persistence_cleanup_queue(markers)
|
||||
next_unresolved_marker = next(
|
||||
(
|
||||
item
|
||||
@@ -2817,6 +3073,17 @@ def _build_decision_assistant_scan_handoff_summary(cards: list[dict]) -> dict:
|
||||
if state not in {"resolved_ready", "resolved_carry_forward"}
|
||||
),
|
||||
"next_resolved_lane_prompt": next_unresolved_marker.get("resolved_lane_prompt"),
|
||||
"resume_history": resume_history[:8],
|
||||
"resume_history_count": len(resume_history),
|
||||
"resume_history_latest_event": resume_history[0].get("event") if resume_history else None,
|
||||
"stale_persistence_cleanup_queue": stale_persistence_cleanup_queue[:5],
|
||||
"stale_persistence_cleanup_count": len(stale_persistence_cleanup_queue),
|
||||
"next_stale_persistence_cleanup_key": (
|
||||
stale_persistence_cleanup_queue[0].get("resume_persistence_key") if stale_persistence_cleanup_queue else None
|
||||
),
|
||||
"next_stale_persistence_cleanup_prompt": (
|
||||
stale_persistence_cleanup_queue[0].get("stale_persistence_cleanup_prompt") if stale_persistence_cleanup_queue else None
|
||||
),
|
||||
"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": [
|
||||
@@ -2940,6 +3207,100 @@ def _build_scan_handoff_resolved_lane_state_counts(markers: list[dict]) -> dict:
|
||||
return counts
|
||||
|
||||
|
||||
def _scan_handoff_resume_history(
|
||||
*,
|
||||
title: str,
|
||||
handoff_status: str,
|
||||
persistence_key: str,
|
||||
stale_after_seconds: int,
|
||||
) -> list[dict]:
|
||||
key = str(persistence_key or title or "handoff").strip() or "handoff"
|
||||
history = [
|
||||
{
|
||||
"event": "handoff_marker_seen",
|
||||
"title": title,
|
||||
"status": handoff_status or "ready_handoff",
|
||||
"persistence_key": key,
|
||||
},
|
||||
{
|
||||
"event": "resume_state_persisted",
|
||||
"title": title,
|
||||
"status": _scan_handoff_resume_persistence_status(handoff_status),
|
||||
"persistence_key": _scan_handoff_resume_persistence_key(
|
||||
persistence_key=key,
|
||||
handoff_marker=key,
|
||||
),
|
||||
},
|
||||
{
|
||||
"event": "resolved_lane_checked",
|
||||
"title": title,
|
||||
"status": _scan_handoff_resolved_lane_state(handoff_status),
|
||||
"persistence_key": key,
|
||||
},
|
||||
]
|
||||
cleanup_status = _scan_handoff_stale_persistence_cleanup_status(handoff_status)
|
||||
if cleanup_status in {"cleanup_required", "cleanup_after_resume"}:
|
||||
history.append(
|
||||
{
|
||||
"event": "stale_persistence_cleanup_candidate",
|
||||
"title": title,
|
||||
"status": cleanup_status,
|
||||
"persistence_key": key,
|
||||
"stale_after_seconds": stale_after_seconds or 3600,
|
||||
}
|
||||
)
|
||||
return history
|
||||
|
||||
|
||||
def _scan_handoff_stale_persistence_cleanup_status(handoff_status: str) -> str:
|
||||
if handoff_status in {"blocked_handoff", "refresh_handoff"}:
|
||||
return "cleanup_required"
|
||||
if handoff_status == "ready_handoff":
|
||||
return "cleanup_after_resume"
|
||||
if handoff_status == "next_handoff":
|
||||
return "monitor_stale_window"
|
||||
return "no_cleanup_needed"
|
||||
|
||||
|
||||
def _scan_handoff_stale_persistence_cleanup_prompt(*, title: str, handoff_status: str) -> str:
|
||||
cleanup_status = _scan_handoff_stale_persistence_cleanup_status(handoff_status)
|
||||
if cleanup_status == "cleanup_required":
|
||||
return f"Clean or refresh stale persisted context for {title} before reusing this handoff."
|
||||
if cleanup_status == "cleanup_after_resume":
|
||||
return f"Clear the persisted {title} resume state after the lane is reopened."
|
||||
if cleanup_status == "monitor_stale_window":
|
||||
return f"Keep {title} persistence active, then clean it if the stale window expires."
|
||||
return f"{title} does not need stale persistence cleanup."
|
||||
|
||||
|
||||
def _build_scan_handoff_stale_persistence_cleanup_queue(markers: list[dict]) -> list[dict]:
|
||||
cleanup_status_rank = {
|
||||
"cleanup_required": 1,
|
||||
"cleanup_after_resume": 2,
|
||||
"monitor_stale_window": 3,
|
||||
}
|
||||
queue = [
|
||||
{
|
||||
"handoff_marker": item.get("handoff_marker"),
|
||||
"title": item.get("title"),
|
||||
"resume_persistence_key": item.get("resume_persistence_key"),
|
||||
"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"),
|
||||
}
|
||||
for item in markers
|
||||
if item.get("stale_persistence_cleanup_status") in cleanup_status_rank
|
||||
]
|
||||
return sorted(
|
||||
queue,
|
||||
key=lambda row: (
|
||||
cleanup_status_rank.get(str(row.get("stale_persistence_cleanup_status") or ""), 99),
|
||||
int(row.get("resume_queue_rank") or 99),
|
||||
str(row.get("title") or ""),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _build_scan_handoff_resume_queue(markers: list[dict]) -> list[dict]:
|
||||
return sorted(
|
||||
[
|
||||
@@ -2956,6 +3317,10 @@ def _build_scan_handoff_resume_queue(markers: list[dict]) -> list[dict]:
|
||||
"resume_persistence_status": item.get("resume_persistence_status"),
|
||||
"resolved_lane_state": item.get("resolved_lane_state"),
|
||||
"resolved_lane_prompt": item.get("resolved_lane_prompt"),
|
||||
"resume_history": item.get("resume_history") or [],
|
||||
"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"),
|
||||
}
|
||||
for item in markers
|
||||
if item.get("lifecycle_state") != "carry_forward"
|
||||
@@ -3314,6 +3679,18 @@ def _build_setup_replay_broader_baseline_summary(
|
||||
"review_lane_age_status": "aging_collect_samples",
|
||||
"review_lane_age_score": 30,
|
||||
"review_lane_aging_hint": f"{setup_family} review lane is aging until enough replay samples exist.",
|
||||
"review_lane_sla_seconds": 86400,
|
||||
"review_lane_sla_status": "collecting_samples",
|
||||
"promotion_backlog_priority": "low",
|
||||
"promotion_backlog_rank": 5,
|
||||
"promotion_backlog_reason": "insufficient_samples",
|
||||
"promotion_backlog_item": {
|
||||
"setup_family": setup_family,
|
||||
"priority": "low",
|
||||
"rank": 5,
|
||||
"reason": "insufficient_samples",
|
||||
"action": "Collect replay samples before promotion backlog review.",
|
||||
},
|
||||
"demotion_history": [],
|
||||
"demotion_history_count": 0,
|
||||
"focus_recommendation": f"Collect samples before changing focus for {setup_family}.",
|
||||
@@ -3535,6 +3912,18 @@ def _build_setup_family_trend_delta(
|
||||
"review_lane_age_status": "aging_collect_samples",
|
||||
"review_lane_age_score": 30,
|
||||
"review_lane_aging_hint": f"{setup_family} review lane is aging until enough replay samples exist.",
|
||||
"review_lane_sla_seconds": 86400,
|
||||
"review_lane_sla_status": "collecting_samples",
|
||||
"promotion_backlog_priority": "low",
|
||||
"promotion_backlog_rank": 5,
|
||||
"promotion_backlog_reason": "insufficient_samples",
|
||||
"promotion_backlog_item": {
|
||||
"setup_family": setup_family,
|
||||
"priority": "low",
|
||||
"rank": 5,
|
||||
"reason": "insufficient_samples",
|
||||
"action": "Collect replay samples before promotion backlog review.",
|
||||
},
|
||||
"demotion_history": [],
|
||||
"demotion_history_count": 0,
|
||||
"focus_recommendation": f"Collect samples before changing focus for {setup_family}.",
|
||||
@@ -3650,6 +4039,26 @@ def _build_setup_family_trend_delta(
|
||||
sample_gap=sample_gap,
|
||||
drift_alert=drift_alert,
|
||||
)
|
||||
review_lane_sla_seconds = _setup_family_review_lane_sla_seconds(
|
||||
review_lane_age_status=review_lane_age_status,
|
||||
)
|
||||
review_lane_sla_status = _setup_family_review_lane_sla_status(
|
||||
review_lane_age_status=review_lane_age_status,
|
||||
review_lane_age_score=review_lane_age_score,
|
||||
)
|
||||
promotion_backlog_priority = _setup_family_promotion_backlog_priority(
|
||||
promotion_queue_status=promotion_queue_status,
|
||||
review_lane_age_score=review_lane_age_score,
|
||||
promotion_ready=promotion_ready,
|
||||
)
|
||||
promotion_backlog_reason = _setup_family_promotion_backlog_reason(
|
||||
promotion_queue_status=promotion_queue_status,
|
||||
promotion_blockers=promotion_blockers,
|
||||
review_lane_sla_status=review_lane_sla_status,
|
||||
)
|
||||
promotion_backlog_rank = _setup_family_promotion_backlog_rank(
|
||||
promotion_backlog_priority=promotion_backlog_priority,
|
||||
)
|
||||
return {
|
||||
"status": "active",
|
||||
"trend_status": trend_status,
|
||||
@@ -3688,6 +4097,18 @@ def _build_setup_family_trend_delta(
|
||||
setup_family=setup_family,
|
||||
review_lane_age_status=review_lane_age_status,
|
||||
),
|
||||
"review_lane_sla_seconds": review_lane_sla_seconds,
|
||||
"review_lane_sla_status": review_lane_sla_status,
|
||||
"promotion_backlog_priority": promotion_backlog_priority,
|
||||
"promotion_backlog_rank": promotion_backlog_rank,
|
||||
"promotion_backlog_reason": promotion_backlog_reason,
|
||||
"promotion_backlog_item": _setup_family_promotion_backlog_item(
|
||||
setup_family=setup_family,
|
||||
promotion_backlog_priority=promotion_backlog_priority,
|
||||
promotion_backlog_rank=promotion_backlog_rank,
|
||||
promotion_backlog_reason=promotion_backlog_reason,
|
||||
promotion_next_action=promotion_next_action,
|
||||
),
|
||||
"demotion_history": demotion_history[:5],
|
||||
"demotion_history_count": len(demotion_history),
|
||||
"focus_recommendation": focus_recommendation,
|
||||
@@ -3903,6 +4324,84 @@ def _setup_family_review_lane_aging_hint(*, setup_family: str, review_lane_age_s
|
||||
return f"{setup_family} remains in watch mode while review-lane evidence accumulates."
|
||||
|
||||
|
||||
def _setup_family_review_lane_sla_seconds(*, review_lane_age_status: str) -> int:
|
||||
if review_lane_age_status == "ready_now":
|
||||
return 3600
|
||||
if review_lane_age_status == "stale_demotion_review":
|
||||
return 1800
|
||||
if review_lane_age_status == "aging_watch":
|
||||
return 14400
|
||||
if review_lane_age_status == "aging_collect_samples":
|
||||
return 86400
|
||||
return 28800
|
||||
|
||||
|
||||
def _setup_family_review_lane_sla_status(*, review_lane_age_status: str, review_lane_age_score: int) -> str:
|
||||
if review_lane_age_status == "stale_demotion_review":
|
||||
return "breached"
|
||||
if review_lane_age_status == "ready_now":
|
||||
return "due_now"
|
||||
if review_lane_age_status == "aging_collect_samples":
|
||||
return "collecting_samples"
|
||||
if int(review_lane_age_score or 0) >= 70:
|
||||
return "due_soon"
|
||||
return "within_sla"
|
||||
|
||||
|
||||
def _setup_family_promotion_backlog_priority(
|
||||
*,
|
||||
promotion_queue_status: str,
|
||||
review_lane_age_score: int,
|
||||
promotion_ready: bool,
|
||||
) -> str:
|
||||
if promotion_queue_status == "demotion_review":
|
||||
return "urgent"
|
||||
if promotion_ready or promotion_queue_status == "ready_for_operator_review":
|
||||
return "high"
|
||||
if int(review_lane_age_score or 0) >= 60:
|
||||
return "medium"
|
||||
return "low"
|
||||
|
||||
|
||||
def _setup_family_promotion_backlog_rank(*, promotion_backlog_priority: str) -> int:
|
||||
return {
|
||||
"urgent": 1,
|
||||
"high": 2,
|
||||
"medium": 3,
|
||||
"low": 4,
|
||||
}.get(str(promotion_backlog_priority or "low"), 4)
|
||||
|
||||
|
||||
def _setup_family_promotion_backlog_reason(
|
||||
*,
|
||||
promotion_queue_status: str,
|
||||
promotion_blockers: list[str],
|
||||
review_lane_sla_status: str,
|
||||
) -> str:
|
||||
if review_lane_sla_status in {"breached", "due_now", "due_soon"}:
|
||||
return review_lane_sla_status
|
||||
if promotion_blockers:
|
||||
return str(promotion_blockers[0])
|
||||
return str(promotion_queue_status or "watch_lane")
|
||||
|
||||
|
||||
def _setup_family_promotion_backlog_item(
|
||||
*,
|
||||
setup_family: str,
|
||||
promotion_backlog_priority: str,
|
||||
promotion_backlog_rank: int,
|
||||
promotion_backlog_reason: str,
|
||||
promotion_next_action: str,
|
||||
) -> dict:
|
||||
return {
|
||||
"setup_family": setup_family,
|
||||
"priority": promotion_backlog_priority,
|
||||
"rank": promotion_backlog_rank,
|
||||
"reason": promotion_backlog_reason,
|
||||
"action": promotion_next_action,
|
||||
}
|
||||
|
||||
|
||||
def _rank_session_composite(rows: list[dict], *, session_code: str) -> int | None:
|
||||
if not session_code:
|
||||
return None
|
||||
|
||||
@@ -582,6 +582,11 @@ function renderDecisionAssistant(summary) {
|
||||
['resolved lanes', String(scanHandoffSummary.resolved_lane_count ?? 0)],
|
||||
['unresolved lanes', String(scanHandoffSummary.unresolved_lane_count ?? 0)],
|
||||
['next resolved lane prompt', scanHandoffSummary.next_resolved_lane_prompt || 'n/a'],
|
||||
['resume history', String(scanHandoffSummary.resume_history_count ?? 0)],
|
||||
['resume latest event', scanHandoffSummary.resume_history_latest_event || 'n/a'],
|
||||
['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'],
|
||||
['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'],
|
||||
@@ -718,6 +723,12 @@ function renderDecisionAssistant(summary) {
|
||||
['resolved proofs', String(digestSurface.history_summary?.resolved_proof_count ?? 0)],
|
||||
['proof resolution states', formatJsonInline(digestSurface.history_summary?.proof_resolution_state_counts || {})],
|
||||
['proof resolution history', formatList((digestSurface.history_summary?.proof_resolution_history || []).map((item) => `${item.kind}:${item.state}`))],
|
||||
['proof SLA states', formatJsonInline(digestSurface.history_summary?.proof_resolution_sla_status_counts || {})],
|
||||
['proof SLA breaches', String(digestSurface.history_summary?.proof_resolution_sla_breach_count ?? 0)],
|
||||
['proof SLA queue', String(digestSurface.history_summary?.proof_resolution_sla_queue_count ?? 0)],
|
||||
['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'],
|
||||
['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}`))],
|
||||
@@ -812,6 +823,10 @@ function renderDecisionAssistant(summary) {
|
||||
['proof follow-up age', item.proof_follow_up_age_band || 'n/a'],
|
||||
['proof stale queue', String(Boolean(item.proof_follow_up_stale_queue))],
|
||||
['proof resolution history', formatList((item.proof_resolution_history || []).map((row) => `${row.proof_label || row.state}: ${formatTimestamp(row.ts)}`))],
|
||||
['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'],
|
||||
['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}`))],
|
||||
['refresh evidence', item.refresh_evidence_anchor || 'n/a'],
|
||||
@@ -874,6 +889,11 @@ function renderDecisionAssistant(summary) {
|
||||
['preference recovery queue', String(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.preference_recovery_count ?? 0)],
|
||||
['next preference recovery', reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.next_preference_recovery_template_id || 'n/a'],
|
||||
['guardrail audit trail', String(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.guardrail_audit_trail_count ?? 0)],
|
||||
['recovery recommendations', String(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.preference_recovery_recommendation_count ?? 0)],
|
||||
['next recovery action', reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.next_preference_recovery_action || 'n/a'],
|
||||
['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)],
|
||||
['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)],
|
||||
@@ -938,7 +958,11 @@ function renderDecisionAssistant(summary) {
|
||||
['decay reason', item.preferred_template_decay_reason || 'n/a'],
|
||||
['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 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}`))],
|
||||
['filtered audit trail', formatList((item.guardrail_audit_filtered_trail || []).map((row) => `${row.label || row.signal}: ${row.resolution_status}`))],
|
||||
['feedback trend history', formatList((item.feedback_trend_history || []).map((row) => `${row.label || row.signal}: ${formatTimestamp(row.created_ts)}`))],
|
||||
],
|
||||
[
|
||||
@@ -1246,6 +1270,11 @@ function renderDecisionAssistant(summary) {
|
||||
['review lane age', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.review_lane_age_status || 'n/a'],
|
||||
['review lane age score', String(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.review_lane_age_score ?? 'n/a')],
|
||||
['review lane aging hint', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.review_lane_aging_hint || 'n/a'],
|
||||
['review lane SLA status', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.review_lane_sla_status || 'n/a'],
|
||||
['review lane SLA', formatDurationSeconds(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.review_lane_sla_seconds)],
|
||||
['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'],
|
||||
['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')],
|
||||
|
||||
@@ -208,6 +208,12 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("next_preference_recovery_template_id", summary["review_entrypoints"]["template_reuse_metrics"]["template_feedback_aggregation"])
|
||||
self.assertIn("guardrail_audit_trail", summary["review_entrypoints"]["template_reuse_metrics"]["template_feedback_aggregation"])
|
||||
self.assertIn("guardrail_audit_trail_count", summary["review_entrypoints"]["template_reuse_metrics"]["template_feedback_aggregation"])
|
||||
self.assertIn("preference_recovery_recommendations", summary["review_entrypoints"]["template_reuse_metrics"]["template_feedback_aggregation"])
|
||||
self.assertIn("preference_recovery_recommendation_count", summary["review_entrypoints"]["template_reuse_metrics"]["template_feedback_aggregation"])
|
||||
self.assertIn("next_preference_recovery_action", summary["review_entrypoints"]["template_reuse_metrics"]["template_feedback_aggregation"])
|
||||
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.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")
|
||||
@@ -225,6 +231,11 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("preference_recovery_prompt", summary["review_entrypoints"]["note_templates"][0])
|
||||
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("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])
|
||||
self.assertIn("guardrail_audit_filtered_count", summary["review_entrypoints"]["note_templates"][0])
|
||||
self.assertTrue(any(item["template_id"] == "next_review" for item in summary["review_entrypoints"]["note_templates"]))
|
||||
self.assertTrue(any(item["template_id"] == "queue_follow_up" for item in summary["review_entrypoints"]["note_templates"]))
|
||||
self.assertTrue(any(item["reuse_hint"] for item in summary["review_entrypoints"]["note_templates"]))
|
||||
@@ -255,6 +266,12 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("proof_resolution_history_count", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("oldest_proof_follow_up_kind", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("oldest_proof_follow_up_age_seconds", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("proof_resolution_sla_status_counts", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("proof_resolution_sla_breach_count", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("proof_resolution_sla_queue", summary["digest_surface"]["history_summary"])
|
||||
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("refresh_prompts", summary["digest_surface"]["history_summary"])
|
||||
self.assertIn("digest_history_groups", summary)
|
||||
self.assertIn("group_status", summary["digest_history_groups"][0])
|
||||
@@ -280,6 +297,10 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("proof_follow_up_age_band", summary["digest_history_groups"][0])
|
||||
self.assertIn("proof_follow_up_stale_queue", summary["digest_history_groups"][0])
|
||||
self.assertIn("proof_resolution_history", summary["digest_history_groups"][0])
|
||||
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("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])
|
||||
self.assertTrue(summary["digest_history_preview"])
|
||||
@@ -328,6 +349,12 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("review_lane_age_status", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("review_lane_age_score", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("review_lane_aging_hint", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("review_lane_sla_seconds", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("review_lane_sla_status", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
self.assertIn("promotion_backlog_priority", summary["setup_learning_rollup"]["replay_statistics"]["broader_baseline_summary"]["setup_family_trend_delta"])
|
||||
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("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"])
|
||||
@@ -373,6 +400,13 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("resolved_lane_count", summary["scan_handoff_summary"])
|
||||
self.assertIn("unresolved_lane_count", summary["scan_handoff_summary"])
|
||||
self.assertIn("next_resolved_lane_prompt", summary["scan_handoff_summary"])
|
||||
self.assertIn("resume_history", summary["scan_handoff_summary"])
|
||||
self.assertIn("resume_history_count", summary["scan_handoff_summary"])
|
||||
self.assertIn("resume_history_latest_event", summary["scan_handoff_summary"])
|
||||
self.assertIn("stale_persistence_cleanup_queue", summary["scan_handoff_summary"])
|
||||
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("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])
|
||||
@@ -386,6 +420,10 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertIn("resume_persistence_status", summary["scan_handoff_summary"]["handoff_markers"][0])
|
||||
self.assertIn("resolved_lane_state", summary["scan_handoff_summary"]["handoff_markers"][0])
|
||||
self.assertIn("resolved_lane_prompt", summary["scan_handoff_summary"]["handoff_markers"][0])
|
||||
self.assertIn("resume_history", summary["scan_handoff_summary"]["handoff_markers"][0])
|
||||
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.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