Use explicit fresh hosted capability snapshots for plan checks

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
tegwick 2026-09-08 12:10:17 +02:00
parent 5501b8bb56
commit 2621cf2752
6 changed files with 353 additions and 11 deletions

View file

@ -22,6 +22,7 @@ from reuse_surface.hub_sync import (
)
from reuse_surface.overlaps import find_overlaps
from reuse_surface.statehub_bridge import list_open_capability_requests
from reuse_surface.plan_snapshot import SnapshotUnavailable
from reuse_surface.plan_check import (
format_plan_check_json,
format_plan_check_markdown,
@ -661,13 +662,19 @@ def cmd_plan_check(args: argparse.Namespace) -> int:
print("error: provide a workplan file or --intent", file=sys.stderr)
return 1
result = run_plan_check(
query,
reuse_threshold=args.reuse_threshold,
extend_threshold=args.extend_threshold,
use_llm=not args.no_llm,
llm_url=args.llm_url,
)
try:
result = run_plan_check(
query,
reuse_threshold=args.reuse_threshold,
extend_threshold=args.extend_threshold,
use_llm=not args.no_llm,
llm_url=args.llm_url,
hub_url=args.hub_url,
max_index_age_hours=args.max_index_age_hours,
)
except SnapshotUnavailable as exc:
print(f"error: {exc}", file=sys.stderr)
return 2
recorded = None
if args.record_outcome:
@ -941,6 +948,13 @@ def main(argv: list[str] | None = None) -> int:
"workplan", nargs="?", help="path to a workplan file (frontmatter + body)"
)
plan_check.add_argument("--intent", help="free-text intent instead of a workplan file")
plan_check.add_argument(
"--hub-url", help="read the hosted /v1/federated index; fail on unavailable, stale or warned composition",
)
plan_check.add_argument(
"--max-index-age-hours", type=float, default=24,
help="maximum hosted composition age in hours (default: 24); requires --hub-url",
)
plan_check.add_argument(
"--format", choices=["markdown", "json"], default="markdown"
)
@ -1210,4 +1224,4 @@ def main(argv: list[str] | None = None) -> int:
if __name__ == "__main__":
raise SystemExit(main())
raise SystemExit(main())

View file

@ -16,6 +16,7 @@ from reuse_surface import hub_client
from reuse_surface.federation import FEDERATED_INDEX_PATH
from reuse_surface.llm_bridge import execute_prompt, extract_json_object
from reuse_surface.overlaps import TOKEN_RE
from reuse_surface.plan_snapshot import load_hosted_snapshot
from reuse_surface.registry import ROOT, load_index
from reuse_surface.statehub_bridge import file_capability_request
@ -260,8 +261,16 @@ def run_plan_check(
top_n: int = 5,
use_llm: bool = True,
llm_url: str | None = None,
hub_url: str | None = None,
max_index_age_hours: float = 24,
) -> dict[str, Any]:
capabilities, updated = load_federated_capabilities()
if hub_url is not None:
capabilities, updated, provenance = load_hosted_snapshot(
hub_url, max_age_hours=max_index_age_hours
)
else:
capabilities, updated = load_federated_capabilities()
provenance = {"kind": "local", "capability_count": len(capabilities)}
matches = match_query(query, capabilities, tie_window=tie_window)
top_score = matches[0].score if matches else 0.0
verdict = verdict_for_score(
@ -302,6 +311,7 @@ def run_plan_check(
],
"federated_index_updated": updated,
"federated_index_stale_warning": _staleness_warning(updated),
"index_source": provenance,
}
if notes:
result["notes"] = notes
@ -334,7 +344,13 @@ def format_plan_check_markdown(result: dict[str, Any]) -> str:
elif verdict == "extend":
lines.append("**Verdict: EXTEND** — scope overlaps closely enough that extending the top match is likely cheaper than a new capability.")
else:
lines.append("**Verdict: NEW** — no close match in the federated index; proceed.")
lines.append("**Verdict: NEW** — no close lexical match in this index; review candidates and owner contracts before building.")
source = result.get("index_source", {})
if source.get("kind") == "hosted":
lines.extend(["", f"Source: {source['url']} ({source['capability_count']} capabilities)",
f"Composed: {source['composed_at']}; fetched: {source['fetched_at']}",
f"Snapshot SHA-256: `{source['sha256']}`"])
warning = result.get("federated_index_stale_warning")
if warning:

View file

@ -0,0 +1,109 @@
"""Read-only hosted discovery with explicit, bounded source acceptance."""
from __future__ import annotations
import hashlib
import json
import math
import urllib.error
import urllib.parse
import urllib.request
from datetime import date, datetime, timedelta, timezone
from typing import Any
MAX_SNAPSHOT_BYTES = 5 * 1024 * 1024
class SnapshotUnavailable(ValueError):
"""The selected source cannot support a planning recommendation."""
def load_hosted_snapshot(
base_url: str, *, max_age_hours: float = 24
) -> tuple[list[dict[str, Any]], str, dict[str, Any]]:
"""Fetch the public index; never send intent, tokens or mutation requests.
Acceptance concerns the hub's composition and warning signals. It does not
certify that every upstream repository has published its latest capability.
Failure must not become a local fallback or a 'new capability' verdict.
"""
url = base_url.rstrip("/") + "/v1/federated"
try:
parsed = urllib.parse.urlsplit(base_url)
valid_url = (parsed.scheme in {"http", "https"} and parsed.hostname
and not parsed.username and not parsed.password
and not parsed.query and not parsed.fragment)
except ValueError:
valid_url = False
if not valid_url:
raise SnapshotUnavailable("hub URL must be HTTP(S), without credentials, query or fragment")
if not math.isfinite(max_age_hours) or max_age_hours <= 0:
raise SnapshotUnavailable("maximum index age must be a positive finite number of hours")
request = urllib.request.Request(
url, headers={"Accept": "application/json", "User-Agent": "reuse-surface/0.1"}
)
try:
with urllib.request.urlopen(request, timeout=30) as response:
if response.status != 200:
raise SnapshotUnavailable(f"hosted index returned HTTP {response.status}")
if response.headers.get("X-Federation-Warnings"):
raise SnapshotUnavailable("hosted index reports federation warnings; inspect its sources")
raw = response.read(MAX_SNAPSHOT_BYTES + 1)
except urllib.error.HTTPError as exc:
raise SnapshotUnavailable(f"hosted index returned HTTP {exc.code}") from exc
except (urllib.error.URLError, OSError, ValueError) as exc:
if isinstance(exc, SnapshotUnavailable):
raise
raise SnapshotUnavailable("hosted index could not be fetched; no local fallback used") from exc
if len(raw) > MAX_SNAPSHOT_BYTES:
raise SnapshotUnavailable("hosted index exceeds the 5 MiB response limit")
try:
payload = json.loads(raw)
except (ValueError, UnicodeError) as exc:
raise SnapshotUnavailable("hosted index is not valid JSON") from exc
if not isinstance(payload, dict) or payload.get("stale") is not False:
raise SnapshotUnavailable("hosted index is stale or its freshness signal is missing")
now = datetime.now(timezone.utc)
try:
composed = datetime.fromisoformat(payload["composed_at"])
updated = payload["updated"]
date.fromisoformat(updated)
if composed.tzinfo is None:
raise ValueError("timezone missing")
except (KeyError, TypeError, ValueError) as exc:
raise SnapshotUnavailable("hosted index has invalid composition metadata") from exc
age = (now - composed).total_seconds()
if age < -timedelta(minutes=5).total_seconds() or age > max_age_hours * 3600:
raise SnapshotUnavailable("hosted index composition is outside the accepted age window")
capabilities = payload.get("capabilities")
if not isinstance(capabilities, list) or not capabilities:
raise SnapshotUnavailable("hosted index has no usable capability collection")
ids: set[str] = set()
for item in capabilities:
if (not isinstance(item, dict)
or not isinstance(item.get("id"), str) or not item["id"].strip()
or item["id"] in ids
or not isinstance(item.get("name"), str)
or not isinstance(item.get("summary"), str)
or not isinstance(item.get("tags", []), list)
or any(not isinstance(tag, str) for tag in item.get("tags", []))
or any(item.get(key) is not None and not isinstance(item[key], str)
for key in ("owner", "vector"))):
raise SnapshotUnavailable("hosted index contains malformed or duplicate capabilities")
ids.add(item["id"])
provenance = {
"kind": "hosted",
"url": url,
"fetched_at": now.isoformat(),
"composed_at": payload["composed_at"],
"capability_count": len(capabilities),
"sha256": hashlib.sha256(raw).hexdigest(),
"digest_format": "response-bytes",
"max_age_hours": max_age_hours,
"stale": False,
"federation_warnings": False,
}
return capabilities, updated, provenance