← 모듈 센터

02. 모듈 사이트 구축 명세

작성 2026-10-04 · 따르는 표준: 01_모듈_표준.md (표준 1) 작업 장소: 라이센스 서버 저장소 hnbbos/isolo_lic (lic.isolo.co.kr)

쉬운 요약


0. Claude 요청문 (isolo_lic 저장소에서 붙여 넣기)

lic.isolo.co.kr(isolo_lic) 에 「모듈」 메뉴를 추가해 줘.
명세: docs/module-platform/02_모듈사이트_구축.md, 지킬 약속: 01_모듈_표준.md (이 두 파일을 먼저 끝까지 읽어).

[순서]
1. 먼저 지금 구조를 읽고 보고해: index.js 의 표 만드는 방식(CREATE TABLE IF NOT EXISTS)과 PG 경로(db.js shim),
   requireAdmin / requireRoleAdmin / resolveAuth(api_keys scope), auditLicense, lib/vendor/vendor-sign.js(signPayload·canonical),
   public/admin.html 의 탭 방식(data-tab / switchTab / #tab-*) 과 public/admin/js/pages.js.
2. 명세 §13 의 「1단계」만 만든다. 2·3단계는 하지 마.
   화면은 docs/module-platform/design/모듈센터_시안.html 과 design/screens/*.png 를 그대로 따른다(§4.0 디자인 표).
3. §12 의 검사 항목 중 1단계 해당 것을 scripts/ 아래 검사 스크립트로 만들고 MySQL·PG 두 경로에서 돌려 결과(통과/실패 수)를 보고해.

[지킬 것]
- 기존 기능(라이센스 발급·검증·업데이트·.lic)은 동작이 바뀌면 안 된다. 기존 e2e 검사(scripts/e2e-test.js)도 그대로 통과해야 한다.
- 서명 개인키는 지금처럼 다룬다(VENDOR-KEY-RUNBOOK.md). 키가 없으면 발행을 막고(503), 서명 없는 발행은 절대 만들지 않는다.
- 안내서(INTEGRATION.md)를 화면에 보여 줄 때는 반드시 정화(XSS)한다.
- ON DUPLICATE KEY UPDATE 는 쓰지 않는다(PG shim 미지원) — vendor_instances 처럼 SELECT 후 UPDATE/INSERT.
- 운영 서버 배포는 하지 말고, 바뀐 파일·새 표·새 API·검사 결과를 보고해.

1. 목표와 하지 않는 것

목표

  1. 모듈 목록과 버전(검사 중·발행·회수)을 한곳에서 봅니다.
  2. 어느 프로그램이 어느 모듈의 몇 번을 쓰는지, 누가 뒤처졌는지 봅니다.
  3. 새 버전을 모든 사용처의 검사를 통과해야 발행하고, 발행은 서명합니다.
  4. 사용처에 「새 번호 나옴 / 이 번호 회수됨」을 알립니다.
  5. 고객 서버로 보낼 묶음을 만들어 서명하고, 기존 업데이트 기능에 연결합니다.
  6. 나혼자워크 패키지 센터에 고객용 목록(카탈로그)을 줍니다.
  7. 기본 모듈(라이센스 보호 장치·설치기·업데이트 도우미)을 지정하고, 단독 설치 제품이 빠뜨리지 못하게 막습니다.

하지 않는 것


2. 왜 lic.isolo.co.kr 안에 두나 — 그대로 쓰는 것

이미 있는 것 (isolo_lic)모듈 사이트에서 쓰는 곳
products 표 (product_code)사용처 = 제품. 사용처 이름은 제품 코드로 통일
api_keys 연동 키 + resolveAuth (scope read/full)CI·도구 인증. scope modules를 새로 추가(§9)
requireAdmin / requireRoleAdmin (x-admin-key)화면·관리 API. 발행·회수·묶음은 requireRoleAdmin
auditLicense() → license_audit_log모든 쓰기를 action: 'module.*'로 기록
lib/vendor/vendor-sign.js signPayload()·canonical + 키링발행 기록·묶음 서명
lib/vendor/license-guard.js publicKeysB64()공개키 목록 제공
update_packages · /api/update/check · /api/update/report묶음을 업데이트 패키지로 연결
알림 설정(admin_settings의 notify_*, notification_logs)발행·회수 알림
public/admin.html 탭 방식 (<a data-tab="…" onclick="switchTab('…')"> + <div class="tab-page" id="tab-…">), public/admin/js/pages.js「모듈」 탭 추가
db.js MySQL/PG 겸용 shim새 표도 두 경로에서 돌아야 함

3. 전체 흐름

[개발자]  isolo_modules 에서 고침 → modctl lint·test → 번호 올림 → 태그 license-guard-v1.5.0 push
   │
[GitHub Actions (isolo_modules)]
   │  단위·약속 검사 → npm 패키지 게시 / 이미지 게시(ghcr) → 지문 수집
   │  modctl register  ─────────────────────────►  POST /api/modules/register   (상태: 검사 중)
   │  (3단계) 사용처 저장소들에 「검사해 줘」 신호(repository_dispatch)
   │
[각 프로그램 저장소 CI]  새 번호로 바꿔 자기 검사 → modctl check-report ─► POST …/checks
   │
[관리자 — 모듈 탭]  호환표 확인 → 「발행」 ─► 서명 · 상태: 발행 · 알림
   │
[각 프로그램]  modctl outdated / update → 검사 → 커밋 → modctl report ─► POST /api/modules/report
   │
[관리자 — 묶음]  제품 버전 + 모듈 번호 + 이미지 지문 → 서명 → update_packages 연결
   │
[고객 서버 업데이트 도우미]  묶음 받기 → 서명·typ 확인 → 지문으로 이미지 받기 → 백업 → 교체 → 보고

4. 화면 — 「모듈 센터」

4.0 디자인과 위치

요소값출처
글꼴Pretendard두 사이트 공통
강조색#3B6FE0 (연한 바탕 #eaf0fc)매뉴얼 센터 --accent
글자 / 흐린 글자 / 테두리 / 옆판#2f3437 / #908e89 / #ededec / #fbfbfa매뉴얼 센터 변수
영웅 카드둥글기 22px, 그림자 0 14px 44px rgba(0,0,0,.07)매뉴얼 센터 첫 화면
모듈 카드둥글기 16px, 위 4px 색띠, 올리면 떠오름매뉴얼 센터 home-card
모듈 배지남색 그라데이션 #22325b → #111c33, 48px·둥글기 14px매뉴얼 센터 「EP」 배지
상태 배지빨강 #fce8e6/#c5221f, 노랑 #fef7e0/#b06000, 초록·파랑버그개선 badge
통계 숫자36px 그라데이션 글자버그개선 stat-num
활동판머리줄 + LIVE 점 + 줄 목록버그개선 커뮤니티 보드
아이콘Material Symbols Outlined버그개선
상세 화면왼쪽 옆판(현재 항목 강조) + 경로 + 제목 + 버전 알약매뉴얼 센터 문서 화면

4.1 모듈 목록

칸내용
이름 · id라이센스 보호 장치 · license-guard
종류부품 / 서비스(중앙·곁붙이) / 시작 틀
기본「기본 · 단독 필수」 표시 (required_for)
최신 발행1.5.0 (발행일)
검사 중1.6.0-rc.1 등
사용처4곳
뒤처짐2곳 (주황) · 회수된 번호를 쓰는 곳 1곳 (빨강)

거르기: 종류 · 「뒤처진 곳 있음」 · 「회수 번호 사용 중」

4.2 모듈 상세 — 탭 6개

  1. 개요: 이름표 요약(연결부·설정 칸·바깥 접속·회사 격리), 원본 위치 링크
  2. 버전: 번호·상태·변경 크기(patch/minor/major)·발행자·발행일·「사용처가 할 일」
  3. 사용처: 제품별 사용 번호, 마지막 보고, 임시 덧댐 여부, 뒤처짐
  4. 호환표: 행 = 버전, 열 = 사용처, 칸 = 검사 결과(통과 수/실패 수, 기록 링크)
  5. 안내서: INTEGRATION.md 표시(정화 필수) + 「Claude 요청문 복사」 버튼
  6. 기록: 이 모듈의 감사 기록

4.3 발행 화면

4.4 회수

4.5 묶음

4.6 제품 화면과 연결


5. DB 표

기존 표(products 등)와 같은 방식으로 시작할 때 CREATE TABLE IF NOT EXISTS로 만듭니다. PG 경로는 기존 표를 PG에서 어떻게 만드는지 먼저 확인하고 똑같이 따릅니다. ENUM 대신 VARCHAR(값 제약은 앱에서), ON DUPLICATE KEY UPDATE 금지, 묶음 고유 제약은 양쪽 공통인 UNIQUE (a, b) 문법을 씁니다.

-- 모듈 (이름표의 바뀌지 않는 부분 + 화면용 캐시)
CREATE TABLE IF NOT EXISTS modules (
  id INT AUTO_INCREMENT PRIMARY KEY,
  module_id VARCHAR(40) NOT NULL UNIQUE,
  name VARCHAR(200) NOT NULL,
  kind VARCHAR(10) NOT NULL,                 -- part | service | starter
  summary VARCHAR(500) NOT NULL DEFAULT '',
  owner VARCHAR(100) NOT NULL DEFAULT '',
  source_repo VARCHAR(200) NOT NULL,
  source_path VARCHAR(300) NOT NULL DEFAULT '',
  latest_version VARCHAR(40) DEFAULT NULL,   -- 발행된 것 중 가장 높은 번호(캐시)
  required_for VARCHAR(10) NOT NULL DEFAULT '', -- 기본 모듈: '' 선택 | 'onprem' 단독 설치 제품 필수 | 'all' 모든 제품 필수
  catalog JSON DEFAULT NULL,                 -- module.json 의 catalog
  is_active BOOLEAN DEFAULT TRUE,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 버전 (발행 후에는 바꾸지 않음 — 회수 표시만)
CREATE TABLE IF NOT EXISTS module_versions (
  id INT AUTO_INCREMENT PRIMARY KEY,
  module_id VARCHAR(40) NOT NULL,
  version VARCHAR(40) NOT NULL,
  status VARCHAR(12) NOT NULL DEFAULT 'testing',   -- testing | released | withdrawn
  bump VARCHAR(6) NOT NULL DEFAULT '',             -- patch | minor | major (서버가 계산)
  suspect_bump BOOLEAN DEFAULT FALSE,              -- 연결부·설정·표가 바뀌었는데 patch 로 올린 경우
  manifest JSON NOT NULL,
  manifest_sha256 CHAR(64) NOT NULL,
  artifacts JSON NOT NULL,                         -- npm / git / image (01 §14.1)
  changelog TEXT,                                  -- 이 번호의 변경 기록(60KB 이하)
  integration_md TEXT,                             -- 안내서 원문(60KB 이하, 보여 줄 때 정화)
  payload JSON DEFAULT NULL,                       -- 서명한 내용 그대로
  signature TEXT DEFAULT NULL,
  note VARCHAR(500) DEFAULT '',                    -- 강제 발행·회수 사유
  created_by VARCHAR(100) DEFAULT '',
  released_by VARCHAR(100) DEFAULT '',
  released_at TIMESTAMP NULL DEFAULT NULL,
  withdrawn_at TIMESTAMP NULL DEFAULT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  UNIQUE (module_id, version)
);

-- 사용처 검사 결과 (이력으로 쌓음 — 같은 버전·사용처는 마지막 것이 유효)
CREATE TABLE IF NOT EXISTS module_checks (
  id INT AUTO_INCREMENT PRIMARY KEY,
  module_id VARCHAR(40) NOT NULL,
  version VARCHAR(40) NOT NULL,
  product_code VARCHAR(50) NOT NULL,
  product_version VARCHAR(40) DEFAULT '',
  runtime VARCHAR(10) DEFAULT '',
  passed INT DEFAULT 0,
  failed INT DEFAULT 0,
  ok BOOLEAN NOT NULL,
  log_url VARCHAR(500) DEFAULT '',
  reported_by VARCHAR(100) DEFAULT '',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 사용처 (각 프로그램의 modules.lock.json 보고값)
CREATE TABLE IF NOT EXISTS module_consumers (
  id INT AUTO_INCREMENT PRIMARY KEY,
  module_id VARCHAR(40) NOT NULL,
  product_code VARCHAR(50) NOT NULL,
  version_in_use VARCHAR(40) NOT NULL,
  runtime VARCHAR(10) DEFAULT '',
  patched BOOLEAN DEFAULT FALSE,                   -- 임시 덧댐 있음
  patch_remove_by DATE DEFAULT NULL,
  repo VARCHAR(200) DEFAULT '',
  reported_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  UNIQUE (module_id, product_code)
);

-- 묶음 (고객 서버로 보내는 세트)
CREATE TABLE IF NOT EXISTS module_bundles (
  id INT AUTO_INCREMENT PRIMARY KEY,
  product_code VARCHAR(50) NOT NULL,
  bundle_version VARCHAR(40) NOT NULL,
  payload JSON NOT NULL,                           -- 01 §14.2 그대로
  signature TEXT NOT NULL,
  update_package_id INT DEFAULT NULL,
  status VARCHAR(12) DEFAULT 'active',             -- active | withdrawn
  created_by VARCHAR(100) DEFAULT '',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  UNIQUE (product_code, bundle_version)
);

기존 products 표에는 칸 하나를 더합니다. 기존 productNewCols 방식(시작 시 ALTER TABLE … ADD COLUMN, 이미 있으면 무시)을 따릅니다.

-- 설치 형태: 'saas' | 'onprem' | 'saas,onprem'  (단독이 들어 있으면 기본 모듈 필수)
ALTER TABLE products ADD COLUMN deploy_modes VARCHAR(20) DEFAULT 'saas';

감사 기록은 새 표를 만들지 않고 auditLicense({ action: 'module.<동작>', detail })로 남깁니다(licenseId는 비움).


6. API

6.1 관리자 화면용 — requireAdmin (★ = requireRoleAdmin)

방법주소하는 일
GET/api/modules목록 + 최신 발행·검사 중·사용처 수·뒤처진 수
GET/api/modules/:id상세: 버전들, 사용처, 호환표
GET/api/modules/:id/versions/:ver버전 상세(이름표·변경 기록·검사·서명)
PATCH/api/modules/:id카탈로그·담당·활성 수정 (이름표의 다른 칸은 못 바꿈). required_for(기본 모듈 지정) 변경은 ★
POST ★/api/modules/:id/versions/:ver/release발행. 본문 { force?: boolean, note?: string }
POST ★/api/modules/:id/versions/:ver/withdraw회수. 본문 { note } 필수
GET/api/modules/consumers?product=제품이 쓰는 모듈
GET/api/modules/bundles?product=묶음 목록
POST ★/api/modules/bundles묶음 만들기·서명 (§8)

6.2 CI·도구용 — requireModulesKey (연동 키 scope modules 또는 full)

방법주소하는 일
POST/api/modules/register이름표 + 산출물 + 안내서 + 변경 기록 → 모듈이 없으면 만들고, 버전을 「검사 중」으로 등록
POST/api/modules/:id/versions/:ver/checks사용처 검사 결과(01 §11.4 형식)
POST/api/modules/report프로그램의 modules.lock.json 보고 → module_consumers 갱신
GET/api/modules/:id/released발행된 번호 목록(회수 표시 포함)
GET/api/modules/updates?product=이 제품이 받을 수 있는 새 번호 — 모듈마다 patch / minor / major / withdrawnInUse

6.3 공개 — 인증 없음, 속도 제한

방법주소하는 일
GET/api/public/modules/keys서명 확인용 공개키 목록(publicKeysB64()). 받는 쪽은 이 목록을 믿지 않고 내장 키와 대조만 합니다
GET/api/public/modules/catalog고객 패키지 센터용 목록 (§10)
GET/api/public/modules/bundles/:product/:ver묶음 서명본. X-License-Key가 그 제품의 유효한 라이센스일 때만 줌

6.4 등록 요청 예 (POST /api/modules/register)

{
  "manifest": { "moduleStandard": 1, "id": "license-guard", "version": "1.5.0", "kind": "part", "...": "..." },
  "artifacts": {
    "npm": { "name": "@hnbbos/license-guard", "version": "1.5.0", "integrity": "sha512-…" },
    "git": { "repo": "hnbbos/isolo_modules", "tag": "license-guard-v1.5.0", "commit": "0a1b…(40자리)" }
  },
  "changelog": "## 1.5.0 — 2026-10-20\n### 새 기능\n- …\n### 사용처가 할 일\n- 없음",
  "integration": "# 라이센스 보호 장치 붙이기 안내서 …"
}

응답: 201 { "module": "license-guard", "version": "1.5.0", "status": "testing", "bump": "minor", "suspectBump": false, "consumers": ["nahonza-work", "zerolabel-saas"] }


7. 등록·발행 규칙

7.1 등록할 때 확인 (어기면 거절)

  1. 이름표가 표준 형식에 맞는지(schemas/module-1.json): moduleStandard, id 형식, kind, 버전 형식, 필수 칸. 틀리면 400 MANIFEST_INVALID + 틀린 칸 목록.
  2. 같은 번호는 상태와 상관없이 다시 등록할 수 없습니다 → 409 VERSION_EXISTS.
  3. 같은 MAJOR 줄에서 가장 높은 번호보다 커야 합니다 → 409 VERSION_NOT_GREATER. 옛 줄의 보안 고침은 허용합니다(01 §7).
  4. 종류별 산출물 필수
  5. 부품: npm 또는 git(태그 + 40자리 커밋)
  6. 서비스: image(@sha256: 지문 필수)
  7. 시작 틀: git
  8. 없으면 400 ARTIFACT_MISSING.
  9. 크기 제한: 이름표 64KB, 안내서 60KB, 변경 기록 60KB.
  10. 변경 크기 계산: 직전 발행 번호와 비교해 bump를 정합니다. 연결부·설정·provides.tables가 바뀌었는데 patch면 suspect_bump = true로 표시하고 화면에 경고합니다.
  11. 이미 있는 모듈의 kind·source가 바뀌면 거절합니다(409 MODULE_IDENTITY_CHANGED).

7.2 발행할 때 확인

  1. 상태가 「검사 중」이어야 합니다.
  2. 사용처 = module_consumers에 있는 제품 + 이름표 compat에 적힌 제품.
  3. 사용처마다 이 번호의 마지막 검사 결과가 통과여야 합니다. 아니면 409 CHECKS_MISSING / 409 CHECKS_FAILED(어느 사용처인지 포함).
  4. force: true는 관리자 역할 + note 10자 이상일 때만 허용하고, 기록에 남깁니다.
  5. 사용처가 아직 하나도 없으면(첫 발행) 모듈 자체 검사 결과만으로 발행할 수 있습니다.
  6. 서명 키가 없으면 503 SIGNING_KEY_MISSING입니다. 서명 없는 발행은 만들지 않습니다.
  7. 서명 내용은 01 §14.1의 typ: "isolo.module-release/1" 형식이고, vendor-sign.js의 signPayload()로 서명합니다.
  8. 성공하면 status=released, modules.latest_version을 갱신하고, 알림을 보내고(§9.3), 기록을 남깁니다.

7.3 회수


8. 묶음 (고객 서버용)

요청: POST /api/modules/bundles (관리자 역할)

{
  "product": "nahonza-work",
  "bundleVersion": "1.8.0",
  "minFrom": "1.6.0",
  "images": {
    "server": "ghcr.io/hnbbos/nahonza-server@sha256:…",
    "client": "ghcr.io/hnbbos/nahonza-client@sha256:…"
  },
  "modules": { "license-guard": "1.5.0" },
  "registerUpdate": true
}

규칙

  1. 이미지 값은 모두 @sha256:을 포함해야 합니다(이름표 :latest 거절).
  2. modules의 번호는 모두 발행 상태여야 합니다(회수·검사 중 거절). modules를 생략하면 그 제품의 마지막 보고값을 씁니다.
  3. 기본 모듈 확인: 묶음은 단독 설치용이므로, required_for가 onprem 또는 all인 모듈이 모두 modules에 발행 번호로 들어 있어야 합니다. 빠지면 409 BASE_MODULE_MISSING(빠진 모듈 목록 포함)입니다. 제품의 deploy_modes에 onprem이 없으면 묶음을 만들 수 없습니다(409 PRODUCT_NOT_ONPREM).
  4. 01 §14.2 형식(typ: "isolo.bundle/1")으로 서명하고 module_bundles에 저장합니다.
  5. registerUpdate: true이면 update_packages에 한 줄을 만듭니다.
  6. product_code = 제품, version = 묶음 번호, download_url = 공개 묶음 주소, file_sha256 = 서명 내용의 sha256
  7. 기존 /api/update/check가 그대로 「새 버전 있음」을 알려 줍니다.
  8. 진품 확인은 기존 HMAC 서명(고객 쪽에서 확인 불가)이 아니라 묶음의 Ed25519 서명으로 합니다.

9. 인증·보안

9.1 연동 키 scope modules

9.2 그 밖의 규칙

  1. 안내서·변경 기록을 화면에 보여 줄 때 정화합니다. <script>, on*= 속성, javascript:/data: 링크, <iframe>을 제거합니다.
  2. 사이트는 올라온 코드를 실행하지 않습니다. 이름표는 JSON 형식 검사만 합니다.
  3. 쓰기 API는 모두 auditLicense로 기록합니다. 기록에는 누가(키 이름/관리자), 무엇을(모듈·번호), 왜(사유)가 들어갑니다.
  4. 공개 API에는 기존 속도 제한 방식(예: limitUpdate)과 같은 제한을 겁니다.
  5. 서명 개인키 취급은 기존 VENDOR-KEY-RUNBOOK.md를 따릅니다(키 백업·교체·키링).

9.3 알림


10. 카탈로그 — 나혼자워크 패키지 센터와 연결

나혼자워크는 LICENSE_CATALOG_URL에서 { programs: [...] }를 받아 normalizeCatalog()로 정리합니다 (server/src/controllers/integration.controller.ts). 이 모양에 맞춰 줍니다.

GET /api/public/modules/catalog (요청 머리 X-License-Key 선택)

{
  "programs": [
    {
      "appKey": "messenger",
      "name": "팀 메신저",
      "description": "대화방·공지·첨부·실시간 알림",
      "icon": "message-circle",
      "category": "업무",
      "baseUrl": "https://messenger.isolo.co.kr/embed/messenger",
      "tier": "licensed",
      "licenseRequired": true,
      "licensed": false,
      "link": "https://lic.isolo.co.kr/modules/messenger"
    }
  ]
}

10-2. 패키지 카탈로그 (1.26.0) — 기능 패키지 + 외부 프로그램, 서명

나혼자워크 1.9 부터 메뉴를 「기본 프로그램」(메일·캘린더·주소록·할일)과 「추가 제품」으로 나누고, 추가 제품 목록을 이 사이트에서 받습니다.

GET /api/public/packages/catalog?product=<제품 코드>&appVersion=<판> (요청 머리 X-License-Key 선택)

{
  "catalog": {
    "typ": "isolo.package-catalog/1", "product": "nahonza-work", "generatedAt": "…",
    "packages": [
      { "key": "estimates", "name": "견적서", "summary": "…", "features": ["…"], "tier": "free", "licenseRequired": false, "licensed": true, "version": "1.9.1" },
      { "key": "places", "name": "내 장소", "tier": "free", "licenseRequired": false, "licensed": true, "hidden": true },
      { "type": "external", "appKey": "erp", "name": "회계 ERP", "baseUrl": "https://erp.example.com/embed", "tier": "licensed", "licenseRequired": true, "licensed": false }
    ]
  },
  "signature": "<base64 Ed25519(canonical(catalog))>", "keyId": "…"
}
기록 (1.27.0)
표무엇을언제 쌓이나
package_history패키지 하나의 변경 — action(seed·create·update·status·revert·delete·grant·grant.delete), 누가, 바뀐 칸만 before_json/after_json넣기·고침·지움·라이센스 허용 때마다
package_install_events받는 쪽 보고 중 더해진·빠진 패키지(회사는 해시)package-installs 보고가 이전과 다를 때만
package_fetches제품·날짜마다 카탈로그를 받아 간 횟수 · 그중 라이센스 키로 받은 수 · 마지막 앱 판공개 카탈로그를 받을 때(하루 한 줄로 모음)
연동 설정 · 켜고 끄기 · 쓰는 모듈 (1.59.0)
표무엇을
package_switches라이센스(target_type license, 키 sha256)·설치본(install, 보고한 scope)마다 패키지 켬(1)·끔(0). 줄이 없으면 공개 상태대로
package_modules패키지가 쓰는 모듈 id — 쓰는 판은 제품의 사용처(module_consumers)에서 읽음
package_installs 칸 더함fetched_at · fetch_app_version · license_hash · license_hint — 받는 쪽이 ?scope= 를 붙여 받으면 그 설치 줄에
package_grants.fetched_at그 라이센스 키로 마지막으로 받아 간 때

11. 모듈 도구 modctl

11.1 모듈 저장소에서

명령하는 일
`modctl init <id> --kind part\service\starter [--runtime node,python]`01 §5 모양의 폴더·이름표·안내서·변경 기록 뼈대
modctl lint [<id>]이름표 형식 · 번호 일치(package.json/pyproject) · 안내서 필수 목차 · CHANGELOG의 「사용처가 할 일」 · 금지 검사(아래)
modctl test <id>단위 검사 + 약속 검사(contract/cases.json을 Node·Python 두 판에 돌려 결과 대조)
`modctl bump <id> patch\minor\major`번호 올리기(세 파일 동시) + CHANGELOG 머리 추가 + 태그 이름 출력. 태그·push는 사람이 함
modctl register <id>(CI) 산출물 지문을 모아 POST /api/modules/register

lint의 금지 검사(완벽하지 않은 휴리스틱이며, 걸리면 사람이 확인합니다)

11.2 프로그램 저장소에서

명령하는 일
modctl add <id>[@<ver>]발행된 최신(또는 지정) 번호를 설치합니다. Node는 package.json에 정확한 번호로 넣고 .npmrc 안내, Python은 requirements에 git+…@<id>-v<ver>#subdirectory=… 줄을 넣습니다. modules.lock.json을 기록하고, 호스트 연결 파일 뼈대(연결부 목록대로 빈 함수)를 만듭니다
modctl outdated뒤처진 모듈 표(patch / minor / major), 회수 번호 사용 경고, 임시 덧댐 기한 경고
modctl update <id> [--to <ver>]번호 교체 + 그사이 CHANGELOG의 「사용처가 할 일」을 모아 출력. MAJOR는 --yes-major가 있어야 진행
modctl verifylock의 모든 모듈이 사이트에서 발행·서명된 번호인지, 서명이 내장 공개키로 확인되는지 검사
modctl check-report <id> --result <파일>(CI) 사용처 검사 결과 보고
modctl report(CI) modules.lock.json 보고
modctl new <starter-id> <폴더> --var KEY=값 …시작 틀 복사(자리표시자 치환) + lock에 appliedAt 기록
modctl starter-diff시작 틀의 새 번호 CHANGELOG 중 아직 반영하지 않은 항목 목록

11.3 자동화 (3단계)


12. 검사 항목 (만든 뒤 반드시 돌릴 것)

#항목단계
1표준에 안 맞는 이름표 거절: kind 틀림, id 대문자, 번호 형식, moduleStandard 없음1
2같은 번호 재등록 409 (회수된 번호 포함)1
3같은 MAJOR 줄에서 낮은 번호 409 / 옛 줄 보안 고침은 허용1
4연동 키 read로 등록 → 403 / modules 키로 발행(관리자 API) → 4031
5서명 키가 없으면 발행 503, 상태는 그대로 「검사 중」1
6발행 서명을 공개키로 확인 통과 / 내용 한 글자 바꾸면 실패 / typ 다르면 거절1
7발행된 버전의 이름표·산출물은 어떤 API로도 바뀌지 않음1
8report로 사용처 갱신(있으면 고침, 없으면 추가) — MySQL·PG 두 경로 모두1
9updates가 patch / minor / major를 맞게 구분1
10사용처 검사 없음 → 발행 409 CHECKS_MISSING / 실패 → 409 CHECKS_FAILED / 강제는 관리자 역할 + 사유, 기록 남음2
11회수 후 updates에 안 나오고, 쓰는 곳에 withdrawnInUse2
12연결부가 바뀌었는데 patch → suspectBump = true2
13안내서 표시 XSS: <img src=x onerror=…>, [a](javascript:…), <iframe>이 무력화됨1
14묶음: 이미지에 지문이 없으면 거절 / 회수·검사 중 모듈 포함 거절 / 서명 확인 통과3
15공개 묶음: 다른 제품 라이센스 키 → 403, 키 없음 → 4013
16카탈로그: 서비스·visible만 / 키 없으면 licensed=false / 기능 있으면 true / 나혼자워크 normalizeCatalog가 그대로 받음3
17모든 쓰기에 감사 기록1
18공개 API 속도 제한 동작1
19기존 라이센스·업데이트 검사(scripts/e2e-test.js) 그대로 통과1
20기본 모듈 지정(required_for)은 관리자 역할만 / 단독 제품 묶음에 기본 모듈이 빠지면 409 BASE_MODULE_MISSING / SaaS 전용 제품 묶음은 409 PRODUCT_NOT_ONPREM1(지정)·3(묶음)

검사는 결과 줄이 없으면 실패로 칩니다. 또 검사 시작 시 지금 도는 서버의 버전을 출력해, 낡은 서버로 통과하는 일을 막습니다.


13. 단계

단계만드는 것끝난 기준
1표 5개 + products.deploy_modes · 등록 · 목록/상세(개요·버전·사용처·안내서·기록) · 기본 모듈 지정 · 사용처 보고 · 발행(서명) · updates · modctl (init lint test bump register add outdated update report verify)라이센스 보호 장치 1.0.0을 기본 모듈(onprem)로 등록·발행하고, 나혼자워크·제로라벨·라이센스 서버가 add로 가져와 report까지
2사용처 검사 결과 · 호환표 · 강제 발행 · 회수 · 알림 · 의심 변경 크기 · check-report한쪽에서 1.0.1을 만들어 두 사용처 검사 통과 후 발행, 다른 쪽이 update로 받음
3기본 모듈 installer·update-agent 등록 · 묶음(기본 모듈 확인 포함) · update_packages 연결 · 공개 묶음 · 카탈로그 · 자동 검사 신호 · 주간 업데이트 PR · new · starter-diff고객 서버(시험 PC)에 한 줄 설치 → 묶음으로 업데이트되고 결과가 보고됨

14. 대표님이 직접 하실 일

  1. GitHub에 비공개 저장소 hnbbos/isolo_modules 만들기 + 태그 보호 규칙 켜기
  2. GitHub Packages 사용 설정(npm @hnbbos 범위)
  3. lic.isolo.co.kr에서 연동 키(scope modules)를 만들어 isolo_modules의 Actions 비밀값 LIC_MODULES_KEY로 넣기
  4. (3단계) 다른 저장소에 신호를 보낼 권한이 좁은 토큰 만들기
  5. 운영 서버(lic.isolo.co.kr)에 반영