Post-v1: per-RFC model availability (UX half) folded into §6.6
First §19.2 candidate settled after v1. The heavier per-RFC-model
topic subdivided into UX (this) and credential delegation + funder
role (still §19.2). New §6.6 carries the rule: an optional `models:`
frontmatter field on the meta-repo RFC entry; absent inherits the
operator universe, populated narrows the picker to the intersection
with provisioned providers, `[]` opts the RFC out of AI entirely.
The first resolved entry is the RFC default. §18's ENABLED_MODELS is
reframed as the operator universe.
Code: migration 009 adds nullable cached_rfcs.models_json (NULL ≠ []
is load-bearing); entry.py grows the optional field with absent-vs-
empty round-tripping in parse/serialize; new models_resolver module
holds the rule; api_branches replaces /api/models with the slug-aware
/api/rfcs/{slug}/models and threads the chat + reask paths through
the resolver; api_prs §10.2 uses the resolver and extends the stub
fallback to the opt-out case; frontend passes slug to listModels.
Tests 106/106 green (96 prior + 10 in test_per_rfc_models.py). No
behavioral change for entries without `models:` — operator universe
preserved as default.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+86
@@ -44,6 +44,92 @@ rare and surgical and live in the appropriate numbered section per
|
||||
|
||||
## State of the codebase
|
||||
|
||||
### Post-v1: §6.6 per-RFC model availability (UX half)
|
||||
|
||||
The first §19.2 candidate settled after v1 shipped. The heavier
|
||||
per-RFC-model topic explicitly subdivided into "model availability"
|
||||
(UX-shaped) and "credential delegation + funder role" (security,
|
||||
billing, rotation); this session folded in the lighter half and
|
||||
left the heavier one as its own §19.2 entry.
|
||||
|
||||
The settlement: a new optional `models:` frontmatter field on the
|
||||
meta-repo RFC entry, in the same shape as `owners:` / `arbiters:`.
|
||||
Absent inherits the operator's universe (the §18 `ENABLED_MODELS`
|
||||
env contract, now reframed as the operator's provisioned set).
|
||||
Populated lists narrow the picker to the intersection with that
|
||||
universe, preserving the entry's stated order. An empty list
|
||||
(`models: []`) opts the RFC out of AI participation entirely —
|
||||
every AI surface honors the refusal honestly. The first entry of
|
||||
the resolved list is the RFC's default model; the §10.2 PR-draft
|
||||
uses it when present and falls back to its existing deterministic
|
||||
stub when the resolved list is empty.
|
||||
|
||||
§6.6 carries the structural rule. §2.1's frontmatter example shows
|
||||
the field. §8.12 names the picker's option-list source. §10.2
|
||||
names the model used for drafting. §16 narrows its "model picker"
|
||||
deferral to the chrome only. §18 reframes `ENABLED_MODELS` as the
|
||||
operator universe. §19.2's per-RFC-model entry is split — the UX
|
||||
half is marked *settled* with a pointer to §6.6; the credential
|
||||
half lives on as its own entry.
|
||||
|
||||
Code changes:
|
||||
|
||||
- [`backend/migrations/009_per_rfc_models.sql`](../backend/migrations/009_per_rfc_models.sql)
|
||||
adds `cached_rfcs.models_json` as a nullable column — NULL means
|
||||
the frontmatter field is absent (inherit the universe), `'[]'`
|
||||
means the explicit opt-out, `'[...]'` is the populated list. The
|
||||
absent-vs-empty distinction is load-bearing per §6.6, so this
|
||||
column cannot collapse to a `NOT NULL DEFAULT '[]'` the way the
|
||||
other `*_json` columns do.
|
||||
- [`backend/app/entry.py`](../backend/app/entry.py) grows an
|
||||
optional `models: list[str] | None` field on the `Entry`
|
||||
dataclass. The parser uses a sentinel to distinguish "key not in
|
||||
YAML" from "key set to null" so both round-trip cleanly. The
|
||||
serializer emits `models:` only when set, preserving the absent
|
||||
case in canonical entry text.
|
||||
- [`backend/app/cache.py`](../backend/app/cache.py)'s
|
||||
`_upsert_cached_rfc` writes `NULL` or `json.dumps(entry.models)`
|
||||
on the round-trip from frontmatter through the cache.
|
||||
- [`backend/app/models_resolver.py`](../backend/app/models_resolver.py)
|
||||
is the new small module that holds the rule. Two functions:
|
||||
`resolve_models_for_rfc(slug, providers)` returns the resolved
|
||||
list, and `default_model_for_rfc(slug, providers)` returns the
|
||||
first entry or empty string. Every AI surface calls into one of
|
||||
these; the rule lives in one place.
|
||||
- [`backend/app/api_branches.py`](../backend/app/api_branches.py)
|
||||
replaces the slug-less `GET /api/models` with the per-RFC
|
||||
`GET /api/rfcs/{slug}/models` per §6.6. The chat-stream and
|
||||
reask-change paths now resolve through the per-RFC list and
|
||||
refuse with 503 when it is empty. Both `default_model` references
|
||||
inside the module are removed; the resolver replaces them.
|
||||
- [`backend/app/api_prs.py`](../backend/app/api_prs.py)'s §10.2
|
||||
`pr-draft` endpoint resolves the RFC's default via the new
|
||||
helper. The internal `_draft_with_provider` falls back to the
|
||||
deterministic stub when `default_model` is empty (the §6.6
|
||||
opt-out case), extending the prior "no providers configured"
|
||||
fallback symmetrically.
|
||||
- [`frontend/src/api.js`](../frontend/src/api.js) and
|
||||
[`frontend/src/components/RFCView.jsx`](../frontend/src/components/RFCView.jsx)
|
||||
pass the slug to `listModels(slug)`, hitting the new per-RFC
|
||||
endpoint.
|
||||
|
||||
Settlement ships covered by
|
||||
[`test_per_rfc_models.py`](../backend/tests/test_per_rfc_models.py)
|
||||
— ten integration tests: absent-frontmatter inheriting the
|
||||
operator universe, populated-frontmatter narrowing, the
|
||||
empty-list opt-out, intersection with the operator's provisioned
|
||||
set, intersection-empty matching the opt-out shape, the §10.2
|
||||
draft using the RFC default, the §10.2 stub fallback on opt-out,
|
||||
the chat surface's 503 refusal on opt-out, the cache round-trip
|
||||
from meta-repo frontmatter through `refresh_meta_repo`, and the
|
||||
entry parser/serializer round-trip preserving the absent /
|
||||
empty / populated distinction.
|
||||
|
||||
The full test suite is 106/106 green (96 prior + 10 new). No
|
||||
behavioral change for RFCs without `models:` in frontmatter — the
|
||||
operator universe is preserved as the default, so existing
|
||||
deployments see no surface change until a frontmatter edit lands.
|
||||
|
||||
### Slice 1 — shipped
|
||||
|
||||
The repository scaffolding (`backend/`, `frontend/`, `scripts/`,
|
||||
|
||||
Reference in New Issue
Block a user