init: company-haness 설계
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
"""arm 산출물을 arm-무관 canonical package 로 **규칙기반** 투영(LLM 요약 금지 — 그러면 judge 가
|
||||
sanitizer 품질을 비교하게 된다). arm 식별 토큰은 제거하되 빈 필드는 구조 누설 방지 위해 유지한다."""
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
|
||||
import yaml
|
||||
|
||||
from . import SANITIZER_VERSION
|
||||
|
||||
CANON_FIELDS = [
|
||||
"problem-framing", "user-and-core-task", "explored-directions", "selected-direction",
|
||||
"selection-rationale", "rejected-directions", "locked-invariants", "coded-prototype",
|
||||
"critique-findings", "revisions", "design-system-handoff-readiness",
|
||||
]
|
||||
# 실질(비면 omission) 필드
|
||||
SUBSTANTIVE = ["problem-framing", "user-and-core-task", "selected-direction", "coded-prototype"]
|
||||
# arm 을 누설하는 토큰(하네스 스캐폴딩)
|
||||
LEAK_TOKENS = [
|
||||
r"\brole-id\b", r"\bmethod-execution\b", r"\bcontract-sha256\b", r"\bworkflow-id\b",
|
||||
r"\bactivation\b", r"\b[0-9a-f]{40}\b", r"\barm[ _-]?[ABC]\b",
|
||||
]
|
||||
|
||||
|
||||
def _dig(obj, dotted):
|
||||
cur = obj
|
||||
for k in dotted.split("."):
|
||||
if isinstance(cur, dict) and k in cur:
|
||||
cur = cur[k]
|
||||
else:
|
||||
return None
|
||||
return cur
|
||||
|
||||
|
||||
def project(arm_artifacts_dir, extraction_map):
|
||||
"""extraction_map: {canon_field: {file, path}}. 규칙기반 추출 — 요약/생성 없음."""
|
||||
pkg = {}
|
||||
for f in CANON_FIELDS:
|
||||
pkg[f] = [] if f in ("explored-directions", "rejected-directions", "locked-invariants",
|
||||
"critique-findings", "revisions") else None
|
||||
src_count = set()
|
||||
projected = 0
|
||||
for field, spec in (extraction_map or {}).items():
|
||||
fp = os.path.join(arm_artifacts_dir, spec["file"])
|
||||
if not os.path.exists(fp):
|
||||
continue
|
||||
raw = open(fp, "rb").read()
|
||||
sha = hashlib.sha256(raw).hexdigest()
|
||||
data = yaml.safe_load(raw.decode("utf-8"))
|
||||
val = _dig(data, spec["path"])
|
||||
if val is None:
|
||||
continue
|
||||
prov = [{"artifact-ref": spec["file"], "artifact-sha256": sha, "source-fields": [spec["path"]]}]
|
||||
pkg[field] = {"value": val, "source-artifacts": prov} if not isinstance(pkg[field], list) else val
|
||||
src_count.add(spec["file"])
|
||||
projected += 1
|
||||
metrics = {"source-artifact-count": len(src_count), "projected-artifact-count": projected,
|
||||
"omitted-substantive-fields": check_omission(pkg)}
|
||||
return {"candidate-package": pkg, "projection-metrics": metrics, "sanitizer-version": SANITIZER_VERSION}
|
||||
|
||||
|
||||
def leak_scan(text):
|
||||
return [tok for tok in LEAK_TOKENS if re.search(tok, text)]
|
||||
|
||||
|
||||
def check_omission(package):
|
||||
out = []
|
||||
for f in SUBSTANTIVE:
|
||||
v = package.get(f)
|
||||
empty = v is None or (isinstance(v, dict) and not v.get("value")) or (isinstance(v, list) and not v)
|
||||
if empty:
|
||||
out.append(f)
|
||||
return out
|
||||
|
||||
|
||||
def build_bundle(run_id, candidate_id, package, prototype_dir=None, render=True):
|
||||
"""candidate 번들 조립: candidate.yaml + 렌더 png(있으면). 렌더는 preview_ui.py 산출을 복사(재생성
|
||||
금지 — 결정론). prototype_dir 없거나 render=False 면 design 은 not-evaluable.
|
||||
|
||||
judge-visible candidate.yaml 에는 candidate-package 만 쓴다(projection-metrics·sanitizer-version
|
||||
같은 프로세스 메타는 judge 에게 arm 정보를 누설할 수 있어 제외). 쓰기 전 leak_scan 을 통과해야
|
||||
한다 — 통과 못 하면 채점 자체를 막는다(fail-loud, spec §4.5)."""
|
||||
from . import paths
|
||||
cp = package.get("candidate-package", package)
|
||||
_leaks = leak_scan(yaml.safe_dump(cp, allow_unicode=True))
|
||||
if _leaks:
|
||||
raise ValueError(f"candidate 누설 토큰 검출 — 채점 금지: {_leaks}")
|
||||
bdir = os.path.join(paths.candidates_dir(run_id), candidate_id)
|
||||
os.makedirs(bdir, exist_ok=True)
|
||||
with open(os.path.join(bdir, "candidate.yaml"), "w", encoding="utf-8") as f:
|
||||
yaml.safe_dump(cp, f, allow_unicode=True, sort_keys=False)
|
||||
renders = []
|
||||
if render and prototype_dir and os.path.isdir(prototype_dir):
|
||||
for name in ("prototype-desktop.png", "prototype-mobile.png"):
|
||||
src = os.path.join(prototype_dir, name)
|
||||
if os.path.exists(src):
|
||||
shutil.copy2(src, os.path.join(bdir, name))
|
||||
renders.append(name)
|
||||
manifest = {"design-evaluable": len(renders) >= 1, "renders": renders,
|
||||
"sanitizer-version": SANITIZER_VERSION}
|
||||
with open(os.path.join(bdir, "prototype-manifest.json"), "w", encoding="utf-8") as f:
|
||||
json.dump(manifest, f)
|
||||
return {"bundle-dir": bdir, "renders": renders, "design-evaluable": manifest["design-evaluable"]}
|
||||
|
||||
|
||||
def design_status(bundle):
|
||||
"""bundle의 design-evaluable 상태를 평가한다."""
|
||||
return "evaluable" if bundle.get("design-evaluable") else "not-evaluable"
|
||||
Reference in New Issue
Block a user