feat(portfolio): K7 — SDK/API preflight, offline pre-spend boundary (parity row 20) [skip-docs]

Everything that CAN be validated WITHOUT a model call is validated BEFORE the
operator pays for one (S4.1-analog, SDK-native — Foundry-auth is MAF-specific,
not mirrored). The preflight IS the boundary: it never calls query(), never
validates a credential VALUE online, never touches the network. It returns a
list of structured, actionable Refusals; the CLI exits non-zero on any, so a
broken config stops cheaply instead of on the first billed call.

- preflight.py: run_preflight + `python -m …preflight --profile anthropic`.
  Four offline checks:
  * credential — ANTHROPIC_API_KEY set + not a placeholder form (the value is
    NEVER checked online, only presence, §1); the bundled CLI's own
    CLAUDE_CODE_OAUTH_TOKEN also satisfies it (run_s10 relies on it — refusing
    would be a false alarm).
  * model_map — the requested profile exists and every id it resolves to,
    THROUGH resolve_model (so the default fall-through is covered), is real,
    not a placeholder left in config.
  * sdk — claude_agent_sdk imports (a missing install is a structured refusal
    naming `uv sync`, never an ImportError out of the preflight), the run-path
    symbols exist, and the bundled Claude Code CLI is present on disk — located
    OFFLINE via the SDK package's own files, mirroring the SDK's _find_cli
    order (bundled first, then a claude on PATH). Verified against installed
    0.2.120 (bundled binary present; the STATE 0.2.110 note was stale).
  * termination — the §8 stop contract constructs with positive caps and the
    per-call USD belt is positive.
- tests/test_preflight.py: credential contract, placeholder detector, model_map
  incl. unknown-profile-without-raising, SDK + bundled-CLI offline probe (with a
  monkeypatched missing-SDK refusal), termination, run_preflight aggregation,
  CLI both paths, and the offline guards (no network import, no query() call —
  AST-based so prose stays green). THREE seams detach-proven RED: credential
  branch, placeholder model-id guard, no-network grep-guard.

478→514 green, golden byte-exact, full gate clean (ruff+format+mypy strict,
24 src files), run_s10.py/runs/ byte-untouched. README test-count sync ×2 +
preflight.py module note + load-bearing mention. IKKE-scope (held): the actual
API call (ALDRI — the preflight IS the boundary) and Foundry/Azure auth.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RiTwaKLesgcwXx2mDviqpt
This commit is contained in:
Kjell Tore Guttormsen 2026-07-24 01:34:18 +02:00
commit c08d92a358
3 changed files with 588 additions and 3 deletions

View file

@ -0,0 +1,314 @@
"""SDK/API preflight (method-spec §4.1/§10 analog; S4.1; paritetsrad 20; K7).
Everything that CAN be validated WITHOUT a model call is validated HERE, before
the operator pays for one. The preflight is the BOUNDARY: it never calls
``query()``, never validates the credential VALUE online (only that one is SET
and is not an obvious placeholder form), never touches the network. It returns a
list of structured ``Refusal``s one actionable message per deficiency; an
empty list means "clear to run". The CLI exits non-zero on any refusal so a
broken config stops here, cheaply, instead of on the first billed call.
Four offline checks (for a given backend profile, ``anthropic`` today):
* **credential** a credential is available (``ANTHROPIC_API_KEY``, or the
bundled CLI's own ``CLAUDE_CODE_OAUTH_TOKEN``) and is not a placeholder form.
The value itself is NEVER checked online a bad key surfaces on the first
call, never here (§1 honesty: the preflight claims no more than it proves).
* **model_map** the requested profile exists, and every id it resolves to
(THROUGH ``resolve_model``, so the ``default`` fall-through is covered) is a
real id, not a placeholder left in the config.
* **sdk** ``claude_agent_sdk`` imports (a missing install is a structured
refusal naming ``uv sync``, never an ImportError), the run-path symbols exist,
and the bundled Claude Code CLI is present on disk located offline via the
SDK package's own files, mirroring the SDK's ``_find_cli`` order (bundled
first, then a ``claude`` on PATH). Verified against installed 0.2.120.
* **termination** the §8 stop contract constructs with positive round/token
caps and the per-call USD cap is positive (the budget belt is SET).
Importing the SDK to prove it imports is offline-safe (no key, no socket) the
whole suite already imports the client. The no-network grep-guard
(``tests/test_preflight.py``) proves this module carries no HTTP path of its own.
Run: uv run python -m portfolio_optimiser_claude.preflight [--profile anthropic]
[--max-rounds N] [--max-tokens N] [--max-budget-usd-per-call F]
"""
from __future__ import annotations
import argparse
import importlib
import os
import platform
import shutil
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Mapping
from pydantic import ValidationError
from portfolio_optimiser_claude.contracts import (
ModelMapContract,
TerminationContract,
_bundled_model_map,
resolve_model,
)
# Tokens that never appear in a real Anthropic key or Claude model id, but do in
# the placeholders operators leave behind. Matched case-insensitively as a
# SUBSTRING so ``sk-ant-YOUR-KEY`` and ``<model>`` are both caught. Kept
# conservative to avoid ever refusing a genuine value (§1: no false alarms).
_PLACEHOLDER_TOKENS: tuple[str, ...] = (
"placeholder",
"your-",
"your_",
"yourkey",
"changeme",
"change-me",
"change_me",
"replace",
"example",
"todo",
"fixme",
"dummy",
"xxxx",
"<",
">",
"...",
)
# A probe role guaranteed absent from any mapping, used to exercise the
# resolve_model default fall-through (the id an UNMAPPED role would receive).
_FALLTHROUGH_PROBE = "__preflight_fallthrough_probe__"
# The run-path symbols sdk_client.py depends on; their presence is what
# "the SDK imports" must actually mean (a bare module is not enough).
_REQUIRED_SDK_SYMBOLS: tuple[str, ...] = ("query", "ClaudeAgentOptions")
@dataclass(frozen=True)
class Refusal:
"""One preflight deficiency: which check tripped + an actionable message."""
check: str
detail: str
def _looks_like_placeholder(value: str) -> bool:
"""True for an empty/whitespace value or one carrying a placeholder token.
Shared by the credential and model-id checks so a stub left in either place
is caught by the same conservative rule (never validates a value online)."""
low = value.strip().lower()
if not low:
return True
return any(token in low for token in _PLACEHOLDER_TOKENS)
def _check_credentials(env: Mapping[str, str]) -> list[Refusal]:
"""A usable credential is present (§10) — the VALUE is never checked online.
``ANTHROPIC_API_KEY`` is the named contract; the bundled CLI's own
``CLAUDE_CODE_OAUTH_TOKEN`` also satisfies it (run_s10 relies on the CLI's
credentials when no key is exported refusing that would be a false alarm).
A credential set to a placeholder form is refused; NEITHER set is refused.
"""
key = env.get("ANTHROPIC_API_KEY", "").strip()
oauth = env.get("CLAUDE_CODE_OAUTH_TOKEN", "").strip()
if key:
if _looks_like_placeholder(key):
return [
Refusal(
"credential",
"ANTHROPIC_API_KEY is set to a placeholder-form value — export a "
"real key (its value is never validated online, only that one is set).",
)
]
return []
if oauth and not _looks_like_placeholder(oauth):
return [] # the bundled CLI authenticates via its OAuth token
return [
Refusal(
"credential",
"no API credential: export ANTHROPIC_API_KEY (or authenticate the bundled "
"CLI, which sets CLAUDE_CODE_OAUTH_TOKEN). The value is never checked online "
"— only that a credential is present.",
)
]
def _check_model_map(profile: str, *, model_map: ModelMapContract | None = None) -> list[Refusal]:
"""The requested profile exists and every id it RESOLVES to is real (§10).
Placeholder refusal runs THROUGH ``resolve_model`` (not a raw dict read), so
the ``default`` fall-through an unmapped role receives is checked too. An
unknown profile which ``resolve_model`` would raise on becomes a
structured refusal, never a stack trace before spend.
"""
resolved = ModelMapContract(**_bundled_model_map()) if model_map is None else model_map
mapping = resolved.profiles.get(profile)
if mapping is None:
have = ", ".join(sorted(resolved.profiles)) or "(none)"
return [
Refusal(
"model_map",
f"backend profile '{profile}' is not in model_map (have: {have}).",
)
]
refusals: list[Refusal] = []
# Every configured role PLUS the fall-through the default serves — all via
# resolve_model, deduped so one placeholder id is reported once.
for role in sorted(mapping) + [_FALLTHROUGH_PROBE]:
model_id = resolve_model(resolved, role, profile=profile)
if _looks_like_placeholder(model_id):
refusals.append(
Refusal(
"model_map",
f"model id '{model_id}' (profile '{profile}') is a placeholder form — "
"set a real Claude model id (verified against platform.claude.com/docs).",
)
)
break # a placeholder id is a single config fault; one message suffices
return refusals
def _locate_cli(sdk: Any) -> Path | None:
"""The CLI the SDK would spawn, resolved OFFLINE via the package's files.
Mirrors the SDK's ``_find_cli`` order: the bundled binary shipped in the
wheel first, then a ``claude`` on PATH. No network, no subprocess just a
file-existence probe, so the preflight can prove there IS a CLI to run.
"""
cli_name = "claude.exe" if platform.system() == "Windows" else "claude"
bundled = Path(sdk.__file__).parent / "_bundled" / cli_name
if bundled.is_file():
return bundled
found = shutil.which("claude")
return Path(found) if found else None
def _check_sdk() -> list[Refusal]:
"""The SDK imports, exposes the run-path symbols, and its CLI is present.
A missing install is a structured refusal naming the fix (never an
ImportError raised out of the preflight itself). Importing is offline-safe.
"""
try:
sdk = importlib.import_module("claude_agent_sdk")
except ImportError as exc:
return [
Refusal(
"sdk",
f"claude-agent-sdk is not importable ({exc}); run 'uv sync' to install the "
"pinned SDK (it bundles the Claude Code CLI — no separate install).",
)
]
missing = [name for name in _REQUIRED_SDK_SYMBOLS if not hasattr(sdk, name)]
if missing:
return [
Refusal(
"sdk",
f"claude-agent-sdk is installed but missing run-path symbols {missing}"
"the pin may be out of the verified range (see test_sdk_version_guard).",
)
]
if _locate_cli(sdk) is None:
pkg_file = sdk.__file__
bundled = f"{Path(pkg_file).parent / '_bundled'}" if pkg_file else "the SDK package"
return [
Refusal(
"sdk",
f"no Claude Code CLI found (neither the SDK's bundled binary under {bundled} "
"nor a 'claude' on PATH); reinstall claude-agent-sdk or pass "
"ClaudeAgentOptions(cli_path=...).",
)
]
return []
def _check_termination(
max_rounds: int, max_tokens: int, max_budget_usd_per_call: float
) -> list[Refusal]:
"""The §8 stop contract constructs and the per-call USD belt is positive."""
refusals: list[Refusal] = []
try:
TerminationContract(max_rounds=max_rounds, max_tokens=max_tokens)
except ValidationError as exc:
refusals.append(
Refusal(
"termination",
f"stop contract invalid — max_rounds/max_tokens must be positive (§8): {exc}",
)
)
if max_budget_usd_per_call <= 0:
refusals.append(
Refusal(
"termination",
f"max_budget_usd_per_call must be positive, got {max_budget_usd_per_call} "
"(the per-call USD belt must be SET before any spend).",
)
)
return refusals
def run_preflight(
*,
profile: str = "anthropic",
env: Mapping[str, str] | None = None,
max_rounds: int = 12,
max_tokens: int = 150_000,
max_budget_usd_per_call: float = 0.25,
) -> list[Refusal]:
"""Run every offline check; the refusals are the union (empty = clear to run).
No check makes a model call or a network request this is the boundary the
operator crosses BEFORE spending. ``env`` defaults to ``os.environ``.
"""
environ = os.environ if env is None else env
return [
*_check_credentials(environ),
*_check_model_map(profile),
*_check_sdk(),
*_check_termination(max_rounds, max_tokens, max_budget_usd_per_call),
]
def main(argv: list[str] | None = None) -> int:
"""The thin CLI: run the offline preflight → print refusals → exit code.
Exit 0: clear to run (no refusal). Exit 1: one or more refusals printed
with their fix, and a line stating no model call was made (the preflight IS
the boundary; a green preflight never implies a validated key or a run).
"""
parser = argparse.ArgumentParser(
description=(
"Validate everything that CAN be checked WITHOUT a model call, BEFORE any "
"spend — credential presence, model_map, SDK + bundled CLI, stop/budget "
"contract (offline; the preflight IS the boundary, it never calls the API)."
)
)
parser.add_argument("--profile", type=str, default="anthropic")
parser.add_argument("--max-rounds", type=int, default=12)
parser.add_argument("--max-tokens", type=int, default=150_000)
parser.add_argument("--max-budget-usd-per-call", type=float, default=0.25)
args = parser.parse_args(argv)
refusals = run_preflight(
profile=args.profile,
max_rounds=args.max_rounds,
max_tokens=args.max_tokens,
max_budget_usd_per_call=args.max_budget_usd_per_call,
)
if not refusals:
print(
f"PREFLIGHT OK — clear to run (profile={args.profile}); no model call was "
"made (the preflight is the boundary, not a validated key or a run)."
)
return 0
print(f"PREFLIGHT FAILED — {len(refusals)} refusal(s), no spend (no model call was made):")
for refusal in refusals:
print(f" [{refusal.check}] {refusal.detail}")
return 1
if __name__ == "__main__":
raise SystemExit(main())