55a8be051a
Second §19.2 settlement after v1. New §6.7 alongside §6.6: optional `funder:` frontmatter field names a single gitea_login; a `funder_consents` app-db row records funder-side opt-in; both halves required for the binding to activate (two-key rule). Funder universe replaces — does not augment — the operator universe per-RFC for attribution-clean resolution. Funder role grants zero §6.1/§6.3 authority. Three revocation paths each restore the operator-credentials status quo. §19.2's credential-delegation entry is split: lighter half marked settled with a pointer to §6.7; operational-realities half (mid-call failure, rotation, billing, rate-limit attribution) lives on as its own entry. Test suite is 125/125 green (106 prior + 19 new). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
80 lines
3.2 KiB
Python
80 lines
3.2 KiB
Python
"""§6.6 per-RFC model availability — the resolver, extended in §6.7
|
|
with the funder-universe layer.
|
|
|
|
The meta-repo entry's optional `models:` frontmatter and the operator's
|
|
provisioned providers (the §18 `ENABLED_MODELS` universe) combine into a
|
|
single resolved list per RFC. Every AI surface picks against that list:
|
|
the §8.12 chat picker, the §10.2 PR-draft, the §8.13 flag-resolution
|
|
invocation, the §9.1 tag suggestions when graduation is in scope.
|
|
|
|
Rule, in one place:
|
|
|
|
- The base universe is normally the operator's provisioned providers.
|
|
When a consenting funder is in effect for the RFC per §6.7, the base
|
|
universe is replaced (not augmented) by the funder's registered
|
|
universe — the subset of operator-enabled picker keys the funder has
|
|
supplied credentials for.
|
|
- Cache row's `models_json` is NULL → the field is absent on the entry.
|
|
Resolved list = the base universe.
|
|
- Cache row's `models_json` is a JSON array → the field is set on the
|
|
entry. Resolved list = intersection of the array with the base
|
|
universe, preserving the entry's stated order.
|
|
|
|
The empty case folds in naturally: `models: []` yields empty, an entry
|
|
whose listed models aren't in the operator's enabled set yields empty,
|
|
a consenting funder whose registrations don't intersect the operator's
|
|
enabled set yields empty. Callers treat all four the same — refuse
|
|
cleanly per §6.6.
|
|
|
|
The function is slug-aware and provider-aware; it does not depend on
|
|
the FastAPI request lifecycle, which keeps it cheap to call inside any
|
|
endpoint that knows the slug and has the providers dict in scope.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
from . import db, funder
|
|
from .providers import BaseProvider
|
|
|
|
|
|
def resolve_models_for_rfc(
|
|
slug: str, providers: dict[str, BaseProvider]
|
|
) -> list[str]:
|
|
"""Return the per-RFC resolved model keys per §6.6, extended by §6.7.
|
|
|
|
The first entry is the RFC's default model. An empty list means
|
|
AI is unavailable on this RFC and callers refuse the AI surface.
|
|
"""
|
|
# §6.7: the funder universe (if any) replaces the operator universe
|
|
# as the base set the §6.6 frontmatter intersects against.
|
|
funder_universe = funder.resolve_funder_universe(slug, providers)
|
|
base_universe = funder_universe if funder_universe is not None else list(providers.keys())
|
|
row = db.conn().execute(
|
|
"SELECT models_json FROM cached_rfcs WHERE slug = ?",
|
|
(slug,),
|
|
).fetchone()
|
|
if row is None or row["models_json"] is None:
|
|
return list(base_universe)
|
|
try:
|
|
listed = [str(m) for m in json.loads(row["models_json"])]
|
|
except (json.JSONDecodeError, TypeError):
|
|
return list(base_universe)
|
|
if not listed:
|
|
return []
|
|
base_set = set(base_universe)
|
|
return [m for m in listed if m in base_set]
|
|
|
|
|
|
def default_model_for_rfc(
|
|
slug: str, providers: dict[str, BaseProvider]
|
|
) -> str:
|
|
"""The first entry in the resolved list, or empty string if none.
|
|
|
|
Callers that need a deterministic single choice — §10.2's draft,
|
|
§9.1's tag suggestions — use this. An empty return signals the
|
|
refusal path per §6.6.
|
|
"""
|
|
resolved = resolve_models_for_rfc(slug, providers)
|
|
return resolved[0] if resolved else ""
|