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:
zzabisagent-lab
2026-11-06 08:40:09 +00:00
co-authored by Claude Fable 5.1
parent 0f5e01a4cb
commit 2b31543a86
34 changed files with 4639 additions and 72 deletions
+2 -2
View File
@@ -91,7 +91,7 @@ await test('4·6) 배포 릴리스: HAL 은 배포 대상 아님, HAL 먼저 규
await test('2) 역할별 통합 배포: 프로필대로 모듈을 순서대로 배포하고 서버에 프로필·역할·슬롯을 기록, 포털은 하나만(이미 있으면 거부) + 포털 이전 작업, 슬롯 기반 배치', async () => {
const h = makeHal(); const op = await loginOp(h); const s1 = await addServer(h, { name: '새서버', role: 'web' }); const s2 = await addServer(h, { name: '컨트롤', role: 'control' }); bootstrapDone(h.d, '0.3.17'); addRelease(h, '0.3.17', ['agent', 'portal', 'webmaker', 'arimail']);
ok(SERVER_PROFILES.webmaker && SERVER_PROFILES.webmaker.modules.includes('webmaker') && SERVER_PROFILES.webmaker.modules.includes('hawkeye') && SERVER_PROFILES.webmaker.modules[0] === 'agent', JSON.stringify(SERVER_PROFILES.webmaker));
let r = await op.as('GET', '/ops/api/provision/profiles'); eq(r.status, 200); ok(r.json.profiles.map((p) => p.id).sort().join(',') === 'arimail,backup,portal,webhosting,webmaker');
let r = await op.as('GET', '/ops/api/provision/profiles'); eq(r.status, 200); ok(r.json.profiles.map((p) => p.id).sort().join(',') === 'arimail,backup,hal,portal,webhosting,webmaker'); // [0.4.1] hal 프로필(HAL 서버: agent·hawkeye)
// webmaker 프로필을 새서버에: agent → webmaker → hawkeye 순서로 자식 작업, 서버 profile·role·slots 기록
r = await op.as('POST', '/ops/api/provision', { serverId: s1.serverId, profile: 'webmaker', version: '0.3.17' }); eq(r.status, 200, JSON.stringify(r.json)); const pj = r.json.job;
const seen = []; await runAgents(h, [s1, s2], (job) => { seen.push(job.payload.module); return { status: 'done', result: { module: job.payload.module, actualVersion: job.payload.version || '0.1.1', health: { ok: true }, active: true } }; });
@@ -141,7 +141,7 @@ await test('3·5·7) 화면: 부분 갱신 diff(순수)·ops.js 정적 계약,
});
await test('버전·문서(0.3.17)', async () => {
const h = makeHal(); eq(JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8')).version, '0.4.0'); eq(h.d.version, '0.4.0');
const h = makeHal(); eq(JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8')).version, '0.4.1'); eq(h.d.version, '0.4.1');
ok(readFileSync(join(ROOT, 'Ariweb-Suitle_Dev-Order_v0.3.17.md'), 'utf8').includes('Oz')); ok(readFileSync(join(ROOT, 'VERSION_HISTORY.md'), 'utf8').includes('## 0.3.17'));
ok(readFileSync(join(ROOT, 'hal', 'ai', 'skill.md'), 'utf8').includes('HawkEye') && !/observability\b(?! 는 옛 이름)/.test(readFileSync(join(ROOT, 'hal', 'ai', 'skill.md'), 'utf8').replace('옛 이름 observability', '')), '스킬 문서도 HawkEye');
});