문서 / 기능 붙이기
이 문서의 큐
Groups
회원들이 모임을 만들고 가입하는 구조입니다. 상회·팀·동아리처럼 이름은 달라도 필요한 것은 같습니다. 누가 개설할 수 있는지, 가입에 승인이 필요한지, 몇 명부터 정식으로 활동하는지, 그 안에 어떤 자리가 있는지.
새로온은 그 규칙을 코드가 아니라 데이터로 다룹니다. 사이트가 정책을 정의하면 회원들은 그 안에서 스스로 움직입니다.
두 평면을 먼저 구분하세요
이 기능은 주체가 둘이고, 어느 통로를 쓸지가 거기서 갈립니다.
| 하는 일 | 통로 | |
|---|---|---|
| 운영자 | 그룹의 종류와 규칙, 직위 어휘를 정한다 | CLI group · MCP saeroon_define_group_policy |
| 회원 | 그룹을 만들고, 가입하고, 직위를 주고받는다 | data-saeroon-group-* 속성 (회원 세션) |
운영자 키로 회원의 가입을 대신 처리하는 경로는 없습니다. 그래서 CLI 에는 "그룹 만들기" 커맨드가 없습니다 — 그것은 회원이 자기 세션으로 하는 일입니다. 운영자가 정하는 것은 회원들이 딛고 설 규칙입니다.
정책 정의하기
npx @saeroon/cli group apply ./policies.json --site <siteId>
{
"policies": [
{
"key": "guilds",
"name": "상회",
"requiresApproval": true,
"activationMinMembers": 3,
"allowMemberCreatedGroups": true,
"maxMembers": 30,
"formingExpiryDays": 7,
"roles": [
{ "roleKey": "leader", "label": "회장", "rankLevel": 10,
"isExclusive": true, "isLeadership": true, "isRequiredForActivation": true,
"defaultPermissions": ["group.edit", "members.approve", "members.manage-roles"] },
{ "roleKey": "vice", "label": "부회장", "rankLevel": 5,
"isLeadership": true, "defaultPermissions": ["members.approve"] },
{ "roleKey": "member", "label": "회원", "rankLevel": 0, "isDefault": true }
]
}
]
}
key 는 사이트 안에서 유일하며 한 번 정하면 바꿀 수 없습니다.
운영 규칙
| 필드 | 뜻 |
|---|---|
requiresApproval | 가입에 임원 승인이 필요한가 |
activationMinMembers | 결성 중인 그룹이 정식 활동을 시작하는 최소 인원 |
maxMembers | 인원 상한 |
formingExpiryDays | 결성 중인 그룹이 이 일수를 넘기면 자동 해산하고 멤버십을 회수한다 (1~365, 생략 = 자동 해산 없음) |
allowMultipleMemberships | 한 회원이 여러 그룹에 동시에 속할 수 있는가 |
allowMemberCreatedGroups | 일반 회원이 그룹을 개설할 수 있는가 (아니면 운영자만) |
requireInviteWhileForming | 결성 중에는 초대로만 들어올 수 있는가 |
actingLeaderEnabled | 대표 자리가 비었을 때 권한대행을 둘 수 있는가 |
activationMinMembers 가 maxMembers 보다 크면 그룹이 영원히 활성화되지 못합니다. CLI 가 요청 전에 거절합니다.
인원 세 축은 「생략 = 해제」입니다
activationMinMembers · maxMembers · formingExpiryDays 셋은 매니페스트가 곧 전체 상태입니다. 정책 갱신은 전체 교체라, 이 셋 중 하나를 매니페스트에서 빼면 「그 값을 그대로 둔다」가 아니라 그 규칙을 끕니다. 나머지 불리언 규칙과 attributes 는 반대로 생략하면 손대지 않습니다.
일부러 그렇게 두었습니다. 생략을 보존으로 읽으면 한 번 켠 만료를 매니페스트로는 다시 끌 수 없기 때문입니다. 그래서 손으로 쓴 파일을 적용하기 전에는 현재 값을 받아 두는 편이 안전합니다.
npx @saeroon/cli group export --site <siteId> -o policies.json # 현재 상태를 그대로 받는다
formingExpiryDays 를 비워 두면 정원을 못 채운 그룹이 결성 중 상태로 남습니다. allowMultipleMemberships 가 꺼져 있다면 그 그룹에 들어간 회원들은 다른 그룹에 가입하지 못한 채 자리를 계속 잡고 있게 됩니다.
직위와 서열
직위는 이름·서열·기본 권한을 가집니다. 여기서 가장 중요한 것이 서열(rankLevel)입니다.
그룹 해산과 배타 직위 배정은 권한이 아니라 서열로 판정합니다. 권한 위임으로 자기보다 높은 자리를 만들어 내는 통로를 막기 위해서입니다. 회장이 부회장에게 권한을 넘겨도, 부회장이 회장 자리를 자기에게 배정할 수는 없습니다.
| 플래그 | 뜻 |
|---|---|
rankLevel | 서열. 높을수록 상위 |
isExclusive | 그룹당 한 명만 앉는 자리 (회장 등) |
isLeadership | 임원. 카탈로그의 GroupLeader 쓰기 정책을 만족시킨다 |
isRequiredForActivation | 이 직위를 가진 사람이 있어야 그룹이 활성화된다 |
canCreateGroup | 이 직위 보유자가 새 그룹을 열 수 있다 |
isDefault | 새로 들어온 사람이 받는 직위. 정책당 하나 |
isExclusive 는 부분 유니크 인덱스로 강제되지만 기존 멤버십에 소급되지 않습니다. 값을 바꾼 시점 이후의 재배정부터 적용됩니다.
isDefault 를 둘 이상에 켜면 CLI 가 거절합니다. 서버는 마지막에 처리한 것만 남기므로, 배열 순서에 따라 결과가 달라지는 상태를 만들지 않기 위해서입니다.
권한은 대부분 강제되지 않습니다
직위의 defaultPermissions 에는 아무 문자열이나 넣을 수 있고 플랫폼은 그것을 저장하고 돌려줍니다. 사이트가 자기 화면에서 쓸 권한을 자유롭게 정의하라는 뜻입니다.
다만 서버가 실제로 막아 주는 권한은 열 개뿐이고, 그 열은 기본값이 정반대인 두 갈래로 나뉩니다.
기본 거부 (8) — 주기 전에는 아무도 가지지 않습니다.
| 키 | 서버가 막는 것 |
|---|---|
group.edit | 그룹 이름·설명·속성 변경 |
members.manage-roles | 그룹원 직위 변경 |
members.approve | 가입 신청 수락·반려, 초대 코드 발급 |
members.remove | 그룹원 내보내기 |
members.delegate | 권한 위임 (자기가 가진 것만 넘길 수 있음) |
marks.manage | 조율 표식 편집 |
posts.delete | 그룹 게시판에서 남의 글 삭제 (자기 글은 이 권한 없이도 지웁니다) |
notices.manage | 그룹 게시판에 공지 올리기 |
선언됐을 때만 강제 (2) — 아무 직위도 선언하지 않으면 활성 회원 전원이 할 수 있습니다.
| 키 | 서버가 막는 것 |
|---|---|
posts.write | 그룹 게시판에 글 쓰기 |
forms.submit | 그룹 스코프 폼에 제출 |
⚠️ 이 둘은 한 직위에만 달면 나머지 직위가 막힙니다. 회장에게만 posts.write 를 주는 것은 회장을 올려 주는 일이 아니라 나머지 전원의 글쓰기를 거두는 일입니다. 둘 다 안 쓰면 그 축을 안 쓰는 것으로 보고 전원 허용이 기본입니다.
왜 공지와 삭제가 기본 거부인가 — 둘 다 「내 것을 내는」 동작이 아니기 때문입니다. 공지는 그룹 전체 위에 올라가고, 삭제는 남의 글을 건드립니다. 이런 것을 완화 축에 두면 아무도 선언하지 않은 그룹에서 활성 회원 누구나 공지를 올리고 남의 글을 지우게 됩니다 — 완화 규칙이 정확히 반대로 작동합니다. 반면 글쓰기와 제출은 자기 것을 내는 동작이라, 직위 정의를 아직 안 채운 갓 만든 그룹이 잠기지 않도록 기본이 허용입니다.
이 열 밖의 문자열, 예를 들어 missions.coordinate 는 사이트 프론트가 스스로 해석하는 값이지 백엔드 보호가 아닙니다. 그것을 서버가 지켜 준다고 믿으면 값은 있는데 판정이 없는 게이트가 생깁니다. CLI 는 목록 밖 키를 거절하지 않고 경고로 알려 줍니다.
정책 조회
npx @saeroon/cli group list --site <siteId>
npx @saeroon/cli group get guilds --site <siteId> # 규칙 + 직위 + 속성 스키마
npx @saeroon/cli group export --site <siteId> -o policies.json
정리
정책은 삭제가 아니라 보관입니다.
npx @saeroon/cli group archive guilds --site <siteId>
보관하면 신규 그룹 개설만 막히고 기존 그룹은 계속 돕니다. 되돌리려면 group unarchive 를 씁니다.
직위 정의 삭제는 그 직위를 가진 활성 그룹원이 없을 때만 됩니다. 있으면 서버가 거절합니다 — 정의를 지우면 그 소속이 서열 0인 미아가 되기 때문입니다.
npx @saeroon/cli group role-delete guilds vice --site <siteId>
group apply 는 매니페스트에 없는 정책과 직위를 건드리지 않습니다. 정리하려면 --prune 을 붙이세요. 그때도 보유자가 있는 직위는 건너뛰고 결과에 따로 보고합니다.
회원 쪽 화면 붙이기
여기서부터는 회원 세션으로 도는 표면입니다. 회원 인증이 먼저 배선되어 있어야 합니다.
<!-- 정책 아래 그룹 목록 (공개 읽기) -->
<div data-saeroon-group-list data-policy="guilds">
<template>
<li>
<span data-saeroon-field="name"></span>
<small data-saeroon-field="memberCount"></small>
</li>
</template>
<p data-saeroon-empty>아직 개설된 그룹이 없습니다.</p>
</div>
<!-- 개설 (정책의 allowMemberCreatedGroups 가 켜져 있어야 함) -->
<form data-saeroon-group-create data-policy="guilds">
<input name="name" required />
<input name="description" />
<button type="submit">개설</button>
</form>
<!-- 가입 신청 · 명부 -->
<button data-saeroon-group-apply data-group-id="<groupId>">가입 신청</button>
<div data-saeroon-group-members data-group-id="<groupId>">
<template>
<li>
<span data-saeroon-field="displayName"></span>
<em data-saeroon-field="roleLabel"></em>
</li>
</template>
</div>
명부는 서열 내림차순으로 그려집니다. 승인제 정책이면 group-apply 가 신청을 남기고, 아니면 서버가 즉시 가입으로 처리합니다.
동작이 끝나면 이벤트가 올라옵니다. 사이트 코드가 이어받아 화면을 갱신할 수 있습니다.
saeroon:group-created—detail.groupsaeroon:group-applied—detail.groupId,detail.applicationsaeroon:group-marked—detail.targetMemberId,detail.mark
REST 로 직접 부르기
SDK 속성으로 안 되는 화면 — 승인 대기 목록, 직위 변경 다이얼로그, 권한 위임 — 은 API 를 직접 부릅니다. 아래는 전부 회원 평면입니다. 운영자 키(sk_live_)로는 닿지 않습니다.
모든 경로 앞에 이 접두사가 붙습니다.
https://api.saeroon.com/api/v1/hosting/public/sites/{siteId}
인증
회원 세션을 싣는 방법이 둘이고, 둘 다 유효합니다.
| 방법 | 헤더 | 언제 |
|---|---|---|
| 액세스 토큰 | Authorization: Bearer samt_… | 주 경로. SDK 가 쓰는 방식 |
| 세션 쿠키 | Cookie: site_member_session=… | 같은 도메인에서 부를 때 |
🔴 쿠키는 SameSite=Lax 라 크로스사이트 요청에 실리지 않습니다. 고객 사이트는 자기 도메인에서 api.saeroon.com 을 부르므로 그 요청에 쿠키가 없습니다. 그래서 SDK 의 주 인증 수단이 samt_ 액세스 토큰이고, 쿠키는 그 토큰을 재교환하는 refresh 용입니다.
쿠키만 보고 판단하는 코드는 로그인한 회원을 익명으로 취급합니다. 같은 도메인에서 시험하면 우연히 통과하고 배포하면 어긋나는 자리라, 직접 부를 때는 Authorization 헤더를 기본으로 잡으세요.
세션이 없으면 401 AUTH_REQUIRED 입니다.
정책과 그룹 읽기
| 메서드 | 경로 | 돌려주는 것 |
|---|---|---|
GET | /group-policies | 정책 목록 (규칙 + 직위 + 속성 스키마) |
GET | /group-policies/{policyKey} | 정책 하나 |
GET | /group-policies/{policyKey}/groups | 그 정책 아래 그룹 목록 |
GET | /groups/{groupId} | 그룹 하나 — 목록형 |
GET | /groups/{groupId}/detail | 그룹 하나 — 상세형 |
GET | /my-groups | 내 소속 전부 |
위 넷 — 정책 목록·정책 단건·그룹 목록·그룹 단건 — 은 로그인 없이 됩니다. detail 과 my-groups 는 세션이 필요합니다.
/groups/{groupId} 와 /groups/{groupId}/detail 은 다른 것을 줍니다. 이름이 비슷해 바꿔 써도 될 것 같지만 아닙니다. 방문자에게 보여 줄 것은 앞이고, 회원이 고치려고 읽는 것이 뒤입니다.
// GET /groups/{groupId} — GroupListItem
{ "id", "policyKey", "name", "description", "externalRef", "attributes", "memberCount", "status" }
// GET /groups/{groupId}/detail — GroupSummary
{ "id", "groupPolicyId", "policyKey", "name", "description", "externalRef", "status",
"creatorMemberId", "attributes", "memberCount", "pendingApplicationCount",
"createdAt", "dissolvedAt", "version" }
상세형에만 있는 것이 pendingApplicationCount 와 version 입니다. 그룹을 고칠 생각이면 상세형으로 읽어야 합니다 — 아래 「덮어쓰기 막기」가 그 version 을 씁니다.
GET /my-groups 는 소속마다 두 덩이를 짝지어 줍니다.
[ { "group": { …GroupSummary… }, "membership": { …GroupMemberView… } } ]
상태 어휘
status 는 넷 중 하나입니다.
| 값 | 뜻 |
|---|---|
Forming | 결성 중. 정원(activationMinMembers)을 아직 못 채웠다 |
PendingActivation | 인원은 찼고 활성화를 기다린다 |
Active | 정식 활동 중 |
Dissolved | 해산됨 |
🔑 소속으로 치는 상태는 Active 하나뿐입니다. GET /my-groups 는 결성 중인 소속도 함께 주므로, 「이 회원이 그룹에 속해 있는가」를 판정할 때는 group.status === 'Active' 를 명시적으로 거세요. 배열이 비었는지만 보면 결성 중인 그룹을 소속으로 셉니다.
목록과 단건 양쪽에 status 가 실리므로 디렉터리 화면에서 결성 중과 활성을 가를 수 있습니다.
그룹 만들고 고치기
| 메서드 | 경로 | 본문 |
|---|---|---|
POST | /groups | {policyKey, name, description?, externalRef?, attributes?, roleKey?} |
PUT | /groups/{groupId} | {name?, description?, externalRef?, attributes?} |
DELETE | /groups/{groupId} | — 해산(soft). 기록은 남습니다 |
POST | /groups/{groupId}/activate | {admitApplicationIds?: []} |
개설은 정책의 allowMemberCreatedGroups 가 켜져 있거나 직위에 canCreateGroup 이 있어야 합니다. 아니면 403 ROLE_CANNOT_CREATE.
활성화는 activationMinMembers 를 채우고 isRequiredForActivation 직위가 앉아 있어야 통과합니다. 모자라면 INSUFFICIENT_MEMBERS, 필수 직위가 비어 있으면 REQUIRED_ROLE_UNFILLED 입니다. admitApplicationIds 를 실으면 대기 중인 신청을 함께 받으면서 활성화합니다.
가입과 초대
| 메서드 | 경로 | 하는 일 |
|---|---|---|
POST | /groups/{groupId}/applications | 가입 신청 — {roleKey?, message?, inviteCode?} |
GET | /groups/{groupId}/applications | 신청 목록 (members.approve 권한) |
POST | /groups/{groupId}/applications/{applicationId}/approve | 수락 → GroupMemberView |
POST | /groups/{groupId}/applications/{applicationId}/reject | 반려 |
DELETE | /groups/{groupId}/applications/{applicationId} | 신청 철회 (신청한 본인) |
POST | /groups/{groupId}/invites | 초대 코드 발급 — {maxUses?, ttlHours?} |
DELETE | /groups/{groupId}/invites/{inviteId} | 초대 회수 |
POST | /groups/{groupId}/leave | 탈퇴 |
승인제(requiresApproval)면 신청으로 남고, 아니면 서버가 즉시 가입 처리합니다.
🔴 초대 목록을 돌려주는 경로는 아직 없습니다. 발급 응답({id, code, expiresAt, maxUses, usedCount, revokedAt})의 id 를 놓치면 그 초대를 회수할 수단이 없습니다. 발급 즉시 보관하세요.
자기 자신을 내보내려 하면 USE_LEAVE 로 막습니다 — 탈퇴는 /leave 입니다.
명부와 직위
| 메서드 | 경로 | 하는 일 |
|---|---|---|
GET | /groups/{groupId}/members | 명부 (서열 내림차순) |
PUT | /groups/{groupId}/members/{memberId}/role | 직위 변경 — {roleKey} |
PUT | /groups/{groupId}/members/{memberId}/permissions | 권한 전체 치환 — {permissions: []} |
DELETE | /groups/{groupId}/members/{memberId} | 내보내기 |
명부 원소가 GroupMemberView 입니다.
{ "membershipId", "siteMemberId", "displayName",
"roleKey", "roleLabel", "rankLevel", "isLeadership", "isActingLeader",
"effectivePermissions": [], "delegatedPermissions": [],
"status", "joinedAt" }
🔑 권한 배열이 둘인 것이 핵심입니다. effectivePermissions 는 직위 기본값과 개인 위임분을 합친 실효 권한이고, delegatedPermissions 는 그 회원에게 따로 넘긴 것만 담습니다. 위임 다이얼로그를 열 때 실효 권한을 채워 넣으면 직위 기본값까지 개인 위임으로 굳어집니다 — 그 화면이 읽을 것은 delegatedPermissions 입니다.
PUT …/permissions 는 전체 치환이라, 하나를 더하려면 현재 delegatedPermissions 에 얹은 새 배열을 보내야 합니다. 그리고 위임은 자기가 가진 것만 넘길 수 있습니다.
배타 직위(isExclusive)는 서열로 판정합니다. 자기보다 높은 자리를 자기에게 배정하려 하면 CANNOT_ASSIGN_EXCLUSIVE_ROLE, 이미 앉은 사람이 있는 자리면 CANNOT_CHANGE_EXCLUSIVE_ROLE 입니다.
표식
그룹 안에서 서로의 항목에 남기는 조율 표식은 Holdings 에 있습니다. 경로만 옮겨 적으면 이렇습니다.
| 메서드 | 경로 | 하는 일 |
|---|---|---|
GET | /groups/{groupId}/marks | 그룹 표식 전부 (그룹원 전원 조회 가능) |
PUT | /groups/{groupId}/marks | 설정·변경 |
POST | /groups/{groupId}/marks/clear | 1건 해제 (멱등) |
DELETE | /groups/{groupId}/marks | 전부 삭제 (라운드 초기화) |
공지 올리기
notices.manage 를 서버가 강제하는 지점은 게시판 쪽입니다.
POST /api/v1/hosting/public/sites/{siteId}/community/boards/{boardId}/posts
{ …, "groupId": "<groupId>", "isNotice": true }
그룹 스코프 게시판에서만 됩니다. 사이트 게시판에 보내면 NOTICE_NOT_ALLOWED, 권한이 없으면 GROUP_NOTICE_DENIED 입니다. 클라이언트에서 버튼을 감추는 것은 편의이고 판정은 서버가 합니다.
덮어쓰기 막기
PUT /groups/{groupId} 는 보낸 것이 전부인 표면입니다. 두 사람이 같은 상태를 읽고 각자 저장하면 뒤가 앞을 조용히 덮습니다. 이름 편집기와 속성 편집기가 같은 경로를 쓰므로 한 사람 안에서도 두 탭이면 벌어집니다.
막으려면 읽을 때 받은 판본을 되돌려 보냅니다.
GET /groups/{groupId}/detail → ETag: W/"g7"
PUT /groups/{groupId} ← If-Match: W/"g7" 맞지 않으면 412
- 본문의
version값과 같은 것이라 헤더가 불편하면 그쪽을 읽어도 됩니다 W/접두사나 따옴표를 떼고 돌려줘도 통과합니다- 412 = 「다시 읽어서 그 위에 다시 얹어라」 · 409 = 「입력을 고쳐라」
🔴 이건 옵트인입니다. If-Match 를 안 보내면 종전대로 마지막 쓰기가 이깁니다. 필수로 만들면 이미 도는 호출자가 그 자리에서 전부 412 로 죽기 때문에 그렇게 두었습니다. 즉 헤더를 안 보내고도 지켜지는 것이 아닙니다 — 보내야 지켜집니다.
에러 코드
응답 본문은 { "code": "...", "message": "..." } 입니다. 화면 분기는 message 가 아니라 code 로 하세요.
🔴 규칙 위반은 거의 전부 400 입니다. 「이미 소속」이나 「인원 초과」처럼 충돌로 읽히는 것도 409 가 아니라 400 으로 옵니다. 상태 코드로 갈래를 나누면 그 가지가 안 타니, code 로 분기하세요.
400 — 규칙 위반
| 코드 | 뜻 |
|---|---|
ALREADY_IN_GROUP | 이미 소속 (allowMultipleMemberships 꺼짐) |
GROUP_FULL | maxMembers 초과 |
GROUP_NOT_ACTIVE · GROUP_DISSOLVED | 활성이 아닌 그룹에 대한 동작 |
ALREADY_ACTIVE | 이미 활성인데 활성화 요청 |
INSUFFICIENT_MEMBERS | 활성화 인원 미달 |
REQUIRED_ROLE_UNFILLED | 활성화 필수 직위가 빔 |
REQUIRED_ROLE_NOT_UNIQUE | 배타 필수 직위가 둘 이상 |
NO_ROLE_DEFINITIONS · NO_DEFAULT_ROLE | 정책에 직위·기본 직위가 없음 |
ROLE_CANNOT_CREATE | 그 직위로는 개설 불가 |
CANNOT_ASSIGN_EXCLUSIVE_ROLE | 서열이 모자란 배타 직위 배정 |
CANNOT_CHANGE_EXCLUSIVE_ROLE | 이미 앉은 배타 직위 |
APPLICATION_NOT_PENDING | 이미 처리된 신청 |
INVITE_INVALID · INVITE_EXPIRED · INVITE_REVOKED · INVITE_EXHAUSTED | 초대 코드 문제 |
USE_LEAVE | 자기 자신 내보내기 → /leave 를 쓰세요 |
그 밖
| 코드 | 상태 | 뜻 |
|---|---|---|
AUTH_REQUIRED | 401 | 세션 없음 |
FORBIDDEN | 403 | 권한 없음 (일반) |
GROUP_NOTICE_DENIED | 403 | 공지 권한 없음 |
GROUP_WRITE_DENIED | 403 | 그룹 게시판 쓰기 권한 없음 |
GROUP_SUBMIT_DENIED | 403 | 그룹 폼 제출 권한 없음 |
NOT_FOUND | 404 | 없는 그룹·정책·회원 |
CONFLICT | 409 | 이미 접수된 가입 신청 |
| — | 412 | If-Match 불일치 (아래 「덮어쓰기 막기」) |
| — | 429 | 호출 한도 초과 |
호출 한도
IP 기준 고정창입니다. 초과하면 429 와 함께 retryAfterSeconds 가 오니 그 값을 따르세요.
| 대상 | 한도 |
|---|---|
읽기 (/group-policies*, /groups/{groupId}) | 분당 120회 |
| 쓰기·회원 세션 경로 (그 밖 전부) | 분당 10회 |
쓰기 한도가 낮으니 명부를 회원 수만큼 순회하며 부르는 화면은 만들지 마세요. 명부는 한 번에 옵니다.
아직 없는 것
우회로를 짜기 전에 여기부터 확인하세요. 아래는 저희가 닫을 목록입니다.
- 초대 목록 조회 (
GET /groups/{groupId}/invites) — 405 - 목록 상한·페이지네이션 계약 — 지금은 전량 반환
- 게시판 단건 조회, 그리고 게시판 slug 지정 (GUID 만 받습니다)
관련 문서
요약
회원들이 모임을 만들고 가입하는 구조입니다. 상회·팀·동아리처럼 이름은 달라도 필요한 것은 같습니다. 누가 개설할 수 있는지, 가입에 승인이 필요한지, 몇 명부터 정식으로 활동하는지, 그 안에 어떤 자리가 있는지.