generated from coulomb/repo-seed
Complete WARDEN-WP-0012 routing scenario playbooks
Add platform-secret playbooks for issue-core ingestion, OpenRouter llm-connect, object-storage STS, and database dynamic credentials. Extend the routing catalog with draft entries and implement `warden route list --stale` for quarterly drift review. Document the review cadence in AccessRouting and mark the workplan finished.
This commit is contained in:
@@ -547,17 +547,35 @@ def _entry_summary(entry) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def _print_entry_table(entries, title: str) -> None:
|
||||
def _print_entry_table(
|
||||
entries, title: str, *, show_reviewed: bool = False, stale_threshold_days: int = 90
|
||||
) -> None:
|
||||
table = Table(title=title)
|
||||
table.add_column("ID")
|
||||
table.add_column("Need")
|
||||
table.add_column("Owner")
|
||||
table.add_column("warden")
|
||||
if show_reviewed:
|
||||
table.add_column("Reviewed")
|
||||
table.add_column("Days")
|
||||
table.add_column("Status")
|
||||
from warden.routing.catalog import days_since_review
|
||||
|
||||
for e in entries:
|
||||
executes = "[green]issue[/green]" if e.warden_executes else "route"
|
||||
status_styled = e.status if e.status == "active" else f"[yellow]{e.status}[/yellow]"
|
||||
table.add_row(e.id, e.title, e.owner_repo, executes, status_styled)
|
||||
if show_reviewed:
|
||||
days = days_since_review(e.reviewed)
|
||||
reviewed_styled = (
|
||||
f"[yellow]{e.reviewed}[/yellow]"
|
||||
if days > stale_threshold_days
|
||||
else e.reviewed
|
||||
)
|
||||
table.add_row(
|
||||
e.id, e.title, e.owner_repo, executes, reviewed_styled, str(days), status_styled
|
||||
)
|
||||
else:
|
||||
table.add_row(e.id, e.title, e.owner_repo, executes, status_styled)
|
||||
console.print(table)
|
||||
|
||||
|
||||
@@ -566,22 +584,55 @@ def route_list(
|
||||
output_json: Annotated[bool, typer.Option("--json", help="Output JSON")] = False,
|
||||
all_entries: Annotated[bool, typer.Option("--all", help="Include draft entries")] = False,
|
||||
tag: Annotated[Optional[str], typer.Option("--tag", help="Filter by need keyword")] = None,
|
||||
stale_only: Annotated[
|
||||
bool, typer.Option("--stale", help="Show entries past review cadence (see --stale-days)")
|
||||
] = False,
|
||||
stale_days: Annotated[
|
||||
int,
|
||||
typer.Option(
|
||||
"--stale-days",
|
||||
help="Days since reviewed before an entry is stale (default 90)",
|
||||
min=1,
|
||||
),
|
||||
] = 90,
|
||||
) -> None:
|
||||
"""List routing scenarios. Active-only unless --all."""
|
||||
from warden.routing.catalog import days_since_review
|
||||
|
||||
catalog = _load_catalog()
|
||||
entries = catalog.listed(include_draft=all_entries)
|
||||
if stale_only:
|
||||
entries = catalog.stale(include_draft=all_entries, threshold_days=stale_days)
|
||||
else:
|
||||
entries = catalog.listed(include_draft=all_entries)
|
||||
if tag:
|
||||
t = tag.lower()
|
||||
entries = [e for e in entries if t in [k.lower() for k in e.need_keywords]]
|
||||
|
||||
if output_json:
|
||||
print(json.dumps([_entry_summary(e) for e in entries], indent=2))
|
||||
payload = []
|
||||
for e in entries:
|
||||
row = _entry_summary(e)
|
||||
if stale_only:
|
||||
row["days_since_review"] = days_since_review(e.reviewed)
|
||||
row["stale_threshold_days"] = stale_days
|
||||
payload.append(row)
|
||||
print(json.dumps(payload, indent=2))
|
||||
return
|
||||
|
||||
if not entries:
|
||||
console.print("No matching routing entries.")
|
||||
if stale_only:
|
||||
console.print(f"No stale routing entries (threshold: {stale_days} days since reviewed).")
|
||||
else:
|
||||
console.print("No matching routing entries.")
|
||||
return
|
||||
_print_entry_table(entries, "Routing scenarios")
|
||||
title = (
|
||||
f"Stale routing scenarios (>{stale_days}d since reviewed)"
|
||||
if stale_only
|
||||
else "Routing scenarios"
|
||||
)
|
||||
_print_entry_table(
|
||||
entries, title, show_reviewed=stale_only, stale_threshold_days=stale_days
|
||||
)
|
||||
|
||||
|
||||
@route_app.command("show")
|
||||
|
||||
@@ -15,6 +15,7 @@ from __future__ import annotations
|
||||
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
@@ -36,6 +37,26 @@ _REQUIRED_FIELDS = (
|
||||
)
|
||||
_VALID_STATUS = ("active", "draft")
|
||||
|
||||
# Default review cadence — see wiki/AccessRouting.md#drift-review-cadence
|
||||
DEFAULT_STALE_DAYS = 90
|
||||
|
||||
|
||||
def days_since_review(reviewed: str, *, today: Optional[date] = None) -> int:
|
||||
"""Calendar days between reviewed date (YYYY-MM-DD) and today."""
|
||||
reviewed_date = date.fromisoformat(reviewed)
|
||||
ref = today or date.today()
|
||||
return (ref - reviewed_date).days
|
||||
|
||||
|
||||
def is_review_stale(
|
||||
reviewed: str,
|
||||
*,
|
||||
threshold_days: int = DEFAULT_STALE_DAYS,
|
||||
today: Optional[date] = None,
|
||||
) -> bool:
|
||||
"""True when reviewed date is older than the cadence threshold."""
|
||||
return days_since_review(reviewed, today=today) > threshold_days
|
||||
|
||||
|
||||
class CatalogError(Exception):
|
||||
"""Raised when the routing catalog is missing or invalid."""
|
||||
@@ -89,6 +110,20 @@ class Catalog:
|
||||
scored.sort(key=lambda pair: (-pair[0], pair[1].id))
|
||||
return [e for _, e in scored[:limit]]
|
||||
|
||||
def stale(
|
||||
self,
|
||||
include_draft: bool = False,
|
||||
threshold_days: int = DEFAULT_STALE_DAYS,
|
||||
*,
|
||||
today: Optional[date] = None,
|
||||
) -> List[RouteEntry]:
|
||||
"""Entries whose reviewed date is past the cadence threshold."""
|
||||
return [
|
||||
e
|
||||
for e in self.listed(include_draft=include_draft)
|
||||
if is_review_stale(e.reviewed, threshold_days=threshold_days, today=today)
|
||||
]
|
||||
|
||||
|
||||
def _parse_entry(raw: dict, index: int) -> RouteEntry:
|
||||
if not isinstance(raw, dict):
|
||||
|
||||
Reference in New Issue
Block a user