87279fc54510e6ded3f302ced548c6b0e9c8f1fc
6 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1dab24eef0 |
Merge feat/multi-project (§22 M1+M2) into main
Integrates the multi-project work so far — the §22 design drafts, M1 (the schema spine), and M2 (project-scoped authorization + the §22.7 resolver) — on top of the v0.32.0 retire/§13 changes that landed on main meanwhile. Integration decisions: - Renumbered the M1 projects migration 025_projects.sql -> 026_projects.sql. v0.32.0 shipped 025_retired_state.sql, which rebuilds cached_rfcs and predates the project_id column; running projects *after* the retire rebuild is required so project_id survives on a fresh database (the runner applies *.sql in filename order). Comment/doc references bumped to match. - Resolved the get_rfc conflict in api.py by composing both gates: compute the viewer once, apply the §22.5 visibility gate, then v0.32.0's §13.7 retired-entry owner-only check. - api_graduation.py / api_discussion.py auto-merged cleanly (M2's threaded viewer/visibility gate coexists with the new retire endpoints + retired state). Full suite 401 passed on a fresh DB. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
503689bf1a |
feat(projects): M2 — project-scoped authorization + the §22.7 resolver (§22)
Builds the three-tier authorization resolver on M1's schema spine: the most-permissive union of deployment role (§6.1), project role (§22.6), and per-RFC authority (§6.3/§12), with the §22.5 visibility gate subtractive on top (§22.7). Pure app-layer — no migration (M1 shipped the tables), no behavior change on the public default project. Verifiable on the single default project by flipping its visibility and granting/revoking roles. - app/auth.py: the §22.7 resolver primitives — project_visibility, project_member_role, project_of_rfc, is_project_superuser, can_read_project, effective write/discuss standing (can_contribute_in_project / can_discuss_in_project), require_project_readable, visible_project_ids. The three per-RFC capability helpers (can_discuss_rfc / can_contribute_to_rfc / can_invite_to_rfc) now compose all three tiers, so the ~20 call sites inherit M2 unchanged. - Read pass: the §22.5 visibility gate (404 to non-members) threaded into every RFC-resolution helper across api.py / api_branches / api_prs / api_graduation / api_discussion / api_contributions / api_invitations, plus the catalog + proposals listings filtered by visible_project_ids. - Write pass: the deep gates (branch read/contribute/owner, PR merge/withdraw/ edit, graduation, discussion resolve) fold in project_admin via is_project_superuser; the "any-contributor" branch mode routes through can_contribute_in_project; propose + claim gate on project contribute standing. Deployment-level surfaces (admin idea-PR merge/decline, account notification-mute) stay deployment-scoped. - Operator decisions: implicit-on-public (a granted deployment contributor keeps its pre-M2 write baseline on a public project, no membership row, so the N=1 case stays whole) and preserve-curation (that baseline does not override per-RFC owner curation — only an explicit project grant or a deployment owner/admin does), keeping the v0.16.0 per-RFC invite contract intact on public. - tests: test_multi_project_authz_vertical.py — 9 vertical assertions on the resolver tiers, public-unchanged regression, the gated 404 read gate, the gated contribution gate, union + subtractive visibility, and revocation. Full suite 390 passed. - docs: mark Part C M2 "(landed)" with the two operator decisions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
49ba06e0c2 |
§13: make graduation's RFC number optional; add retire (soft delete)
Optional number (§13.2/§13.3): GraduateBody.rfc_id is now optional.
A blank/absent id graduates to `active` with `id: null`, leaving the
slug as the canonical identifier (§2.3). /graduate/check treats a blank
id as valid (ok:true); /graduate only validates the RFC-NNNN regex +
collision check when a number is supplied. The Graduate dialog allows an
empty number and renders number-less entries by slug (no RFC-undefined).
Retire (§3, §3.1, §13.7): new `retired` soft-delete state. An RFC's own
owners (frontmatter) and site `owner`-role holders — not app admins —
retire via POST /api/rfcs/<slug>/retire, an auto-merged meta-repo flip
PR (graduation machinery reused). Retired entries leave every browsing
surface: catalog, get_rfc (404 except site owner), discussion/branch
reads. Un-retire is site-owner-only (POST .../unretire), discoverable
via the owner-gated GET /api/admin/retired-rfcs ("Retired" admin tab).
Migration 025 widens the cached_rfcs.state CHECK to include 'retired'
(table rebuild, all columns preserved).
Tests: graduation no-number cases (active+null id by slug; check accepts
blank) added to test_graduation_vertical.py; new test_retire_vertical.py
covers perms (owner/site-owner allowed, admin/contributor 403),
catalog/read exclusion, and a graduate→retire→unretire round-trip. Full
backend suite 386 passing; frontend builds clean. SPEC §3/§3.1/§13
updated; CHANGELOG + VERSION → 0.33.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
e794523079 |
v0.26.0: auto-link referenced RFCs in PR text + comments (roadmap #28 Part 1)
Part 1 of item #28: references to existing accepted RFCs inside PR descriptions and comment text now render as inline links to the referenced RFC. Parts 2 (offer-to-create) and 3 (offer-to-contribute- to-pending) are deliberately deferred — Part 1 ships first as the easy win, per the roadmap row. Shipped in parallel with the v0.25.0 security-hardening session; this took the next free version slot (0.26.0) per the roadmap's "claims the next available version number" rule. Expect a top-of-file CHANGELOG/VERSION merge with 0.25.0 — distinct concerns, trivial to resolve. Backend: - rfc_links.py (new): builds a term index from the live accepted (state='active') RFC corpus and segments plain text into text / rfc-link segments. Conservative matching — links only rfc_id tokens (RFC-0001), multi-word titles (Open Human Model), and hyphenated slugs (open-human-model); a single common-word title/slug is NOT linked (would turn every prose "human" into a link). Case-insensitive, word-boundary-anchored, longest-match-wins, self-reference suppressed. - api_prs.py get_pr(): enriches the PR description (description_segments) and every PR comment (text_segments). - api_discussion.py: enriches PR-less discussion comments (text_segments). Read-time, not submit-time: the roadmap says "at submit time" but the intent it names is "not as live compose preview", which read-time honors. Chosen for correctness (links track the live active set — newly-accepted RFCs start linking, withdrawn ones stop), zero migration, and cheapness (small cache-resident corpus). Recorded as a §19.3-rule-2 note in the session transcript. Frontend: - LinkedText.jsx (new): maps backend segments onto React text nodes + anchors. No dangerouslySetInnerHTML — XSS-safe by construction, independent of any HTML-sanitization layer. Falls back to raw text when segments are absent. - PRView.jsx: description + PR conversation comment bodies render via LinkedText. - RFCDiscussionPanel.jsx: discussion comment bodies render via LinkedText. - App.css: .rfc-autolink (subtle accent + dotted underline, tokenized). Tests: 12 new (test_rfc_links_vertical.py) — 9 scanner units + 3 end-to-end (PR description / review comment / discussion comment all surface *_segments; self-reference suppression). Full suite 363 green; frontend builds clean. No upgrade steps: additive, no migration, no secret, no config. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ee4925b6ac |
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
(
|
||
|
|
c92730a737 |
Release 0.5.0: PR-less per-RFC discussion (contribution still requires PR)
Roadmap item #3. An RFC's main view now carries a discussion surface distinct from PR comments and from branch chat. The substrate is the existing threads/thread_messages tables — rows with branch_name IS NULL scope to the RFC's main view; the schema already permitted that shape, v0.5.0 is the first build to write it. Five new endpoints under /api/rfcs/<slug>/discussion/..., a new RFCDiscussionPanel right-column component used when branchParam === main, SPEC §10.10 settling discussion-vs-contribution, and §17 listing the new routes. Notification routing reuses the existing chat_message_in_participated_thread / chat_reply_to_my_message event kinds with branch_name=null on the fan-out row; a distinct event_kind is a §19.2 candidate. Anonymous viewers can read; writes require contributor — v0.6.0's item #4 will harden adjacent gates. No schema migration; minor bump, no operator action required beyond rebuild and restart. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |