cowork 게시판(board) 재기획 — 업로드·다운로드·로그/보고/공유 통합
한 줄 요약
다른 컴퓨터에서 ①파일 올리기 ②패키지 내려받기 ③기록 남기고 보고·공유하기가 제각각이라 자꾸 막힌다. 이걸 전부 "게시판을 만들어 글을 올린다" 라는 한 가지 쉬운 개념으로 합친다.게시판은 세 종류다.
- 자료 게시판: 파일을 올리고 내려받는 곳 (업로드/다운로드)
- 로그 게시판: 시스템이 자동으로 기록을 쌓고, 사람이 보고 댓글로 보고하고 공유하는 곳
- 이야기 게시판: 자유롭게 묻고 답하는 곳 (기존 지원/협업 흡수)
1. 왜 지금 안 되나 (감사 요약)
- 업로드는 되지만 multipart 미지원이라 브라우저
<input type=file>직접 전송 불가, base64 50MB(실제 ~37MB) 한계. - 패키지 다운로드는 로그인만 보면 통과, 등급(티어)별 차등이 없다. 세팅번들만
canAccessTier()를 쓴다. - 활동로그(activity.json)는 쌓이지만 집계·보고·공유·구독 API가 하나도 없다.
- 게시판이라는 개념·API 자체가 없다. 자유가입 계정도 없다.
support.js— 글+댓글 스레드, 상태, 증분 폴링(since) → 이야기/로그 게시판의 댓글 엔진collab.js— 결과물 업로드+커밋(provenance)+오너 승인 → 자료 게시판의 첨부 저장 엔진auth-tier.js—authSession(),canAccessTier(gradeLevel, tier),recordCommit(),accessibleTiers()→ 권한·첨부저장 그대로 사용- server.js
/api/uploadrev15 — base64→Buffer 바이너리 무손상,dest:'package'→ packages/ 적재 → 첨부 업로드 그대로 사용
2. (a) 게시판 데이터 모델
2.1 구조 (4계층: board → post → comment / attachment)
board 게시판 { id, key, name, kind, desc, perm, createdAt }
post 글 { id, boardId, author, gradeLevel, title, body, tags,
attachments[], status, pinned, auto, createdAt, updatedAt, comments[] }
comment 댓글 { author, role, gradeLevel, body, kind, at } // role: user|support|owner|system
attach 첨부 { name, size, sha256, commitId, path, tier, createdAt }
board.kind=자료(files) |로그(log) |이야기(talk)post.auto = true→ 시스템이 자동 기록한 글(로그 게시판). 사람이 댓글로 "보고" 추가.attachments[]는 collab의recordCommit()커밋ID로 저장 → 변조방지 provenance 유지.comment.kind=말(일반) |보고(report) |공유(share-link). 로그 게시판 보고/공유를 한 스레드에서 구분.- 저장:
data/boards.json(게시판 정의),data/board-posts.json(글+댓글). 첨부 실체는 기존data/commits/<id>/.
2.2 기본 게시판 시드 (부팅 시 자동 생성)
| key | name | kind | 용도 | 기존 흡수 |
|---|---|---|---|---|
pkg | 패키지 자료실 | 자료 | 패키지 업로드/다운로드 | /api/packages, /api/upload(dest:package) |
share | 결과물 공유 | 자료 | 작업 결과물 올리고 받기 | collab.js works.json |
activity | 활동 로그 | 로그 | 로그인·업로드·다운로드·커밋 자동기록 | activity.json |
notice | 공지 | 이야기 | 업데이트 공지 (읽기 전용 글) | updates.json |
qna | 묻고답하기 | 이야기 | 질문·에러리포트 | support.js tickets |
2.3 티어별 읽기·쓰기 권한 매트릭스
규칙 원칙: 등급 N(1~4)은 티어번호 ≥ N 인 자원에 접근(기존canAccessTier와 동일). 게스트(9)=공유자원만.| 등급 | 자료(읽기) | 자료(쓰기/업로드) | 로그(읽기) | 로그(보고댓글) | 이야기(읽기) | 이야기(쓰기) |
|---|---|---|---|---|---|---|
| 슈퍼관리자(0) | 전부 | 전부 | 전부 | O | 전부 | O |
| 티어1(1) | T1~T4 | 본인 글+T1~T4 | 본인 관련 | O | 전부 | O |
| 티어2(2) | T2~T4 | 본인 글+T2~T4 | 본인 관련 | O | 전부 | O |
| 티어3(3) | T3·T4 | 본인 글+T3·T4 | 본인 관련 | O | 전부 | O |
| 티어4(4) | T4 | 본인 글+T4 | 본인 관련 | O | 전부 | O |
| 게스트(9) | 공유자원만(notice·공개 share글) | X | X | X | 읽기만 | X(댓글 X) |
- 게시판/글마다
perm.tier(필요 등급) +perm.share(공개 여부) 두 값으로 판정. - 글의 첨부 다운로드는 그 글의
perm.tier로canAccessTier()게이팅 → 패키지 다운로드 등급 차등 문제 해결. - 로그 게시판은 자동기록(시스템) + 사람의 보고댓글만. 일반 사용자는 자기 관련 항목만, 슈퍼는 전체.
- 게스트는
perm.share===true(공유자원) 글만 읽기. 업로드·댓글 불가.
3. (b) REST API 명세
모든 쓰기/첨부 라우트는 tier.authSession(req) 필수(미인증 401, 게스트 쓰기 403).
다운로드는 Authorization: Bearer 또는 ?token=(브라우저 <a> 폴백) 둘 다 허용(기존 /api/download 패턴 그대로).
3.1 게시판
| 메서드 | 경로 | 권한 | 설명 |
|---|---|---|---|
| GET | /api/boards | 공개(게스트는 share/이야기만) | 게시판 목록 + kind + 내 접근여부 |
| GET | /api/board/<key> | 권한판정 | 게시판 1개 메타 |
| POST | /api/board | 슈퍼 | 게시판 생성 {key,name,kind,desc,perm} |
3.2 글(post)
| 메서드 | 경로 | 권한 | 설명 | ||
|---|---|---|---|---|---|
| GET | /api/board/<key>/posts?since=&limit=&tag= | 읽기권한 | 글 목록(증분 폴링 지원) | ||
| GET | /api/board/<key>/post/<id> | 읽기권한 | 글 + 댓글 스레드 | ||
| POST | /api/board/<key>/post | 쓰기권한 | 글 작성 {title,body,tags,perm:{tier,share}} | ||
| POST | /api/board/<key>/post/<id>/comment | 댓글권한 | 댓글 {body, kind:말\ | 보고\ | 공유, status?} |
| GET | /api/board/<key>/post/<id>/messages?since= | 읽기권한 | 댓글 증분(support.messagesSince 재사용) |
3.3 첨부 (업로드/다운로드 — 인증게이팅)
| 메서드 | 경로 | 권한 | 설명 |
|---|---|---|---|
| POST | /api/board/<key>/post/<id>/attach | 쓰기권한 | 첨부 추가 {name, content(base64), final?} → recordCommit 저장 |
| POST | /api/board/<key>/post/<id>/attach/chunk | 쓰기권한 | 청크 업로드 {name,seq,total,chunk(base64),uploadId} |
| GET | /api/board/<key>/post/<id>/attach/<name> | 첨부 다운로드 | 글 perm.tier로 canAccessTier 게이팅, 스트리밍, Bearer/?token= |
authSession→ 미인증 401- 글
perm.tier와 사용자gradeLevel로canAccessTier(gradeLevel, post.perm.tier)→ 실패 403 - 게스트(9)는
perm.share===true인 글만 허용 - 통과 시
data/commits/<commitId>/에서 basename 강제(경로탈출 차단) 스트리밍 sync-chain에pull-board:<postId>기록(다운로드 추적)- 한글 파일명:
filename="ascii폴백"; filename*=UTF-8''<percent>(Node ERR_INVALID_CHAR 회피)
3.4 로그 게시판 (자동기록 + 보고 + 공유)
| 메서드 | 경로 | 권한 | 설명 |
|---|---|---|---|
| (내부) | boardAutoLog(event) | 시스템 | 로그인·업로드·다운로드·커밋·학습 발생 시 activity 게시판에 auto 글 생성/누적 |
| GET | /api/board/activity/posts?since=&account=&from=&to= | 읽기권한 | 집계·필터 조회(기간·계정) — 감사 지적 "집계 없음" 해결 |
| POST | /api/board/activity/post/<id>/comment {kind:'보고'} | 보고권한 | 사람이 로그에 보고 댓글 |
| POST | /api/board/activity/post/<id>/comment {kind:'공유', to} | 공유권한 | 공유 링크 생성(docs.crowny.org 보관, collab.approveWork 패턴 재사용) |
| GET | /api/board/activity/report?from=&to=&account= | 슈퍼/본인 | 기간 보고서(json/md) 내보내기 |
/api/feed(슈퍼 통합 피드)는 로그 게시판의 슈퍼 뷰로 유지.4. (c) 셀코어 룰 — 게시판 권한판정 (4상)
if-else 대신 셀코어 룰 저장소에 누적해 진화/재사용한다(감사 지적: 4상 7곳 전부 if-else).
판정 결과 4상:
- 티(예스/실행): 즉시 허용 — 등급·티어 충족, 자기자원
- 옴(연구·검색 후 예스): 조건부 — 공유자원 확인 후 허용(게스트가 share글 읽기 등)
- 타(노): 거부 — 등급 부족, 게스트 쓰기
- 음(상급 이관): 오너 동의 필요 — 공개공유/구축/특허 요청, 새 게시판 생성
javascript가져오기 "셀코어"
함수 권한룰초기화() {
// 읽기 허용: 티어 충족
변수 r1 = 룰생성("게시판_읽기_허용", 10)
r1 = 룰조건추가(r1, "동작", "==", "읽기")
r1 = 룰조건추가(r1, "티어충족", "==", 1)
r1 = 룰변경추가(r1, "판정", "티")
룰저장(r1)
// 게스트는 공유자원만 → 옴(공유 확인 후 허용)
변수 r2 = 룰생성("게스트_공유자원", 20)
r2 = 룰조건추가(r2, "등급", "==", 9)
r2 = 룰조건추가(r2, "공유", "==", 1)
r2 = 룰변경추가(r2, "판정", "옴")
룰저장(r2)
// 쓰기/업로드: 티어 부족 → 타(거부)
변수 r3 = 룰생성("쓰기_티어부족", 15)
r3 = 룰조건추가(r3, "동작", "==", "쓰기")
r3 = 룰조건추가(r3, "티어충족", "==", 0)
r3 = 룰변경추가(r3, "판정", "타")
룰저장(r3)
// 게스트 쓰기 → 타
변수 r4 = 룰생성("게스트_쓰기_거부", 25)
r4 = 룰조건추가(r4, "등급", "==", 9)
r4 = 룰조건추가(r4, "동작", "==", "쓰기")
r4 = 룰변경추가(r4, "판정", "타")
룰저장(r4)
// 공개공유/구축/특허/게시판생성 → 음(오너 이관)
변수 r5 = 룰생성("오너동의_필요", 30)
r5 = 룰조건추가(r5, "오너요청", "==", 1)
r5 = 룰변경추가(r5, "판정", "음")
룰저장(r5)
}
4상 → HTTP 매핑: 티=200 실행 / 옴=공유플래그 확인 후 200 또는 403 / 타=403 / 음=needs-owner 큐 적재 + support.refreshPending() (오너 감지).
5. (d) 동반 .한선 스켈레톤 — 게시판.한선 함수 시그니처
위치: /Users/ef/crowny-cowork/게시판.한선 (hanseonc_high 컴파일 대상).
규칙: 배열 인덱스 읽기는 배열[i] (설정()은 쓰기 전용 — 감사 지적 L57 오용 회피).
javascript가져오기 "셀코어"
// ── 권한 판정 (4상) ──
// 반환: "티"=허용 "옴"=공유확인후허용 "타"=거부 "음"=오너이관
함수 티어충족(등급, 필요티어) {
만약 (등급 == 0) { 반환 1 } // 슈퍼
만약 (등급 >= 1 그리고 등급 <= 4) {
만약 (필요티어 >= 등급) { 반환 1 }
반환 0
}
반환 0 // 게스트
}
함수 권한판정(등급, 동작, 필요티어, 공유, 오너요청) {
만약 (오너요청 == 1) { 반환 "음" }
만약 (등급 == 9) {
만약 (동작 == "읽기" 그리고 공유 == 1) { 반환 "옴" }
반환 "타"
}
만약 (티어충족(등급, 필요티어) == 0) { 반환 "타" }
반환 "티"
}
// ── 게시판 종류별 동작 결정 ──
함수 게시판종류처리(종류) {
만약 (종류 == "자료") { 반환 "첨부저장+다운로드게이팅" }
만약 (종류 == "로그") { 반환 "자동기록+보고댓글+공유" }
만약 (종류 == "이야기") { 반환 "글+댓글스레드" }
반환 "알수없음"
}
// ── 첨부 청크 합치기 결정 ──
함수 청크완료여부(받은수, 전체수) {
만약 (받은수 >= 전체수) { 반환 참 }
반환 거짓
}
// ── 로그 보고/공유 댓글 분류 (4상) ──
함수 로그댓글분류(종류, 공개) {
만약 (종류 == "공유" 그리고 공개 == 1) { 반환 "음" } // 공개공유=오너동의
만약 (종류 == "공유") { 반환 "옴" } // 내부공유=확인후
만약 (종류 == "보고") { 반환 "티" } // 보고=즉시기록
반환 "티"
}
// ── 다운로드 게이팅 ──
함수 다운로드허용(등급, 글티어, 글공유) {
변수 결과 = 권한판정(등급, "읽기", 글티어, 글공유, 0)
만약 (결과 == "티") { 반환 참 }
만약 (결과 == "옴" 그리고 글공유 == 1) { 반환 참 }
반환 거짓
}
함수 자가검사() {
출력값("권한판정 게스트공유읽기: " + 권한판정(9, "읽기", 4, 1, 0)) // 옴
출력값("권한판정 티어3가T4: " + 권한판정(3, "읽기", 4, 0, 0)) // 티
출력값("권한판정 티어3가T2: " + 권한판정(3, "읽기", 2, 0, 0)) // 타
출력값("권한판정 오너요청: " + 권한판정(1, "쓰기", 4, 0, 1)) // 음
출력값("다운로드 게스트비공유: " + 문자열변환(다운로드허용(9, 4, 0))) // 거짓
}
자가검사()
JS 측 board.js는 이 4상 판정을 그대로 미러링하고, 셀코어 룰 저장소(~/Downloads/CrownyTVM/std/규칙저장소.dat)에 누적해 재사용.
6. 기존 자산 재활용 매핑 (새로 만들지 않음)
| 새 개념 | 재활용 대상 | 방법 |
|---|---|---|
| 글+댓글 스레드 | support.js messages/messagesSince/addComment/lastRole | type에 boardId 추가, 그대로 호출 |
| 첨부 저장(provenance) | auth-tier recordCommit/commitTarball/listCommits | 첨부=커밋, attachments[]에 commitId |
| 업로드 바이너리 무손상 | server.js /api/upload rev15 (base64→Buffer) | 청크 누적 후 동일 경로 |
| 결과물 공유+오너승인 | collab.js submitWork/approveWork/levels | share 게시판 = works 뷰 |
| 자동 로그 | activity.json + syncToDocsServer | boardAutoLog가 activity 게시판 글로 래핑 |
| 다운로드 게이팅 | server.js /api/download (Bearer/?token=, basename, 한글 CD) | 글 perm.tier로 canAccessTier 추가 |
| 권한 등급 | auth-tier canAccessTier/accessibleTiers/authSession | 그대로 |
7. multipart 미지원 대안 — base64 + 청크 업로드
브라우저<input type=file> multipart 파싱이 없고 parseBody 50MB 컷(실제 ~37MB)이라 큰 파일이 막힌다. 해결:- 클라이언트가 파일을 2MB 청크로 잘라 base64 인코딩.
- 각 청크를
POST /api/board/<key>/post/<id>/attach/chunk {name, uploadId, seq, total, chunk}로 순차 전송(청크당 ~2.7MB < 50MB 안전). - 서버는
data/uploads-tmp/<uploadId>/<seq>에 append 저장. 청크완료여부(받은수, 전체수)가 참이면 순서대로 병합 →recordCommit()로 첨부 확정 → tmp 정리.- 무결성: 최종 sha256을 클라이언트가 보내 서버 병합본과 대조(불일치=재전송).
8. 윈도우 접속 함정 반영 (UX 감사 연계)
- 모든 클라이언트 URL은 포트 없는
https://cowork.crowny.org(게이트웨이 443).:9720직접 접속 금지. - 첨부 다운로드
<a>는?token=폴백 + clipboard 폴백(execCommand) 동반. - 한글 첨부명은 RFC5987
filename*=UTF-8''우선 파싱.
9. 관련 파일 경로
- 신규(예정):
/Users/ef/crowny-cowork/board.js,/Users/ef/crowny-cowork/게시판.한선 - 데이터(예정):
data/boards.json,data/board-posts.json,data/uploads-tmp/ - 재활용:
support.js,collab.js,auth-tier.js,server.js(/api/upload, /api/download),data/commits/ - 감사 근거:
2026-06-27-cowork-감사-백엔드.md,2026-06-27-cowork-감사-UX.md,2026-06-27-cowork-감사-한선씨셀코어.md
10. 잔여 이슈
- 자유가입 라우트 신설 여부(게스트 self-signup) = 오너 결정 필요 — 현재는 슈퍼만 계정 생성.
- 청크 업로드 동시성(같은 uploadId 병렬) 잠금 처리 필요.
- 게시판.한선 런타임 검증(인증.한선처럼 타임아웃 가능성 — 자가검사() 비대화형 확인).
- updates.json rev+1(게시판 도입 시 rev16) 및 changelog 항목 추가.
- 셀코어 룰 저장소 실제 누적/매칭 E2E 테스트.