← 목록
기타 2026-06-27 16KB 읽기 17분

cowork 게시판(board) 재기획 — 업로드·다운로드·로그/보고/공유 통합

한 줄 요약

다른 컴퓨터에서 ①파일 올리기 ②패키지 내려받기 ③기록 남기고 보고·공유하기가 제각각이라 자꾸 막힌다. 이걸 전부 "게시판을 만들어 글을 올린다" 라는 한 가지 쉬운 개념으로 합친다.

게시판은 세 종류다.

  • 자료 게시판: 파일을 올리고 내려받는 곳 (업로드/다운로드)
  • 로그 게시판: 시스템이 자동으로 기록을 쌓고, 사람이 보고 댓글로 보고하고 공유하는 곳
  • 이야기 게시판: 자유롭게 묻고 답하는 곳 (기존 지원/협업 흡수)

1. 왜 지금 안 되나 (감사 요약)

  • 업로드는 되지만 multipart 미지원이라 브라우저 <input type=file> 직접 전송 불가, base64 50MB(실제 ~37MB) 한계.
  • 패키지 다운로드는 로그인만 보면 통과, 등급(티어)별 차등이 없다. 세팅번들만 canAccessTier()를 쓴다.
  • 활동로그(activity.json)는 쌓이지만 집계·보고·공유·구독 API가 하나도 없다.
  • 게시판이라는 개념·API 자체가 없다. 자유가입 계정도 없다.
재활용 가능한 뼈대(이미 있음):
  • support.js — 글+댓글 스레드, 상태, 증분 폴링(since) → 이야기/로그 게시판의 댓글 엔진
  • collab.js — 결과물 업로드+커밋(provenance)+오너 승인 → 자료 게시판의 첨부 저장 엔진
  • auth-tier.jsauthSession(), canAccessTier(gradeLevel, tier), recordCommit(), accessibleTiers()권한·첨부저장 그대로 사용
  • server.js /api/upload rev15 — 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 기본 게시판 시드 (부팅 시 자동 생성)

keynamekind용도기존 흡수
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글)XXX읽기만X(댓글 X)
핵심 규칙:
  • 게시판/글마다 perm.tier(필요 등급) + perm.share(공개 여부) 두 값으로 판정.
  • 글의 첨부 다운로드는 그 글의 perm.tiercanAccessTier() 게이팅 → 패키지 다운로드 등급 차등 문제 해결.
  • 로그 게시판은 자동기록(시스템) + 사람의 보고댓글만. 일반 사용자는 자기 관련 항목만, 슈퍼는 전체.
  • 게스트는 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=
다운로드 게이팅 흐름(자료 게시판):
  1. authSession → 미인증 401
  2. perm.tier 와 사용자 gradeLevelcanAccessTier(gradeLevel, post.perm.tier) → 실패 403
  3. 게스트(9)는 perm.share===true 인 글만 허용
  4. 통과 시 data/commits/<commitId>/ 에서 basename 강제(경로탈출 차단) 스트리밍
  5. sync-chainpull-board:<postId> 기록(다운로드 추적)
  6. 한글 파일명: 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/lastRoletype에 boardId 추가, 그대로 호출
첨부 저장(provenance)auth-tier recordCommit/commitTarball/listCommits첨부=커밋, attachments[]에 commitId
업로드 바이너리 무손상server.js /api/upload rev15 (base64→Buffer)청크 누적 후 동일 경로
결과물 공유+오너승인collab.js submitWork/approveWork/levelsshare 게시판 = works 뷰
자동 로그activity.json + syncToDocsServerboardAutoLog가 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)이라 큰 파일이 막힌다. 해결:

  1. 클라이언트가 파일을 2MB 청크로 잘라 base64 인코딩.
  2. 각 청크를 POST /api/board/<key>/post/<id>/attach/chunk {name, uploadId, seq, total, chunk} 로 순차 전송(청크당 ~2.7MB < 50MB 안전).
  3. 서버는 data/uploads-tmp/<uploadId>/<seq> 에 append 저장.
  4. 청크완료여부(받은수, 전체수) 가 참이면 순서대로 병합 → recordCommit() 로 첨부 확정 → tmp 정리.
  5. 무결성: 최종 sha256을 클라이언트가 보내 서버 병합본과 대조(불일치=재전송).
장점: parseBody·인증·바이너리 무손상 로직 전부 재사용, 윈도우 방화벽 영향 없음(전부 https 443 경유). (향후 여유 시 multipart 파서를 추가하면 단일요청 업로드도 가능하나, 현 구조는 청크로 충분.)


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 테스트.