WARDEN-WP-0029: implement plan front door, org posture, desk, freshness
Ship posture-aware access planning: organization_posture=build (axis C), catalog freshness warnings, warden plan verdicts, localhost founder desk, and playbook/agent guidance that retire /tmp file-drop patterns. Compose route catalog + handoff rather than a second routing layer.
This commit is contained in:
parent
5c6b71b83b
commit
5149946a4c
18 changed files with 1690 additions and 102 deletions
|
|
@ -5,12 +5,19 @@ subsystem. It loads the machine-readable routing catalog and answers "who owns
|
|||
this need and where is the authoritative doc". The one lane ops-warden executes
|
||||
(SSH certificate issuance) is the only entry that carries authored steps.
|
||||
"""
|
||||
from warden.routing.catalog import Catalog, CatalogError, find_catalog_path, load_catalog
|
||||
from warden.routing.catalog import (
|
||||
Catalog,
|
||||
CatalogError,
|
||||
CatalogFreshness,
|
||||
find_catalog_path,
|
||||
load_catalog,
|
||||
)
|
||||
from warden.routing.models import RouteEntry
|
||||
|
||||
__all__ = [
|
||||
"Catalog",
|
||||
"CatalogError",
|
||||
"CatalogFreshness",
|
||||
"RouteEntry",
|
||||
"find_catalog_path",
|
||||
"load_catalog",
|
||||
|
|
|
|||
|
|
@ -13,10 +13,11 @@ never restates another subsystem's procedure.
|
|||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from datetime import date
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import date, datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
|
|
@ -112,6 +113,45 @@ def find_catalog_path(start: Optional[Path] = None) -> Path:
|
|||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class CatalogFreshness:
|
||||
"""Install vs source freshness for the routing catalog (WARDEN-WP-0029 T05).
|
||||
|
||||
Surfaces the path that was loaded, whether it is the wheel-bundled fallback
|
||||
(the stale-CLI failure mode), a content hash, and entry review age. Never
|
||||
carries secret material.
|
||||
"""
|
||||
|
||||
path: str
|
||||
source: str # "override" | "repo" | "bundled"
|
||||
content_hash: str
|
||||
mtime_iso: str
|
||||
package_version: str
|
||||
entry_count: int
|
||||
active_count: int
|
||||
newest_reviewed: Optional[str]
|
||||
oldest_reviewed: Optional[str]
|
||||
stale_entry_count: int
|
||||
using_bundled: bool
|
||||
warnings: List[str] = field(default_factory=list)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"path": self.path,
|
||||
"source": self.source,
|
||||
"content_hash": self.content_hash,
|
||||
"mtime_iso": self.mtime_iso,
|
||||
"package_version": self.package_version,
|
||||
"entry_count": self.entry_count,
|
||||
"active_count": self.active_count,
|
||||
"newest_reviewed": self.newest_reviewed,
|
||||
"oldest_reviewed": self.oldest_reviewed,
|
||||
"stale_entry_count": self.stale_entry_count,
|
||||
"using_bundled": self.using_bundled,
|
||||
"warnings": list(self.warnings),
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class Catalog:
|
||||
path: Path
|
||||
|
|
@ -161,6 +201,85 @@ class Catalog:
|
|||
if is_review_stale(e.reviewed, threshold_days=threshold_days, today=today)
|
||||
]
|
||||
|
||||
def freshness(
|
||||
self,
|
||||
*,
|
||||
stale_threshold_days: int = DEFAULT_STALE_DAYS,
|
||||
today: Optional[date] = None,
|
||||
) -> CatalogFreshness:
|
||||
"""Describe which catalog was loaded and how fresh it is (WP-0029 T05)."""
|
||||
path = self.path.resolve()
|
||||
text = path.read_text(encoding="utf-8") if path.exists() else ""
|
||||
digest = hashlib.sha256(text.encode("utf-8")).hexdigest()[:12]
|
||||
mtime_iso = ""
|
||||
if path.exists():
|
||||
mtime_iso = datetime.fromtimestamp(
|
||||
path.stat().st_mtime, tz=timezone.utc
|
||||
).isoformat()
|
||||
|
||||
source = _classify_catalog_source(path)
|
||||
using_bundled = source == "bundled"
|
||||
reviewed_dates = [e.reviewed for e in self.entries if e.reviewed]
|
||||
newest = max(reviewed_dates) if reviewed_dates else None
|
||||
oldest = min(reviewed_dates) if reviewed_dates else None
|
||||
stale_count = len(self.stale(include_draft=True, threshold_days=stale_threshold_days, today=today))
|
||||
|
||||
package_version = _package_version()
|
||||
warnings: List[str] = []
|
||||
if using_bundled:
|
||||
warnings.append(
|
||||
"using wheel-bundled catalog fallback — reinstall from checkout "
|
||||
"(`uv tool install -e .` or `pip install -e .`) if lanes look missing"
|
||||
)
|
||||
if stale_count:
|
||||
warnings.append(
|
||||
f"{stale_count} catalog entr{'y' if stale_count == 1 else 'ies'} "
|
||||
f"past {stale_threshold_days}d review cadence"
|
||||
)
|
||||
|
||||
return CatalogFreshness(
|
||||
path=str(path),
|
||||
source=source,
|
||||
content_hash=digest,
|
||||
mtime_iso=mtime_iso,
|
||||
package_version=package_version,
|
||||
entry_count=len(self.entries),
|
||||
active_count=len(self.listed(include_draft=False)),
|
||||
newest_reviewed=newest,
|
||||
oldest_reviewed=oldest,
|
||||
stale_entry_count=stale_count,
|
||||
using_bundled=using_bundled,
|
||||
warnings=warnings,
|
||||
)
|
||||
|
||||
|
||||
def _package_version() -> str:
|
||||
try:
|
||||
from importlib.metadata import version
|
||||
|
||||
return version("ops-warden")
|
||||
except Exception: # noqa: BLE001
|
||||
try:
|
||||
from warden import __version__
|
||||
|
||||
return str(__version__)
|
||||
except Exception: # noqa: BLE001
|
||||
return "unknown"
|
||||
|
||||
|
||||
def _classify_catalog_source(path: Path) -> str:
|
||||
"""Classify catalog load path for freshness warnings."""
|
||||
if os.environ.get("WARDEN_ROUTING_CATALOG"):
|
||||
return "override"
|
||||
resolved = str(path.resolve())
|
||||
if "/_registry/" in resolved or resolved.endswith("/warden/_registry/routing/catalog.yaml"):
|
||||
return "bundled"
|
||||
# hatch force-include places registry at warden/_registry
|
||||
parts = path.resolve().parts
|
||||
if "_registry" in parts:
|
||||
return "bundled"
|
||||
return "repo"
|
||||
|
||||
|
||||
def _assert_no_secret_material(
|
||||
entry_id: str, field_name: str, value: str, *, prose: bool = False
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue