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:
Ben Stull
2026-05-25 05:42:15 -07:00
parent 36635049c7
commit a255429e57
11 changed files with 755 additions and 57 deletions
+122 -28
View File
@@ -77,6 +77,9 @@ graduated_by: null
owners: [] # contributors elevated for this RFC
arbiters: [ben] # contributors with merge authority for this RFC
tags: [identity, schema]
# models: # optional per-§6.6 — absent inherits the
# - claude # operator universe; [] opts the RFC out of AI
# - gemini # entirely; populated narrows the picker
---
## Why this RFC is needed
@@ -395,6 +398,67 @@ the acting user. Gitea's commit log is for code archaeology; the
`actions` and `permission_events` tables are the real accountability
record.
### 6.6 Per-RFC model availability
Which AI models contributors can pick from is configurable per RFC.
The configuration lives in the meta-repo entry's frontmatter as an
optional `models:` list of model identifiers (see §2.1), in the same
shape as `owners:` and `arbiters:` — frontmatter-native, edited via
the meta-repo PR flow that already governs the rest of the entry's
canonical state, mirrored into the §4 cache for read-without-roundtrip.
The field is structurally
*optional* and the absent/empty distinction is load-bearing:
- **Absent** (`models:` key omitted) — the RFC inherits the operator's
universe. The operator's universe is the set of models the
deployment is provisioned to run, configured at the process level
per §18. A freshly proposed super-draft and any RFC whose
contributors haven't expressed a preference fall here.
- **Present and non-empty** — the RFC opts into a specific subset.
The picker's option list, at every AI surface scoped to this RFC,
is the intersection of this list with the operator's currently
provisioned models. Models the RFC names but the operator no
longer provisions are silently hidden from the picker; the
frontmatter list is preserved so a later operator change can
restore them.
- **Present and empty** (`models: []`) — the RFC opts out of AI
participation entirely. Every AI surface on the RFC honors the
refusal honestly: the chat composer's AI affordances are absent,
flag-resolution's "Ask Claude to propose a fix" is absent, the
§10.2 PR-draft falls back to its deterministic stub, the §9.1
AI-suggested tags surface is absent. A contributor who tries to
invoke the AI sees the refusal in the surface, not a mid-turn
failure.
The **RFC default model** is the first entry in the resolved list
(intersection with the operator's provisioned set; or the operator
universe when the field is absent). Where a deterministic single
model must be chosen and the contributor has not picked one — the
§10.2 PR-draft, the §9.1 tag suggestions, the §8.13 "Ask Claude to
propose a fix" invocation from an empty thread — that's the model
used. Per-message picker grain inside a chat thread (the §18
prototype carryover) is preserved: each message can name a different
model from the resolved list, and the picker's currently-selected
entry persists across messages within a session.
Editing the field — adding a model, removing one, switching to the
empty-list opt-out — follows the same authority rules as editing
`owners:` or `arbiters:`. Super-draft entries: the claimed owners
and app-wide admins. Active RFCs: the RFC's owners and arbiters per
§6.3, plus app-wide admins. The dedicated chrome for the edit (the
metadata pane equivalent that the §19.2 metadata-pane UX topic will
settle for super-drafts, and whatever surface admins use for active-
RFC frontmatter edits) is a downstream concern — the structural
commitment here is the field, the resolution rule, and the
configuration capability, not the click path.
This section names *which* models are permitted on a given RFC. It
does not name *whose API resources pay for them* — credentials
remain operator-supplied per §18. Credential delegation and the
funder role are a separate topic (§19.2) whose settlement will
attach a parallel "credential set" notion alongside this one
without changing the availability surface.
---
## 7. The left pane
@@ -783,6 +847,15 @@ the scroll-to-editor binding. Resolved and stale threads stay in the
data; the chat feed's filter affordances surface or hide them on
demand.
The chat's model picker (the §18 prototype carryover) draws its
option list from the resolved per-RFC set per §6.6 — the
intersection of the entry's optional `models:` frontmatter (or the
operator universe when absent) with the operator's currently
provisioned providers. An RFC whose resolved list is empty surfaces
the AI affordances as absent rather than disabled-but-present, so
the refusal reads as a property of the RFC, not as a transient
error.
### 8.13 Flags
A flag is the lightweight "I'm pointing at this, it's a problem"
@@ -1290,8 +1363,13 @@ branch chat, both editable inline before submit:
to consider.
The model is told the audience is *an arbiter*, not Ben specifically —
the framework has to scale past one person. Title and description remain
editable post-open by the contributor or any of the RFC's arbiters.
the framework has to scale past one person. The model used for the
draft is the RFC's default per §6.6 — the first entry in the
resolved per-RFC list. When that list is empty (the RFC opts out of
AI per §6.6), the draft falls back to a deterministic stub naming
the RFC title; the contributor edits the prefilled text as usual.
Title and description remain editable post-open by the contributor
or any of the RFC's arbiters.
There is no reviewer picker. The RFC's arbiters (§6.3) are the implicit
reviewer set; surfacing a per-PR picker would either duplicate that or
@@ -2202,7 +2280,9 @@ specified* and what is intentionally out of scope for v1.
rich markdown, headings, links, code blocks.
- **The per-RFC and per-branch chat UX.** Threading model, AI
participation, the discuss-vs-contribute mode distinction from the
prototype, the selection tooltip, the prompt bar, model picker.
prototype, the selection tooltip, the prompt bar, the model picker
chrome (its option-list source is settled in §6.6 / §8.12; the
visual treatment and per-thread persistence remain).
- **The revision flow.** How proposed changes from AI or contributors
appear, the change-card panel, accept/decline/edit, tracked
insertions/deletions in the editor.
@@ -2434,7 +2514,11 @@ them:
- The structured `<change>` / `<original>` / `<proposed>` / `<reason>`
protocol for AI-proposed edits.
- Multi-provider LLM support: Anthropic Claude, Google Gemini, OpenAI /
GitHub Copilot. `ENABLED_MODELS` and per-provider API keys via env.
GitHub Copilot. `ENABLED_MODELS` and per-provider API keys via env
these now define the operator's *universe* of available models, the
set the deployment is provisioned to run. Per-RFC selection from
that universe is settled in §6.6; credential delegation and the
funder role remain a §19.2 candidate.
- The discuss-vs-contribute distinction inside an RFC view (to be
fully specified in the follow-up session).
- Gitea OAuth for user authentication. The OAuth identity is the basis
@@ -2604,24 +2688,34 @@ build surfaces evidence for which one matters next. Topics are
listed roughly in order of expected weight; the order is not
binding.
- **Per-RFC model availability and credential delegation.** Which
AI models contributors can pick from when chatting on a given
RFC, and who supplies the API resources for those models.
Replaces §18's app-level `ENABLED_MODELS` and env-supplied keys
with per-RFC-scoped configuration. Touches every AI surface —
every chat thread (§8.12), every change-proposal turn (§8.9,
§8.11), every flag-resolution invocation (§8.13), the AI-drafted
PR title and description (§10.2), and the propose modal's
AI-suggested tags (§9.1). Touches §5 (schema for model config
and credentials), §6 (possibly a `funder` role, or a per-RFC
capability extension along the lines of §6.2's per-user
overrides), and §18 (carryover supersession). Subdividable into
"model availability" (lighter, UX-shaped) and "credential
delegation and the funder role" (heavier — security, billing,
abuse mitigation, rotation, mid-conversation key failure) if the
session driver judges the combined scope too large. Load-bearing
once the framework runs past single-operator deployment;
defer-able until then.
- **Per-RFC model availability — UX half.** *Settled in the
post-v1 session that picked it. The meta-repo entry frontmatter
now carries an optional `models:` list per §6.6; the resolution
rule (absent inherits the operator universe, populated narrows
the picker by intersection, `[]` opts the RFC out of AI
entirely) is uniform across every AI surface — §8.12 chat
picker, §8.9 / §8.11 change-proposal turns, §8.13 flag
resolution, §10.2 PR draft, §9.1 tag suggestions. The §18
`ENABLED_MODELS` env contract is reframed as the operator's
universe of provisioned models; the per-RFC list picks from
within it. The dedicated chrome for editing the field is
downstream — clustered with the metadata-pane UX candidate
below for super-drafts, and with whatever surface admins use
for active-RFC frontmatter edits.*
- **Per-RFC credential delegation and the funder role.** The
heavier half of the original per-RFC-model topic — who supplies
the API resources behind the models a given RFC permits. The
availability half (§6.6) names *which* models are allowed;
credential delegation names *whose keys pay for the calls*.
Touches security (delegation grant shape, revocation), billing
(per-RFC spend, attribution), abuse mitigation (rate limits per
funder, quota exhaustion), key rotation (mid-conversation
failure handling, retry-with-fallback), and a possible
`funder` role distinct from owner/arbiter that scopes credential
authority without conferring frontmatter authority. Load-bearing
once the framework runs past single-operator deployment; the v1
shape — operator-supplied keys per §18 — is the right default
until a real multi-operator scenario surfaces.
- **Admin surfaces.** Where role management, muting, audit-log
views, the graduation-readiness queue, and Topic 13's
notification-preferences settings (email categories per §15.4,
@@ -2763,12 +2857,12 @@ binding.
- **The §10.2 modal's AI-drafted text when no provider is
configured.** Slice 3 falls back to a deterministic stub
(`Edits to <RFC title>` plus a character-count line) when the
app has no LLM provider. The fallback is functional but does
not produce spec-voice text. Per-RFC model availability (the
first §19.2 candidate, on the funder-role topic) will need to
settle the credential-delegation shape before this earns its
own topic; until then, the stub is the right shape for the
no-credential-available case.
app has no LLM provider. The §6.6 settlement extends the same
fallback to the case where the RFC's resolved model list is
empty — the RFC has opted out of AI entirely. The fallback is
functional but does not produce spec-voice text; improving the
no-credential-available render remains its own future topic,
defer-able until evidence shows the stub bites.
- **§10.9 replay AI participation.** Slice 3 implements the
structural §10.9 path — fresh resolution branch off main, replay
the accepted changes whose `original` text still locates exactly