/* * **개체에서 품목으로** — 정체성 모양이 하나가 아니다 (2026-08-23). * * ── 무엇이 알리지 않고 비어 있었나 ─────────────────────────────────────────────── * 화면이 「이 개체는 선언된 자재가 아닙니다(라인 내부의 중간 산물일 수 있습니다)」라고 말했는데, 그 * 품목은 원본의 선언에 이름까지 있었다(「냉동그린빈스」). 대응 표(`productionSpec.binding`)도 멀쩡했다. * * 원인은 **개체에서 품목을 꺼내는 규칙**이었다. 그 규칙이 GS1 SGTIN URN 하나만 알았다. * * urn:epc:id:sgtin:0614141.077101.42 → 077101 (알던 모양) * https://…/class/product-M0000438@M2R0001 → null (모르던 모양) * * 웹 URI 로 정체성을 말하는 원본에서는 언제나 `null` 이 나왔고, 그래서 **개체를 자재에 이을 수 없었다.** * 그 결과가 화면 두 곳에서 들어온 것이 없는 빈칸이었다(계보 적합성 · 오더가 무엇을 만드나). 「없으면 붙이지 * 않는다」는 규율을 지키기 때문에 아무 오류도 나지 않았다. * * ── 무엇을 돌려주나 ───────────────────────────────────────────────────────── * **그 트윈의 대응 표가 쓰는 것과 같은 모양**이다. 두 세계가 서로 다른 것을 값으로 쓴다. * * SGTIN 세계 binding 값 = 품목 참조 숫자(`077101`) → 숫자를 돌려준다 * URI 세계 binding 값 = 클래스 URI(`…/product-M…`) → 클래스 URI 를 돌려준다 * * 그래서 두 세계를 한 함수가 답한다 — 부르는 자리가 원본의 정체성 방식을 알지 않아도 된다. * * ── 무엇을 지어내지 않나 ──────────────────────────────────────────────────── * **어느 부호가 개체를 가리키는지 모른다.** 그것은 원본마다 다르고 커널 계약이 정한 적이 없다 — 한 * 원본(chef)이 쓰는 `@` 를 규칙으로 굳히면 이 화면이 그 레거시 위에 서게 된다. 자르는 판단은 그 트윈의 * **선언**이 하고(§`resolveByDeclaration`), 후보가 둘 이상이면 답하지 않는다. 짐작으로 이으면 남의 * 레시피와 비교하게 되고, 그것이 「설계와 다르다」는 거짓 진단이 된다. */ /** * 개체 식별자 → **품목 참조**. 대응 표의 값과 비교할 수 있는 모양으로 돌려준다. * * SGTIN 은 표준이 자리를 정해 두었으므로 그 자리에서 품목 참조를 꺼낸다. 그 밖의 모양은 **손대지 * 않는다** — 어느 부호가 개체를 가리키는지는 원본마다 다르고, 커널 계약이 그 구분자를 정한 적이 없다. * 앞서 이 자리에 「`@` 뒤는 개체다」를 넣었는데, 그것은 한 원본(chef)의 모양을 규칙으로 굳힌 것이었다. * * 그래서 자르는 일은 여기서 하지 않고, **그 트윈의 선언과 비교해** 판정한다(§`resolveByDeclaration`). * 선언이 답을 주지 못하면 `null` 이다 — 짐작으로 이으면 남의 레시피와 비교하게 되고, 그것이 「설계와 * 다르다」는 거짓 진단이 된다. */ export function itemRefOf(epc: unknown): string | null { if (typeof epc !== 'string') return null const s = epc.trim() if (!s) return null /* GS1 SGTIN — 회사 접두사와 일련번호 사이의 품목 참조. `idpat` 의 `*` 도 같은 자리에서 걸린다. */ const sgtin = /sgtin:[^.]+\.([^.]+)\./.exec(s) if (sgtin) return sgtin[1] /* 그 밖의 정체성(웹 URI 등)은 그대로 낸다 — 자르는 판단은 선언이 한다. */ return s } /** * 이 식별자가 가리키는 **선언 항목**을 찾는다 — 원본의 정체성 관행을 알지 않고, 구분자도 모른다. * * 세 가지를 차례로 본다. 어느 단계든 **후보가 둘 이상이면 답하지 않는다**(모르는 것이 틀린 답보다 낫다). * * ① 그대로 같다 * ② 관측된 식별자가 선언을 **확장**한다 — 개체는 자기 클래스로 시작한다(`…/product-M1` + 개체 표시) * ③ 화면이 **짧게 줄인** 식별자를 건넸다 — 선언이 그것으로 끝난다(`net/class/product-M1`) * * ②③은 부호를 모른다. 그래서 원본이 `@`·`#`·`/` 무엇을 쓰든, 또는 아무것도 쓰지 않든 같은 규칙이 선다. */ export function resolveByDeclaration(ref: string | null, declared: Map): T | undefined { if (!ref) return undefined const exact = declared.get(ref) if (exact !== undefined) return exact let hit: T | undefined let found = 0 for (const [id, value] of declared) { if (!(ref.startsWith(id) || id.endsWith(ref))) continue found++ if (found > 1) return undefined hit = value } return hit }