@things-factory/headless-twin 10.1.223 → 10.1.227
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-server/engine/kpi-query.js +1 -1
- package/dist-server/engine/kpi-query.js.map +1 -1
- package/dist-server/engine/twin-engine.d.ts +1 -1
- package/dist-server/engine/twin-engine.js +1 -1
- package/dist-server/engine/twin-engine.js.map +1 -1
- package/dist-server/service/actuation/command-dispatcher.d.ts +1 -1
- package/dist-server/service/actuation/command-dispatcher.js.map +1 -1
- package/dist-server/service/reference/actuation-routing.d.ts +1 -1
- package/dist-server/service/reference/actuation-routing.js.map +1 -1
- package/dist-server/service/reference/connection-portability.d.ts +1 -1
- package/dist-server/service/reference/connection-portability.js +2 -2
- package/dist-server/service/reference/connection-portability.js.map +1 -1
- package/dist-server/service/reference/hook-contract.js +1 -1
- package/dist-server/service/reference/hook-contract.js.map +1 -1
- package/dist-server/service/reference/reference-adapter.d.ts +1 -1
- package/dist-server/service/reference/reference-adapter.js.map +1 -1
- package/dist-server/service/reference/reference-live.js +1 -1
- package/dist-server/service/reference/reference-live.js.map +1 -1
- package/dist-server/service/reference/reference-progress.js +1 -1
- package/dist-server/service/reference/reference-progress.js.map +1 -1
- package/dist-server/service/twin-model/choose-recipe.d.ts +1 -1
- package/dist-server/service/twin-model/choose-recipe.js +1 -1
- package/dist-server/service/twin-model/choose-recipe.js.map +1 -1
- package/dist-server/service/twin-model/twin-lineage-query.js +1 -1
- package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
- package/package.json +6 -6
- package/server/engine/kpi-query.ts +1 -1
- package/server/engine/twin-engine.ts +1 -1
- package/server/service/actuation/command-dispatcher.ts +1 -1
- package/server/service/reference/actuation-routing.ts +1 -1
- package/server/service/reference/connection-portability.ts +2 -2
- package/server/service/reference/hook-contract.ts +1 -1
- package/server/service/reference/reference-adapter.ts +1 -1
- package/server/service/reference/reference-live.ts +1 -1
- package/server/service/reference/reference-progress.ts +1 -1
- package/server/service/twin-model/choose-recipe.ts +1 -1
- package/server/service/twin-model/twin-lineage-query.ts +1 -1
- package/server/service/twin-subject/twin-subject-backfill.ts +1 -1
- package/test/ack-shape-alignment.test.ts +1 -1
- package/test/canonical-ingest-vocabularies.test.ts +1 -1
- package/test/checkpoint-refuses-empty.test.ts +1 -1
- package/test/choose-recipe.test.ts +1 -1
- package/test/event-type-split.test.ts +1 -1
- package/test/live-cursor-wiring.test.ts +1 -1
- package/test/master-to-twin.test.ts +1 -1
- package/test/read-failure-visible.test.ts +1 -1
- package/tsconfig.shared.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"choose-recipe.js","sourceRoot":"","sources":["../../../server/service/twin-model/choose-recipe.ts"],"names":[],"mappings":";;AAkBA,oCAYC;AA9BD;;;;;;GAMG;AACH;;;;;;;;;;GAUG;AACH,SAAgB,YAAY,CAC1B,OAAc,EACd,UAAiB,EACjB,aAAiC;IAEjC,IAAI,aAAa,EAAE,CAAC;QAClB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,aAAa,CAAC,CAAA;QAC/D,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAA;IACnF,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAA;IACtF,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,CAAA;IACxD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;AAC1B,CAAC","sourcesContent":["/*\n * **어느 레시피로 만들었나** — 판정만. 순수하다.\n *\n * ── 왜 따로 있나 ───────────────────────────────────────────────────────────\n * 계보 질의는 TypeORM 과 shell 을 끌어온다. 규칙만 재려는 시험이 그것까지 들고 오면 무겁고, 이\n * 저장소에서 그 이유로 시험이 안 서는 일을 겪었다. 판단과 배선을 나눈다 — 여기는 값만 보고 답한다.\n */\n/**\n * 짐작하지 않는 규칙.\n *\n * order 오더가 말했다.
|
|
1
|
+
{"version":3,"file":"choose-recipe.js","sourceRoot":"","sources":["../../../server/service/twin-model/choose-recipe.ts"],"names":[],"mappings":";;AAkBA,oCAYC;AA9BD;;;;;;GAMG;AACH;;;;;;;;;;GAUG;AACH,SAAgB,YAAY,CAC1B,OAAc,EACd,UAAiB,EACjB,aAAiC;IAEjC,IAAI,aAAa,EAAE,CAAC;QAClB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,aAAa,CAAC,CAAA;QAC/D,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAA;IACnF,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAA;IACtF,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,CAAA;IACxD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;AAC1B,CAAC","sourcesContent":["/*\n * **어느 레시피로 만들었나** — 판정만. 순수하다.\n *\n * ── 왜 따로 있나 ───────────────────────────────────────────────────────────\n * 계보 질의는 TypeORM 과 shell 을 끌어온다. 규칙만 재려는 시험이 그것까지 들고 오면 무겁고, 이\n * 저장소에서 그 이유로 시험이 안 서는 일을 겪었다. 판단과 배선을 나눈다 — 여기는 값만 보고 답한다.\n */\n/**\n * 짐작하지 않는 규칙.\n *\n * order 오더가 말했다. 버전이 둘이든 열이든 그것을 쓴다\n * only-candidate 오더는 말 안 했지만 후보가 하나다 — 고를 것이 없으면 짐작이 아니다\n * ambiguous 후보가 둘 이상인데 말해 주는 것이 없다 → **답하지 않는다**\n * said-but-undeclared 오더는 말했는데 그 레시피가 선언에 없다 — 모르는 것이 아니라 어긋난 것이다\n *\n * 마지막 둘을 가르는 이유: 사람이 할 일이 다르다. 어긋남은 선언이나 원본을 고칠 일이고,\n * 모호함은 그 개체가 어느 것으로 만들어졌는지 기록이 없는 일이다.\n */\nexport function chooseRecipe(\n recipes: any[],\n candidates: any[],\n saidRecipeKey: string | undefined\n): { recipe?: any; basis: 'order' | 'only-candidate' | 'ambiguous' | 'said-but-undeclared' | 'none' } {\n if (saidRecipeKey) {\n const said = recipes.find((r: any) => r?.key === saidRecipeKey)\n return said ? { recipe: said, basis: 'order' } : { basis: 'said-but-undeclared' }\n }\n if (candidates.length === 1) return { recipe: candidates[0], basis: 'only-candidate' }\n if (candidates.length > 1) return { basis: 'ambiguous' }\n return { basis: 'none' }\n}\n"]}
|
|
@@ -133,7 +133,7 @@ let TwinLineageQuery = class TwinLineageQuery {
|
|
|
133
133
|
* 세 자리가 다 있었고 마지막 연결만 없었다. 「가릴 수 없다」가 아니라 **안 읽고 있었다.**
|
|
134
134
|
*
|
|
135
135
|
* ── 규칙 셋 — 짐작하지 않는다 ───────────────────────────────────────────────
|
|
136
|
-
* 오더가 말한다 그것을 쓴다.
|
|
136
|
+
* 오더가 말한다 그것을 쓴다. 버전이 둘이든 열이든 상관없다
|
|
137
137
|
* 오더가 말 안 하고
|
|
138
138
|
* 후보가 하나 그것을 쓴다 — 고를 것이 없으면 짐작이 아니다
|
|
139
139
|
* 후보가 둘 이상 **답하지 않는다.** 어느 것인지 모르는 채로 견주면 거짓 진단이 된다
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"twin-lineage-query.js","sourceRoot":"","sources":["../../../server/service/twin-model/twin-lineage-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Int, Query, Resolver } from 'type-graphql'\n\nimport { ScalarObject, getRepository } from '@things-factory/shell'\n\nimport { itemRefOf, resolveByDeclaration } from './item-ref.js'\nimport { chooseRecipe } from './choose-recipe.js'\n\nimport { TwinInstance } from '../twin-instance/twin-instance.js'\nimport { TwinEvent } from '../twin-event/twin-event.js'\n\n/*\n * 계보 — **이 개체는 무엇에서 나왔나**, 그리고 **설계대로 만들어졌나.**\n *\n * ── 구성과 계보는 주어가 다르다 ────────────────────────────────────────────────\n * 구성(BOM)의 주어는 품목 **정의**다 — \"트레일러는 프레임 1개로 만든다\"(선언).\n * 계보의 주어는 품목 **개체**다 — \"이 트레일러는 저 프레임 로트에서 나왔다\"(관측).\n * 둘은 같은 모양이라 겹쳐 볼 수 있고, **그 차이가 곧 진단**이다: \"설계는 1개인데 이건 2개를 먹었다\".\n * 트윈이 아니면 못 하는 답이고, 이 질의가 그 답의 재료다.\n *\n * ── 어디서 읽나 ────────────────────────────────────────────────────────────────\n * 계보는 상태가 아니라 **저널**에 있다(EPCIS `TransformationEvent` 의 입력·산출 목록). 상태는\n * 지금 무엇이 있는지만 알고, 없어진 원자재가 무엇이었는지는 이벤트만 안다.\n *\n * ── 규모 ───────────────────────────────────────────────────────────────────────\n * 입력 목록은 **컬럼이 아니라 봉투 안**에 있어서 인덱스로 좁힐 수 없다. 그래서 변환 이벤트만\n * 창으로 끊어 읽고, **끊었으면 끊었다고 말한다.** 그 사실을 감추면 \"이 개체는 원자재가 없다\" 로\n * 읽히고, 계보에서 그 오해는 리콜 범위를 좁게 잡는 결과로 이어진다.\n */\n\n\n@Resolver()\nexport class TwinLineageQuery {\n @Query(returns => ScalarObject, {\n nullable: true,\n description:\n 'Where one item came from (observed) laid next to what its type declares (BOM). Walks TransformationEvent input/output lists in the journal; reports the window it read and whether it was truncated. The declared|observed difference is the diagnosis: \"the recipe says one frame, this one consumed two\".'\n })\n async twinItemLineage(\n @Arg('instanceId') instanceId: string,\n @Arg('epc') epc: string,\n @Ctx() context: ResolverContext,\n @Arg('maxDepth', type => Int, { nullable: true }) maxDepth?: number,\n @Arg('window', type => Int, { nullable: true }) window?: number\n ): Promise<any | null> {\n const domainId = context.state.domain.id\n const inst = await getRepository(TwinInstance).findOne({ where: { domain: { id: domainId }, instanceId } })\n if (!inst) return null\n\n const depthCap = Math.min(Math.max(maxDepth ?? 6, 1), 12)\n const windowCap = Math.min(Math.max(window ?? 5000, 1), 50000)\n\n /*\n * 변환 이벤트만 읽는다. **최신부터** 읽는다 — 계보를 묻는 개체는 대개 최근에 만들어진 것이고,\n * 창을 넘겨 잘릴 때 잃는 쪽이 오래된 것이어야 덜 아프다.\n */\n /*\n * 관계를 **컬럼으로** 묻는다 (2026-08-28). `find({ where: { domain: { id } } })` 은 관계를 가진\n * 엔티티에서 두 단계 질의가 되고 **안쪽에 상한이 없다** — `take` 가 있어도 조건에 맞는 행을 전부\n * 만든 뒤 밖에서 자른다. 변환 사건이 적은 트윈에서는 표가 안 나지만, 변환을 많이 내는 트윈에서는\n * 이 한 번이 그 종류 전체를 만든다. 질의 빌더로 물으면 상한이 안쪽에 걸린다.\n *\n * 종류가 정확일치이므로 `(domain, instance, event_type, event_time)` 색인이 시각 순서까지 준다.\n */\n const rows: any[] = await getRepository(TwinEvent)\n .createQueryBuilder('e')\n .where('e.domain = :domainId', { domainId })\n .andWhere('e.instanceId = :instanceId', { instanceId })\n .andWhere('e.eventType = :eventType', { eventType: 'epcis.TransformationEvent' })\n .orderBy('e.eventTime', 'DESC')\n .limit(windowCap)\n .getMany()\n const truncated = rows.length >= windowCap\n\n /* 산출 → 그 변환. 한 개체는 한 번만 만들어지지만, 데이터가 그렇지 않을 수 있으므로 목록으로 든다. */\n const madeBy = new Map<string, { inputs: string[]; at?: string; tx?: string }[]>()\n for (const r of rows) {\n const d = r.payload?.data ?? {}\n const outs: string[] = d.outputEPCList ?? []\n const ins: string[] = d.inputEPCList ?? []\n const step = { inputs: ins, at: r.eventTime, tx: d.bizTransactionList?.[0]?.bizTransaction }\n for (const o of outs) {\n const list = madeBy.get(o) ?? []\n list.push(step)\n madeBy.set(o, list)\n }\n }\n\n /* 거슬러 올라간다 — 너비 우선, 지나온 것은 다시 파지 않는다(사이클·공유 부품). */\n const items: any[] = [{ epc, itemRef: itemRefOf(epc), depth: 0 }]\n const edges: any[] = []\n const seen = new Set<string>([epc])\n let depthTruncated = false\n let frontier = [{ epc, depth: 0 }]\n while (frontier.length) {\n const next: typeof frontier = []\n for (const cur of frontier) {\n if (cur.depth >= depthCap) { depthTruncated = true; continue }\n for (const step of madeBy.get(cur.epc) ?? []) {\n for (const input of step.inputs) {\n edges.push({ from: cur.epc, to: input, at: step.at, tx: step.tx, depth: cur.depth + 1 })\n if (seen.has(input)) continue\n seen.add(input)\n items.push({ epc: input, itemRef: itemRefOf(input), depth: cur.depth + 1 })\n next.push({ epc: input, depth: cur.depth + 1 })\n }\n }\n }\n frontier = next\n }\n\n /*\n * **겹쳐 본다.** 이 개체의 품목이 어느 레시피의 산출이면, 그 레시피가 선언한 투입과 실제로\n * 소비된 것을 자재별로 나란히 센다.\n *\n * 개체(EPC)와 자재 키를 잇는 것은 `productionSpec.binding`(자재 키 → GS1 품목 참조)이다.\n * 바인딩이 없으면 **비교하지 않는다** — 짐작으로 이으면 \"설계와 다르다\" 는 거짓 진단이 나오고,\n * 그건 아무 말도 안 하는 것보다 나쁘다.\n */\n const spec: any = (inst.model as any)?.productionSpec\n const binding: Record<string, string> = spec?.binding ?? {}\n const refToMaterial = new Map(Object.entries(binding).map(([mat, ref]) => [String(ref), mat]))\n /*\n * ── **화면은 끝마디로 묻는다** (2026-08-23) ─────────────────────────────────\n *\n * 서랍의 포커스 식별자는 클래스의 끝마디다(`net/class/product-M0000438`). 대응 표의 값은 전체\n * 식별자(`https://…/class/product-M0000438`)이므로 곧바로는 맞지 않고, 그래서 「선언된 자재가\n * 아닙니다」가 떴다 — 그 품목은 대응 표에 있었다.\n *\n * 끝마디로도 찾되 **하나뿐일 때만** 인정한다. 둘 이상이 같은 끝마디를 가지면 어느 것인지 모르므로\n * 아무 말도 하지 않는다 — 짐작으로 이으면 남의 레시피와 견주게 되고, 그것이 「설계와 다르다」는\n * 거짓 진단이 된다.\n */\n const materialOf = (e: string) => resolveByDeclaration(itemRefOf(e), refToMaterial) ?? null\n\n const myMaterial = materialOf(epc)\n const recipes: any[] = spec?.definition?.recipes ?? []\n\n /*\n * ── 「무엇으로 만들었나」는 **적혀 있다** (2026-09-05) ───────────────────────\n *\n * 예전에는 이 자리가 자재로 되짚어 `recipes.find(...)` 로 **먼저 걸리는 하나**를 골랐다. 배열\n * 순서가 답을 정한다는 뜻이고, 한 자재에 레시피가 둘이면 틀린 답이 확신에 찬 얼굴로 나온다.\n * 실제로 그런 자재가 있다 — `BOLT-M8` 에 `RCP-BOLT-STD` · `RCP-BOLT-FAST` 둘이다.\n *\n * 그런데 답은 이미 기록되어 있다. 만들 때 정해지는 사실이고 커널이 오더에 싣는다\n * (`FlowOrder.recipeKey`). 변환 사건은 그 오더를 거래 식별자로 가리키고(`bizTransactionList`),\n * 이 함수는 그 값을 이미 `tx` 로 꺼내 놓고도 쓰지 않았다.\n *\n * 세 자리가 다 있었고 마지막 연결만 없었다. 「가릴 수 없다」가 아니라 **안 읽고 있었다.**\n *\n * ── 규칙 셋 — 짐작하지 않는다 ───────────────────────────────────────────────\n * 오더가 말한다 그것을 쓴다. 판이 둘이든 열이든 상관없다\n * 오더가 말 안 하고\n * 후보가 하나 그것을 쓴다 — 고를 것이 없으면 짐작이 아니다\n * 후보가 둘 이상 **답하지 않는다.** 어느 것인지 모르는 채로 견주면 거짓 진단이 된다\n *\n * 마지막 줄이 이 함수가 식별자 되찾기에 이미 적용하던 규율이다(「후보가 둘 이상이면 답하지\n * 않는다」). 두 줄 위에서만 안 지키고 있었다.\n */\n const candidates = myMaterial\n ? recipes.filter((r: any) => (r.outputs ?? []).some((o: any) => o.material === myMaterial))\n : []\n\n /* 이 개체를 만든 변환이 가리키는 거래 — 그 오더가 레시피를 말한다. */\n const madeTx = (madeBy.get(epc) ?? []).map(s => s.tx).find(Boolean)\n const saidRecipeKey = madeTx ? await recipeKeyOfTransaction(domainId, instanceId, String(madeTx)) : undefined\n\n const chosen = chooseRecipe(recipes, candidates, saidRecipeKey)\n const recipe = chosen.recipe\n\n /*\n * ── 「모르는 개체」와 「대응이 없는 개체」는 다른 사실이다 (2026-08-23) ──────\n *\n * 화면이 「이 개체는 선언된 자재가 아닙니다(라인 내부의 중간 산물일 수 있습니다)」라고 말했는데,\n * 그 품목은 원본의 품목 선언에 **이름까지 있었다**(「냉동그린빈스」). 바인딩 표가 그 품목을 담지\n * 못한 것이고, 그것은 「중간 산물」과 전혀 다른 이야기다 — 사용자가 할 일도 다르다(원본을 볼 일이\n * 아니라 대응을 고칠 일이다).\n *\n * 그래서 개체가 가리키는 품목이 **모델의 품목 선언에 있는지** 함께 본다. 이름은 그 자리에 있으니\n * 함께 낸다(화면이 사람에게 무엇을 말하는지 알 수 있게).\n */\n const definitions: any[] = (inst.model as any)?.materialDefinitions ?? []\n const defByRef = new Map(definitions.filter(d => d?.id).map(d => [String(d.id), d]))\n /* 정의도 같은 문으로 되찾는다 — 규칙 하나, 후보가 둘 이상이면 답하지 않는다. */\n const myRef = itemRefOf(epc)\n const myDef = resolveByDeclaration(myRef, defByRef)\n\n let conformance: any = null\n if (!Object.keys(binding).length) {\n conformance = { comparable: false, reason: 'no-binding' }\n } else if (!myMaterial && myDef) {\n /* 원본은 이 품목을 안다 — 개체를 자재에 잇는 대응이 없어서 비교하지 못한다. */\n conformance = {\n comparable: false,\n reason: 'item-known-not-bound',\n ...(typeof myDef.name === 'string' && myDef.name ? { itemName: myDef.name } : {})\n }\n } else if (!myMaterial) {\n conformance = { comparable: false, reason: 'item-not-bound' }\n } else if (chosen.basis === 'said-but-undeclared') {\n /* 오더는 말했는데 그 레시피가 모델의 선언에 없다 — 원본과 선언이 어긋난 것이지 모르는 것이 아니다. */\n conformance = { comparable: false, reason: 'recipe-not-declared', recipe: saidRecipeKey }\n } else if (chosen.basis === 'ambiguous') {\n /* 짐작하지 않는다 — 어느 것으로 만들었는지 말해 주는 사실이 없다. 후보를 밝혀 사람이 판단하게 한다. */\n conformance = {\n comparable: false,\n reason: 'many-recipes-for-material',\n recipes: candidates.map((r: any) => r.key).sort()\n }\n } else if (!recipe) {\n conformance = { comparable: false, reason: 'no-recipe-for-material' }\n } else if (!(madeBy.get(epc) ?? []).length) {\n /*\n * ── **안 본 것을 「다르다」고 하지 않는다** (2026-09-18 · :3000 화면에서 확인) ─────────\n *\n * 이 개체를 만든 변환이 저널에 하나도 없으면 투입을 하나도 관측하지 못한 것이다. 그런데\n * 아래 셈은 그대로 돌아 관측을 전부 0 으로 적었고, 레시피가 요구한 자재 23 줄이 전부\n * 「안 맞는다」가 됐다 — 화면은 「레시피와 다릅니다」를 확신에 찬 얼굴로 그렸다. 실제로는\n * 원본이 변환을 아직 안 보냈을 뿐이다(`mes-miratek` 의 카메라 로트가 그 경우다).\n *\n * 이 파일의 규율이 이미 그것을 말한다 — 짐작으로 이으면 「설계와 다르다」는 거짓 진단이\n * 되고, 그건 아무 말도 안 하는 것보다 나쁘다. 관측이 **없는** 것도 같은 부류다.\n */\n conformance = { comparable: false, reason: 'made-not-observed', recipe: recipe.key }\n } else {\n /*\n * 관측: **바인딩된 자재까지 내려가 센다** — 중간 산물(WIP)은 통과한다.\n *\n * 다단 라인에서 완제품의 직접 투입은 원자재가 아니라 **WIP** 다: 원자재는 첫 공정에서\n * 소비되고 완제품은 마지막 공정에서 나온다. 그래서 직접 투입만 세면 레시피가 선언한 자재와\n * **영원히 안 맞는다**(검사가 이것을 잡았다).\n *\n * WIP 은 라우트의 내부 산물이고 선언된 자재가 아니다 — 레시피는 자재로 말하므로 비교도\n * 자재로 해야 한다. 바인딩에 있으면 거기서 멈추고, 없으면 그 위로 계속 따라간다.\n *\n * 다만 **다른 완제품까지 넘어가지 않는다**: 바인딩된 것을 만나면 멈추므로, 하위조립도\n * 자재로 선언돼 있으면 그 자리에서 세어진다(그 아래는 그 자재 자신의 레시피가 볼 일이다).\n */\n const observed = new Map<string, number>()\n let unbound = 0\n const parentsOf = (e: string) => edges.filter(x => x.from === e).map(x => x.to)\n const walked = new Set<string>([epc])\n let layer = parentsOf(epc)\n while (layer.length) {\n const deeper: string[] = []\n for (const e of layer) {\n if (walked.has(e)) continue\n walked.add(e)\n const mat = materialOf(e)\n if (mat) { observed.set(mat, (observed.get(mat) ?? 0) + 1); continue }\n const up = parentsOf(e)\n /* 바인딩에도 없고 더 올라갈 곳도 없다 — 정말 모르는 투입이다(수만 낸다). */\n if (!up.length) { unbound++; continue }\n deeper.push(...up)\n }\n layer = deeper\n }\n const declared = new Map<string, number>((recipe.inputs ?? []).map((i: any) => [i.material, i.qty ?? null]))\n const keys = [...new Set([...declared.keys(), ...observed.keys()])]\n conformance = {\n comparable: true,\n material: myMaterial,\n recipe: recipe.key,\n /* 그 레시피를 **어떻게 알았나** — 화면이 「기록으로 안다」와 「하나뿐이라 안다」를 구별할 수 있게. */\n recipeBasis: chosen.basis,\n /* 자재마다 선언·관측을 나란히. 어느 한쪽에만 있는 것도 사실이다(빠뜨리지 않는다). */\n lines: keys.map(m => ({\n material: m,\n declared: declared.has(m) ? declared.get(m) : null,\n observed: observed.get(m) ?? 0,\n matches: declared.get(m) === (observed.get(m) ?? 0)\n })),\n /* 바인딩에 없는 투입 — 설계에 없는 것이 들어갔을 수도, 바인딩이 덜 선언됐을 수도 있다.\n 어느 쪽인지 우리는 모르므로 **수만 낸다.** */\n unboundInputs: unbound\n }\n }\n\n return {\n instanceId,\n epc,\n itemRef: itemRefOf(epc),\n material: myMaterial,\n items,\n edges,\n conformance,\n /* 무엇을 근거로 답했나 — 창을 넘겨 끊겼으면 계보가 실제보다 얕게 보인다. */\n basis: { source: 'journal', transformationsRead: rows.length, truncated, depthTruncated, depthCap, windowCap }\n }\n }\n}\n\n/**\n * 그 거래를 가리키는 오더가 **어느 레시피로** 만들었다고 말하는가.\n *\n * ── 왜 거래 식별자로 묻나 ───────────────────────────────────────────────────\n * EPCIS 변환 사건에는 레시피가 없다 — 표준에 그 자리가 없고, 대신 생산 오더를 거래로 가리킨다\n * (`bizTransactionList`). 오더 사건은 `recipeKey` 와 그 거래 식별자를 **둘 다** 든다. 그래서 이\n * 두 칸이 EPCIS 에서 오더로 건너가는 다리다(§`twin-event-keys.ts`).\n *\n * ── 색인을 탄다 ─────────────────────────────────────────────────────────────\n * `ix_twin_event_9 (domain, instanceId, bizTransaction)` 로 좁힌다. 이 칸이 없던 때는 payload 를\n * 문자열로 훑어야 했고 32만 행에 3분이 걸렸다 — 화면이 기다릴 비용이 아니다.\n *\n * ── 못 찾으면 **답하지 않는다** ──────────────────────────────────────────────\n * 옛 행에는 이 칸이 비어 있다(과거 행은 채우지 않는다는 규율). 그때는 `undefined` 를 내고, 부르는\n * 쪽이 후보 수로 판단한다 — 여기서 아무거나 고르면 예전과 같은 결함이 된다.\n */\nasync function recipeKeyOfTransaction(\n domainId: string,\n instanceId: string,\n bizTransaction: string\n): Promise<string | undefined> {\n const row = await getRepository(TwinEvent)\n .createQueryBuilder('e')\n .where('e.domain = :domainId', { domainId })\n .andWhere('e.instanceId = :instanceId', { instanceId })\n .andWhere('e.bizTransaction = :tx', { tx: bizTransaction })\n .andWhere('e.eventType = :eventType', { eventType: 'order.status' })\n /* 어느 줄이든 같은 오더를 말하므로 하나면 된다 — 가장 이른 것이 그 오더가 선 순간이다. */\n .orderBy('e.eventTime', 'ASC')\n .limit(1)\n .getOne()\n\n const said = (row?.payload as any)?.data?.recipeKey\n return typeof said === 'string' && said ? said : undefined\n}\n"]}
|
|
1
|
+
{"version":3,"file":"twin-lineage-query.js","sourceRoot":"","sources":["../../../server/service/twin-model/twin-lineage-query.ts"],"names":[],"mappings":";;;;AAAA,+CAA6D;AAE7D,iDAAmE;AAEnE,+CAA+D;AAC/D,yDAAiD;AAEjD,wEAAgE;AAChE,+DAAuD;AAEvD;;;;;;;;;;;;;;;;;GAiBG;AAII,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IAMrB,AAAN,KAAK,CAAC,eAAe,CACA,UAAkB,EACzB,GAAW,EAChB,OAAwB,EACmB,QAAiB,EACnB,MAAe;QAE/D,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAA;QACxC,MAAM,IAAI,GAAG,MAAM,IAAA,qBAAa,EAAC,+BAAY,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,UAAU,EAAE,EAAE,CAAC,CAAA;QAC3G,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAA;QAEtB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;QACzD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;QAE9D;;;WAGG;QACH;;;;;;;WAOG;QACH,MAAM,IAAI,GAAU,MAAM,IAAA,qBAAa,EAAC,yBAAS,CAAC;aAC/C,kBAAkB,CAAC,GAAG,CAAC;aACvB,KAAK,CAAC,sBAAsB,EAAE,EAAE,QAAQ,EAAE,CAAC;aAC3C,QAAQ,CAAC,4BAA4B,EAAE,EAAE,UAAU,EAAE,CAAC;aACtD,QAAQ,CAAC,0BAA0B,EAAE,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC;aAChF,OAAO,CAAC,aAAa,EAAE,MAAM,CAAC;aAC9B,KAAK,CAAC,SAAS,CAAC;aAChB,OAAO,EAAE,CAAA;QACZ,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,IAAI,SAAS,CAAA;QAE1C,+DAA+D;QAC/D,MAAM,MAAM,GAAG,IAAI,GAAG,EAA4D,CAAA;QAClF,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,IAAI,IAAI,EAAE,CAAA;YAC/B,MAAM,IAAI,GAAa,CAAC,CAAC,aAAa,IAAI,EAAE,CAAA;YAC5C,MAAM,GAAG,GAAa,CAAC,CAAC,YAAY,IAAI,EAAE,CAAA;YAC1C,MAAM,IAAI,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAA;YAC5F,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;gBACrB,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;gBAChC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBACf,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAA;YACrB,CAAC;QACH,CAAC;QAED,oDAAoD;QACpD,MAAM,KAAK,GAAU,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,IAAA,uBAAS,EAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAA;QACjE,MAAM,KAAK,GAAU,EAAE,CAAA;QACvB,MAAM,IAAI,GAAG,IAAI,GAAG,CAAS,CAAC,GAAG,CAAC,CAAC,CAAA;QACnC,IAAI,cAAc,GAAG,KAAK,CAAA;QAC1B,IAAI,QAAQ,GAAG,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAA;QAClC,OAAO,QAAQ,CAAC,MAAM,EAAE,CAAC;YACvB,MAAM,IAAI,GAAoB,EAAE,CAAA;YAChC,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;gBAC3B,IAAI,GAAG,CAAC,KAAK,IAAI,QAAQ,EAAE,CAAC;oBAAC,cAAc,GAAG,IAAI,CAAC;oBAAC,SAAQ;gBAAC,CAAC;gBAC9D,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;oBAC7C,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;wBAChC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC,CAAA;wBACxF,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;4BAAE,SAAQ;wBAC7B,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;wBACf,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,IAAA,uBAAS,EAAC,KAAK,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC,CAAA;wBAC3E,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC,CAAA;oBACjD,CAAC;gBACH,CAAC;YACH,CAAC;YACD,QAAQ,GAAG,IAAI,CAAA;QACjB,CAAC;QAED;;;;;;;WAOG;QACH,MAAM,IAAI,GAAS,IAAI,CAAC,KAAa,EAAE,cAAc,CAAA;QACrD,MAAM,OAAO,GAA2B,IAAI,EAAE,OAAO,IAAI,EAAE,CAAA;QAC3D,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;QAC9F;;;;;;;;;;WAUG;QACH,MAAM,UAAU,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,IAAA,kCAAoB,EAAC,IAAA,uBAAS,EAAC,CAAC,CAAC,EAAE,aAAa,CAAC,IAAI,IAAI,CAAA;QAE3F,MAAM,UAAU,GAAG,UAAU,CAAC,GAAG,CAAC,CAAA;QAClC,MAAM,OAAO,GAAU,IAAI,EAAE,UAAU,EAAE,OAAO,IAAI,EAAE,CAAA;QAEtD;;;;;;;;;;;;;;;;;;;;;WAqBG;QACH,MAAM,UAAU,GAAG,UAAU;YAC3B,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC;YAC3F,CAAC,CAAC,EAAE,CAAA;QAEN,4CAA4C;QAC5C,MAAM,MAAM,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACnE,MAAM,aAAa,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,sBAAsB,CAAC,QAAQ,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QAE7G,MAAM,MAAM,GAAG,IAAA,+BAAY,EAAC,OAAO,EAAE,UAAU,EAAE,aAAa,CAAC,CAAA;QAC/D,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAA;QAE5B;;;;;;;;;;WAUG;QACH,MAAM,WAAW,GAAW,IAAI,CAAC,KAAa,EAAE,mBAAmB,IAAI,EAAE,CAAA;QACzE,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;QACpF,kDAAkD;QAClD,MAAM,KAAK,GAAG,IAAA,uBAAS,EAAC,GAAG,CAAC,CAAA;QAC5B,MAAM,KAAK,GAAG,IAAA,kCAAoB,EAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;QAEnD,IAAI,WAAW,GAAQ,IAAI,CAAA;QAC3B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC;YACjC,WAAW,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,CAAA;QAC3D,CAAC;aAAM,IAAI,CAAC,UAAU,IAAI,KAAK,EAAE,CAAC;YAChC,iDAAiD;YACjD,WAAW,GAAG;gBACZ,UAAU,EAAE,KAAK;gBACjB,MAAM,EAAE,sBAAsB;gBAC9B,GAAG,CAAC,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAClF,CAAA;QACH,CAAC;aAAM,IAAI,CAAC,UAAU,EAAE,CAAC;YACvB,WAAW,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAA;QAC/D,CAAC;aAAM,IAAI,MAAM,CAAC,KAAK,KAAK,qBAAqB,EAAE,CAAC;YAClD,8DAA8D;YAC9D,WAAW,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,qBAAqB,EAAE,MAAM,EAAE,aAAa,EAAE,CAAA;QAC3F,CAAC;aAAM,IAAI,MAAM,CAAC,KAAK,KAAK,WAAW,EAAE,CAAC;YACxC,+DAA+D;YAC/D,WAAW,GAAG;gBACZ,UAAU,EAAE,KAAK;gBACjB,MAAM,EAAE,2BAA2B;gBACnC,OAAO,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE;aAClD,CAAA;QACH,CAAC;aAAM,IAAI,CAAC,MAAM,EAAE,CAAC;YACnB,WAAW,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,wBAAwB,EAAE,CAAA;QACvE,CAAC;aAAM,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;YAC3C;;;;;;;;;;eAUG;YACH,WAAW,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,EAAE,CAAA;QACtF,CAAC;aAAM,CAAC;YACN;;;;;;;;;;;;eAYG;YACH,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAA;YAC1C,IAAI,OAAO,GAAG,CAAC,CAAA;YACf,MAAM,SAAS,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;YAC/E,MAAM,MAAM,GAAG,IAAI,GAAG,CAAS,CAAC,GAAG,CAAC,CAAC,CAAA;YACrC,IAAI,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,CAAA;YAC1B,OAAO,KAAK,CAAC,MAAM,EAAE,CAAC;gBACpB,MAAM,MAAM,GAAa,EAAE,CAAA;gBAC3B,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;oBACtB,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;wBAAE,SAAQ;oBAC3B,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;oBACb,MAAM,GAAG,GAAG,UAAU,CAAC,CAAC,CAAC,CAAA;oBACzB,IAAI,GAAG,EAAE,CAAC;wBAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;wBAAC,SAAQ;oBAAC,CAAC;oBACtE,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,CAAA;oBACvB,gDAAgD;oBAChD,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC;wBAAC,OAAO,EAAE,CAAC;wBAAC,SAAQ;oBAAC,CAAC;oBACvC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;gBACpB,CAAC;gBACD,KAAK,GAAG,MAAM,CAAA;YAChB,CAAC;YACD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAiB,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,CAAA;YAC5G,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,EAAE,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;YACnE,WAAW,GAAG;gBACZ,UAAU,EAAE,IAAI;gBAChB,QAAQ,EAAE,UAAU;gBACpB,MAAM,EAAE,MAAM,CAAC,GAAG;gBAClB,+DAA+D;gBAC/D,WAAW,EAAE,MAAM,CAAC,KAAK;gBACzB,oDAAoD;gBACpD,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;oBACpB,QAAQ,EAAE,CAAC;oBACX,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI;oBAClD,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;oBAC9B,OAAO,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;iBACpD,CAAC,CAAC;gBACH;gDACgC;gBAChC,aAAa,EAAE,OAAO;aACvB,CAAA;QACH,CAAC;QAED,OAAO;YACL,UAAU;YACV,GAAG;YACH,OAAO,EAAE,IAAA,uBAAS,EAAC,GAAG,CAAC;YACvB,QAAQ,EAAE,UAAU;YACpB,KAAK;YACL,KAAK;YACL,WAAW;YACX,+CAA+C;YAC/C,KAAK,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,mBAAmB,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,SAAS,EAAE;SAC/G,CAAA;IACH,CAAC;CACF,CAAA;AAnQY,4CAAgB;AAMrB;IALL,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,oBAAY,EAAE;QAC9B,QAAQ,EAAE,IAAI;QACd,WAAW,EACT,6SAA6S;KAChT,CAAC;IAEC,mBAAA,IAAA,kBAAG,EAAC,YAAY,CAAC,CAAA;IACjB,mBAAA,IAAA,kBAAG,EAAC,KAAK,CAAC,CAAA;IACV,mBAAA,IAAA,kBAAG,GAAE,CAAA;IACL,mBAAA,IAAA,kBAAG,EAAC,UAAU,EAAE,IAAI,CAAC,EAAE,CAAC,kBAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;IAChD,mBAAA,IAAA,kBAAG,EAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC,kBAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;;;;uDAuPhD;2BAlQU,gBAAgB;IAD5B,IAAA,uBAAQ,GAAE;GACE,gBAAgB,CAmQ5B;AAED;;;;;;;;;;;;;;;GAeG;AACH,KAAK,UAAU,sBAAsB,CACnC,QAAgB,EAChB,UAAkB,EAClB,cAAsB;IAEtB,MAAM,GAAG,GAAG,MAAM,IAAA,qBAAa,EAAC,yBAAS,CAAC;SACvC,kBAAkB,CAAC,GAAG,CAAC;SACvB,KAAK,CAAC,sBAAsB,EAAE,EAAE,QAAQ,EAAE,CAAC;SAC3C,QAAQ,CAAC,4BAA4B,EAAE,EAAE,UAAU,EAAE,CAAC;SACtD,QAAQ,CAAC,wBAAwB,EAAE,EAAE,EAAE,EAAE,cAAc,EAAE,CAAC;SAC1D,QAAQ,CAAC,0BAA0B,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC;QACpE,wDAAwD;SACvD,OAAO,CAAC,aAAa,EAAE,KAAK,CAAC;SAC7B,KAAK,CAAC,CAAC,CAAC;SACR,MAAM,EAAE,CAAA;IAEX,MAAM,IAAI,GAAI,GAAG,EAAE,OAAe,EAAE,IAAI,EAAE,SAAS,CAAA;IACnD,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAA;AAC5D,CAAC","sourcesContent":["import { Arg, Ctx, Int, Query, Resolver } from 'type-graphql'\n\nimport { ScalarObject, getRepository } from '@things-factory/shell'\n\nimport { itemRefOf, resolveByDeclaration } from './item-ref.js'\nimport { chooseRecipe } from './choose-recipe.js'\n\nimport { TwinInstance } from '../twin-instance/twin-instance.js'\nimport { TwinEvent } from '../twin-event/twin-event.js'\n\n/*\n * 계보 — **이 개체는 무엇에서 나왔나**, 그리고 **설계대로 만들어졌나.**\n *\n * ── 구성과 계보는 주어가 다르다 ────────────────────────────────────────────────\n * 구성(BOM)의 주어는 품목 **정의**다 — \"트레일러는 프레임 1개로 만든다\"(선언).\n * 계보의 주어는 품목 **개체**다 — \"이 트레일러는 저 프레임 로트에서 나왔다\"(관측).\n * 둘은 같은 모양이라 겹쳐 볼 수 있고, **그 차이가 곧 진단**이다: \"설계는 1개인데 이건 2개를 먹었다\".\n * 트윈이 아니면 못 하는 답이고, 이 질의가 그 답의 재료다.\n *\n * ── 어디서 읽나 ────────────────────────────────────────────────────────────────\n * 계보는 상태가 아니라 **저널**에 있다(EPCIS `TransformationEvent` 의 입력·산출 목록). 상태는\n * 지금 무엇이 있는지만 알고, 없어진 원자재가 무엇이었는지는 이벤트만 안다.\n *\n * ── 규모 ───────────────────────────────────────────────────────────────────────\n * 입력 목록은 **컬럼이 아니라 봉투 안**에 있어서 인덱스로 좁힐 수 없다. 그래서 변환 이벤트만\n * 창으로 끊어 읽고, **끊었으면 끊었다고 말한다.** 그 사실을 감추면 \"이 개체는 원자재가 없다\" 로\n * 읽히고, 계보에서 그 오해는 리콜 범위를 좁게 잡는 결과로 이어진다.\n */\n\n\n@Resolver()\nexport class TwinLineageQuery {\n @Query(returns => ScalarObject, {\n nullable: true,\n description:\n 'Where one item came from (observed) laid next to what its type declares (BOM). Walks TransformationEvent input/output lists in the journal; reports the window it read and whether it was truncated. The declared|observed difference is the diagnosis: \"the recipe says one frame, this one consumed two\".'\n })\n async twinItemLineage(\n @Arg('instanceId') instanceId: string,\n @Arg('epc') epc: string,\n @Ctx() context: ResolverContext,\n @Arg('maxDepth', type => Int, { nullable: true }) maxDepth?: number,\n @Arg('window', type => Int, { nullable: true }) window?: number\n ): Promise<any | null> {\n const domainId = context.state.domain.id\n const inst = await getRepository(TwinInstance).findOne({ where: { domain: { id: domainId }, instanceId } })\n if (!inst) return null\n\n const depthCap = Math.min(Math.max(maxDepth ?? 6, 1), 12)\n const windowCap = Math.min(Math.max(window ?? 5000, 1), 50000)\n\n /*\n * 변환 이벤트만 읽는다. **최신부터** 읽는다 — 계보를 묻는 개체는 대개 최근에 만들어진 것이고,\n * 창을 넘겨 잘릴 때 잃는 쪽이 오래된 것이어야 덜 아프다.\n */\n /*\n * 관계를 **컬럼으로** 묻는다 (2026-08-28). `find({ where: { domain: { id } } })` 은 관계를 가진\n * 엔티티에서 두 단계 질의가 되고 **안쪽에 상한이 없다** — `take` 가 있어도 조건에 맞는 행을 전부\n * 만든 뒤 밖에서 자른다. 변환 사건이 적은 트윈에서는 표가 안 나지만, 변환을 많이 내는 트윈에서는\n * 이 한 번이 그 종류 전체를 만든다. 질의 빌더로 물으면 상한이 안쪽에 걸린다.\n *\n * 종류가 정확일치이므로 `(domain, instance, event_type, event_time)` 색인이 시각 순서까지 준다.\n */\n const rows: any[] = await getRepository(TwinEvent)\n .createQueryBuilder('e')\n .where('e.domain = :domainId', { domainId })\n .andWhere('e.instanceId = :instanceId', { instanceId })\n .andWhere('e.eventType = :eventType', { eventType: 'epcis.TransformationEvent' })\n .orderBy('e.eventTime', 'DESC')\n .limit(windowCap)\n .getMany()\n const truncated = rows.length >= windowCap\n\n /* 산출 → 그 변환. 한 개체는 한 번만 만들어지지만, 데이터가 그렇지 않을 수 있으므로 목록으로 든다. */\n const madeBy = new Map<string, { inputs: string[]; at?: string; tx?: string }[]>()\n for (const r of rows) {\n const d = r.payload?.data ?? {}\n const outs: string[] = d.outputEPCList ?? []\n const ins: string[] = d.inputEPCList ?? []\n const step = { inputs: ins, at: r.eventTime, tx: d.bizTransactionList?.[0]?.bizTransaction }\n for (const o of outs) {\n const list = madeBy.get(o) ?? []\n list.push(step)\n madeBy.set(o, list)\n }\n }\n\n /* 거슬러 올라간다 — 너비 우선, 지나온 것은 다시 파지 않는다(사이클·공유 부품). */\n const items: any[] = [{ epc, itemRef: itemRefOf(epc), depth: 0 }]\n const edges: any[] = []\n const seen = new Set<string>([epc])\n let depthTruncated = false\n let frontier = [{ epc, depth: 0 }]\n while (frontier.length) {\n const next: typeof frontier = []\n for (const cur of frontier) {\n if (cur.depth >= depthCap) { depthTruncated = true; continue }\n for (const step of madeBy.get(cur.epc) ?? []) {\n for (const input of step.inputs) {\n edges.push({ from: cur.epc, to: input, at: step.at, tx: step.tx, depth: cur.depth + 1 })\n if (seen.has(input)) continue\n seen.add(input)\n items.push({ epc: input, itemRef: itemRefOf(input), depth: cur.depth + 1 })\n next.push({ epc: input, depth: cur.depth + 1 })\n }\n }\n }\n frontier = next\n }\n\n /*\n * **겹쳐 본다.** 이 개체의 품목이 어느 레시피의 산출이면, 그 레시피가 선언한 투입과 실제로\n * 소비된 것을 자재별로 나란히 센다.\n *\n * 개체(EPC)와 자재 키를 잇는 것은 `productionSpec.binding`(자재 키 → GS1 품목 참조)이다.\n * 바인딩이 없으면 **비교하지 않는다** — 짐작으로 이으면 \"설계와 다르다\" 는 거짓 진단이 나오고,\n * 그건 아무 말도 안 하는 것보다 나쁘다.\n */\n const spec: any = (inst.model as any)?.productionSpec\n const binding: Record<string, string> = spec?.binding ?? {}\n const refToMaterial = new Map(Object.entries(binding).map(([mat, ref]) => [String(ref), mat]))\n /*\n * ── **화면은 끝마디로 묻는다** (2026-08-23) ─────────────────────────────────\n *\n * 서랍의 포커스 식별자는 클래스의 끝마디다(`net/class/product-M0000438`). 대응 표의 값은 전체\n * 식별자(`https://…/class/product-M0000438`)이므로 곧바로는 맞지 않고, 그래서 「선언된 자재가\n * 아닙니다」가 떴다 — 그 품목은 대응 표에 있었다.\n *\n * 끝마디로도 찾되 **하나뿐일 때만** 인정한다. 둘 이상이 같은 끝마디를 가지면 어느 것인지 모르므로\n * 아무 말도 하지 않는다 — 짐작으로 이으면 남의 레시피와 견주게 되고, 그것이 「설계와 다르다」는\n * 거짓 진단이 된다.\n */\n const materialOf = (e: string) => resolveByDeclaration(itemRefOf(e), refToMaterial) ?? null\n\n const myMaterial = materialOf(epc)\n const recipes: any[] = spec?.definition?.recipes ?? []\n\n /*\n * ── 「무엇으로 만들었나」는 **적혀 있다** (2026-09-05) ───────────────────────\n *\n * 예전에는 이 자리가 자재로 되짚어 `recipes.find(...)` 로 **먼저 걸리는 하나**를 골랐다. 배열\n * 순서가 답을 정한다는 뜻이고, 한 자재에 레시피가 둘이면 틀린 답이 확신에 찬 얼굴로 나온다.\n * 실제로 그런 자재가 있다 — `BOLT-M8` 에 `RCP-BOLT-STD` · `RCP-BOLT-FAST` 둘이다.\n *\n * 그런데 답은 이미 기록되어 있다. 만들 때 정해지는 사실이고 커널이 오더에 싣는다\n * (`FlowOrder.recipeKey`). 변환 사건은 그 오더를 거래 식별자로 가리키고(`bizTransactionList`),\n * 이 함수는 그 값을 이미 `tx` 로 꺼내 놓고도 쓰지 않았다.\n *\n * 세 자리가 다 있었고 마지막 연결만 없었다. 「가릴 수 없다」가 아니라 **안 읽고 있었다.**\n *\n * ── 규칙 셋 — 짐작하지 않는다 ───────────────────────────────────────────────\n * 오더가 말한다 그것을 쓴다. 버전이 둘이든 열이든 상관없다\n * 오더가 말 안 하고\n * 후보가 하나 그것을 쓴다 — 고를 것이 없으면 짐작이 아니다\n * 후보가 둘 이상 **답하지 않는다.** 어느 것인지 모르는 채로 견주면 거짓 진단이 된다\n *\n * 마지막 줄이 이 함수가 식별자 되찾기에 이미 적용하던 규율이다(「후보가 둘 이상이면 답하지\n * 않는다」). 두 줄 위에서만 안 지키고 있었다.\n */\n const candidates = myMaterial\n ? recipes.filter((r: any) => (r.outputs ?? []).some((o: any) => o.material === myMaterial))\n : []\n\n /* 이 개체를 만든 변환이 가리키는 거래 — 그 오더가 레시피를 말한다. */\n const madeTx = (madeBy.get(epc) ?? []).map(s => s.tx).find(Boolean)\n const saidRecipeKey = madeTx ? await recipeKeyOfTransaction(domainId, instanceId, String(madeTx)) : undefined\n\n const chosen = chooseRecipe(recipes, candidates, saidRecipeKey)\n const recipe = chosen.recipe\n\n /*\n * ── 「모르는 개체」와 「대응이 없는 개체」는 다른 사실이다 (2026-08-23) ──────\n *\n * 화면이 「이 개체는 선언된 자재가 아닙니다(라인 내부의 중간 산물일 수 있습니다)」라고 말했는데,\n * 그 품목은 원본의 품목 선언에 **이름까지 있었다**(「냉동그린빈스」). 바인딩 표가 그 품목을 담지\n * 못한 것이고, 그것은 「중간 산물」과 전혀 다른 이야기다 — 사용자가 할 일도 다르다(원본을 볼 일이\n * 아니라 대응을 고칠 일이다).\n *\n * 그래서 개체가 가리키는 품목이 **모델의 품목 선언에 있는지** 함께 본다. 이름은 그 자리에 있으니\n * 함께 낸다(화면이 사람에게 무엇을 말하는지 알 수 있게).\n */\n const definitions: any[] = (inst.model as any)?.materialDefinitions ?? []\n const defByRef = new Map(definitions.filter(d => d?.id).map(d => [String(d.id), d]))\n /* 정의도 같은 문으로 되찾는다 — 규칙 하나, 후보가 둘 이상이면 답하지 않는다. */\n const myRef = itemRefOf(epc)\n const myDef = resolveByDeclaration(myRef, defByRef)\n\n let conformance: any = null\n if (!Object.keys(binding).length) {\n conformance = { comparable: false, reason: 'no-binding' }\n } else if (!myMaterial && myDef) {\n /* 원본은 이 품목을 안다 — 개체를 자재에 잇는 대응이 없어서 비교하지 못한다. */\n conformance = {\n comparable: false,\n reason: 'item-known-not-bound',\n ...(typeof myDef.name === 'string' && myDef.name ? { itemName: myDef.name } : {})\n }\n } else if (!myMaterial) {\n conformance = { comparable: false, reason: 'item-not-bound' }\n } else if (chosen.basis === 'said-but-undeclared') {\n /* 오더는 말했는데 그 레시피가 모델의 선언에 없다 — 원본과 선언이 어긋난 것이지 모르는 것이 아니다. */\n conformance = { comparable: false, reason: 'recipe-not-declared', recipe: saidRecipeKey }\n } else if (chosen.basis === 'ambiguous') {\n /* 짐작하지 않는다 — 어느 것으로 만들었는지 말해 주는 사실이 없다. 후보를 밝혀 사람이 판단하게 한다. */\n conformance = {\n comparable: false,\n reason: 'many-recipes-for-material',\n recipes: candidates.map((r: any) => r.key).sort()\n }\n } else if (!recipe) {\n conformance = { comparable: false, reason: 'no-recipe-for-material' }\n } else if (!(madeBy.get(epc) ?? []).length) {\n /*\n * ── **안 본 것을 「다르다」고 하지 않는다** (2026-09-18 · :3000 화면에서 확인) ─────────\n *\n * 이 개체를 만든 변환이 저널에 하나도 없으면 투입을 하나도 관측하지 못한 것이다. 그런데\n * 아래 셈은 그대로 돌아 관측을 전부 0 으로 적었고, 레시피가 요구한 자재 23 줄이 전부\n * 「안 맞는다」가 됐다 — 화면은 「레시피와 다릅니다」를 확신에 찬 얼굴로 그렸다. 실제로는\n * 원본이 변환을 아직 안 보냈을 뿐이다(`mes-miratek` 의 카메라 로트가 그 경우다).\n *\n * 이 파일의 규율이 이미 그것을 말한다 — 짐작으로 이으면 「설계와 다르다」는 거짓 진단이\n * 되고, 그건 아무 말도 안 하는 것보다 나쁘다. 관측이 **없는** 것도 같은 부류다.\n */\n conformance = { comparable: false, reason: 'made-not-observed', recipe: recipe.key }\n } else {\n /*\n * 관측: **바인딩된 자재까지 내려가 센다** — 중간 산물(WIP)은 통과한다.\n *\n * 다단 라인에서 완제품의 직접 투입은 원자재가 아니라 **WIP** 다: 원자재는 첫 공정에서\n * 소비되고 완제품은 마지막 공정에서 나온다. 그래서 직접 투입만 세면 레시피가 선언한 자재와\n * **영원히 안 맞는다**(검사가 이것을 잡았다).\n *\n * WIP 은 라우트의 내부 산물이고 선언된 자재가 아니다 — 레시피는 자재로 말하므로 비교도\n * 자재로 해야 한다. 바인딩에 있으면 거기서 멈추고, 없으면 그 위로 계속 따라간다.\n *\n * 다만 **다른 완제품까지 넘어가지 않는다**: 바인딩된 것을 만나면 멈추므로, 하위조립도\n * 자재로 선언돼 있으면 그 자리에서 세어진다(그 아래는 그 자재 자신의 레시피가 볼 일이다).\n */\n const observed = new Map<string, number>()\n let unbound = 0\n const parentsOf = (e: string) => edges.filter(x => x.from === e).map(x => x.to)\n const walked = new Set<string>([epc])\n let layer = parentsOf(epc)\n while (layer.length) {\n const deeper: string[] = []\n for (const e of layer) {\n if (walked.has(e)) continue\n walked.add(e)\n const mat = materialOf(e)\n if (mat) { observed.set(mat, (observed.get(mat) ?? 0) + 1); continue }\n const up = parentsOf(e)\n /* 바인딩에도 없고 더 올라갈 곳도 없다 — 정말 모르는 투입이다(수만 낸다). */\n if (!up.length) { unbound++; continue }\n deeper.push(...up)\n }\n layer = deeper\n }\n const declared = new Map<string, number>((recipe.inputs ?? []).map((i: any) => [i.material, i.qty ?? null]))\n const keys = [...new Set([...declared.keys(), ...observed.keys()])]\n conformance = {\n comparable: true,\n material: myMaterial,\n recipe: recipe.key,\n /* 그 레시피를 **어떻게 알았나** — 화면이 「기록으로 안다」와 「하나뿐이라 안다」를 구별할 수 있게. */\n recipeBasis: chosen.basis,\n /* 자재마다 선언·관측을 나란히. 어느 한쪽에만 있는 것도 사실이다(빠뜨리지 않는다). */\n lines: keys.map(m => ({\n material: m,\n declared: declared.has(m) ? declared.get(m) : null,\n observed: observed.get(m) ?? 0,\n matches: declared.get(m) === (observed.get(m) ?? 0)\n })),\n /* 바인딩에 없는 투입 — 설계에 없는 것이 들어갔을 수도, 바인딩이 덜 선언됐을 수도 있다.\n 어느 쪽인지 우리는 모르므로 **수만 낸다.** */\n unboundInputs: unbound\n }\n }\n\n return {\n instanceId,\n epc,\n itemRef: itemRefOf(epc),\n material: myMaterial,\n items,\n edges,\n conformance,\n /* 무엇을 근거로 답했나 — 창을 넘겨 끊겼으면 계보가 실제보다 얕게 보인다. */\n basis: { source: 'journal', transformationsRead: rows.length, truncated, depthTruncated, depthCap, windowCap }\n }\n }\n}\n\n/**\n * 그 거래를 가리키는 오더가 **어느 레시피로** 만들었다고 말하는가.\n *\n * ── 왜 거래 식별자로 묻나 ───────────────────────────────────────────────────\n * EPCIS 변환 사건에는 레시피가 없다 — 표준에 그 자리가 없고, 대신 생산 오더를 거래로 가리킨다\n * (`bizTransactionList`). 오더 사건은 `recipeKey` 와 그 거래 식별자를 **둘 다** 든다. 그래서 이\n * 두 칸이 EPCIS 에서 오더로 건너가는 다리다(§`twin-event-keys.ts`).\n *\n * ── 색인을 탄다 ─────────────────────────────────────────────────────────────\n * `ix_twin_event_9 (domain, instanceId, bizTransaction)` 로 좁힌다. 이 칸이 없던 때는 payload 를\n * 문자열로 훑어야 했고 32만 행에 3분이 걸렸다 — 화면이 기다릴 비용이 아니다.\n *\n * ── 못 찾으면 **답하지 않는다** ──────────────────────────────────────────────\n * 옛 행에는 이 칸이 비어 있다(과거 행은 채우지 않는다는 규율). 그때는 `undefined` 를 내고, 부르는\n * 쪽이 후보 수로 판단한다 — 여기서 아무거나 고르면 예전과 같은 결함이 된다.\n */\nasync function recipeKeyOfTransaction(\n domainId: string,\n instanceId: string,\n bizTransaction: string\n): Promise<string | undefined> {\n const row = await getRepository(TwinEvent)\n .createQueryBuilder('e')\n .where('e.domain = :domainId', { domainId })\n .andWhere('e.instanceId = :instanceId', { instanceId })\n .andWhere('e.bizTransaction = :tx', { tx: bizTransaction })\n .andWhere('e.eventType = :eventType', { eventType: 'order.status' })\n /* 어느 줄이든 같은 오더를 말하므로 하나면 된다 — 가장 이른 것이 그 오더가 선 순간이다. */\n .orderBy('e.eventTime', 'ASC')\n .limit(1)\n .getOne()\n\n const said = (row?.payload as any)?.data?.recipeKey\n return typeof said === 'string' && said ? said : undefined\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/headless-twin",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.227",
|
|
4
4
|
"main": "dist-server/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dist-server/index.js",
|
|
@@ -41,11 +41,11 @@
|
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@operato/ops-contract": "^0.9.40",
|
|
43
43
|
"@operato/twin-kernel": "^0.11.29",
|
|
44
|
-
"@things-factory/auth-base": "^10.1.
|
|
45
|
-
"@things-factory/cache-service": "^10.1.
|
|
44
|
+
"@things-factory/auth-base": "^10.1.227",
|
|
45
|
+
"@things-factory/cache-service": "^10.1.227",
|
|
46
46
|
"@things-factory/env": "^10.1.149",
|
|
47
|
-
"@things-factory/ingest": "^10.1.
|
|
48
|
-
"@things-factory/shell": "^10.1.
|
|
47
|
+
"@things-factory/ingest": "^10.1.227",
|
|
48
|
+
"@things-factory/shell": "^10.1.227"
|
|
49
49
|
},
|
|
50
|
-
"gitHead": "
|
|
50
|
+
"gitHead": "f66591e3864aab08a6fb93fb1fcf1c9de6b546ac"
|
|
51
51
|
}
|
|
@@ -602,7 +602,7 @@ async function energyTopologyOf(
|
|
|
602
602
|
* 이 대상들의 **마지막 업무 기록 시각**(ISO). 기록이 없으면 null.
|
|
603
603
|
*
|
|
604
604
|
* ── 두 가지를 함께 고쳤다 (2026-08-28 실측) ─────────────────────────────────
|
|
605
|
-
* 예전
|
|
605
|
+
* 예전 버전은 이랬다.
|
|
606
606
|
*
|
|
607
607
|
* findOne({ where: { domain: { id }, instanceId: In(…), eventType: In(셋) }, order: { revision: 'DESC' } })
|
|
608
608
|
*
|
|
@@ -967,7 +967,7 @@ export class TwinEngine {
|
|
|
967
967
|
* 한 번의 지우기가 다루는 행 수 — 쓰기 잠금을 이 만큼만 잡는다.
|
|
968
968
|
*
|
|
969
969
|
* ── 20,000 이 왜 컸나 (2026-08-28 실측) ────────────────────────────────────
|
|
970
|
-
* 앞
|
|
970
|
+
* 앞 버전은 20,000 이었고 근거는 「한 문장으로 수백만 행을 지우면 74초였다」였다. 그것은 **더 나쁜
|
|
971
971
|
* 쪽과 견준 수**이고, 20,000 자체를 잰 것이 아니었다. 실제로 재 보니 이렇다(서버 기록):
|
|
972
972
|
*
|
|
973
973
|
* hatio-mx1 20,123줄 5.6초
|
|
@@ -117,7 +117,7 @@ export type StoredCommand = TwinCommand & {
|
|
|
117
117
|
*
|
|
118
118
|
* ── 계약에도 같은 칸을 두었다 ──────────────────────────────────────────
|
|
119
119
|
* `ops-contract` 의 `TwinCommand.effect` 가 이것이다. 다만 이 저장소는 그 패키지를 레지스트리에서
|
|
120
|
-
* 받으므로(`^0.9.0`), 다음
|
|
120
|
+
* 받으므로(`^0.9.0`), 다음 버전이 올라올 때까지 여기에 산다 — 위의 `approvalRef`·`causeRef` 와 같다.
|
|
121
121
|
*/
|
|
122
122
|
effect?: { status: string; at: string; seen: number }
|
|
123
123
|
/** 받아들였으나 저쪽이 덧붙인 말 — 해석하지 않고 그대로(§`CommandAck.note`). */
|
|
@@ -37,7 +37,7 @@ import { APPROVAL_STANDS, actuationTarget, isActuationAllowed, type ActuationTar
|
|
|
37
37
|
*
|
|
38
38
|
* 값은 흘렀다 — JS 객체가 키를 안 버리므로. 어제 `dispatch_note` 에 실제로 앉았다. 그런데
|
|
39
39
|
* **커넥터를 만드는 사람이 그 칸이 있다는 것을 알 방법이 없었다.** 우리가 어제 하루 종일
|
|
40
|
-
* 「자리는 있고 길이 없다」로 부른 것의 타입
|
|
40
|
+
* 「자리는 있고 길이 없다」로 부른 것의 타입 버전이다.
|
|
41
41
|
*
|
|
42
42
|
* 좁히지 않는다: 세 칸을 다 실어 `CommandAck` 와 같은 모양으로 둔다. 커널이 어느 칸을 어떻게
|
|
43
43
|
* 쓰는지는 `dispatch` 가 정하고, 이 자리는 나르기만 한다.
|
|
@@ -162,7 +162,7 @@ export function planImport(
|
|
|
162
162
|
if (b.kind !== 'operato-twin/connections') {
|
|
163
163
|
return { actions: [], needsHuman: [], collisions: [], errors: [`이 파일은 연결 설정 파일가 아닙니다(kind: ${JSON.stringify((b as any).kind ?? null)})`] }
|
|
164
164
|
}
|
|
165
|
-
if (b.version !== 1) errors.push(`모르는
|
|
165
|
+
if (b.version !== 1) errors.push(`모르는 버전입니다(version: ${JSON.stringify((b as any).version)}) — 읽을 수 있는 것만 읽습니다`)
|
|
166
166
|
if (!Array.isArray(b.connections)) return { actions: [], needsHuman: [], collisions: [], errors: [...errors, 'connections 가 목록이 아닙니다'] }
|
|
167
167
|
|
|
168
168
|
const have = new Map(existing.map(e => [e.source, e]))
|
|
@@ -233,7 +233,7 @@ export function sayPlan(plan: ImportPlan): string {
|
|
|
233
233
|
*
|
|
234
234
|
* 패키지에는 비밀 칸의 이름만 있고 값은 환경에서 옵니다. 그 이름을 **사람이 손으로 적으므로** 규칙이
|
|
235
235
|
* 흔들리면 「넣었는데 안 읽힌다」가 됩니다 — 오늘 종일 잡은 「양쪽 다 초록인데 안 간다」의 설정 쪽
|
|
236
|
-
*
|
|
236
|
+
* 버전입니다. 그래서 이 규칙을 한 곳에 두고 시험으로 못 박습니다.
|
|
237
237
|
*
|
|
238
238
|
* 낙타 이름의 경계를 밑줄로 가릅니다 — 안 그러면 `HOOKSECRET` 과 `HOOK_SECRET` 이 갈려 한쪽만 읽힙니다.
|
|
239
239
|
*/
|
|
@@ -317,7 +317,7 @@ export function takeInSequence(args: {
|
|
|
317
317
|
if (verdict.kind === 'first') {
|
|
318
318
|
firstSeq = item.seq as number
|
|
319
319
|
/*
|
|
320
|
-
* 계약이 `unseenBefore` 를 내면 그것을 쓴다. 설치된
|
|
320
|
+
* 계약이 `unseenBefore` 를 내면 그것을 쓴다. 설치된 버전이 그보다 오래됐으면 번호에서 센다 —
|
|
321
321
|
* 첫 봉투의 번호 아래로 그만큼이 있고 우리는 그것을 받은 적이 없다.
|
|
322
322
|
*
|
|
323
323
|
* ⚠ 이 두 번째 길은 계약 0.9.9 가 깔리면 지운다. 그때까지 두는 이유는, 계약을 기다리며
|
|
@@ -832,7 +832,7 @@ export type ReferenceGroundingLevel = 'synthetic' | 'mock' | 'instance' | 'produ
|
|
|
832
832
|
export interface ReferenceGrounding {
|
|
833
833
|
level: ReferenceGroundingLevel
|
|
834
834
|
/**
|
|
835
|
-
* **무엇에 대고 확인했나** — 사람이 읽는 한 줄(원본의
|
|
835
|
+
* **무엇에 대고 확인했나** — 사람이 읽는 한 줄(원본의 버전·인스턴스·목의 이름).
|
|
836
836
|
*
|
|
837
837
|
* `instance`·`product` 를 주장하면서 이것이 비어 있으면 `groundingOf` 가 `unknown` 으로 내린다 —
|
|
838
838
|
* 가리키는 대상이 없는 주장은 주장이 아니고, 그런 주장은 낡아도 아무도 알 수 없다.
|
|
@@ -228,7 +228,7 @@ async function loadLiveCursor(
|
|
|
228
228
|
*
|
|
229
229
|
* 부팅 재부착은 `status: 'connected'` 인 레퍼런스만 되살린다(§`resumeReferenceLiveFeeds`). 그래서
|
|
230
230
|
* 원본이 **잠깐** 끊긴 것이 그 행을 `error` 로 만들고, 다음 재기동에서 그 피드는 아예 붙지 않았다 —
|
|
231
|
-
* 실측으로 그렇게 났다(09:25
|
|
231
|
+
* 실측으로 그렇게 났다(09:25 버전에 `[twin-live]` 줄이 한 줄도 없었다).
|
|
232
232
|
*
|
|
233
233
|
* 두 축을 한 칸에 섞은 것이 원인이다:
|
|
234
234
|
* `status` **연결 설정이 성립하나** — 접속 시험·마스터 동기가 정하는 축(재부착의 자격이다)
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
/**
|
|
9
9
|
* 짐작하지 않는 규칙.
|
|
10
10
|
*
|
|
11
|
-
* order 오더가 말했다.
|
|
11
|
+
* order 오더가 말했다. 버전이 둘이든 열이든 그것을 쓴다
|
|
12
12
|
* only-candidate 오더는 말 안 했지만 후보가 하나다 — 고를 것이 없으면 짐작이 아니다
|
|
13
13
|
* ambiguous 후보가 둘 이상인데 말해 주는 것이 없다 → **답하지 않는다**
|
|
14
14
|
* said-but-undeclared 오더는 말했는데 그 레시피가 선언에 없다 — 모르는 것이 아니라 어긋난 것이다
|
|
@@ -149,7 +149,7 @@ export class TwinLineageQuery {
|
|
|
149
149
|
* 세 자리가 다 있었고 마지막 연결만 없었다. 「가릴 수 없다」가 아니라 **안 읽고 있었다.**
|
|
150
150
|
*
|
|
151
151
|
* ── 규칙 셋 — 짐작하지 않는다 ───────────────────────────────────────────────
|
|
152
|
-
* 오더가 말한다 그것을 쓴다.
|
|
152
|
+
* 오더가 말한다 그것을 쓴다. 버전이 둘이든 열이든 상관없다
|
|
153
153
|
* 오더가 말 안 하고
|
|
154
154
|
* 후보가 하나 그것을 쓴다 — 고를 것이 없으면 짐작이 아니다
|
|
155
155
|
* 후보가 둘 이상 **답하지 않는다.** 어느 것인지 모르는 채로 견주면 거짓 진단이 된다
|
|
@@ -98,7 +98,7 @@ export async function backfillSubjects(
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
/*
|
|
101
|
-
* ── 진행 지점을
|
|
101
|
+
* ── 진행 지점을 **회차 안에서만** 든다 (2026-08-27 · 사용자 판단) ────────────────
|
|
102
102
|
*
|
|
103
103
|
* 처음에는 테이블에 적었다. 사용자 판단으로 없앴다 — 사건이 들어올 때 파생 테이블도 같이 적히므로,
|
|
104
104
|
* 옛 사건을 옮기는 일은 새 파생 테이블을 만들 때와 계산 규칙을 고쳐 다시 만들 때뿐이다. 그 두 경우가
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 버린다고 말했다.** 값은 흘렀다 — JS 객체가 키를 안 버리므로. 실제로 `dispatch_note` 에 앉았다.
|
|
13
13
|
*
|
|
14
14
|
* 그런데 **커넥터를 만드는 사람이 그 칸이 있다는 것을 알 방법이 없었다.** 이 저장소에서 하루
|
|
15
|
-
* 종일 「자리는 있고 길이 없다」로 부른 것의 타입
|
|
15
|
+
* 종일 「자리는 있고 길이 없다」로 부른 것의 타입 버전이다 — 동작은 하고 계약이 안 알려 준다.
|
|
16
16
|
*
|
|
17
17
|
* ── 왜 원문을 읽나 ──────────────────────────────────────────────────────────
|
|
18
18
|
* 타입은 런타임에 없다. 그래서 「세 모양이 같은가」를 값으로 잴 수 없고, 원문에서 읽는 것이
|
|
@@ -67,7 +67,7 @@ test('빈 입력에서 모든 갈래가 들어온 것이 없다 — 없는 것
|
|
|
67
67
|
* 어휘가 다섯이다 — 사건 넷과 사건이 아닌 것 하나(마스터데이터). 어느 갈래도 값을 지어내지 않는다.
|
|
68
68
|
*
|
|
69
69
|
* `undated` 는 `masterData` 와 같은 규칙으로 언제나 있다(빈 것이면 빈 객체). 없을 때 칸을 빼면
|
|
70
|
-
* 읽는 쪽이 「이
|
|
70
|
+
* 읽는 쪽이 「이 버전이 그것을 안 센다」와 「셌는데 0이다」를 구별하지 못한다.
|
|
71
71
|
*/
|
|
72
72
|
const none = { accepted: [], rejected: [], masterData: [], undated: {} }
|
|
73
73
|
assert.deepEqual(ingestCanonicalRecords([], 't', NOW), none)
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* 저널을 계산하면 items 741 · orders 2,856 · tasks 9,250 · nowTime 2026-04-15
|
|
12
12
|
* 라이브 미러 items 0 · orders 0 · tasks 0 · nowTime 2026-01-01
|
|
13
13
|
* 저장된 스냅샷 rev 221,884 · items 741 · orders 2,780 · tasks 6,353 (어제 것)
|
|
14
|
-
* 그런데 이
|
|
14
|
+
* 그런데 이 버전이 같은 키에 **빈 상태**를 쓰고 있었다
|
|
15
15
|
*
|
|
16
16
|
* 그러면 되살릴 캐시가 사라진다. 재기동이 상태를 버리는 것은 저널에서 되살릴 수 있지만, **캐시를
|
|
17
17
|
* 지우는 것은 그 복구 수단을 함께 지운다.**
|
|
@@ -34,7 +34,7 @@ const ALL = [STD, FAST, OTHER]
|
|
|
34
34
|
|
|
35
35
|
/* ── 오더가 말하면 그것을 쓴다 ───────────────────────────────────────────── */
|
|
36
36
|
|
|
37
|
-
test('★ 오더가 말한 레시피를 쓴다 —
|
|
37
|
+
test('★ 오더가 말한 레시피를 쓴다 — 버전이 둘이어도 고민하지 않는다', () => {
|
|
38
38
|
const r = chooseRecipe(ALL, [STD, FAST], 'RCP-BOLT-FAST')
|
|
39
39
|
|
|
40
40
|
assert.equal(r.recipe, FAST, '오더가 말했는데 다른 것을 골랐다')
|
|
@@ -109,7 +109,7 @@ test('★ 걷을 조건이 적혀 있다 — 부르는 데가 없어지면 지
|
|
|
109
109
|
*
|
|
110
110
|
* ★ 칩을 고른 순간 이 갈래가 꺼져서, **고르지 않은 쪽이 더 빨랐다.**
|
|
111
111
|
*
|
|
112
|
-
* 원장은 종류 넷을 담아 묻고 그 위에 단계 칩을 겹친다. 앞
|
|
112
|
+
* 원장은 종류 넷을 담아 묻고 그 위에 단계 칩을 겹친다. 앞 버전은 `bizStep` 필터를 만나면 모양을
|
|
113
113
|
* 알아보지 못해 예전 경로로 떨어졌다 — 좁혀 물으면 느려지는 화면이 된다.
|
|
114
114
|
*/
|
|
115
115
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* 값이 있다. 배선이 다시 끊기면 이 시험이 먼저 말한다.
|
|
17
17
|
*
|
|
18
18
|
* ── 왜 「늘었나」로 확인하면 안 되나 ────────────────────────────────────────
|
|
19
|
-
* 재기동 뒤 수가 늘어도 그것은 커서가 이어진 증거가 아니다 — 실제로 같은
|
|
19
|
+
* 재기동 뒤 수가 늘어도 그것은 커서가 이어진 증거가 아니다 — 실제로 같은 버전에 다른 변경(공정별 전이)이
|
|
20
20
|
* 들어가 작업 수가 배로 늘었고, 그 사이 커서는 여전히 매번 새로 만들어졌다. 확인할 것은 **배선**이다.
|
|
21
21
|
*/
|
|
22
22
|
import { test } from 'node:test'
|
|
@@ -237,7 +237,7 @@ test('검사 명세가 모델로 통과한다 — 표시명은 name 으로', ()
|
|
|
237
237
|
assert.equal(specs[0].id, 'weld-cert')
|
|
238
238
|
assert.equal(specs[0].name, 'twin.x.test.weld', '표시명은 name 자리에 온다(어휘를 하나로)')
|
|
239
239
|
assert.equal('description' in specs[0], false, '두 자리에 같은 값을 두지 않는다')
|
|
240
|
-
assert.equal(specs[0].version, '2026.1', '
|
|
240
|
+
assert.equal(specs[0].version, '2026.1', '버전이 남아야 한다 — 어느 버전으로 검증했나')
|
|
241
241
|
})
|
|
242
242
|
|
|
243
243
|
test('선언하지 않으면 키를 만들지 않는다 — 빈 배열을 "선언했다" 로 보고하지 않는다', () => {
|
|
@@ -109,7 +109,7 @@ test('「유입 없음」과 「닿지 못함」은 다른 판정이다', () =>
|
|
|
109
109
|
*
|
|
110
110
|
* 처음 구현에서 읽기 실패가 레퍼런스 행의 `status` 를 `'error'` 로 적었다. 그것이 **피드를 영구히
|
|
111
111
|
* 껐다**: 부팅 재부착은 `status: 'connected'` 인 레퍼런스만 되살리므로, 원본이 **잠깐** 끊긴 것이
|
|
112
|
-
* 다음 재기동에서 그 트윈을 아예 굶겼다. 실측으로 그렇게 났다 — 그
|
|
112
|
+
* 다음 재기동에서 그 트윈을 아예 굶겼다. 실측으로 그렇게 났다 — 그 버전의 로그에 `[twin-live]` 줄이
|
|
113
113
|
* 한 줄도 없었다.
|
|
114
114
|
*
|
|
115
115
|
* 두 축을 한 칸에 섞은 것이 원인이다.
|