HAL 0.4.1: 서버 등록 TLS 수정·"이미 등록된 서버" 안내, HAL 배포가 HAL 서버의 Node Agent·HawkEye 를 함께 설치해 부트스트랩을 닫음
- hal/tls.mjs: 공개 주소 TLS 신뢰 확인(시스템 CA → 실패면 체인·공개키 핀), 신뢰 앵커(체인 자체 서명 / <data>/tls/hal-ca.pem), 10분 캐시, 핀 설치 명령(curl -k --pinnedpubkey … --hal-pin). 공개 경로 GET /agent/v1/hal-ca.pem · /agent/v1/tls, CLI tls-probe
- deploy/agent-install.sh: --hal-pin(핀으로 CA 받기)·--ca·--insecure, curl --cacert / Node NODE_EXTRA_CA_CERTS / register --ca / 유닛 Environment, 연결 실패 원인·조치 한 줄, 이미 등록된 서버 안내
- agent/halca.mjs + agent/index.mjs·config.mjs: caFile 런타임 적용(tls.setDefaultCACertificates), fetch 실패를 읽을 수 있는 한 줄로(종료 1), 이미 등록(종료 2, --force), HAL 거부(종료 3), heartbeat observability → hawkeye
- core.registerAgent: 같은 서버의 두 번째 토큰 409 server_already_registered / 쓴 토큰 409 token_used("이미 등록된 서버"), 거부 토큰 미소비, 재등록 허용(replace: 기존 Agent 해지, 감사 agent.replace), 마이그레이션 hal_0011_token_replace, API/CLI --replace --json(재인증), ops.js 재등록 확인·핀 안내
- deploy/hal-host-agent.sh(server-create --profile hal → server-token --json → agent-install loopback → server-approve), bootstrap-hal.sh 6/7 단계(--no-agent), CLI server-create·server-approve, SERVER_PROFILES.hal
- core.deploymentStatus/selfHealth.deployment, 배너 "부트스트랩 완료 · 기능 단위 서버 배포 대기: …" / "진행 중", 배포 릴리스 화면 문구, heartbeat canonicalModuleName(0.3.17 잔여 결함)
- 개발 지시 v0.4.1(Oz)·VERSION_HISTORY 0.4.1·README·docs/HAL-AGENT §6·RUNBOOK §11·AI 스킬, 도입 모듈 변경 없음(Webmaker 1.9.0·AriMail 1.1.0)
- test/hal-041.test.mjs RED → GREEN 7/7(실제 자체 서명 TLS 픽스처·별도 프로세스 CLI), 기존 배너·프로필·버전 테스트 갱신, 전체 32파일 통과, 로컬 E2E 보고서 2026-11-06
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
0f5e01a4cb
commit
2b31543a86
+7
-1
@@ -100,7 +100,9 @@ export function releasesView(data, changelog = {}) {
|
||||
{ l: '변경 이력', f: (r) => changelog[r.version] ? `<details><summary>보기</summary><pre class="pre">${esc(changelog[r.version])}</pre></details>` : '<span class="muted">항목 없음</span>' },
|
||||
{ l: '이 버전 배포', f: (r) => r.status === 'approved' ? modules.filter((m) => r.artifacts && (r.artifacts[m] || (guides[m] && /Agent 안의/.test(guides[m].description)))).map((m) => `<button class="btn small" data-act="module-deploy" data-m="${esc(m)}" data-v="${esc(r.version)}">${esc(m)}</button>`).join(' ') : '<span class="muted">승인 후</span>' },
|
||||
], data.releases || [], { empty: '등록된 릴리스가 없습니다 — scripts/release.mjs 로 artifact 를 만들고 등록하세요' });
|
||||
return `<p class="muted">HAL 실행 버전: <b>${esc(running.hal_running_version || '확인 전')}</b>${running.manual_install_locked ? ' · 수동 설치 잠김(HAL 전용 운영)' : ' · <span class="err">부트스트랩 미완료</span>'} — <b>HAL 은 배포 대상이 아닙니다</b>(Suite 를 설치하기 전에 먼저 설치하는 도구이며 별도 절차로 갱신). 모듈 배포는 HAL 이 릴리스보다 오래되지 않으면 진행됩니다. 배포 순서: ${(data.deployOrder || []).map(esc).join(' → ')}.</p>
|
||||
// [0.4.1] 부트스트랩 완료 뒤에는 기능 단위 서버 배포 대기 목록을 보인다("부트스트랩 미완료" 문구는 진행 중일 때만)
|
||||
const pendingFeat = (data.deployment && data.deployment.pending) || [];
|
||||
return `<p class="muted">HAL 실행 버전: <b>${esc(running.hal_running_version || '확인 전')}</b>${running.manual_install_locked ? ' · 수동 설치 잠김(HAL 전용 운영) · 부트스트랩 완료' : ' · <span class="err">부트스트랩 진행 중(HAL 서버 Agent 등록·승인 전)</span>'}${pendingFeat.length ? ` · <b>기능 단위 서버 배포 대기: ${pendingFeat.map((m) => esc(moduleLabel(m))).join(', ')}</b>` : running.manual_install_locked ? ' · 기능 모듈 모두 배치됨' : ''} — <b>HAL 은 배포 대상이 아닙니다</b>(Suite 를 설치하기 전에 먼저 설치하는 도구이며 별도 절차로 갱신). 모듈 배포는 HAL 이 릴리스보다 오래되지 않으면 진행됩니다. 배포 순서: ${(data.deployOrder || []).map(esc).join(' → ')}.</p>
|
||||
<h2>최신 릴리스 배포(모듈별)</h2><div class="cards rel-cards">${cards}</div>
|
||||
<div class="actions"><button class="btn primary" data-act="provision">역할별 통합 배포(새 서버)</button> <button class="btn" data-act="module-reconcile">목표·실제 상태 맞추기(전체)</button></div>
|
||||
<h2>상호 연결(어떤 모듈이 있어야 무엇이 동작하는가)</h2><ul class="small">${(data.links || []).map((l) => `<li>${esc(l)}</li>`).join('')}</ul>
|
||||
@@ -229,3 +231,7 @@ export function backupView(d) { return `<h2>복원 승인(§30.3)</h2>${table([{
|
||||
<h2>백업 목록</h2>${table([{ l: '백업', f: (b) => `<span class="mono">${esc(b.id)}</span><br>${esc(b.site_name || b.site_id)}` }, { l: '상태', f: (b) => jobBadge(b.status) }, { l: '검증', f: (b) => b.verify_state === 'ok' || b.verify_state === 'verified' ? badge('ok', '검증') : badge('warn', b.verify_state) }, { l: '생성·만료', f: (b) => `${fmt(b.created_at)} · ${fmt(b.expires_at)}` }, { l: '크기·위치', f: (b) => `${bytes(b.size_bytes)} <span class="muted">${esc(b.location || '')}</span>` }], d.backups || [], { empty: '백업이 없습니다' })}`; }
|
||||
export function relocationView(d, tr) { return `<h2>서버 이전(§30.4)</h2>${table([{ l: '대상', f: (r) => `<b>${esc(r.subjectName || r.subject_id)}</b> <span class="muted">${esc(r.subject_type)} · ${esc(r.group_name || '')}</span><br>${esc(r.from_server_id || '?')} → ${esc(r.to_server_id || '?')}` }, { l: '종류·이유', f: (r) => `${esc(r.kind)}<br><span class="muted">${esc(r.reason || '')}</span>` }, { l: '상태', f: (r) => jobBadge(r.status) + (r.approval_state ? `<br><span class="muted">승인 ${esc(r.approval_state)}</span>` : '') }, { l: '현재 단계', f: (r) => r.job ? `${esc(r.job.currentStep ? r.job.currentStep.title : '')} ${r.job.progress || 0}%${r.job.waitReason ? `<br><span class="muted">${esc(r.job.waitReason)}</span>` : ''}` : '—' }, { l: '전환 직전 재점검', f: (r) => r.precheckChanged || (r.boundaries && r.boundaries.autoStopped) ? badge('warn', '조건 변경 — 자동 중지') : r.boundaries && r.boundaries.switchAt ? `전환 ${fmt(r.boundaries.switchAt)}` : '—' }, { l: '고객 통지', f: (r) => r.notifiedAt || (r.boundaries && r.boundaries.notifiedAt) ? fmt(r.notifiedAt || r.boundaries.notifiedAt) : '—' }, { l: '', f: (r) => ['awaiting_approval', 'requested'].includes(r.status) || r.approval_state === 'pending' ? `<button class="btn small primary" data-act="reloc-approve" data-id="${esc(r.id)}">승인</button> <button class="btn small danger" data-act="reloc-reject" data-id="${esc(r.id)}">거절</button>` : ['failed', 'paused'].includes(r.status) ? `<button class="btn small" data-act="reloc-retry" data-id="${esc(r.id)}">재시도</button> <button class="btn small" data-act="reloc-resume" data-id="${esc(r.id)}">이어서</button> <button class="btn small danger" data-act="reloc-revert" data-id="${esc(r.id)}">되돌리기</button>` : '' }], d.relocations || [], { empty: '이전 요청이 없습니다' })}<div class="actions"><button class="btn" data-act="reloc-new">플랫폼 사유 이전 만들기</button></div>
|
||||
<h2>소유권 이전 심사(§22)</h2>${table([{ l: '요청', f: (t) => `<span class="mono">${esc(t.id)}</span><br>${esc(t.fromGroupName || t.from_group_id)} → ${esc(t.toGroupName || t.to_group_id || t.to_email || '')}` }, { l: '상태', f: (t) => jobBadge(t.status) }, { l: '범위', f: (t) => t.scope ? Object.entries(t.scope).map(([k, v]) => `${esc(k)}: ${Array.isArray(v) ? v.length : esc(String(v))}`).join(', ') : '—' }, { l: '만료', f: (t) => fmt(t.expires_at) }, { l: '', f: (t) => ['reviewing', 'accepted'].includes(t.status) ? `<button class="btn small primary" data-act="transfer-approve" data-id="${esc(t.id)}">승인</button> <button class="btn small danger" data-act="transfer-reject" data-id="${esc(t.id)}">거절</button>` : t.status === 'disputed' ? `<button class="btn small" data-act="transfer-resolve" data-id="${esc(t.id)}">분쟁 해결</button>` : '' }], (tr && tr.transfers) || [], { empty: '소유권 이전 요청이 없습니다' })}`; }
|
||||
|
||||
/** [0.4.1] 모듈 표시 이름(배너·릴리스 화면의 "기능 단위 서버 배포 대기" 목록). 브라우저에서도 쓰므로 hal/modules 를 import 하지 않고 여기 둔다. */
|
||||
export const MODULE_LABELS = { hal: 'HAL', agent: 'Node Agent', portal: '고객 포털', webmaker: 'Webmaker', arimail: 'AriMail', backup: '백업', hawkeye: 'HawkEye', observability: 'HawkEye' };
|
||||
export function moduleLabel(m) { return MODULE_LABELS[m] || String(m); }
|
||||
|
||||
Reference in New Issue
Block a user