Post-v1: per-RFC credential delegation (funder role) folded into §6.7
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>
This commit is contained in:
@@ -45,6 +45,11 @@ class Entry:
|
||||
# means an explicit opt-out from AI on this RFC. A populated list
|
||||
# narrows the picker to its intersection with the operator universe.
|
||||
models: list[str] | None = None
|
||||
# §6.7: optional gitea_login naming the user whose registered API
|
||||
# credentials pay for AI calls on this RFC. None means absent —
|
||||
# operator credentials per §18 are used. The binding is inert until
|
||||
# the named user has a funder_consents row (the hybrid two-key rule).
|
||||
funder: str | None = None
|
||||
body: str = ""
|
||||
|
||||
|
||||
@@ -59,6 +64,8 @@ def parse(text: str) -> Entry:
|
||||
models: list[str] | None = None
|
||||
else:
|
||||
models = [str(m) for m in raw_models]
|
||||
raw_funder = fm.get("funder")
|
||||
funder = str(raw_funder).strip() if raw_funder else None
|
||||
return Entry(
|
||||
slug=str(fm.get("slug") or ""),
|
||||
title=str(fm.get("title") or ""),
|
||||
@@ -73,6 +80,7 @@ def parse(text: str) -> Entry:
|
||||
arbiters=list(fm.get("arbiters") or []),
|
||||
tags=list(fm.get("tags") or []),
|
||||
models=models,
|
||||
funder=funder,
|
||||
body=body,
|
||||
)
|
||||
|
||||
@@ -97,6 +105,11 @@ def serialize(entry: Entry) -> str:
|
||||
# is meaningfully different from `models: []` per §6.6.
|
||||
if entry.models is not None:
|
||||
fm["models"] = entry.models
|
||||
# §6.7: emit `funder:` only when set. Empty / None means absent —
|
||||
# operator credentials are used. There is no "explicit opt-out"
|
||||
# second meaning here as with `models:`; one set of semantics.
|
||||
if entry.funder:
|
||||
fm["funder"] = entry.funder
|
||||
yaml_text = yaml.safe_dump(fm, sort_keys=False, default_flow_style=False).rstrip()
|
||||
body = entry.body.lstrip("\n")
|
||||
if body:
|
||||
|
||||
Reference in New Issue
Block a user