agent review: reject future-dated sources at submit; treat fetched pages as data

validate() now takes the submission stamp and rejects any source without a
readable publication date or dated after the submission day (UTC). The skill
tells the agent and its subagents that web pages, news and filings are data,
never instructions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-11 22:24:47 -07:00
parent cd0721eada
commit 2ff7bc07cf
3 changed files with 50 additions and 7 deletions

View File

@@ -136,8 +136,23 @@ def cmd_candidates(args) -> None:
f"{'the next session rebalances' if until == 0 else f'next rebalance in {until} sessions (vetoes act daily)'}")
def validate(sub: dict, pack: dict) -> tuple[dict, list[str]]:
"""Normalized scores and warnings; raises ValueError on a bad file."""
def _source_day(src) -> pd.Timestamp | None:
"""A source's publication day (UTC), or None when it has no readable date."""
try:
ts = pd.Timestamp(src["date"])
except (TypeError, KeyError, ValueError):
return None
if pd.isna(ts):
return None
if ts.tzinfo is not None:
ts = ts.tz_convert("UTC").tz_localize(None)
return ts.normalize()
def validate(sub: dict, pack: dict, now: pd.Timestamp) -> tuple[dict, list[str]]:
"""Normalized scores and warnings; raises ValueError on a bad file.
``now`` is the submission stamp: no source may be dated after its day (UTC)."""
today = now.tz_convert("UTC").tz_localize(None).normalize()
want = [c["ticker"] for c in pack["candidates"]]
rows = sub.get("scores")
if not isinstance(rows, dict):
@@ -160,10 +175,20 @@ def validate(sub: dict, pack: dict) -> tuple[dict, list[str]]:
raise ValueError(f"{t}: a veto needs the evidence in 'risks'")
if not str(v.get("summary", "")).strip():
raise ValueError(f"{t}: 'summary' is required")
sources = v.get("sources", [])
if not isinstance(sources, list):
raise ValueError(f"{t}: 'sources' must be a list of {{url, date}}")
for src in sources:
day = _source_day(src)
if day is None:
raise ValueError(f"{t}: source {src!r} needs a publication 'date' (YYYY-MM-DD)")
if day > today:
raise ValueError(f"{t}: source {src.get('url')} is dated {src['date']}, "
f"after the submission day {today.date()} (UTC)")
out[t] = {"score": s, "veto": veto, "veto_reason": reason if veto else None,
"confidence": v.get("confidence"),
**{k: v.get(k, "") for k in ("earnings", "fundamentals", "logic", "risks", "summary")},
"sources": v.get("sources", [])}
"sources": sources}
extreme = sum(abs(v["score"]) == hi for v in out.values())
if extreme > 10:
warn.append(f"{extreme} names at +/-{hi}; the rubric expects at most about 10")
@@ -179,11 +204,11 @@ def cmd_submit(args) -> None:
sys.exit(f"no candidate pack for as_of={as_of!r} ({pack_path})")
with open(pack_path) as fh:
pack = json.load(fh)
now = pd.Timestamp.now(tz="UTC")
try:
scores, warn = validate(sub, pack)
scores, warn = validate(sub, pack, now)
except ValueError as exc:
sys.exit(f"rejected: {exc}")
now = pd.Timestamp.now(tz="UTC")
deadline = pd.Timestamp(pack["deadline_utc"])
out = {"strategy": NAME, "as_of": as_of, "created_at": now.isoformat(timespec="seconds"),
"model": sub.get("model", ""), "n_candidates": len(scores), "scores": scores}