Files

35 lines
4.2 KiB
JavaScript

/**
* lib/provenance.mjs — 도입 소스 출처(IMPORTED_SOURCES.json) 검사. 릴리스 build·register --approve·HAL 승인이 공유하는 순수 규칙(0.2.0 검토 수정, 차단 항목 11).
*
* 깨끗한 출처(clean provenance)란: 각 소스가 40자리 고정 커밋(commit)을 가지고, 파일 목록이 비어 있지 않으며(각 항목에 from·to·origHash),
* upstream 기록이 있으면 완전하고(version·branch·headCommit) headCommit 이 commit 과 같고 workingTreeDirty 가 정확히 false 인 것.
* - `--allow-dirty` 로 참조 저장소의 작업 트리 변경을 함께 가져온 기록(workingTreeDirty=true)은 "원본 저장소에 아직 없는 코드" 를 담고 있어 릴리스로 승인할 수 없다.
* 원본 저장소에 커밋되면 pin 을 올려 `scripts/import-sources.mjs` 를 다시 실행해 깨끗한 기록을 만든다.
* - upstream 칸이 없는 기록은 0.1.x 의 import-sources 가 만든 것이다(그 스크립트는 깨끗한 고정 커밋 체크아웃에서만 가져왔다) → legacy 로 표시하고 받아들인다.
* 모르는 형태·빈 목록은 모두 "불완전" 으로 보아 거절한다(fail-closed).
* @param {object} manifest IMPORTED_SOURCES.json 내용
* @returns {{ ok: boolean, problems: string[], sources: { name: string, commit: string|null, upstream: object|null, legacy: boolean }[] }}
*/
export function checkProvenance(manifest) {
const problems = []; const sources = [];
if (!manifest || typeof manifest !== 'object' || !Array.isArray(manifest.sources)) return { ok: false, problems: ['IMPORTED_SOURCES: sources 목록이 없습니다(불완전한 기록)'], sources };
if (!manifest.sources.length) return { ok: false, problems: ['IMPORTED_SOURCES: 도입 소스가 하나도 기록되지 않았습니다(불완전한 기록)'], sources };
for (const s of manifest.sources) {
const name = (s && typeof s.name === 'string' && s.name) || '(이름 없음)';
const up = s && s.upstream && typeof s.upstream === 'object' ? s.upstream : null;
const summary = { name, commit: s && typeof s.commit === 'string' ? s.commit : null, upstream: up ? { version: up.version ?? null, branch: up.branch ?? null, headCommit: up.headCommit ?? null, workingTreeDirty: up.workingTreeDirty } : null, legacy: !up };
sources.push(summary);
if (!s || typeof s !== 'object') { problems.push(`${name}: 기록 형식이 올바르지 않습니다`); continue; }
if (!/^[0-9a-f]{40}$/.test(String(s.commit || ''))) problems.push(`${name}: 고정 커밋(commit)이 40자리 커밋 해시가 아닙니다 (${String(s.commit || '없음').slice(0, 12)})`);
if (!Array.isArray(s.files) || !s.files.length) problems.push(`${name}: 가져온 파일 목록이 비어 있습니다(불완전한 기록)`);
else if (s.files.some((f) => !f || typeof f.from !== 'string' || typeof f.to !== 'string' || typeof f.origHash !== 'string')) problems.push(`${name}: 파일 기록에 from·to·origHash 가 빠진 항목이 있습니다`);
if (!up) continue; // 0.1.x 기록: 깨끗한 고정 커밋 체크아웃만 받던 스크립트가 만든 것(legacy)
if (up.workingTreeDirty !== false) problems.push(`${name}: upstream.workingTreeDirty=${JSON.stringify(up.workingTreeDirty)} — 원본 저장소에 커밋되지 않은 작업 트리 변경을 함께 가져온 기록은 릴리스로 승인할 수 없습니다. 원본에 커밋한 뒤 pin 을 올려 scripts/import-sources.mjs 를 다시 실행하세요`);
if (typeof up.version !== 'string' || !up.version) problems.push(`${name}: upstream.version 이 없습니다(불완전한 기록)`);
if (typeof up.branch !== 'string' || !up.branch) problems.push(`${name}: upstream.branch 가 없습니다(불완전한 기록)`);
if (!/^[0-9a-f]{40}$/.test(String(up.headCommit || ''))) problems.push(`${name}: upstream.headCommit 이 없거나 형식이 틀립니다(불완전한 기록)`);
else if (up.headCommit !== s.commit) problems.push(`${name}: upstream.headCommit(${String(up.headCommit).slice(0, 7)}) ≠ 고정 커밋 commit(${String(s.commit).slice(0, 7)}) — 기록이 어긋났습니다. scripts/import-sources.mjs 를 다시 실행하세요`);
}
return { ok: problems.length === 0, problems, sources };
}