Release 0.16.0: owner-only invite for per-RFC contribution + discussion (+ #21 Part C Amplitude wiring)
Wave 5 / Track B. Roadmap item #12. Folds in #21 Part C Amplitude
wiring inline (operator ask: "best practices from the very get-go").
RFC owners can invite specific users to one of two per-RFC roles:
contributor (open PRs + join discussion) or discussant (discussion
only). Non-invited users keep the v0.6.0 anonymous-read contract.
The per-RFC write gate layers on top of the existing
require_contributor gate; a super-draft with no owners yet falls
through to the platform-granted contract, preserving the
v0.6.0/v0.7.0/v0.8.0 contracts in their domains.
Backend: migration 018_rfc_invitations.sql (auto-applied — two
tables: rfc_invitations + rfc_collaborators); api_invitations.py
with five endpoints + transactional email; auth.py helpers
(is_rfc_owner / is_rfc_collaborator / can_discuss_rfc /
can_contribute_to_rfc / can_invite_to_rfc); api_discussion +
api_branches + api_prs gate composition; api_admin.py additive
rfc_invitations[] per user. 237 backend tests pass (18 new in
test_rfc_invitations_vertical.py).
Frontend: InvitationsModal.jsx (owner surface), AcceptInvitation.jsx
(/invitations/accept route), api.js helpers, RFCView.jsx
Invitations button, App.jsx route registration.
Amplitude wiring (inline, #21 Part C):
- INVITATION_SENT from InvitationsModal { rfc_slug, role_in_rfc }
- INVITATION_ACCEPTED from AcceptInvitation { rfc_slug, role_in_rfc }
- identify() BEFORE the accept event with properties: invited_at
(setOnce), last_invited_to_rfc, last_invite_role_in_rfc,
claim_method: 'rfc-invite'
- EVENTS taxonomy extended with INVITATION_SENT + INVITATION_ACCEPTED
No new secrets, no new overlay keys, no operator gesture beyond the
v0.15.0 overlay-set + restart. Frontend build verified green.
Subagent ν shipped the feature on feature/v0.16.0-owner-invite
(a51beec). Driver-side integration squash-merged into main,
hand-resolved VERSION + package.json + CHANGELOG (strict-descending
0.16.0 → 0.15.0), and added the inline Amplitude wiring.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -322,6 +322,48 @@ export async function resolveThread(slug, branch, threadId) {
|
||||
return jsonOrThrow(res)
|
||||
}
|
||||
|
||||
// ── v0.16.0: owner-only invite for per-RFC PR or PR-less discussion ──────
|
||||
//
|
||||
// roadmap item #12 / §6 / §10. The RFC's owner invites specific emails
|
||||
// to one of two per-RFC roles ('contributor' or 'discussant'); the
|
||||
// invitee accepts via the email-encoded token after signing in. The
|
||||
// platform-level grant remains the admin's decision (per item #6 /
|
||||
// v0.8.0) — these endpoints control per-RFC membership only.
|
||||
|
||||
export async function listRFCInvitations(slug) {
|
||||
return jsonOrThrow(await fetch(`/api/rfcs/${slug}/invitations`))
|
||||
}
|
||||
|
||||
export async function createRFCInvitation(slug, { inviteeEmail, roleInRFC }) {
|
||||
const res = await fetch(`/api/rfcs/${slug}/invitations`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ invitee_email: inviteeEmail, role_in_rfc: roleInRFC }),
|
||||
})
|
||||
return jsonOrThrow(res)
|
||||
}
|
||||
|
||||
export async function revokeRFCInvitation(slug, invitationId) {
|
||||
const res = await fetch(`/api/rfcs/${slug}/invitations/${invitationId}/revoke`, {
|
||||
method: 'POST',
|
||||
})
|
||||
return jsonOrThrow(res)
|
||||
}
|
||||
|
||||
export async function previewInvitation(token) {
|
||||
const params = new URLSearchParams({ token })
|
||||
return jsonOrThrow(await fetch(`/api/invitations/accept?${params}`))
|
||||
}
|
||||
|
||||
export async function acceptInvitation(token) {
|
||||
const res = await fetch('/api/invitations/accept', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ token }),
|
||||
})
|
||||
return jsonOrThrow(res)
|
||||
}
|
||||
|
||||
// ── v0.5.0: PR-less per-RFC discussion (§5 / §10) ────────────────────────
|
||||
//
|
||||
// The substrate is `threads.branch_name IS NULL` — the same threads
|
||||
|
||||
Reference in New Issue
Block a user