-- v0.29.0 / roadmap #28 Part 3 — offer-to-contribute-to-a-pending-RFC. -- -- When the #28 scanner matches a term in submitted PR/comment text to a -- *pending* RFC (a super-draft: accepted-as-an-idea but not yet graduated -- to an active RFC), the reader is offered a "ask to contribute" popover. -- Submitting it lands a row here AND a notification in each owner's §15 -- inbox; the owner can accept (which fires #12's owner-invite flow with -- the requester as the invitee) or decline (the requester is notified and -- the request closes). -- -- A "pending RFC" is scoped to a super-draft (cached_rfcs.state = -- 'super-draft'): it is in cached_rfcs (so the rfc_invitations FK that the -- accept path reuses resolves), it carries owners (owners_json) to route -- the request to, and it already has a discussion/contribution surface to -- open. Pre-merge idea PRs (not yet in cached_rfcs, no contribution -- surface) are deliberately out of scope — see backend/app/rfc_links.py. -- -- The request row is the persistent record; the inbox notification is the -- owner-facing actionable surface keyed back to it via `notification_id`. CREATE TABLE IF NOT EXISTS contribution_requests ( id INTEGER PRIMARY KEY AUTOINCREMENT, rfc_slug TEXT NOT NULL REFERENCES cached_rfcs(slug) ON DELETE CASCADE, requester_user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, -- The term in the PR/comment text that surfaced the offer (e.g. the -- super-draft's title). Carried for the owner's context line and the -- requester's "what RFC" anchor; not a foreign key. matched_term TEXT NOT NULL, -- The three contribute-request fields (§15 / #26 vocabulary). -- `who_i_am` and `why` are required; `use_case` mirrors #26's -- optional ground-truth field. who_i_am TEXT NOT NULL, why TEXT NOT NULL, use_case TEXT, status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'accepted', 'declined')), created_at TEXT NOT NULL DEFAULT (datetime('now')), decided_at TEXT, decided_by_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL, -- The rfc_invitations row minted on accept (the #12 reuse), and the -- owner-facing notification row that carries the Accept/Decline action. invitation_id INTEGER REFERENCES rfc_invitations(id) ON DELETE SET NULL, notification_id INTEGER REFERENCES notifications(id) ON DELETE SET NULL ); CREATE INDEX IF NOT EXISTS idx_contribution_requests_rfc ON contribution_requests(rfc_slug, status); CREATE INDEX IF NOT EXISTS idx_contribution_requests_requester ON contribution_requests(requester_user_id, status); -- At most one open (pending) request per (RFC, requester): a second ask -- while one is still pending is a 409, not a duplicate row. A decided -- request (accepted/declined) does not block a fresh ask later. CREATE UNIQUE INDEX IF NOT EXISTS idx_contribution_requests_one_open ON contribution_requests(rfc_slug, requester_user_id) WHERE status = 'pending';