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

View file

@ -0,0 +1,126 @@
from __future__ import annotations
import hashlib
import io
import json
import urllib.error
from datetime import datetime, timedelta, timezone
import pytest
from reuse_surface.cli import main
from reuse_surface.plan_check import load_query_from_intent, run_plan_check
from reuse_surface.plan_snapshot import MAX_SNAPSHOT_BYTES, SnapshotUnavailable
def snapshot():
return {
"updated": datetime.now(timezone.utc).date().isoformat(),
"composed_at": datetime.now(timezone.utc).isoformat(),
"stale": False,
"capabilities": [{"id": "capability.hosted.only", "name": "Tender evidence",
"summary": "Tender evidence", "tags": ["tender"]}],
}
def serve(monkeypatch, payload, *, warnings=None, raw=None):
body = raw if raw is not None else json.dumps(payload).encode()
def urlopen(request, *, timeout):
assert request.full_url == "https://registry.example/v1/federated"
assert request.get_method() == "GET"
assert request.data is None
assert not request.has_header("Authorization")
assert timeout == 30
response = io.BytesIO(body)
response.status = 200
response.headers = {"X-Federation-Warnings": warnings} if warnings else {}
return response
monkeypatch.setattr("urllib.request.urlopen", urlopen)
return body
def check(**kwargs):
return run_plan_check(load_query_from_intent("Tender evidence"),
hub_url="https://registry.example", use_llm=False, **kwargs)
def test_hosted_only_capability_and_provenance_without_local_fallback(monkeypatch):
raw = serve(monkeypatch, snapshot())
monkeypatch.setenv("REUSE_SURFACE_TOKEN", "must-never-be-sent")
monkeypatch.setattr("reuse_surface.plan_check.load_federated_capabilities",
lambda: pytest.fail("local source must not be consulted"))
result = check()
assert result["verdict"] == "reuse"
assert result["matches"][0]["id"] == "capability.hosted.only"
assert result["index_source"]["sha256"] == hashlib.sha256(raw).hexdigest()
assert result["index_source"]["capability_count"] == 1
@pytest.mark.parametrize("change", [
{"stale": True}, {"stale": None}, {"composed_at": None},
{"composed_at": "2020-01-01T00:00:00Z"},
{"composed_at": "2026-01-01T00:00:00"}, {"updated": "bad-date"},
{"capabilities": []}, {"capabilities": [None]}, {"capabilities": {}},
{"capabilities": [{"id": "bad", "name": "bad", "summary": "bad", "tags": None}]},
])
def test_invalid_source_never_produces_a_verdict(monkeypatch, change):
serve(monkeypatch, snapshot() | change)
with pytest.raises(SnapshotUnavailable):
check()
def test_partial_federation_refuses_before_verdict_and_side_effects(monkeypatch, capsys):
serve(monkeypatch, snapshot(), warnings="one source unavailable")
monkeypatch.setattr("reuse_surface.cli.record_outcome",
lambda *a, **k: pytest.fail("must not record a failed check"))
monkeypatch.setattr("reuse_surface.cli.maybe_file_capability_request",
lambda *a, **k: pytest.fail("must not create demand on source failure"))
assert main(["plan-check", "--intent", "unrelated", "--hub-url",
"https://registry.example", "--no-llm", "--format", "json",
"--record-outcome", "new", "--file-request"]) == 2
output = capsys.readouterr()
assert output.out == ""
assert "federation warnings" in output.err
@pytest.mark.parametrize("error", [
urllib.error.URLError("unreachable"), TimeoutError(),
urllib.error.HTTPError("https://registry.example", 503, "down", {}, None),
])
def test_transport_failure_is_not_an_empty_index(monkeypatch, error):
def fail(*args, **kwargs):
raise error
monkeypatch.setattr("urllib.request.urlopen", fail)
with pytest.raises(SnapshotUnavailable):
check()
@pytest.mark.parametrize("raw", [b"<html>login</html>", b"[]", b"x" * (MAX_SNAPSHOT_BYTES + 1)])
def test_invalid_or_oversized_response(monkeypatch, raw):
serve(monkeypatch, None, raw=raw)
with pytest.raises(SnapshotUnavailable):
check()
@pytest.mark.parametrize("age", [-2, 0, float("nan"), float("inf")])
def test_invalid_age_limit(monkeypatch, age):
monkeypatch.setattr("urllib.request.urlopen", lambda *a, **k: pytest.fail("invalid input"))
with pytest.raises(SnapshotUnavailable):
check(max_index_age_hours=age)
def test_future_and_over_age_composition(monkeypatch):
for delta in (timedelta(hours=1), timedelta(hours=-25)):
serve(monkeypatch, snapshot() | {"composed_at": (datetime.now(timezone.utc) + delta).isoformat()})
with pytest.raises(SnapshotUnavailable):
check()
def test_duplicate_ids_refused(monkeypatch):
payload = snapshot()
payload["capabilities"] *= 2
serve(monkeypatch, payload)
with pytest.raises(SnapshotUnavailable):
check()

View file

@ -318,4 +318,27 @@ Stable IDs and maturity fields are preserved for agent consumption (UC-RS-019).
- UC-RS-013 — Use registry metadata in agentic coding
- UC-RS-019 — Publish a machine-readable registry export
- UC-RS-023 — Validate registry entries against schema
- UC-RS-023 — Validate registry entries against schema
### Hosted query-before-build
The default `plan-check` still uses the local composed index. To consult the
hosted registry explicitly:
```bash
reuse-surface plan-check --intent "tender document evidence" \
--hub-url https://reuse.coulomb.social --no-llm --format json
```
Hosted mode fetches the public `GET /v1/federated`; it does not send the query or
a mutation token. It requires a nonempty usable collection, `stale: false`, no
`X-Federation-Warnings`, and a timezone-aware composition no older than 24 hours
(overridable with positive finite `--max-index-age-hours`). Malformed, oversized,
unavailable, stale and partial responses exit **2** with no verdict or opt-in
outcome/request side effects. There is no automatic local fallback.
`index_source` records URL, capability count, composition/retrieval times and
SHA-256 of the response bytes. This is evidence about the accepted hub snapshot;
it does not certify every upstream repository's publication freshness. The
matching verdict is lexical advice; review candidates and owner contracts before
building, including when the verdict is `new`. `--no-llm` makes the check free of
additional model inference. Vergabe-teilnahme consumes it through `make plan-check`.

View file

@ -0,0 +1,54 @@
---
id: REUSE-WP-0022
type: workplan
title: "Use current hosted capability discovery in factory planning"
domain: infotech
repo: reuse-surface
status: active
owner: the-custodian
topic_slug: helix-forge
created: "2026-09-08"
updated: "2026-09-08"
related: [HFACT-WP-0001, VERGABE-WP-0018]
---
# Hosted query-before-build
## Core Idea
The hosted index contains capabilities absent from the checked-in federation.
Provide an explicit hosted plan-check that fetches current discovery, preserves
its provenance and refuses incomplete or stale source evidence before producing
a recommendation. Vergabe-teilnahme is the first delivery-workflow consumer.
## Implement and verify hosted planning
```task
id: REUSE-WP-0022-T01
status: progress
priority: high
assignee: the-custodian
```
Use the existing public GET /v1/federated. Preserve local mode compatibility.
Accept only bounded, structurally usable, recently composed, unwarned responses;
report endpoint, counts, composition/retrieval times and response digest. Never
convert source failure into a NEW verdict, gap request or local fallback. Test
hosted-only matches, malformed/stale/partial sources and transport failures.
No additional service, model call or mutation credential is necessary.
## Publish and return the consumer receipt
```task
id: REUSE-WP-0022-T02
status: wait
priority: high
assignee: the-custodian
depends_on: [REUSE-WP-0022-T01]
blocking_reason: "Await tested source and reviewable PR, then VERGABE-WP-0018 hosted discovery receipt."
```
Publish the tested implementation through the existing Forgejo review path.
Capture a real hosted check invoked from vergabe-teilnahme with pinned source
revision. Return source and consumer evidence to HFACT-WP-0001-T06; it is source
preparation, not proof that the governed Railiance worker performed the change.