feat(namespace): allow member overwrite of published skills via namespace setting - #797
feat(namespace): allow member overwrite of published skills via namespace setting#797felix021 wants to merge 2 commits into
Conversation
…pace setting Add per-namespace allowMemberOverwrite (default false). When off, publish owner-isolation behaves exactly as before. When on, any namespace member may publish a new version to a (namespace, slug) coordinate already published by another member; the version attaches to the original owner's Skill record (Skill.ownerId unchanged, SkillVersion.createdBy records the actual publisher). validateOnly dry-run applies the same rule. Setting is mutable via the existing namespace update API (OWNER/ADMIN only) and exposed in the web namespace edit dialog.
- Overwrite inherits the target skill's visibility: the requested visibility must not change another owner's skill reach (PRIVATE request no longer hides a published skill or moves its pointer to an unpublished version) - Pick the first non-archived published record as overwrite target; reject with error.skill.publish.archived when only an archived record carries the slug - validateOnly mirrors publish target selection: archived check and version-exists check now apply to the overwrite target - Fix NamespaceRequest 4-arg call sites, web test fixtures (tsc), duplicate version-save capture, and super-admin membership lookup assertion - Add tests: non-member rejection, archived-target rejection, dry-run version conflict on target
|
Thanks for the detailed implementation. The underlying problem is valid, but we should not merge the current namespace-wide toggle as-is. The current model grants every namespace member content-write access to every existing skill coordinate. That is broader than the recovery use case, and it is especially risky for PRIVATE skills because the current flow can update the target without the normal review path. Resolving the target from We prefer to separate two product capabilities:
These should be separate, reviewable slices rather than one PR implementing both. A smaller ownership-recovery PR would directly solve the “author left the team” case. Skill Maintainers can then follow as a separate feature when the permission and lifecycle matrix is approved. Please do not spend time rebasing the current implementation yet. First let us agree whether you would like to revise this PR toward the ownership-recovery slice or close it in favor of a new focused PR. |
FenjuFu
left a comment
There was a problem hiding this comment.
Read through this — the engineering is solid and the safety-conscious defaults are the right call. Flagging a blocker and a couple of points that I think need a maintainer decision rather than a plain +1 from me.
Blocker: DCO is failing. The sign-off email doesn't match the commit author/committer, so the DCO check is red. Please git commit --amend -s (or rebase with --signoff) so the Signed-off-by: line matches your author identity, then force-push.
What's good:
- Default-off (
allowMemberOverwrite=false) keeps the current owner-isolation path line-for-line, includingvalidateOnlydry-runs — no behavior change until a namespace OWNER/ADMIN opts in. - Visibility is inherited from the target, so a member's PRIVATE request can't hide an already-published skill. Good — that closes the obvious hijack vector.
- The version still attaches to the original owner (
Skill.ownerIdunchanged,SkillVersion.createdBy= actual publisher) and non-PRIVATE overwrites still go throughPENDING_REVIEW. Attribution + review flow stay intact. SUPER_ADMINgets no exemption — the toggle is the only gate. I read this as deliberate (a platform super-admin still can't silently overwrite another owner's coordinate unless the namespace opted in). Worth confirming that's the intended semantics, since it's the opposite of the usual "super-admin bypasses everything" default elsewhere in the codebase.
Points for a maintainer to own:
- This overlaps #732 (Skill Maintainer — full collaborative maintenance). You've scoped this deliberately smaller (one namespace-level opt-in, reusing the existing publish/review/audit paths, no new permission surface), which I think is the right increment — but whether this lands as the interim step vs. waiting for the #732 design should be a maintainer call.
- The overwrite path writes to the same
(namespace, slug)coordinate that's guarded by the publish-time unique constraints. Worth a sanity check that concurrent overwrite + the existing conflict handling don't interact badly (there's related concurrency work in flight on that publish path).
Because this touches the ownership/authorization boundary I'm not going to rubber-stamp an approve — leaving that to the maintainers. Happy to re-look once DCO is green.
Summary
allowMemberOverwritesetting (default false): namespace governance decides whether members may publish new versions to a(namespace, slug)coordinate already published by another membervalidateOnlydry-runsnamespace_member; SUPER_ADMIN gets no exemption) can publish a new version onto the coordinate:Skill.ownerIdunchanged,SkillVersion.createdByrecords the actual publisher)PENDING_REVIEWreview flowPUT /api/v1/namespaces/{slug}(OWNER/ADMIN only, reusesassertAdminOrOwner) and exposed as a toggle in the web namespace edit dialog (en/zh/ru)Motivation: publishing is owner-isolated today — the first publisher permanently owns a
(namespace, slug)coordinate and there is no owner transfer. When a skill author leaves or moves on, namespace admins cannot take over updates without deleting and recreating the skill, losing version history and download stats.Validation
Commands run:
createdByafter overwrite, visibility inheritance, non-member rejection, archived-target rejection, dry-run version-conflict parity); existing owner-isolation tests pass unchangedtsc --noEmitclean; full Vitest suite green (707 tests, 198 files)web/src/api/generated/schema.d.tsregenerated from a live server; diff contains only the newallowMemberOverwritefieldnameConflict; CLI--dry-runreports the same conflictowner_id= original author, new versioncreated_by= actual publisher, statusPENDING_REVIEW, effective after approvalnotMember; disabling the toggle restores rejectionRisk
V47(namespace.allow_member_overwrite BOOLEAN NOT NULL DEFAULT FALSE)Notes