Release 0.4.0: auto-set RFC owner = proposer

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-05-27 22:53:09 -07:00
parent 21fcbc92d4
commit 0f8b318afa
8 changed files with 132 additions and 13 deletions
+5 -1
View File
@@ -298,7 +298,11 @@ def make_router(
proposed_at=entry_mod.today(),
graduated_at=None,
graduated_by=None,
owners=[],
# §9.2: the proposer is the implicit first owner at propose time.
# The §13.1 claim flow exists for *other* contributors to become
# owners on an RFC they didn't propose; the proposer never needs
# to claim their own RFC.
owners=[user.gitea_login],
arbiters=[],
tags=[t.strip() for t in payload.tags if t.strip()],
body=payload.pitch.strip() + "\n",
+40
View File
@@ -496,6 +496,11 @@ def test_propose_to_super_draft_vertical(app_with_fake_gitea):
proposal = r.json()
assert proposal["entry"]["title"] == "Open Human Model"
assert proposal["entry"]["state"] == "super-draft"
# §9.2: the proposer is the implicit first owner at propose time.
# The owners field is a single-element list containing exactly the
# session user's gitea_login — no request-supplied owner field
# exists or is honored.
assert proposal["entry"]["owners"] == ["alice"]
assert proposal["affordances"]["merge"] is True
# Owner merges. The catalog picks up the new super-draft.
@@ -516,6 +521,10 @@ def test_propose_to_super_draft_vertical(app_with_fake_gitea):
view = r.json()
assert view["state"] == "super-draft"
assert "shared definition" in view["body"]
# §9.2: the auto-set proposer-owner survives the meta-repo round-trip
# — it's in the file's frontmatter on main after merge, not just
# in the pending-PR view above.
assert view["owners"] == ["alice"]
# The pending-ideas list no longer carries the merged proposal.
r = client.get("/api/proposals")
@@ -568,6 +577,37 @@ def test_anonymous_cannot_propose(app_with_fake_gitea):
assert r.status_code == 401
def test_proposer_is_auto_owner_request_payload_ignored(app_with_fake_gitea):
"""§9.2: the owners field on the new entry is always exactly
`[session.gitea_login]`. The propose endpoint never accepts an owner
from the client; a request payload that smuggles one in is ignored
by the Pydantic body model and the auto-set value lands instead.
"""
from fastapi.testclient import TestClient
app, _fake = app_with_fake_gitea
with TestClient(app) as client:
provision_user_row(user_id=11, login="alice", role="contributor")
sign_in_as(client, user_id=11, gitea_login="alice", display_name="Alice", role="contributor")
# Extra unknown fields like `owners` are dropped by the
# ProposeBody model; the session user is the only source of truth.
r = client.post("/api/rfcs/propose", json={
"title": "Spoof attempt",
"slug": "spoof-attempt",
"pitch": "p",
"tags": [],
"owners": ["mallory", "eve"],
"proposed_by": "mallory@test",
})
assert r.status_code == 200, r.text
pr_number = r.json()["pr_number"]
r = client.get(f"/api/proposals/{pr_number}")
assert r.status_code == 200, r.text
entry = r.json()["entry"]
assert entry["owners"] == ["alice"]
# proposed_by also comes from the session, never the body.
assert entry["proposed_by"] in ("alice@test", "alice")
def test_withdraw_by_proposer_works(app_with_fake_gitea):
from fastapi.testclient import TestClient
app, _fake = app_with_fake_gitea