@things-factory/headless-twin 10.0.17 → 10.0.18

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.
Files changed (122) hide show
  1. package/dist-server/engine/canonical-ingest.d.ts +12 -85
  2. package/dist-server/engine/canonical-ingest.js +92 -26
  3. package/dist-server/engine/canonical-ingest.js.map +1 -1
  4. package/dist-server/engine/energy-topology.js +3 -3
  5. package/dist-server/engine/energy-topology.js.map +1 -1
  6. package/dist-server/engine/kpi-fold.d.ts +91 -3
  7. package/dist-server/engine/kpi-fold.js +149 -25
  8. package/dist-server/engine/kpi-fold.js.map +1 -1
  9. package/dist-server/engine/kpi-query.js +99 -11
  10. package/dist-server/engine/kpi-query.js.map +1 -1
  11. package/dist-server/engine/local-declarations.js +6 -5
  12. package/dist-server/engine/local-declarations.js.map +1 -1
  13. package/dist-server/engine/model-vocabulary.js +2 -2
  14. package/dist-server/engine/model-vocabulary.js.map +1 -1
  15. package/dist-server/engine/oee-accumulator.d.ts +2 -1
  16. package/dist-server/engine/oee-accumulator.js +3 -2
  17. package/dist-server/engine/oee-accumulator.js.map +1 -1
  18. package/dist-server/engine/twin-engine.d.ts +72 -2
  19. package/dist-server/engine/twin-engine.js +150 -4
  20. package/dist-server/engine/twin-engine.js.map +1 -1
  21. package/dist-server/routes.js +1 -1
  22. package/dist-server/routes.js.map +1 -1
  23. package/dist-server/service/index.d.ts +1 -1
  24. package/dist-server/service/index.js +3 -0
  25. package/dist-server/service/index.js.map +1 -1
  26. package/dist-server/service/reference/reference-live.js +1 -1
  27. package/dist-server/service/reference/reference-live.js.map +1 -1
  28. package/dist-server/service/reference/reference-master.d.ts +11 -2
  29. package/dist-server/service/reference/reference-master.js +1 -1
  30. package/dist-server/service/reference/reference-master.js.map +1 -1
  31. package/dist-server/service/reference/reference-resolver.js +2 -2
  32. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  33. package/dist-server/service/reference/template-registry.d.ts +1 -1
  34. package/dist-server/service/reference/template-registry.js.map +1 -1
  35. package/dist-server/service/twin-backfill/backfill-period-facts.d.ts +24 -0
  36. package/dist-server/service/twin-backfill/backfill-period-facts.js +96 -0
  37. package/dist-server/service/twin-backfill/backfill-period-facts.js.map +1 -0
  38. package/dist-server/service/twin-backfill/backfill-shape.d.ts +34 -0
  39. package/dist-server/service/twin-backfill/backfill-shape.js +75 -0
  40. package/dist-server/service/twin-backfill/backfill-shape.js.map +1 -0
  41. package/dist-server/service/twin-backfill/index.d.ts +2 -0
  42. package/dist-server/service/twin-backfill/index.js +6 -0
  43. package/dist-server/service/twin-backfill/index.js.map +1 -0
  44. package/dist-server/service/twin-backfill/twin-backfill-resolver.d.ts +10 -0
  45. package/dist-server/service/twin-backfill/twin-backfill-resolver.js +44 -0
  46. package/dist-server/service/twin-backfill/twin-backfill-resolver.js.map +1 -0
  47. package/dist-server/service/twin-control/twin-control-mutation.js +2 -2
  48. package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
  49. package/dist-server/service/twin-lifecycle/domain-catalog.js +2 -1
  50. package/dist-server/service/twin-lifecycle/domain-catalog.js.map +1 -1
  51. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
  52. package/dist-server/service/twin-model/name-index.js +1 -1
  53. package/dist-server/service/twin-model/name-index.js.map +1 -1
  54. package/dist-server/service/twin-model/project-structure.js +4 -3
  55. package/dist-server/service/twin-model/project-structure.js.map +1 -1
  56. package/dist-server/service/twin-model/twin-equipment.d.ts +1 -1
  57. package/dist-server/service/twin-model/twin-equipment.js +2 -2
  58. package/dist-server/service/twin-model/twin-equipment.js.map +1 -1
  59. package/dist-server/service/twin-model/twin-model-item-query.js +1 -1
  60. package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
  61. package/dist-server/service/twin-model/twin-model-query.js +4 -4
  62. package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
  63. package/dist-server/service/twin-model/twin-model-tree-query.js +1 -1
  64. package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
  65. package/dist-server/service/twin-space/twin-space-resolver.js +1 -1
  66. package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
  67. package/dist-shared/entity-delta.js +1 -1
  68. package/dist-shared/entity-delta.js.map +1 -1
  69. package/package.json +7 -6
  70. package/server/engine/canonical-ingest.ts +72 -84
  71. package/server/engine/energy-topology.ts +1 -1
  72. package/server/engine/kpi-fold.ts +219 -36
  73. package/server/engine/kpi-query.ts +97 -4
  74. package/server/engine/local-declarations.ts +2 -1
  75. package/server/engine/model-vocabulary.ts +1 -1
  76. package/server/engine/oee-accumulator.ts +4 -2
  77. package/server/engine/twin-engine.ts +163 -5
  78. package/server/routes.ts +6 -1
  79. package/server/service/index.ts +3 -0
  80. package/server/service/reference/reference-live.ts +8 -2
  81. package/server/service/reference/reference-master.ts +12 -3
  82. package/server/service/reference/reference-resolver.ts +1 -1
  83. package/server/service/reference/template-registry.ts +1 -1
  84. package/server/service/twin-backfill/backfill-period-facts.ts +168 -0
  85. package/server/service/twin-backfill/backfill-shape.ts +86 -0
  86. package/server/service/twin-backfill/index.ts +3 -0
  87. package/server/service/twin-backfill/twin-backfill-resolver.ts +35 -0
  88. package/server/service/twin-control/twin-control-mutation.ts +1 -1
  89. package/server/service/twin-lifecycle/domain-catalog.ts +2 -1
  90. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +1 -1
  91. package/server/service/twin-model/name-index.ts +1 -1
  92. package/server/service/twin-model/project-structure.ts +3 -2
  93. package/server/service/twin-model/twin-equipment.ts +14 -2
  94. package/server/service/twin-model/twin-model-item-query.ts +1 -1
  95. package/server/service/twin-model/twin-model-query.ts +2 -2
  96. package/server/service/twin-model/twin-model-tree-query.ts +1 -1
  97. package/server/service/twin-space/twin-space-resolver.ts +1 -1
  98. package/shared/entity-delta.ts +1 -1
  99. package/test/axis-read.test.ts +4 -3
  100. package/test/backfill-shape.test.ts +89 -0
  101. package/test/broadcast-cost-baseline.test.ts +2 -1
  102. package/test/canonical-ingest-vocabularies.test.ts +82 -3
  103. package/test/capability-mapping.test.ts +1 -1
  104. package/test/contract-layer-guard.test.ts +48 -0
  105. package/test/declaration-reaches-model.test.ts +2 -0
  106. package/test/equipment-identity-reaches.test.ts +57 -0
  107. package/test/fact-scope-wiring.test.ts +96 -0
  108. package/test/generated-not-counted.test.ts +99 -0
  109. package/test/ingest-bench.test.ts +2 -1
  110. package/test/ingest-wiring-guard.test.ts +197 -0
  111. package/test/kpi-fold.test.ts +81 -0
  112. package/test/live-mirror-parity.test.ts +6 -3
  113. package/test/local-declarations.test.ts +3 -2
  114. package/test/master-to-twin.test.ts +2 -2
  115. package/test/oee-accumulator.test.ts +18 -3
  116. package/test/projection-reaches-screen.test.ts +5 -0
  117. package/test/projection-reads-declared.test.ts +109 -0
  118. package/test/property-effects.test.ts +7 -3
  119. package/test/scale-twin-bench.test.ts +2 -1
  120. package/test/vocabulary-guard.test.ts +1 -1
  121. package/tsconfig.shared.tsbuildinfo +1 -1
  122. package/tsconfig.tsbuildinfo +1 -1
@@ -45,7 +45,7 @@ process.on('bootstrap-module-domain-public-route', (_app, router) => {
45
45
  * 것과 밀어 받은 것이 구별되지 않는다. 구별되는 것은 유입 장부의 `sources` 한 줄뿐이다.
46
46
  */
47
47
  ingest: (instanceId, records) => {
48
- const { accepted, rejected, masterData } = (0, canonical_ingest_js_1.ingestCanonicalRecords)(records, domain.id, new Date().toISOString());
48
+ const { accepted, rejected, masterData } = (0, canonical_ingest_js_1.ingestCanonicalRecords)(records, domain.id, new Date().toISOString(), twin_engine_js_1.TwinEngine.factScope(domain.id, instanceId));
49
49
  /* 사건이 아닌 것은 상태만 세운다 — 밀어 받는 길도 같은 규율이다(§`applyMasterData`). */
50
50
  if (masterData.length)
51
51
  twin_engine_js_1.TwinEngine.applyMasterData(domain.id, instanceId, masterData);
@@ -1 +1 @@
1
- {"version":3,"file":"routes.js","sourceRoot":"","sources":["../server/routes.ts"],"names":[],"mappings":";;AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,sEAAqE;AACrE,4DAAoD;AACpD,6EAAkE;AAElE,OAAO,CAAC,EAAE,CAAC,sCAA6C,EAAE,CAAC,IAAS,EAAE,MAAW,EAAE,EAAE;IACnF;;;;;OAKG;IACH,MAAM,CAAC,IAAI,CAAC,gCAAgC,EAAE,KAAK,EAAE,GAAQ,EAAE,EAAE;QAC/D,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,EAAE,MAAM,CAAA;QAChC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,GAAG,CAAC,MAAM,GAAG,GAAG,CAAA;YAChB,GAAG,CAAC,IAAI,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAA;YACpD,OAAM;QACR,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,IAAA,8BAAU,EAAC;YAC/B,QAAQ,EAAE,MAAM,CAAC,EAAE;YACnB,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC;YACvC,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC;YAC/C,OAAO,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,IAAI,EAAE,CAA4B;YAChE,IAAI,EAAE,GAAG,CAAC,OAAO,EAAE,IAAI;YACvB;;;eAGG;YACH,MAAM,EAAE,CAAC,UAAU,EAAE,OAAO,EAAE,EAAE;gBAC9B,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,IAAA,4CAAsB,EAAC,OAAc,EAAE,MAAM,CAAC,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAA;gBACtH,+DAA+D;gBAC/D,IAAI,UAAU,CAAC,MAAM;oBAAE,2BAAU,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,EAAE,UAAU,CAAC,CAAA;gBACpF,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,2BAAU,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAA;gBACpG;;;mBAGG;gBACH,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC,CAAA;gBACvE,2BAAU,CAAC,kBAAkB,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,WAAW,CAAC,CAAA;gBACvG,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAA;YAC3D,CAAC;SACF,CAAC,CAAA;QACF,GAAG,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;QAC3B,GAAG,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAA;IACzB,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,2DAA2D;AAC3D,OAAO,CAAC,EAAE,CAAC,sCAA6C,EAAE,CAAC,IAAS,EAAE,OAAY,EAAE,EAAE,GAAE,CAAC,CAAC,CAAA;AAC1F,OAAO,CAAC,EAAE,CAAC,uCAA8C,EAAE,CAAC,IAAS,EAAE,OAAY,EAAE,EAAE,GAAE,CAAC,CAAC,CAAA;AAC3F,OAAO,CAAC,EAAE,CAAC,uCAA8C,EAAE,CAAC,IAAS,EAAE,OAAY,EAAE,EAAE,GAAE,CAAC,CAAC,CAAA","sourcesContent":["/*\n * 이 패키지가 여는 HTTP 자리 — 지금은 웹훅 하나다.\n *\n * ── 왜 여기인가 ─────────────────────────────────────────────────────────────\n * 커넥터는 주소를 만들 수 없다. 어댑터 계약에 주소를 등록하는 자리가 없고, 있어서도 안 된다 — 주소는\n * 앱의 것이고 커넥터마다 제각기 열면 인증도 제각기가 된다.\n *\n * 그래서 입구는 **하나**다. 어느 연결의 것인지는 주소의 이름으로 가려내고, 본문을 우리 어휘로 옮기는\n * 것은 그 연결의 커넥터가 한다(§`reference-hook`).\n *\n * ── 왜 도메인 공개 라우터인가 ───────────────────────────────────────────────\n * 밀어 주는 쪽은 사람이 아니다. 사람 인증(JWT)을 요구하면 연결된 시스템이 우리 토큰을 발급받아\n * 갱신하며 관리해야 하고, 대부분의 현장에서 그것 때문에 연동이 서지 않는다.\n *\n * 대신 **연결마다 비밀값**을 두고 그것으로 확인한다. 비밀값을 선언하지 않은 연결은 훅을 받지 않는다 —\n * 인증 없는 입구를 열어 두는 것보다 훅을 못 쓰는 것이 낫다.\n */\nimport { ingestCanonicalRecords } from './engine/canonical-ingest.js'\nimport { TwinEngine } from './engine/twin-engine.js'\nimport { handleHook } from './service/reference/reference-hook.js'\n\nprocess.on('bootstrap-module-domain-public-route' as any, (_app: any, router: any) => {\n /*\n * 주소에 트윈까지 적는다 — 어느 트윈에 넣을지 짐작하지 않는다.\n *\n * 한 연결이 여러 사이트를 만들면 트윈도 여럿이다. 본문을 보고 고르게 하면 그 판단이 커넥터마다\n * 달라지고, 틀리면 다른 공장의 사실이 이 공장에 들어간다. 주소가 말하게 하면 그 일이 없다.\n */\n router.post('/twin/hook/:source/:instanceId', async (ctx: any) => {\n const domain = ctx.state?.domain\n if (!domain) {\n ctx.status = 400\n ctx.body = { ok: false, error: 'no domain context' }\n return\n }\n const outcome = await handleHook({\n domainId: domain.id,\n source: String(ctx.params.source ?? ''),\n instanceId: String(ctx.params.instanceId ?? ''),\n headers: (ctx.request?.headers ?? {}) as Record<string, unknown>,\n body: ctx.request?.body,\n /*\n * 폴링이 쓰는 길을 그대로 쓴다 — 검증도 유입도 같은 자리다. 그래서 트윈 쪽에서는 물어서 받은\n * 것과 밀어 받은 것이 구별되지 않는다. 구별되는 것은 유입 장부의 `sources` 한 줄뿐이다.\n */\n ingest: (instanceId, records) => {\n const { accepted, rejected, masterData } = ingestCanonicalRecords(records as any, domain.id, new Date().toISOString())\n /* 사건이 아닌 것은 상태만 세운다 — 밀어 받는 길도 같은 규율이다(§`applyMasterData`). */\n if (masterData.length) TwinEngine.applyMasterData(domain.id, instanceId, masterData)\n const { applied, duplicates } = TwinEngine.ingestLiveResult(domain.id, instanceId, accepted, 'hook')\n /*\n * 커널이 받지 못한 수를 이름으로 남긴다 — 트윈이 멈춘 사이 버려진 레코드를 「통과」로 세면\n * 화면이 100% 라고 말한다. 중복으로 걸러진 것은 버려진 것이 아니므로 빼고 센다.\n */\n const undelivered = Math.max(0, accepted.length - applied - duplicates)\n TwinEngine.recordIngestResult(domain.id, instanceId, records.length, rejected, Date.now(), undelivered)\n return { applied, duplicates, rejected: rejected.length }\n }\n })\n ctx.status = outcome.status\n ctx.body = outcome.body\n })\n})\n\n/* 나머지 셋은 이 패키지가 쓰지 않는다 — 자리는 남겨 둔다(다른 모듈이 같은 신호를 기다린다). */\nprocess.on('bootstrap-module-global-public-route' as any, (_app: any, _router: any) => {})\nprocess.on('bootstrap-module-global-private-route' as any, (_app: any, _router: any) => {})\nprocess.on('bootstrap-module-domain-private-route' as any, (_app: any, _router: any) => {})\n"]}
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../server/routes.ts"],"names":[],"mappings":";;AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,sEAAqE;AACrE,4DAAoD;AACpD,6EAAkE;AAElE,OAAO,CAAC,EAAE,CAAC,sCAA6C,EAAE,CAAC,IAAS,EAAE,MAAW,EAAE,EAAE;IACnF;;;;;OAKG;IACH,MAAM,CAAC,IAAI,CAAC,gCAAgC,EAAE,KAAK,EAAE,GAAQ,EAAE,EAAE;QAC/D,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,EAAE,MAAM,CAAA;QAChC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,GAAG,CAAC,MAAM,GAAG,GAAG,CAAA;YAChB,GAAG,CAAC,IAAI,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAA;YACpD,OAAM;QACR,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,IAAA,8BAAU,EAAC;YAC/B,QAAQ,EAAE,MAAM,CAAC,EAAE;YACnB,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC;YACvC,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC;YAC/C,OAAO,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,IAAI,EAAE,CAA4B;YAChE,IAAI,EAAE,GAAG,CAAC,OAAO,EAAE,IAAI;YACvB;;;eAGG;YACH,MAAM,EAAE,CAAC,UAAU,EAAE,OAAO,EAAE,EAAE;gBAC9B,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,IAAA,4CAAsB,EAC/D,OAAc,EACd,MAAM,CAAC,EAAE,EACT,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EACxB,2BAAU,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,CAAC,CAC5C,CAAA;gBACD,+DAA+D;gBAC/D,IAAI,UAAU,CAAC,MAAM;oBAAE,2BAAU,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,EAAE,UAAU,CAAC,CAAA;gBACpF,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,2BAAU,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAA;gBACpG;;;mBAGG;gBACH,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC,CAAA;gBACvE,2BAAU,CAAC,kBAAkB,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,WAAW,CAAC,CAAA;gBACvG,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAA;YAC3D,CAAC;SACF,CAAC,CAAA;QACF,GAAG,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;QAC3B,GAAG,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAA;IACzB,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,2DAA2D;AAC3D,OAAO,CAAC,EAAE,CAAC,sCAA6C,EAAE,CAAC,IAAS,EAAE,OAAY,EAAE,EAAE,GAAE,CAAC,CAAC,CAAA;AAC1F,OAAO,CAAC,EAAE,CAAC,uCAA8C,EAAE,CAAC,IAAS,EAAE,OAAY,EAAE,EAAE,GAAE,CAAC,CAAC,CAAA;AAC3F,OAAO,CAAC,EAAE,CAAC,uCAA8C,EAAE,CAAC,IAAS,EAAE,OAAY,EAAE,EAAE,GAAE,CAAC,CAAC,CAAA","sourcesContent":["/*\n * 이 패키지가 여는 HTTP 자리 — 지금은 웹훅 하나다.\n *\n * ── 왜 여기인가 ─────────────────────────────────────────────────────────────\n * 커넥터는 주소를 만들 수 없다. 어댑터 계약에 주소를 등록하는 자리가 없고, 있어서도 안 된다 — 주소는\n * 앱의 것이고 커넥터마다 제각기 열면 인증도 제각기가 된다.\n *\n * 그래서 입구는 **하나**다. 어느 연결의 것인지는 주소의 이름으로 가려내고, 본문을 우리 어휘로 옮기는\n * 것은 그 연결의 커넥터가 한다(§`reference-hook`).\n *\n * ── 왜 도메인 공개 라우터인가 ───────────────────────────────────────────────\n * 밀어 주는 쪽은 사람이 아니다. 사람 인증(JWT)을 요구하면 연결된 시스템이 우리 토큰을 발급받아\n * 갱신하며 관리해야 하고, 대부분의 현장에서 그것 때문에 연동이 서지 않는다.\n *\n * 대신 **연결마다 비밀값**을 두고 그것으로 확인한다. 비밀값을 선언하지 않은 연결은 훅을 받지 않는다 —\n * 인증 없는 입구를 열어 두는 것보다 훅을 못 쓰는 것이 낫다.\n */\nimport { ingestCanonicalRecords } from './engine/canonical-ingest.js'\nimport { TwinEngine } from './engine/twin-engine.js'\nimport { handleHook } from './service/reference/reference-hook.js'\n\nprocess.on('bootstrap-module-domain-public-route' as any, (_app: any, router: any) => {\n /*\n * 주소에 트윈까지 적는다 — 어느 트윈에 넣을지 짐작하지 않는다.\n *\n * 한 연결이 여러 사이트를 만들면 트윈도 여럿이다. 본문을 보고 고르게 하면 그 판단이 커넥터마다\n * 달라지고, 틀리면 다른 공장의 사실이 이 공장에 들어간다. 주소가 말하게 하면 그 일이 없다.\n */\n router.post('/twin/hook/:source/:instanceId', async (ctx: any) => {\n const domain = ctx.state?.domain\n if (!domain) {\n ctx.status = 400\n ctx.body = { ok: false, error: 'no domain context' }\n return\n }\n const outcome = await handleHook({\n domainId: domain.id,\n source: String(ctx.params.source ?? ''),\n instanceId: String(ctx.params.instanceId ?? ''),\n headers: (ctx.request?.headers ?? {}) as Record<string, unknown>,\n body: ctx.request?.body,\n /*\n * 폴링이 쓰는 길을 그대로 쓴다 — 검증도 유입도 같은 자리다. 그래서 트윈 쪽에서는 물어서 받은\n * 것과 밀어 받은 것이 구별되지 않는다. 구별되는 것은 유입 장부의 `sources` 한 줄뿐이다.\n */\n ingest: (instanceId, records) => {\n const { accepted, rejected, masterData } = ingestCanonicalRecords(\n records as any,\n domain.id,\n new Date().toISOString(),\n TwinEngine.factScope(domain.id, instanceId)\n )\n /* 사건이 아닌 것은 상태만 세운다 — 밀어 받는 길도 같은 규율이다(§`applyMasterData`). */\n if (masterData.length) TwinEngine.applyMasterData(domain.id, instanceId, masterData)\n const { applied, duplicates } = TwinEngine.ingestLiveResult(domain.id, instanceId, accepted, 'hook')\n /*\n * 커널이 받지 못한 수를 이름으로 남긴다 — 트윈이 멈춘 사이 버려진 레코드를 「통과」로 세면\n * 화면이 100% 라고 말한다. 중복으로 걸러진 것은 버려진 것이 아니므로 빼고 센다.\n */\n const undelivered = Math.max(0, accepted.length - applied - duplicates)\n TwinEngine.recordIngestResult(domain.id, instanceId, records.length, rejected, Date.now(), undelivered)\n return { applied, duplicates, rejected: rejected.length }\n }\n })\n ctx.status = outcome.status\n ctx.body = outcome.body\n })\n})\n\n/* 나머지 셋은 이 패키지가 쓰지 않는다 — 자리는 남겨 둔다(다른 모듈이 같은 신호를 기다린다). */\nprocess.on('bootstrap-module-global-public-route' as any, (_app: any, _router: any) => {})\nprocess.on('bootstrap-module-global-private-route' as any, (_app: any, _router: any) => {})\nprocess.on('bootstrap-module-domain-private-route' as any, (_app: any, _router: any) => {})\n"]}
@@ -21,6 +21,6 @@ export declare const entities: (typeof import("./twin-event/twin-event.js").Twin
21
21
  export declare const subscribers: any[];
22
22
  export declare const schema: {
23
23
  typeDefs: {};
24
- resolverClasses: (typeof import("./twin-state/twin-state-subscription.js").TwinStateSubscription | typeof import("./twin-subject/twin-subject-query.js").TwinSubjectQuery | typeof import("./twin-subject/twin-subject-query.js").TwinSubjectMutation | typeof import("./twin-audit/twin-audit-query.js").TwinAuditQuery | typeof import("./twin-space/twin-space-resolver.js").TwinSpaceResolver | typeof import("./twin-target/twin-target-resolver.js").TwinTargetResolver | typeof import("./reference/reference-resolver.js").TwinReferenceResolver | typeof import("./reference/reference-progress-subscription.js").TwinReferenceProgressSubscription | typeof import("./twin-control/twin-control-mutation.js").TwinControlMutation | typeof import("./twin-model/twin-model-query.js").TwinModelQuery | typeof import("./twin-model/twin-model-item-query.js").TwinModelItemQuery | typeof import("./twin-model/twin-model-tree-query.js").TwinModelTreeQuery | typeof import("./twin-model/twin-model-mutation.js").TwinModelMutation | typeof import("./twin-model/twin-lineage-query.js").TwinLineageQuery | typeof import("./twin-ingest-window/twin-ingest-window-query.js").TwinIngestWindowQuery | typeof import("./twin-lifecycle/twin-lifecycle-mutation.js").TwinLifecycleMutation | typeof import("./twin-journal/twin-journal-query.js").TwinJournalQuery | typeof import("./twin-forecast/twin-forecast-query.js").TwinForecastQuery | typeof import("./twin-attention/twin-attention-query.js").TwinAttentionQuery | typeof import("./twin-metrics/twin-metrics-query.js").TwinMetricsQuery | typeof import("./twin-readiness/twin-readiness-query.js").TwinReadinessQuery)[];
24
+ resolverClasses: (typeof import("./twin-state/twin-state-subscription.js").TwinStateSubscription | typeof import("./twin-subject/twin-subject-query.js").TwinSubjectQuery | typeof import("./twin-subject/twin-subject-query.js").TwinSubjectMutation | typeof import("./twin-audit/twin-audit-query.js").TwinAuditQuery | typeof import("./twin-space/twin-space-resolver.js").TwinSpaceResolver | typeof import("./twin-target/twin-target-resolver.js").TwinTargetResolver | typeof import("./reference/reference-resolver.js").TwinReferenceResolver | typeof import("./reference/reference-progress-subscription.js").TwinReferenceProgressSubscription | typeof import("./twin-control/twin-control-mutation.js").TwinControlMutation | typeof import("./twin-model/twin-model-query.js").TwinModelQuery | typeof import("./twin-model/twin-model-item-query.js").TwinModelItemQuery | typeof import("./twin-model/twin-model-tree-query.js").TwinModelTreeQuery | typeof import("./twin-model/twin-model-mutation.js").TwinModelMutation | typeof import("./twin-model/twin-lineage-query.js").TwinLineageQuery | typeof import("./twin-ingest-window/twin-ingest-window-query.js").TwinIngestWindowQuery | typeof import("./twin-lifecycle/twin-lifecycle-mutation.js").TwinLifecycleMutation | typeof import("./twin-journal/twin-journal-query.js").TwinJournalQuery | typeof import("./twin-forecast/twin-forecast-query.js").TwinForecastQuery | typeof import("./twin-attention/twin-attention-query.js").TwinAttentionQuery | typeof import("./twin-metrics/twin-metrics-query.js").TwinMetricsQuery | typeof import("./twin-readiness/twin-readiness-query.js").TwinReadinessQuery | typeof import("./twin-backfill/twin-backfill-resolver.js").TwinBackfillResolver)[];
25
25
  directives: {};
26
26
  };
@@ -45,6 +45,8 @@ const index_js_15 = require("./twin-forecast/index.js");
45
45
  const index_js_16 = require("./twin-attention/index.js");
46
46
  const index_js_17 = require("./twin-metrics/index.js");
47
47
  const index_js_18 = require("./twin-readiness/index.js");
48
+ /* 지난 기록 채우기 — 저널에만 쓰는 문. 라이브 유입과 판단이 달라 문을 따로 둔다. */
49
+ const index_js_19 = require("./twin-backfill/index.js");
48
50
  exports.entities = [
49
51
  /* ENTITIES */
50
52
  ...index_js_1.entities,
@@ -73,6 +75,7 @@ exports.schema = {
73
75
  ...index_js_16.resolvers,
74
76
  ...index_js_17.resolvers,
75
77
  ...index_js_18.resolvers,
78
+ ...index_js_19.resolvers,
76
79
  ...index_js_12.resolvers,
77
80
  ...index_js_11.resolvers,
78
81
  ...index_js_6.resolvers,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,yBAAyB;AACzB,qEAA0C;AAC1C,2EAAgD;AAChD,qFAA0D;AAC1D,2EAAgD;AAChD,qEAA0C;AAC1C,oFAAyD;AACzD,0EAA+C;AAC/C,uEAA4C;AAC5C,6EAAkD;AAClD,kFAAuD;AACvD,0EAA+C;AAC/C,4EAAiD;AACjD,2EAAgD;AAChD,wEAA6C;AAC7C,wEAA6C;AAC7C,2EAAgD;AAChD,4DAA4D;AAC5D,yEAA8C;AAC9C,iEAAiE;AACjE,8EAAmD;AACnD,sEAAsE;AACtE,6EAAkD;AAElD,mCAAmC;AACnC,oDAAqE;AACrE,sDAA4G;AAC5G,oDAAsG;AACtG,uDAA2E;AAC3E,wDAA6E;AAC7E,oDAAsG;AACtG,qDAAyG;AACzG,mDAA6G;AAC7G,oDAAuE;AACvE,uDAA2E;AAC3E,qDAAsG;AACtG,mEAAmE;AACnE,6DAA4H;AAC5H,yDAA+E;AAC/E,uDAA2E;AAC3E,wDAA6E;AAC7E,yDAA+E;AAC/E,uDAA2E;AAC3E,yDAA+E;AAElE,QAAA,QAAQ,GAAG;IACtB,cAAc;IACd,GAAG,mBAAiB;IACpB,GAAG,mBAAmB;IACtB,GAAG,oBAAwB;IAC3B,GAAG,mBAAiB;IACpB,GAAG,mBAAoB;IACvB,GAAG,mBAAqB;IACxB,GAAG,mBAAiB;IACpB,GAAG,mBAAkB;IACrB,GAAG,mBAAqB;IACxB,4DAA4D;IAC5D,GAAG,oBAAiB;CACrB,CAAA;AAEY,QAAA,WAAW,GAAG,EAAE,CAAA;AAEhB,QAAA,MAAM,GAAG;IACpB,QAAQ,EAAE,EAAE;IACZ,eAAe,EAAE;QACf,sBAAsB;QACtB,GAAG,oBAAkB;QACrB,GAAG,qBAAoB;QACvB,GAAG,qBAAsB;QACzB,GAAG,qBAAoB;QACvB,GAAG,oBAAkB;QACrB,GAAG,qBAAqB;QACxB,GAAG,qBAAsB;QACzB,GAAG,qBAAoB;QACvB,GAAG,qBAAsB;QACzB,GAAG,qBAAyB;QAC5B,GAAG,qBAAkB;QACrB,GAAG,oBAAkB;QACrB,GAAG,oBAAmB;QACtB,GAAG,oBAAsB;QACzB,GAAG,oBAAoB;KACxB;IACD,UAAU,EAAE,EAAE;CACf,CAAA","sourcesContent":["/* EXPORT ENTITY TYPES */\nexport * from './twin-event/twin-event.js'\nexport * from './twin-audit/twin-audit-event.js'\nexport * from './twin-ingest-window/twin-ingest-window.js'\nexport * from './twin-instance/twin-instance.js'\nexport * from './twin-space/twin-space.js'\nexport * from './twin-space/twin-space-representation.js'\nexport * from './twin-space/twin-space-area.js'\nexport * from './twin-target/twin-target.js'\nexport * from './twin-structure/twin-structure.js'\nexport * from './twin-state/twin-state-subscription.js'\nexport * from './reference/reference-master.js'\nexport * from './reference/reference-registry.js'\nexport * from './reference/reference-adapter.js'\nexport * from './reference/reference-live.js'\nexport * from './reference/twin-reference.js'\nexport * from './reference/template-registry.js'\n/* 커넥터 점검 하네스 — 실 원본에 붙여 무엇이 채워지고 무엇이 비는지 센다(앱을 띄우지 않는다). */\nexport * from './reference/reference-probe.js'\n/* 읽은 뒤의 총평(사실 층) — 화면이 표로 그리고, 더 알고 싶을 때 AI 가 이 사실만 근거로 서술한다. */\nexport * from './reference/reference-assessment.js'\n/* 도메인 어휘 카탈로그 조합(커널 SSOT 노출) — 서버측 소비자(board-ai 도구 등)가 같은 형태를 쓰도록. */\nexport * from './twin-lifecycle/domain-catalog.js'\n\n/* IMPORT ENTITIES AND RESOLVERS */\nimport { entities as TwinEventEntities } from './twin-event/index.js'\nimport { entities as TwinSubjectEntities, resolvers as TwinSubjectResolvers } from './twin-subject/index.js'\nimport { entities as TwinAuditEntities, resolvers as TwinAuditResolvers } from './twin-audit/index.js'\nimport { entities as TwinInstanceEntities } from './twin-instance/index.js'\nimport { entities as TwinStructureEntities } from './twin-structure/index.js'\nimport { entities as TwinSpaceEntities, resolvers as TwinSpaceResolvers } from './twin-space/index.js'\nimport { entities as TwinTargetEntities, resolvers as TwinTargetResolvers } from './twin-target/index.js'\nimport { entities as TwinReferenceEntities, resolvers as TwinReferenceResolvers } from './reference/index.js'\nimport { resolvers as TwinStateResolvers } from './twin-state/index.js'\nimport { resolvers as TwinControlResolvers } from './twin-control/index.js'\nimport { entities as TwinModelEntities, resolvers as TwinModelResolvers } from './twin-model/index.js'\n/* 인제스트 창 — 동기화 추이를 재기동 뒤에도 이어 보기 위한 행(리졸버 없음: 메모리 장부가 조회를 답한다). */\nimport { entities as TwinIngestWindowEntities, resolvers as TwinIngestWindowResolvers } from './twin-ingest-window/index.js'\nimport { resolvers as TwinLifecycleResolvers } from './twin-lifecycle/index.js'\nimport { resolvers as TwinJournalResolvers } from './twin-journal/index.js'\nimport { resolvers as TwinForecastResolvers } from './twin-forecast/index.js'\nimport { resolvers as TwinAttentionResolvers } from './twin-attention/index.js'\nimport { resolvers as TwinMetricsResolvers } from './twin-metrics/index.js'\nimport { resolvers as TwinReadinessResolvers } from './twin-readiness/index.js'\n\nexport const entities = [\n /* ENTITIES */\n ...TwinEventEntities,\n ...TwinSubjectEntities,\n ...TwinIngestWindowEntities,\n ...TwinAuditEntities,\n ...TwinInstanceEntities,\n ...TwinStructureEntities,\n ...TwinSpaceEntities,\n ...TwinTargetEntities,\n ...TwinReferenceEntities,\n /* 구조 투영 — model JSON 에서 풀어낸 표준 엔티티(ADR-0032). 원본에서 온 캐시. */\n ...TwinModelEntities\n]\n\nexport const subscribers = []\n\nexport const schema = {\n typeDefs: {},\n resolverClasses: [\n /* RESOLVER CLASSES */\n ...TwinStateResolvers,\n ...TwinControlResolvers,\n ...TwinLifecycleResolvers,\n ...TwinJournalResolvers,\n ...TwinAuditResolvers,\n ...TwinForecastResolvers,\n ...TwinAttentionResolvers,\n ...TwinMetricsResolvers,\n ...TwinReadinessResolvers,\n ...TwinIngestWindowResolvers,\n ...TwinModelResolvers,\n ...TwinSpaceResolvers,\n ...TwinTargetResolvers,\n ...TwinReferenceResolvers,\n ...TwinSubjectResolvers\n ],\n directives: {}\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,yBAAyB;AACzB,qEAA0C;AAC1C,2EAAgD;AAChD,qFAA0D;AAC1D,2EAAgD;AAChD,qEAA0C;AAC1C,oFAAyD;AACzD,0EAA+C;AAC/C,uEAA4C;AAC5C,6EAAkD;AAClD,kFAAuD;AACvD,0EAA+C;AAC/C,4EAAiD;AACjD,2EAAgD;AAChD,wEAA6C;AAC7C,wEAA6C;AAC7C,2EAAgD;AAChD,4DAA4D;AAC5D,yEAA8C;AAC9C,iEAAiE;AACjE,8EAAmD;AACnD,sEAAsE;AACtE,6EAAkD;AAElD,mCAAmC;AACnC,oDAAqE;AACrE,sDAA4G;AAC5G,oDAAsG;AACtG,uDAA2E;AAC3E,wDAA6E;AAC7E,oDAAsG;AACtG,qDAAyG;AACzG,mDAA6G;AAC7G,oDAAuE;AACvE,uDAA2E;AAC3E,qDAAsG;AACtG,mEAAmE;AACnE,6DAA4H;AAC5H,yDAA+E;AAC/E,uDAA2E;AAC3E,wDAA6E;AAC7E,yDAA+E;AAC/E,uDAA2E;AAC3E,yDAA+E;AAC/E,qDAAqD;AACrD,wDAA6E;AAEhE,QAAA,QAAQ,GAAG;IACtB,cAAc;IACd,GAAG,mBAAiB;IACpB,GAAG,mBAAmB;IACtB,GAAG,oBAAwB;IAC3B,GAAG,mBAAiB;IACpB,GAAG,mBAAoB;IACvB,GAAG,mBAAqB;IACxB,GAAG,mBAAiB;IACpB,GAAG,mBAAkB;IACrB,GAAG,mBAAqB;IACxB,4DAA4D;IAC5D,GAAG,oBAAiB;CACrB,CAAA;AAEY,QAAA,WAAW,GAAG,EAAE,CAAA;AAEhB,QAAA,MAAM,GAAG;IACpB,QAAQ,EAAE,EAAE;IACZ,eAAe,EAAE;QACf,sBAAsB;QACtB,GAAG,oBAAkB;QACrB,GAAG,qBAAoB;QACvB,GAAG,qBAAsB;QACzB,GAAG,qBAAoB;QACvB,GAAG,oBAAkB;QACrB,GAAG,qBAAqB;QACxB,GAAG,qBAAsB;QACzB,GAAG,qBAAoB;QACvB,GAAG,qBAAsB;QACzB,GAAG,qBAAqB;QACxB,GAAG,qBAAyB;QAC5B,GAAG,qBAAkB;QACrB,GAAG,oBAAkB;QACrB,GAAG,oBAAmB;QACtB,GAAG,oBAAsB;QACzB,GAAG,oBAAoB;KACxB;IACD,UAAU,EAAE,EAAE;CACf,CAAA","sourcesContent":["/* EXPORT ENTITY TYPES */\nexport * from './twin-event/twin-event.js'\nexport * from './twin-audit/twin-audit-event.js'\nexport * from './twin-ingest-window/twin-ingest-window.js'\nexport * from './twin-instance/twin-instance.js'\nexport * from './twin-space/twin-space.js'\nexport * from './twin-space/twin-space-representation.js'\nexport * from './twin-space/twin-space-area.js'\nexport * from './twin-target/twin-target.js'\nexport * from './twin-structure/twin-structure.js'\nexport * from './twin-state/twin-state-subscription.js'\nexport * from './reference/reference-master.js'\nexport * from './reference/reference-registry.js'\nexport * from './reference/reference-adapter.js'\nexport * from './reference/reference-live.js'\nexport * from './reference/twin-reference.js'\nexport * from './reference/template-registry.js'\n/* 커넥터 점검 하네스 — 실 원본에 붙여 무엇이 채워지고 무엇이 비는지 센다(앱을 띄우지 않는다). */\nexport * from './reference/reference-probe.js'\n/* 읽은 뒤의 총평(사실 층) — 화면이 표로 그리고, 더 알고 싶을 때 AI 가 이 사실만 근거로 서술한다. */\nexport * from './reference/reference-assessment.js'\n/* 도메인 어휘 카탈로그 조합(커널 SSOT 노출) — 서버측 소비자(board-ai 도구 등)가 같은 형태를 쓰도록. */\nexport * from './twin-lifecycle/domain-catalog.js'\n\n/* IMPORT ENTITIES AND RESOLVERS */\nimport { entities as TwinEventEntities } from './twin-event/index.js'\nimport { entities as TwinSubjectEntities, resolvers as TwinSubjectResolvers } from './twin-subject/index.js'\nimport { entities as TwinAuditEntities, resolvers as TwinAuditResolvers } from './twin-audit/index.js'\nimport { entities as TwinInstanceEntities } from './twin-instance/index.js'\nimport { entities as TwinStructureEntities } from './twin-structure/index.js'\nimport { entities as TwinSpaceEntities, resolvers as TwinSpaceResolvers } from './twin-space/index.js'\nimport { entities as TwinTargetEntities, resolvers as TwinTargetResolvers } from './twin-target/index.js'\nimport { entities as TwinReferenceEntities, resolvers as TwinReferenceResolvers } from './reference/index.js'\nimport { resolvers as TwinStateResolvers } from './twin-state/index.js'\nimport { resolvers as TwinControlResolvers } from './twin-control/index.js'\nimport { entities as TwinModelEntities, resolvers as TwinModelResolvers } from './twin-model/index.js'\n/* 인제스트 창 — 동기화 추이를 재기동 뒤에도 이어 보기 위한 행(리졸버 없음: 메모리 장부가 조회를 답한다). */\nimport { entities as TwinIngestWindowEntities, resolvers as TwinIngestWindowResolvers } from './twin-ingest-window/index.js'\nimport { resolvers as TwinLifecycleResolvers } from './twin-lifecycle/index.js'\nimport { resolvers as TwinJournalResolvers } from './twin-journal/index.js'\nimport { resolvers as TwinForecastResolvers } from './twin-forecast/index.js'\nimport { resolvers as TwinAttentionResolvers } from './twin-attention/index.js'\nimport { resolvers as TwinMetricsResolvers } from './twin-metrics/index.js'\nimport { resolvers as TwinReadinessResolvers } from './twin-readiness/index.js'\n/* 지난 기록 채우기 — 저널에만 쓰는 문. 라이브 유입과 판단이 달라 문을 따로 둔다. */\nimport { resolvers as TwinBackfillResolvers } from './twin-backfill/index.js'\n\nexport const entities = [\n /* ENTITIES */\n ...TwinEventEntities,\n ...TwinSubjectEntities,\n ...TwinIngestWindowEntities,\n ...TwinAuditEntities,\n ...TwinInstanceEntities,\n ...TwinStructureEntities,\n ...TwinSpaceEntities,\n ...TwinTargetEntities,\n ...TwinReferenceEntities,\n /* 구조 투영 — model JSON 에서 풀어낸 표준 엔티티(ADR-0032). 원본에서 온 캐시. */\n ...TwinModelEntities\n]\n\nexport const subscribers = []\n\nexport const schema = {\n typeDefs: {},\n resolverClasses: [\n /* RESOLVER CLASSES */\n ...TwinStateResolvers,\n ...TwinControlResolvers,\n ...TwinLifecycleResolvers,\n ...TwinJournalResolvers,\n ...TwinAuditResolvers,\n ...TwinForecastResolvers,\n ...TwinAttentionResolvers,\n ...TwinMetricsResolvers,\n ...TwinReadinessResolvers,\n ...TwinBackfillResolvers,\n ...TwinIngestWindowResolvers,\n ...TwinModelResolvers,\n ...TwinSpaceResolvers,\n ...TwinTargetResolvers,\n ...TwinReferenceResolvers,\n ...TwinSubjectResolvers\n ],\n directives: {}\n}\n"]}
@@ -73,7 +73,7 @@ source) {
73
73
  return;
74
74
  }
75
75
  const offered = Array.isArray(records) ? records.length : records ? 1 : 0;
76
- const { accepted, rejected, masterData } = (0, index_js_1.ingestCanonicalRecords)(records, domainId, new Date().toISOString());
76
+ const { accepted, rejected, masterData } = (0, index_js_1.ingestCanonicalRecords)(records, domainId, new Date().toISOString(), index_js_1.TwinEngine.factScope(domainId, instanceId));
77
77
  /*
78
78
  * ── **사건이 아닌 것은 상태만 세운다** (2026-08-28) ─────────────────────────
79
79
  *
@@ -1 +1 @@
1
- {"version":3,"file":"reference-live.js","sourceRoot":"","sources":["../../../server/service/reference/reference-live.ts"],"names":[],"mappings":"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","sourcesContent":["import { twinLog, twinWarn, twinError } from '../../engine/log.js'\nimport { getRepository } from '@things-factory/shell'\n\nimport { getAdapter, type ConnectionConfig, type LiveFeedContinuity, type ObservedItemFact, type SiteDescriptor } from './reference-adapter.js'\nimport { TwinEngine, ingestCanonicalRecords, registerLiveFeedProbe, type CanonicalRecord } from '../../engine/index.js'\nimport { TwinInstance } from '../twin-instance/twin-instance.js'\nimport { TwinReference } from './twin-reference.js'\n\n/*\n * 커넥터 기반 라이브 피드 결선(reference → live, §0 프레임 ① restartPolicy='resync').\n * 흐름: adapter.openLiveFeed(cfg, site, records) → ingestCanonicalRecords(정규→봉투) → TwinEngine.ingestLive(projector 미러).\n * 커넥터가 \"벤더 이벤트 → 정규 레코드\" 매핑을 소유(코드)한다 — preset 기반 startLiveFeed(operato-twin, http-get+jsonata)와\n * 대칭이되, 매핑 위치만 다르다. 실 시스템 전환은 connectionConfig.baseUrl 교체뿐(코드 경로 동일).\n * 생명주기: 인스턴스 정지(TwinEngine.stop)에 훅으로 묶여 피드도 함께 unsubscribe.\n */\n\n/** 활성 라이브 피드 — instanceId → unsubscribe(어댑터 openLiveFeed 반환). */\nconst feeds = new Map<string, () => void>()\n\n/**\n * 커넥터 라이브 기동 — 어댑터 openLiveFeed 를 인스턴스에 결선.\n * 반환: 라이브 결선 성공 여부(어댑터가 openLiveFeed 미지원이면 false → 호출측이 sim 폴백).\n */\nexport async function startReferenceLiveFeed(\n domainId: string,\n adapterType: string,\n cfg: ConnectionConfig,\n site: SiteDescriptor,\n instanceId: string,\n /**\n * **어느 레퍼런스에서 읽는 피드인가** — 읽기 커서를 저장할 자리를 가리킨다(§`loadLiveCursor`).\n *\n * 부르는 쪽이 `TwinReference` 를 이미 들고 있으므로 그 이름을 넘긴다. 처음에는 `cfg`·`site` 에서\n * 찾으려 했는데 **둘 다 그 값을 가질 수 없다**: `cfg` 는 사람이 화면에서 입력한 접속 설정이고\n * (`endpoint`·`token`…), `site` 는 커넥터가 원본에서 발견한 것이다 — **레퍼런스의 이름은 호스트가\n * 붙이는 것이고 원본은 그것을 모른다.** 커넥터가 채울 수 없는 값을 커넥터에서 읽으려 한 것이 잘못이었다.\n *\n * 주지 않으면 커서를 잇지 않고 **그 사실을 말한다**(알리지 않고 창이 미끄러지지 않게).\n */\n source?: string\n): Promise<boolean> {\n const adapter = getAdapter(adapterType)\n if (!adapter?.openLiveFeed) return false\n\n const det = await TwinEngine.detail(domainId, instanceId)\n if (!det?.model) throw new Error(`instance \"${instanceId}\" not provisioned (no model) — ingest master before live start`)\n\n // 관측 기동 — 커널 tick 없이 projector 가 실 이벤트를 반영한다(startLive 가 restartPolicy='resync' 로 적는다).\n // 시각 기준은 **공간**이 갖는다(테넌트 아님) — 교대의 HH:MM 을 어느 기준으로 읽나.\n TwinEngine.startLive(instanceId, domainId, det.kind, await TwinEngine.withSpaceTimeBase(det.model, domainId))\n\n stopReferenceLiveFeed(instanceId) // 재기동 시 이전 피드 정리\n /*\n * **어디까지 읽었나를 이 층이 들고 건넨다**(§`LiveFeedContinuity`, 2026-08-23).\n *\n * 어댑터가 이 값을 자기 사물함에 두면 원본이 늘 때마다 저장 기제가 늘고, 그중 하나가 알리지 않고 다르게\n * 동작한다. 그리고 커널이 이미 갈라 두었다: 「원천이 애초에 다시 말해 주지 않는 축」은 재기동\n * 연속성으로 이어받는다(§`hydrateContinuity`) — 읽기 커서가 정확히 그 성질이다.\n *\n * 없으면 `undefined` 를 건넨다 — 그것이 「첫 붙음」이라는 사실이고, 그때만 어댑터가 되돌아볼 날수로\n * 첫 창을 만든다. 빈 객체로 메우지 않는다(그러면 첫 붙음과 이어 붙음이 같아진다).\n */\n const continuity = await loadLiveCursor(domainId, instanceId, source)\n /*\n * ── **트윈이 아는 것을 함께 건넨다** (2026-08-28) ───────────────────────────\n *\n * 전량 절대값을 읽는 커넥터는 지난 주기와 견주어 바뀐 것만 낸다. 그 비교 표가 프로세스 안에만\n * 있어서 재기동하면 커넥터가 전량을 「바뀐 것」으로 판정했다 — 승화푸드에서 2,665건이다.\n *\n * 트윈은 저장본에서 되세우므로 잃지 않았다. 그래서 그 사실을 건네면 첫 주기부터 바뀐 것만 나간다.\n * 커널 어휘로 건넨다 — 원본 낱말로 되돌리는 일은 여러 낱말이 한 값으로 모여 불가능하고, 필요도 없다.\n */\n if (continuity) continuity.observedItems = observedItemsOf(domainId, instanceId)\n const unsubscribe = adapter.openLiveFeed(cfg, site, (records: unknown[]) => {\n if (!TwinEngine.owns(domainId, instanceId)) {\n stopReferenceLiveFeed(instanceId) // 인스턴스 사라지면 자가 정지(안전망)\n return\n }\n const offered = Array.isArray(records) ? records.length : records ? 1 : 0\n const { accepted, rejected, masterData } = ingestCanonicalRecords(records as CanonicalRecord[], domainId, new Date().toISOString())\n /*\n * ── **사건이 아닌 것은 상태만 세운다** (2026-08-28) ─────────────────────────\n *\n * 마스터데이터 — 변하지 않는 속성(유통기한·로트번호). 시각축이 없는 값이므로 지난 기록에 적지\n * 않는다(§`applyMasterData`). 이 문이 없던 동안 그 값은 사건에 실려 왔고, 표준이 그것을\n * 「들어왔다」에만 허용하므로 전량을 읽는 원본은 **들어오지 않은 것을 들어왔다고** 말해야 했다.\n *\n * 반영된 건수를 함께 센다 — 라이브로 돌지 않는 트윈은 0 을 내므로, 그 차이가 「바닥에 버려진 수」다.\n */\n const masterApplied = masterData.length ? TwinEngine.applyMasterData(domainId, instanceId, masterData) : 0\n if (masterData.length && masterApplied < masterData.length) {\n twinWarn(\n `[twin-live] \"${instanceId}\": ${masterData.length - masterApplied}/${masterData.length} master data element(s) NOT applied — ` +\n 'the twin is not running live; lot attributes such as expiry will stay empty.'\n )\n }\n /*\n * 커널이 **몇 건을 실제로 받았나**를 함께 본다. 트윈이 라이브로 돌지 않으면 `ingestLive` 가 0 을\n * 돌려주고, 그 차이가 곧 「바닥에 버려진 수」다. 이것을 통과로 세면 멈춘 트윈이 100% 로 보인다.\n */\n /* 어떤 길로 들어왔는지 함께 적는다 — 화면이 「이 트윈은 무엇을 통해 받나」를 말할 수 있게. */\n const applied = accepted.length ? TwinEngine.ingestLive(domainId, instanceId, accepted, 'connector') : 0\n const undelivered = accepted.length - applied\n /*\n * **거부한 것을 알리지 않고 버리지 않는다.** 예전에는 `rejected` 를 받지도 않았다 — 커넥터의 매핑이\n * 틀리면 트윈은 「아무 일도 일어나지 않는」 모습이 되고, 로그에도 아무 흔적이 없었다.\n *\n * 그다음 자리였던 「화면까지 나르기」를 이제 채운다. 건마다 브로드캐스팅하지 않고 **집계**한다 — 통과분까지\n * 함께 세는 이유는, 거부만 세면 「유입 없음」과 「정상」이 둘 다 거부 0 으로 같아 보이기 때문이다.\n * 조회는 `twinIngestHealth`.\n */\n /*\n * **읽었다는 것 자체가 사실이다** — 빈 읽기도 성공이므로 단절 기록을 지운다(§`onReadFailure`).\n * 그 둘을 섞으면 들어온 것이 없는 연결이 끊긴 원본으로 보인다.\n */\n TwinEngine.clearIngestReadFailure(domainId, instanceId)\n TwinEngine.recordIngestResult(domainId, instanceId, offered, rejected, Date.now(), undelivered)\n if (rejected.length) {\n const why = rejected[0]?.errors?.join(' · ') ?? 'unknown'\n twinWarn(`[twin-live] \"${instanceId}\": ${rejected.length}/${offered} record(s) rejected — e.g. ${why}`)\n }\n /* 버려진 것은 로그에도 남긴다 — 화면을 보지 않는 사람도 알아야 한다(사실이 사라지는 중이다). */\n if (undelivered > 0) {\n twinWarn(\n `[twin-live] \"${instanceId}\": ${undelivered}/${offered} record(s) NOT delivered — the twin is not running live; ` +\n `records are being discarded. Start the twin, or disconnect the feed.`\n )\n }\n }, continuity)\n feeds.set(instanceId, unsubscribe)\n return true\n}\n\n/**\n * 트윈이 지금 들고 있는 물품을 커널 어휘로 낸다 — 없으면 `undefined`.\n *\n * 상태가 없으면(트윈이 아직 아무것도 모르면) `undefined` 다. 빈 배열로 메우지 않는다 — 「모른다」와\n * 「하나도 없다」는 다른 사실이고, 커넥터가 그 둘에 다르게 반응해야 한다.\n */\nfunction observedItemsOf(domainId: string, instanceId: string): ObservedItemFact[] | undefined {\n const state = TwinEngine.snapshot(domainId, instanceId)\n const items = (state as any)?.items\n if (!Array.isArray(items)) return undefined\n return items.map((i: any) => ({\n id: String(i?.subLotId ?? i?.epc ?? ''),\n ...(i?.gtin ? { epcClass: String(i.gtin) } : {}),\n ...(i?.location ? { location: String(i.location) } : {}),\n ...(typeof i?.qty === 'number' ? { qty: i.qty } : {}),\n ...(i?.uom ? { uom: String(i.uom) } : {}),\n ...(i?.disposition ? { disposition: String(i.disposition) } : {}),\n ...(typeof i?.expiry === 'number' ? { expiry: i.expiry } : {}),\n ...(i?.lot ? { lot: String(i.lot) } : {})\n })).filter((i: ObservedItemFact) => !!i.id)\n}\n\n/**\n * 그 원본의 읽기 커서를 불러오고, 움직이면 저장하는 통로를 만든다.\n *\n * 커서는 **원본(`TwinReference`)의 것**이지 인스턴스의 것이 아니다 — 한 원본이 여러 인스턴스를 낳고\n * (`scopeSpec.produced`) 읽는 것은 한 번이다. 인스턴스마다 커서를 두면 같은 표를 여러 번 읽는다.\n *\n * 저장 실패는 **삼키지 않고 말한다.** 오류 없이 실패하면 다음 재기동에서 창이 다시 미끄러지고, 그 손실은\n * 오류 없이 사라진다 — 이 컬럼을 만든 이유가 바로 그것이다.\n */\nasync function loadLiveCursor(\n domainId: string,\n /** 읽기 실패를 적을 트윈 — 커서는 레퍼런스의 것이지만 **건강은 인스턴스의 것**이다(한 레퍼런스가 여럿을 낳는다). */\n instanceId: string,\n source?: string\n): Promise<LiveFeedContinuity | undefined> {\n if (!source) {\n /* 원본을 가리킬 수 없으면 저장할 자리도 없다 — 그 사실을 말한다(알리지 않고 커서 없이 돌지 않게). */\n twinWarn('[twin-live] live cursor not carried — the caller did not say which reference this feed reads from')\n return undefined\n }\n const repo = getRepository(TwinReference)\n const row = await repo.findOne({ where: { domain: { id: domainId } as any, source } }).catch(() => null)\n if (!row) {\n twinWarn(`[twin-live] live cursor not carried — no reference row for source \"${source}\"`)\n return undefined\n }\n const stored = row.liveCursor\n if (stored?.firstAttachedAt) {\n twinLog(`[twin-live] \"${source}\": resuming from stored cursor — knowledge starts ${stored.firstAttachedAt}`)\n }\n return {\n cursor: stored ?? undefined,\n /*\n * **원본에 닿지 못했다** — 어댑터가 알리면 상태에 적는다(§`onReadFailure`).\n *\n * 로그로는 어댑터가 이미 말하고 있었다(재시도 경고). 그러나 **로그는 사람이 볼 때만 값이 있다** —\n * 화면이 말하려면 조회 가능한 상태에 있어야 한다.\n *\n * ── `status` 를 건드리지 않는다 (2026-08-23, 되돌린 자리) ────────────────────\n * 처음에는 레퍼런스 행의 `status` 를 `'error'` 로도 적었다 — 「목록이 「연결됨」이라 말하면서 실제로는\n * 못 읽는 상태를 없애려고」. **그것이 피드를 영구히 껐다.**\n *\n * 부팅 재부착은 `status: 'connected'` 인 레퍼런스만 되살린다(§`resumeReferenceLiveFeeds`). 그래서\n * 원본이 **잠깐** 끊긴 것이 그 행을 `error` 로 만들고, 다음 재기동에서 그 피드는 아예 붙지 않았다 —\n * 실측으로 그렇게 났다(09:25 판에 `[twin-live]` 줄이 한 줄도 없었다).\n *\n * 두 축을 한 칸에 섞은 것이 원인이다:\n * `status` **연결 설정이 성립하나** — 접속 시험·마스터 동기가 정하는 축(재부착의 자격이다)\n * 읽기 실패 **지금 닿나** — 오갈 수 있는 사실이고, 유입 장부가 그 축을 갖는다\n *\n * 그러니 여기서는 `lastError` 에만 적는다(사람이 목록에서 이유를 볼 수 있게). 「지금 닿나」는\n * `ingestHealth().readFailure` 가 답하고, 성공한 주기가 그것을 지운다.\n */\n onReadFailure: ({ reason, stream, nextRetryMs }) => {\n TwinEngine.recordIngestReadFailure(domainId, instanceId, reason, Date.now(), stream, nextRetryMs)\n twinWarn(`[twin-live] \"${source}\": cannot read from the source${stream ? ` (${stream})` : ''} — ${reason}`)\n repo\n .update({ id: row.id }, { lastError: `live: ${reason}` } as any)\n .catch(err => twinWarn(`[twin-live] \"${source}\": read failure not recorded — ${err?.message ?? err}`))\n },\n /*\n * **끊겼다가 돌아왔다** — 그 사실을 장부에 남긴다 (2026-08-27).\n *\n * 실패 기록을 지우기만 하면 「끊긴 적이 있었다」가 화면에서 사라진다. 그러면 밤새 두 시간 끊겼던\n * 연결과 한 번도 끊기지 않은 연결이 아침에 똑같이 보인다.\n *\n * `lastError` 도 함께 지운다 — 목록에 남은 옛 이유가 지금의 상태로 읽힌다.\n */\n onRecovered: ({ afterFailures, downMs }) => {\n TwinEngine.recordIngestRecovered(domainId, instanceId, afterFailures, downMs, Date.now())\n twinLog(\n `[twin-live] \"${source}\": reading again after ${afterFailures} failed attempt(s) — it was out for ${Math.round(downMs / 1000)}s`\n )\n repo\n .update({ id: row.id }, { lastError: null } as any)\n .catch(err => twinWarn(`[twin-live] \"${source}\": recovery not recorded — ${err?.message ?? err}`))\n },\n /**\n * **읽었는데 창을 넘길 수 없다** — 위와 조치가 반대인 사실이다 (2026-08-24).\n *\n * 이 문을 만들기 전에는 이 사실이 `onReadFailure` 로 나갔다. 그래서 화면이 「원본에 닿지 못한다」고\n * 말했는데 실제로는 원본이 **답한** 상태였고, 그 답의 모양이 커서를 이긴 것이었다. 사람은 원본을\n * 의심하고 기다리는데 기다림으로는 영원히 풀리지 않는다 — **원인을 반대 방향으로 가리켰다.**\n *\n * `status` 를 건드리지 않는 규율은 위와 같다(재부착의 자격이다). `lastError` 에는 적는다 — 목록에서\n * 이유를 볼 수 있어야 하고, 「닿지 못한다」와 다른 문장이어야 한다.\n */\n /*\n * **세우지 않은 것을 장부에 적는다** — 원본에 있는데 트윈에 세우지 않은 것(이유와 수).\n *\n * 로그로도 남기지만 **로그는 사람이 볼 때만 값이 있다.** 이 사실은 상태의 성질이라(세우지 않은 것은\n * 지금도 없다) 조회되는 자리에 있어야 한다 — 그러지 않으면 프로비저닝 화면에서 한 번 스쳐 지나간다.\n *\n * `status`·`lastError` 를 건드리지 않는다: 이것은 **실패가 아니다.** 어댑터가 옳은 판단으로 안 받은\n * 것이고, 오류로 적으면 화면이 원본을 의심하게 만든다.\n */\n onWithheld: ({ reason, count }) => {\n TwinEngine.recordIngestWithheld(domainId, instanceId, reason, count)\n },\n onCursorStall: ({ reason, stream }) => {\n TwinEngine.recordIngestCursorStall(domainId, instanceId, reason, Date.now(), stream)\n twinWarn(\n `[twin-live] \"${source}\": read the source but the cursor cannot advance` +\n `${stream ? ` (${stream})` : ''} — ${reason}. Waiting will not clear this.`\n )\n repo\n .update({ id: row.id }, { lastError: `live cursor stalled: ${reason}` } as any)\n .catch(err => twinWarn(`[twin-live] \"${source}\": cursor stall not recorded — ${err?.message ?? err}`))\n },\n onCursor: next => {\n /* 어댑터가 준 모양을 그대로 적는다 — 이 층은 흐름의 뜻을 모른다(열쇠는 어댑터가 정한다). */\n repo\n .update({ id: row.id }, { liveCursor: next } as any)\n .catch(err => twinWarn(`[twin-live] \"${source}\": live cursor not saved — ${err?.message ?? err}`))\n }\n }\n}\n\n/** 라이브 피드 중지(unsubscribe). */\nexport function stopReferenceLiveFeed(instanceId: string): boolean {\n const unsub = feeds.get(instanceId)\n if (!unsub) return false\n try { unsub() } catch { /* noop */ }\n feeds.delete(instanceId)\n return true\n}\n\n/** 활성 피드 목록(관측). */\nexport function listReferenceLiveFeeds(): string[] {\n return [...feeds.keys()]\n}\n\n/**\n * 부팅 때 **끊어진 피드를 다시 붙인다** — 도는 미러 트윈에 계측이 계속 들어오게.\n *\n * ── 무엇이 알리지 않고 끊겼나 (2026-08-14) ───────────────────────────────────────\n * `startReferenceLiveFeed` 를 부르는 곳은 `importTwinReference` **하나뿐**이었다. 그래서 서버를 한 번\n * 재기동하면 미러 트윈은 「도는 중」이라 말하면서 아무 계측도 받지 않았다 — 오류도 로그도 없이,\n * 사람은 「값이 안 변하네」로 알게 된다. 실 배포에서 재기동 한 번에 계측이 멈추는 셈이다.\n *\n * ── 되살릴 재료는 이미 영속돼 있다 ─────────────────────────────────────────\n * `TwinReference` 가 어댑터 종류·접속 설정·그 레퍼런스가 만든 인스턴스(`scopeSpec.produced`)를 들고 있다.\n * 사이트 서술자만 없는데(`hint` 는 남기지 않았다) **다시 찾아서** 얻는다(`discoverSites`) — 인제스트가\n * 걷는 것과 같은 길이므로 규칙이 갈라지지 않는다. 되살릴 인스턴스가 없으면 그 호출도 하지 않는다.\n *\n * 재개 대상은 **등록부가 도는 미러라고 말하는 것**뿐이다: 멈춘 트윈을 되살리면 사람이 멈춘 것을\n * 우리가 다시 켜는 것이고, 시뮬 트윈에 실 계측을 부으면 두 현실이 섞인다.\n */\nexport async function resumeReferenceLiveFeeds(): Promise<{ resumed: string[]; failed: { instanceId: string; error: string }[] }> {\n const resumed: string[] = []\n const failed: { instanceId: string; error: string }[] = []\n /*\n * **한 트윈에 피드는 하나다** (2026-08-18).\n *\n * 같은 사이트를 두 레퍼런스가 각각 인제스트하면 둘 다 그 인스턴스를 자기가 만든 것(`produced`)으로\n * 들고 있다. 기동 때 그 목록을 그대로 돌면 같은 트윈에 피드를 두 번 붙이는데, 붙이는 함수가 미러를\n * 다시 세우므로(`startLive`) **먼저 쌓인 수요 구간이 리셋된다** — 실제로 그렇게 났다\n * (로그: `reattached 2 feed(s): ems-plant-1, ems-plant-1`).\n *\n * 그러니 한 번만 붙이고, **두 레퍼런스가 같은 트윈을 주장한다는 사실은 말한다** — 알리지 않고 하나를\n * 고르면 어느 접속이 그 트윈을 먹이고 있는지 아무도 모르게 된다.\n */\n const attached = new Map<string, string>() // instanceId → 먼저 붙인 레퍼런스\n try {\n const refs = await getRepository(TwinReference).find({ where: { status: 'connected' }, relations: ['domain'] })\n for (const ref of refs) {\n const domainId = (ref as any).domain?.id ?? (ref as any).domainId\n const adapter = getAdapter(ref.adapterType)\n if (!domainId || !adapter?.openLiveFeed) continue // 관측만 하는 어댑터에는 되살릴 피드가 없다\n\n const produced: any[] = ((ref.scopeSpec as any)?.produced ?? []).filter((p: any) => p?.instanceId && p?.siteId)\n if (!produced.length) continue\n\n /* 등록부가 「도는 미러」라고 말하는 것만 고른다. */\n const rows = await getRepository(TwinInstance).find({\n where: produced.map((p: any) => ({ domain: { id: domainId }, instanceId: p.instanceId })) as any\n })\n const live = produced.filter((p: any) =>\n rows.some(r => r.instanceId === p.instanceId && r.status === 'running' && r.restartPolicy === 'resync')\n )\n if (!live.length) continue\n\n let sites: SiteDescriptor[]\n try {\n sites = await adapter.discoverSites(ref.connectionConfig ?? {})\n } catch (e: any) {\n /* 원 시스템이 아직 안 떴을 수 있다 — 지어내지 않고 그렇게 적는다(트윈은 계속 「도는 미러」다). */\n for (const p of live) failed.push({ instanceId: p.instanceId, error: `discover failed: ${e?.message ?? 'unknown'}` })\n continue\n }\n\n for (const p of live) {\n const site = sites.find(s => s.siteId === p.siteId)\n if (!site) {\n failed.push({ instanceId: p.instanceId, error: `site \"${p.siteId}\" no longer discoverable` })\n continue\n }\n const owner = attached.get(p.instanceId)\n if (owner) {\n /* 두 접속이 같은 트윈을 주장한다 — 먼저 붙은 것을 지키고, 그 사실을 남긴다(둘 다 붙이면 미러가 리셋된다). */\n failed.push({\n instanceId: p.instanceId,\n error: `also claimed by reference \"${ref.source}\" — kept the feed from \"${owner}\" (two connections feeding one twin would reset the mirror)`\n })\n continue\n }\n attached.set(p.instanceId, ref.source)\n try {\n const ok = await startReferenceLiveFeed(domainId, ref.adapterType, ref.connectionConfig ?? {}, site, p.instanceId, ref.source)\n if (ok) resumed.push(p.instanceId)\n else failed.push({ instanceId: p.instanceId, error: 'adapter has no openLiveFeed' })\n } catch (e: any) {\n failed.push({ instanceId: p.instanceId, error: e?.message ?? 'reattach failed' })\n }\n }\n }\n } catch (e: any) {\n twinError('[twin-live] feed resume scan failed', e?.message)\n }\n\n if (resumed.length) twinLog(`[twin-live] reattached ${resumed.length} feed(s): ${resumed.join(', ')}`)\n /* **알리지 않고 넘기지 않는다** — 붙지 않은 미러는 「도는데 아무것도 오지 않는」 트윈이다. */\n for (const f of failed) twinWarn(`[twin-live] \"${f.instanceId}\" feed NOT reattached — ${f.error}`)\n return { resumed, failed }\n}\n\n/*\n * 이 계층의 피드 장부를 등록한다 — 화면이 「끊김」을 말할 근거는 **묻는 자리 하나**여야 한다.\n * (프리셋 기반 피드는 앱이 자기 장부를 따로 등록한다. 한쪽만 보면 흐르는 트윈을 끊겼다고 부른다.)\n */\nregisterLiveFeedProbe(() => [...feeds.keys()])\n\n/* 인스턴스 정지 시 피드 자동 정리(생명주기 대칭). */\nTwinEngine.onStop(instanceId => stopReferenceLiveFeed(instanceId))\n"]}
1
+ {"version":3,"file":"reference-live.js","sourceRoot":"","sources":["../../../server/service/reference/reference-live.ts"],"names":[],"mappings":"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","sourcesContent":["import { twinLog, twinWarn, twinError } from '../../engine/log.js'\nimport { getRepository } from '@things-factory/shell'\n\nimport { getAdapter, type ConnectionConfig, type LiveFeedContinuity, type ObservedItemFact, type SiteDescriptor } from './reference-adapter.js'\nimport { TwinEngine, ingestCanonicalRecords, registerLiveFeedProbe } from '../../engine/index.js'\nimport { type CanonicalRecord } from '@operato/ops-contract'\nimport { TwinInstance } from '../twin-instance/twin-instance.js'\nimport { TwinReference } from './twin-reference.js'\n\n/*\n * 커넥터 기반 라이브 피드 결선(reference → live, §0 프레임 ① restartPolicy='resync').\n * 흐름: adapter.openLiveFeed(cfg, site, records) → ingestCanonicalRecords(정규→봉투) → TwinEngine.ingestLive(projector 미러).\n * 커넥터가 \"벤더 이벤트 → 정규 레코드\" 매핑을 소유(코드)한다 — preset 기반 startLiveFeed(operato-twin, http-get+jsonata)와\n * 대칭이되, 매핑 위치만 다르다. 실 시스템 전환은 connectionConfig.baseUrl 교체뿐(코드 경로 동일).\n * 생명주기: 인스턴스 정지(TwinEngine.stop)에 훅으로 묶여 피드도 함께 unsubscribe.\n */\n\n/** 활성 라이브 피드 — instanceId → unsubscribe(어댑터 openLiveFeed 반환). */\nconst feeds = new Map<string, () => void>()\n\n/**\n * 커넥터 라이브 기동 — 어댑터 openLiveFeed 를 인스턴스에 결선.\n * 반환: 라이브 결선 성공 여부(어댑터가 openLiveFeed 미지원이면 false → 호출측이 sim 폴백).\n */\nexport async function startReferenceLiveFeed(\n domainId: string,\n adapterType: string,\n cfg: ConnectionConfig,\n site: SiteDescriptor,\n instanceId: string,\n /**\n * **어느 레퍼런스에서 읽는 피드인가** — 읽기 커서를 저장할 자리를 가리킨다(§`loadLiveCursor`).\n *\n * 부르는 쪽이 `TwinReference` 를 이미 들고 있으므로 그 이름을 넘긴다. 처음에는 `cfg`·`site` 에서\n * 찾으려 했는데 **둘 다 그 값을 가질 수 없다**: `cfg` 는 사람이 화면에서 입력한 접속 설정이고\n * (`endpoint`·`token`…), `site` 는 커넥터가 원본에서 발견한 것이다 — **레퍼런스의 이름은 호스트가\n * 붙이는 것이고 원본은 그것을 모른다.** 커넥터가 채울 수 없는 값을 커넥터에서 읽으려 한 것이 잘못이었다.\n *\n * 주지 않으면 커서를 잇지 않고 **그 사실을 말한다**(알리지 않고 창이 미끄러지지 않게).\n */\n source?: string\n): Promise<boolean> {\n const adapter = getAdapter(adapterType)\n if (!adapter?.openLiveFeed) return false\n\n const det = await TwinEngine.detail(domainId, instanceId)\n if (!det?.model) throw new Error(`instance \"${instanceId}\" not provisioned (no model) — ingest master before live start`)\n\n // 관측 기동 — 커널 tick 없이 projector 가 실 이벤트를 반영한다(startLive 가 restartPolicy='resync' 로 적는다).\n // 시각 기준은 **공간**이 갖는다(테넌트 아님) — 교대의 HH:MM 을 어느 기준으로 읽나.\n TwinEngine.startLive(instanceId, domainId, det.kind, await TwinEngine.withSpaceTimeBase(det.model, domainId))\n\n stopReferenceLiveFeed(instanceId) // 재기동 시 이전 피드 정리\n /*\n * **어디까지 읽었나를 이 층이 들고 건넨다**(§`LiveFeedContinuity`, 2026-08-23).\n *\n * 어댑터가 이 값을 자기 사물함에 두면 원본이 늘 때마다 저장 기제가 늘고, 그중 하나가 알리지 않고 다르게\n * 동작한다. 그리고 커널이 이미 갈라 두었다: 「원천이 애초에 다시 말해 주지 않는 축」은 재기동\n * 연속성으로 이어받는다(§`hydrateContinuity`) — 읽기 커서가 정확히 그 성질이다.\n *\n * 없으면 `undefined` 를 건넨다 — 그것이 「첫 붙음」이라는 사실이고, 그때만 어댑터가 되돌아볼 날수로\n * 첫 창을 만든다. 빈 객체로 메우지 않는다(그러면 첫 붙음과 이어 붙음이 같아진다).\n */\n const continuity = await loadLiveCursor(domainId, instanceId, source)\n /*\n * ── **트윈이 아는 것을 함께 건넨다** (2026-08-28) ───────────────────────────\n *\n * 전량 절대값을 읽는 커넥터는 지난 주기와 견주어 바뀐 것만 낸다. 그 비교 표가 프로세스 안에만\n * 있어서 재기동하면 커넥터가 전량을 「바뀐 것」으로 판정했다 — 승화푸드에서 2,665건이다.\n *\n * 트윈은 저장본에서 되세우므로 잃지 않았다. 그래서 그 사실을 건네면 첫 주기부터 바뀐 것만 나간다.\n * 커널 어휘로 건넨다 — 원본 낱말로 되돌리는 일은 여러 낱말이 한 값으로 모여 불가능하고, 필요도 없다.\n */\n if (continuity) continuity.observedItems = observedItemsOf(domainId, instanceId)\n const unsubscribe = adapter.openLiveFeed(cfg, site, (records: unknown[]) => {\n if (!TwinEngine.owns(domainId, instanceId)) {\n stopReferenceLiveFeed(instanceId) // 인스턴스 사라지면 자가 정지(안전망)\n return\n }\n const offered = Array.isArray(records) ? records.length : records ? 1 : 0\n const { accepted, rejected, masterData } = ingestCanonicalRecords(\n records as CanonicalRecord[],\n domainId,\n new Date().toISOString(),\n TwinEngine.factScope(domainId, instanceId)\n )\n /*\n * ── **사건이 아닌 것은 상태만 세운다** (2026-08-28) ─────────────────────────\n *\n * 마스터데이터 — 변하지 않는 속성(유통기한·로트번호). 시각축이 없는 값이므로 지난 기록에 적지\n * 않는다(§`applyMasterData`). 이 문이 없던 동안 그 값은 사건에 실려 왔고, 표준이 그것을\n * 「들어왔다」에만 허용하므로 전량을 읽는 원본은 **들어오지 않은 것을 들어왔다고** 말해야 했다.\n *\n * 반영된 건수를 함께 센다 — 라이브로 돌지 않는 트윈은 0 을 내므로, 그 차이가 「바닥에 버려진 수」다.\n */\n const masterApplied = masterData.length ? TwinEngine.applyMasterData(domainId, instanceId, masterData) : 0\n if (masterData.length && masterApplied < masterData.length) {\n twinWarn(\n `[twin-live] \"${instanceId}\": ${masterData.length - masterApplied}/${masterData.length} master data element(s) NOT applied — ` +\n 'the twin is not running live; lot attributes such as expiry will stay empty.'\n )\n }\n /*\n * 커널이 **몇 건을 실제로 받았나**를 함께 본다. 트윈이 라이브로 돌지 않으면 `ingestLive` 가 0 을\n * 돌려주고, 그 차이가 곧 「바닥에 버려진 수」다. 이것을 통과로 세면 멈춘 트윈이 100% 로 보인다.\n */\n /* 어떤 길로 들어왔는지 함께 적는다 — 화면이 「이 트윈은 무엇을 통해 받나」를 말할 수 있게. */\n const applied = accepted.length ? TwinEngine.ingestLive(domainId, instanceId, accepted, 'connector') : 0\n const undelivered = accepted.length - applied\n /*\n * **거부한 것을 알리지 않고 버리지 않는다.** 예전에는 `rejected` 를 받지도 않았다 — 커넥터의 매핑이\n * 틀리면 트윈은 「아무 일도 일어나지 않는」 모습이 되고, 로그에도 아무 흔적이 없었다.\n *\n * 그다음 자리였던 「화면까지 나르기」를 이제 채운다. 건마다 브로드캐스팅하지 않고 **집계**한다 — 통과분까지\n * 함께 세는 이유는, 거부만 세면 「유입 없음」과 「정상」이 둘 다 거부 0 으로 같아 보이기 때문이다.\n * 조회는 `twinIngestHealth`.\n */\n /*\n * **읽었다는 것 자체가 사실이다** — 빈 읽기도 성공이므로 단절 기록을 지운다(§`onReadFailure`).\n * 그 둘을 섞으면 들어온 것이 없는 연결이 끊긴 원본으로 보인다.\n */\n TwinEngine.clearIngestReadFailure(domainId, instanceId)\n TwinEngine.recordIngestResult(domainId, instanceId, offered, rejected, Date.now(), undelivered)\n if (rejected.length) {\n const why = rejected[0]?.errors?.join(' · ') ?? 'unknown'\n twinWarn(`[twin-live] \"${instanceId}\": ${rejected.length}/${offered} record(s) rejected — e.g. ${why}`)\n }\n /* 버려진 것은 로그에도 남긴다 — 화면을 보지 않는 사람도 알아야 한다(사실이 사라지는 중이다). */\n if (undelivered > 0) {\n twinWarn(\n `[twin-live] \"${instanceId}\": ${undelivered}/${offered} record(s) NOT delivered — the twin is not running live; ` +\n `records are being discarded. Start the twin, or disconnect the feed.`\n )\n }\n }, continuity)\n feeds.set(instanceId, unsubscribe)\n return true\n}\n\n/**\n * 트윈이 지금 들고 있는 물품을 커널 어휘로 낸다 — 없으면 `undefined`.\n *\n * 상태가 없으면(트윈이 아직 아무것도 모르면) `undefined` 다. 빈 배열로 메우지 않는다 — 「모른다」와\n * 「하나도 없다」는 다른 사실이고, 커넥터가 그 둘에 다르게 반응해야 한다.\n */\nfunction observedItemsOf(domainId: string, instanceId: string): ObservedItemFact[] | undefined {\n const state = TwinEngine.snapshot(domainId, instanceId)\n const items = (state as any)?.items\n if (!Array.isArray(items)) return undefined\n return items.map((i: any) => ({\n id: String(i?.subLotId ?? i?.epc ?? ''),\n ...(i?.gtin ? { epcClass: String(i.gtin) } : {}),\n ...(i?.location ? { location: String(i.location) } : {}),\n ...(typeof i?.qty === 'number' ? { qty: i.qty } : {}),\n ...(i?.uom ? { uom: String(i.uom) } : {}),\n ...(i?.disposition ? { disposition: String(i.disposition) } : {}),\n ...(typeof i?.expiry === 'number' ? { expiry: i.expiry } : {}),\n ...(i?.lot ? { lot: String(i.lot) } : {})\n })).filter((i: ObservedItemFact) => !!i.id)\n}\n\n/**\n * 그 원본의 읽기 커서를 불러오고, 움직이면 저장하는 통로를 만든다.\n *\n * 커서는 **원본(`TwinReference`)의 것**이지 인스턴스의 것이 아니다 — 한 원본이 여러 인스턴스를 낳고\n * (`scopeSpec.produced`) 읽는 것은 한 번이다. 인스턴스마다 커서를 두면 같은 표를 여러 번 읽는다.\n *\n * 저장 실패는 **삼키지 않고 말한다.** 오류 없이 실패하면 다음 재기동에서 창이 다시 미끄러지고, 그 손실은\n * 오류 없이 사라진다 — 이 컬럼을 만든 이유가 바로 그것이다.\n */\nasync function loadLiveCursor(\n domainId: string,\n /** 읽기 실패를 적을 트윈 — 커서는 레퍼런스의 것이지만 **건강은 인스턴스의 것**이다(한 레퍼런스가 여럿을 낳는다). */\n instanceId: string,\n source?: string\n): Promise<LiveFeedContinuity | undefined> {\n if (!source) {\n /* 원본을 가리킬 수 없으면 저장할 자리도 없다 — 그 사실을 말한다(알리지 않고 커서 없이 돌지 않게). */\n twinWarn('[twin-live] live cursor not carried — the caller did not say which reference this feed reads from')\n return undefined\n }\n const repo = getRepository(TwinReference)\n const row = await repo.findOne({ where: { domain: { id: domainId } as any, source } }).catch(() => null)\n if (!row) {\n twinWarn(`[twin-live] live cursor not carried — no reference row for source \"${source}\"`)\n return undefined\n }\n const stored = row.liveCursor\n if (stored?.firstAttachedAt) {\n twinLog(`[twin-live] \"${source}\": resuming from stored cursor — knowledge starts ${stored.firstAttachedAt}`)\n }\n return {\n cursor: stored ?? undefined,\n /*\n * **원본에 닿지 못했다** — 어댑터가 알리면 상태에 적는다(§`onReadFailure`).\n *\n * 로그로는 어댑터가 이미 말하고 있었다(재시도 경고). 그러나 **로그는 사람이 볼 때만 값이 있다** —\n * 화면이 말하려면 조회 가능한 상태에 있어야 한다.\n *\n * ── `status` 를 건드리지 않는다 (2026-08-23, 되돌린 자리) ────────────────────\n * 처음에는 레퍼런스 행의 `status` 를 `'error'` 로도 적었다 — 「목록이 「연결됨」이라 말하면서 실제로는\n * 못 읽는 상태를 없애려고」. **그것이 피드를 영구히 껐다.**\n *\n * 부팅 재부착은 `status: 'connected'` 인 레퍼런스만 되살린다(§`resumeReferenceLiveFeeds`). 그래서\n * 원본이 **잠깐** 끊긴 것이 그 행을 `error` 로 만들고, 다음 재기동에서 그 피드는 아예 붙지 않았다 —\n * 실측으로 그렇게 났다(09:25 판에 `[twin-live]` 줄이 한 줄도 없었다).\n *\n * 두 축을 한 칸에 섞은 것이 원인이다:\n * `status` **연결 설정이 성립하나** — 접속 시험·마스터 동기가 정하는 축(재부착의 자격이다)\n * 읽기 실패 **지금 닿나** — 오갈 수 있는 사실이고, 유입 장부가 그 축을 갖는다\n *\n * 그러니 여기서는 `lastError` 에만 적는다(사람이 목록에서 이유를 볼 수 있게). 「지금 닿나」는\n * `ingestHealth().readFailure` 가 답하고, 성공한 주기가 그것을 지운다.\n */\n onReadFailure: ({ reason, stream, nextRetryMs }) => {\n TwinEngine.recordIngestReadFailure(domainId, instanceId, reason, Date.now(), stream, nextRetryMs)\n twinWarn(`[twin-live] \"${source}\": cannot read from the source${stream ? ` (${stream})` : ''} — ${reason}`)\n repo\n .update({ id: row.id }, { lastError: `live: ${reason}` } as any)\n .catch(err => twinWarn(`[twin-live] \"${source}\": read failure not recorded — ${err?.message ?? err}`))\n },\n /*\n * **끊겼다가 돌아왔다** — 그 사실을 장부에 남긴다 (2026-08-27).\n *\n * 실패 기록을 지우기만 하면 「끊긴 적이 있었다」가 화면에서 사라진다. 그러면 밤새 두 시간 끊겼던\n * 연결과 한 번도 끊기지 않은 연결이 아침에 똑같이 보인다.\n *\n * `lastError` 도 함께 지운다 — 목록에 남은 옛 이유가 지금의 상태로 읽힌다.\n */\n onRecovered: ({ afterFailures, downMs }) => {\n TwinEngine.recordIngestRecovered(domainId, instanceId, afterFailures, downMs, Date.now())\n twinLog(\n `[twin-live] \"${source}\": reading again after ${afterFailures} failed attempt(s) — it was out for ${Math.round(downMs / 1000)}s`\n )\n repo\n .update({ id: row.id }, { lastError: null } as any)\n .catch(err => twinWarn(`[twin-live] \"${source}\": recovery not recorded — ${err?.message ?? err}`))\n },\n /**\n * **읽었는데 창을 넘길 수 없다** — 위와 조치가 반대인 사실이다 (2026-08-24).\n *\n * 이 문을 만들기 전에는 이 사실이 `onReadFailure` 로 나갔다. 그래서 화면이 「원본에 닿지 못한다」고\n * 말했는데 실제로는 원본이 **답한** 상태였고, 그 답의 모양이 커서를 이긴 것이었다. 사람은 원본을\n * 의심하고 기다리는데 기다림으로는 영원히 풀리지 않는다 — **원인을 반대 방향으로 가리켰다.**\n *\n * `status` 를 건드리지 않는 규율은 위와 같다(재부착의 자격이다). `lastError` 에는 적는다 — 목록에서\n * 이유를 볼 수 있어야 하고, 「닿지 못한다」와 다른 문장이어야 한다.\n */\n /*\n * **세우지 않은 것을 장부에 적는다** — 원본에 있는데 트윈에 세우지 않은 것(이유와 수).\n *\n * 로그로도 남기지만 **로그는 사람이 볼 때만 값이 있다.** 이 사실은 상태의 성질이라(세우지 않은 것은\n * 지금도 없다) 조회되는 자리에 있어야 한다 — 그러지 않으면 프로비저닝 화면에서 한 번 스쳐 지나간다.\n *\n * `status`·`lastError` 를 건드리지 않는다: 이것은 **실패가 아니다.** 어댑터가 옳은 판단으로 안 받은\n * 것이고, 오류로 적으면 화면이 원본을 의심하게 만든다.\n */\n onWithheld: ({ reason, count }) => {\n TwinEngine.recordIngestWithheld(domainId, instanceId, reason, count)\n },\n onCursorStall: ({ reason, stream }) => {\n TwinEngine.recordIngestCursorStall(domainId, instanceId, reason, Date.now(), stream)\n twinWarn(\n `[twin-live] \"${source}\": read the source but the cursor cannot advance` +\n `${stream ? ` (${stream})` : ''} — ${reason}. Waiting will not clear this.`\n )\n repo\n .update({ id: row.id }, { lastError: `live cursor stalled: ${reason}` } as any)\n .catch(err => twinWarn(`[twin-live] \"${source}\": cursor stall not recorded — ${err?.message ?? err}`))\n },\n onCursor: next => {\n /* 어댑터가 준 모양을 그대로 적는다 — 이 층은 흐름의 뜻을 모른다(열쇠는 어댑터가 정한다). */\n repo\n .update({ id: row.id }, { liveCursor: next } as any)\n .catch(err => twinWarn(`[twin-live] \"${source}\": live cursor not saved — ${err?.message ?? err}`))\n }\n }\n}\n\n/** 라이브 피드 중지(unsubscribe). */\nexport function stopReferenceLiveFeed(instanceId: string): boolean {\n const unsub = feeds.get(instanceId)\n if (!unsub) return false\n try { unsub() } catch { /* noop */ }\n feeds.delete(instanceId)\n return true\n}\n\n/** 활성 피드 목록(관측). */\nexport function listReferenceLiveFeeds(): string[] {\n return [...feeds.keys()]\n}\n\n/**\n * 부팅 때 **끊어진 피드를 다시 붙인다** — 도는 미러 트윈에 계측이 계속 들어오게.\n *\n * ── 무엇이 알리지 않고 끊겼나 (2026-08-14) ───────────────────────────────────────\n * `startReferenceLiveFeed` 를 부르는 곳은 `importTwinReference` **하나뿐**이었다. 그래서 서버를 한 번\n * 재기동하면 미러 트윈은 「도는 중」이라 말하면서 아무 계측도 받지 않았다 — 오류도 로그도 없이,\n * 사람은 「값이 안 변하네」로 알게 된다. 실 배포에서 재기동 한 번에 계측이 멈추는 셈이다.\n *\n * ── 되살릴 재료는 이미 영속돼 있다 ─────────────────────────────────────────\n * `TwinReference` 가 어댑터 종류·접속 설정·그 레퍼런스가 만든 인스턴스(`scopeSpec.produced`)를 들고 있다.\n * 사이트 서술자만 없는데(`hint` 는 남기지 않았다) **다시 찾아서** 얻는다(`discoverSites`) — 인제스트가\n * 걷는 것과 같은 길이므로 규칙이 갈라지지 않는다. 되살릴 인스턴스가 없으면 그 호출도 하지 않는다.\n *\n * 재개 대상은 **등록부가 도는 미러라고 말하는 것**뿐이다: 멈춘 트윈을 되살리면 사람이 멈춘 것을\n * 우리가 다시 켜는 것이고, 시뮬 트윈에 실 계측을 부으면 두 현실이 섞인다.\n */\nexport async function resumeReferenceLiveFeeds(): Promise<{ resumed: string[]; failed: { instanceId: string; error: string }[] }> {\n const resumed: string[] = []\n const failed: { instanceId: string; error: string }[] = []\n /*\n * **한 트윈에 피드는 하나다** (2026-08-18).\n *\n * 같은 사이트를 두 레퍼런스가 각각 인제스트하면 둘 다 그 인스턴스를 자기가 만든 것(`produced`)으로\n * 들고 있다. 기동 때 그 목록을 그대로 돌면 같은 트윈에 피드를 두 번 붙이는데, 붙이는 함수가 미러를\n * 다시 세우므로(`startLive`) **먼저 쌓인 수요 구간이 리셋된다** — 실제로 그렇게 났다\n * (로그: `reattached 2 feed(s): ems-plant-1, ems-plant-1`).\n *\n * 그러니 한 번만 붙이고, **두 레퍼런스가 같은 트윈을 주장한다는 사실은 말한다** — 알리지 않고 하나를\n * 고르면 어느 접속이 그 트윈을 먹이고 있는지 아무도 모르게 된다.\n */\n const attached = new Map<string, string>() // instanceId → 먼저 붙인 레퍼런스\n try {\n const refs = await getRepository(TwinReference).find({ where: { status: 'connected' }, relations: ['domain'] })\n for (const ref of refs) {\n const domainId = (ref as any).domain?.id ?? (ref as any).domainId\n const adapter = getAdapter(ref.adapterType)\n if (!domainId || !adapter?.openLiveFeed) continue // 관측만 하는 어댑터에는 되살릴 피드가 없다\n\n const produced: any[] = ((ref.scopeSpec as any)?.produced ?? []).filter((p: any) => p?.instanceId && p?.siteId)\n if (!produced.length) continue\n\n /* 등록부가 「도는 미러」라고 말하는 것만 고른다. */\n const rows = await getRepository(TwinInstance).find({\n where: produced.map((p: any) => ({ domain: { id: domainId }, instanceId: p.instanceId })) as any\n })\n const live = produced.filter((p: any) =>\n rows.some(r => r.instanceId === p.instanceId && r.status === 'running' && r.restartPolicy === 'resync')\n )\n if (!live.length) continue\n\n let sites: SiteDescriptor[]\n try {\n sites = await adapter.discoverSites(ref.connectionConfig ?? {})\n } catch (e: any) {\n /* 원 시스템이 아직 안 떴을 수 있다 — 지어내지 않고 그렇게 적는다(트윈은 계속 「도는 미러」다). */\n for (const p of live) failed.push({ instanceId: p.instanceId, error: `discover failed: ${e?.message ?? 'unknown'}` })\n continue\n }\n\n for (const p of live) {\n const site = sites.find(s => s.siteId === p.siteId)\n if (!site) {\n failed.push({ instanceId: p.instanceId, error: `site \"${p.siteId}\" no longer discoverable` })\n continue\n }\n const owner = attached.get(p.instanceId)\n if (owner) {\n /* 두 접속이 같은 트윈을 주장한다 — 먼저 붙은 것을 지키고, 그 사실을 남긴다(둘 다 붙이면 미러가 리셋된다). */\n failed.push({\n instanceId: p.instanceId,\n error: `also claimed by reference \"${ref.source}\" — kept the feed from \"${owner}\" (two connections feeding one twin would reset the mirror)`\n })\n continue\n }\n attached.set(p.instanceId, ref.source)\n try {\n const ok = await startReferenceLiveFeed(domainId, ref.adapterType, ref.connectionConfig ?? {}, site, p.instanceId, ref.source)\n if (ok) resumed.push(p.instanceId)\n else failed.push({ instanceId: p.instanceId, error: 'adapter has no openLiveFeed' })\n } catch (e: any) {\n failed.push({ instanceId: p.instanceId, error: e?.message ?? 'reattach failed' })\n }\n }\n }\n } catch (e: any) {\n twinError('[twin-live] feed resume scan failed', e?.message)\n }\n\n if (resumed.length) twinLog(`[twin-live] reattached ${resumed.length} feed(s): ${resumed.join(', ')}`)\n /* **알리지 않고 넘기지 않는다** — 붙지 않은 미러는 「도는데 아무것도 오지 않는」 트윈이다. */\n for (const f of failed) twinWarn(`[twin-live] \"${f.instanceId}\" feed NOT reattached — ${f.error}`)\n return { resumed, failed }\n}\n\n/*\n * 이 계층의 피드 장부를 등록한다 — 화면이 「끊김」을 말할 근거는 **묻는 자리 하나**여야 한다.\n * (프리셋 기반 피드는 앱이 자기 장부를 따로 등록한다. 한쪽만 보면 흐르는 트윈을 끊겼다고 부른다.)\n */\nregisterLiveFeedProbe(() => [...feeds.keys()])\n\n/* 인스턴스 정지 시 피드 자동 정리(생명주기 대칭). */\nTwinEngine.onStop(instanceId => stopReferenceLiveFeed(instanceId))\n"]}
@@ -1,4 +1,4 @@
1
- import type { DomainSystem, EquipmentLevel, TestSpecificationCriterion, WorkCalendarEntry } from '@operato/twin-kernel';
1
+ import type { DomainSystem, EquipmentLevel, TestSpecificationCriterion, WorkCalendarEntry } from '@operato/ops-contract';
2
2
  export interface RefLocation {
3
3
  id: string;
4
4
  type: string;
@@ -139,7 +139,16 @@ export interface RefEquipment {
139
139
  homeLocationId: string;
140
140
  mtbfMs?: number;
141
141
  mttrMs?: number;
142
- gs1Id?: string;
142
+ /**
143
+ * **이 설비의 전역 정체성** — 커널 선언의 `equipment[].identity` 로 그대로 간다.
144
+ *
145
+ * 예전 이름은 `gs1Id` 였다. 그런데 **GS1 키가 아닌 것이 정상이다** — 실측한 값은 인버터의
146
+ * 하드웨어 식별자(`ES100000007d8d061d-1`)이고, 원본이 GIAI 를 주면 그것도 여기 들어온다.
147
+ * 이름이 한 종류만 가리키면 다른 종류가 들어올 때 그 자리를 못 쓴다.
148
+ *
149
+ * 자리(`RefLocation.gs1Id`)는 그대로다 — 그쪽은 실제로 SGLN 이 온다.
150
+ */
151
+ identity?: string;
143
152
  /**
144
153
  * 교대(가동시간) — 이 시간대에만 배정된다. `startHour <= endHour` 면 같은 날, 넘어가면 야간(22→6).
145
154
  * 미지정=24시간 가용. 교대 밖 자원은 고장·계획정지와 **구별되는 이유**로 쉰다(상태에 드러난다).
@@ -237,7 +237,7 @@ function masterToTwin(master, catalog) {
237
237
  });
238
238
  const equipment = (master.equipment ?? []).map(e => ({
239
239
  id: e.id, kind: e.kind, ...(e.name ? { name: e.name } : {}), ...(e.description ? { description: e.description } : {}), ...(e.testResults?.length ? { testResults: e.testResults } : {}),
240
- homeLocation: e.homeLocationId, mtbfMs: e.mtbfMs, mttrMs: e.mttrMs, gs1Id: e.gs1Id,
240
+ homeLocation: e.homeLocationId, mtbfMs: e.mtbfMs, mttrMs: e.mttrMs, ...(e.identity ? { identity: e.identity } : {}),
241
241
  ...(e.window ? { window: e.window } : {}),
242
242
  /* 속성은 손대지 않고 통과 — 이동시간 추정기가 kind 별 속도를 여기서 읽는다(단위 코드 그대로). */
243
243
  ...(e.properties?.length ? { properties: e.properties } : {}), ...(e.workCalendar?.length ? { workCalendar: e.workCalendar } : {}), ...(e.testSpecificationIds?.length ? { testSpecificationIds: e.testSpecificationIds } : {}),