feat(M02): 50-3 ①②⑤ 일위대가 조합 서고 서버 — 단가승격 SP · 사전조합 SC · 참조 펼침 · 굳힘

- 층 양식 길 새 종류 promote · precombo(키 = 파일 이름 · 층마다 다음 번호) · 새로 · 복사 길 POST /layers/{층}/library/{종류}
- 참조 {층 · 주인 · 키} — 위 층만 · 같은 참조 두 번 400 · 순환 422 · 쓰는 곳 있으면 지우기 409(모든 층 일위대가 · 사전조합 훑음)
- calc 가 참조 줄 펼침(재귀 · 캐시 · 깊이 상한 8 · 까닭) · 알약 키 「참조|층|주인|키」 · 분모 = 가리킨 문서 단위 · 배 1 · settle TS 짝 lineId
- 층 사이 복사 = 못 읽는 참조 같이 복사(본뜬) · 프로젝트 가져오기 = 참조사본으로 굳힘 · 로직 지우기 검사에 단가승격 더함 · 새 프로젝트 씨앗은 서고 뺌
- 시험 test_m02_labor_ref.py(펼침 · 순환 · 층 규칙 · 409 · 복사 · 굳힘 · 거울)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYJJRCfHfdCGNhmXTjQKV7
This commit is contained in:
2026-10-05 18:47:02 +09:00
co-authored by Claude Opus 5.5
parent 3e572ef210
commit 9ee6c9f3fd
12 changed files with 966 additions and 53 deletions
+18 -5
View File
@@ -153,6 +153,8 @@ def write(kind: str, name: str, version: str, doc: Any, screen: bool = False) ->
(정본 = 나무 · 머리는 경로에서 다시 만듦 · `tree.keep_paths`)."""
path = _path(kind, name)
_check_skeleton(kind, doc)
if kind in layers.LIBRARY_KINDS: # 서고 문서 키 = 파일 이름(50-3)
doc = {**doc, "키": name}
origins = _take_origins(doc) if kind == "table" else {}
if kind == "table" and name == STRUCTURE_TABLE:
doc = tree.normalize_table(doc) # 경로 · 머리 줄 수 · 빈 폴더(표 저장 · 나무 고침 한 줄기)
@@ -186,6 +188,10 @@ def delete(kind: str, name: str, version: str | None = None) -> None:
path = _path(kind, name)
if (kind, name) in IN_USE:
raise StoreError(409, f"쓰는 양식이라 못 지움 「{name}」 — {IN_USE[(kind, name)]}")
if kind in layers.LIBRARY_KINDS:
from M02_MasterTemplete import M02_Labor_Ref as refs
refs.refuse_used(root(), kind, name)
with _LOCK:
if not path.is_file():
raise StoreError(404, f"없는 양식 「{name}」")
@@ -387,7 +393,7 @@ def copy_from(
body = read(kind, source)["문서"]
body = read(kind, source)["문서"] if body is None else body
doc = {**body, **{k: target.get(k) for k in ("열", _KEY[kind])}}
doc = _server(kind, {k: v for k, v in doc.items() if k != "계산값"})
doc = _server(kind, {k: v for k, v in doc.items() if k != "계산값"}, source_root)
with _LOCK:
atomic_write_json(_path(kind, column), doc)
info = _info(kind, _path(kind, column))
@@ -493,14 +499,21 @@ def renumber() -> dict[str, list[str]]:
return {"옮김": moved, "지움": gone, "바꿈": changed, "새로": made}
def _server(kind: str, doc: Any) -> Any:
"""서버가 적는 값 — 통합문서 계산값(5장 ②) · 일위대가 정리 · 계산값(`M02_Labor_Doc`)."""
def _server(kind: str, doc: Any, source: Path | None = None) -> Any:
"""서버가 적는 값 — 통합문서 계산값(5장 ②) · 일위대가 정리 · 계산값(`M02_Labor_Doc`) ·
서고 문서 정리(`M02_Labor_Ref` · 50-3) · `source` = 층 사이 복사의 원본 층 폴더(지금 층이
못 읽는 참조는 같이 복사)."""
if kind in SHEETS:
return _server_values(doc)
if kind == "labor":
if kind in ("labor", *layers.LIBRARY_KINDS):
from M02_MasterTemplete import M02_Labor_Doc as labor
from M02_MasterTemplete import M02_Labor_Ref as refs
return labor.server(doc)
if source is not None and isinstance(doc, dict) and kind != "promote":
doc = refs.carry(doc, refs.where(source))
if kind == "labor":
return labor.server(doc)
return refs.clean_doc(kind, doc)
return doc