@things-factory/headless-twin 10.0.10 → 10.0.12

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 (138) hide show
  1. package/dist-server/engine/canonical-ingest.d.ts +25 -2
  2. package/dist-server/engine/canonical-ingest.js +48 -10
  3. package/dist-server/engine/canonical-ingest.js.map +1 -1
  4. package/dist-server/engine/declared-stimulus.d.ts +43 -0
  5. package/dist-server/engine/declared-stimulus.js +57 -0
  6. package/dist-server/engine/declared-stimulus.js.map +1 -0
  7. package/dist-server/engine/index.d.ts +2 -0
  8. package/dist-server/engine/index.js +2 -0
  9. package/dist-server/engine/index.js.map +1 -1
  10. package/dist-server/engine/kpi-fold.d.ts +12 -0
  11. package/dist-server/engine/kpi-fold.js +21 -1
  12. package/dist-server/engine/kpi-fold.js.map +1 -1
  13. package/dist-server/engine/kpi-query.d.ts +3 -3
  14. package/dist-server/engine/kpi-query.js +4 -4
  15. package/dist-server/engine/kpi-query.js.map +1 -1
  16. package/dist-server/engine/live-feed-registry.d.ts +1 -1
  17. package/dist-server/engine/live-feed-registry.js +1 -1
  18. package/dist-server/engine/live-feed-registry.js.map +1 -1
  19. package/dist-server/engine/local-declarations.d.ts +3 -6
  20. package/dist-server/engine/local-declarations.js +97 -16
  21. package/dist-server/engine/local-declarations.js.map +1 -1
  22. package/dist-server/engine/measured-yield.d.ts +42 -0
  23. package/dist-server/engine/measured-yield.js +75 -0
  24. package/dist-server/engine/measured-yield.js.map +1 -0
  25. package/dist-server/engine/operation-basis.d.ts +16 -0
  26. package/dist-server/engine/operation-basis.js +20 -2
  27. package/dist-server/engine/operation-basis.js.map +1 -1
  28. package/dist-server/engine/property-effects.js +17 -0
  29. package/dist-server/engine/property-effects.js.map +1 -1
  30. package/dist-server/engine/restart-policy.d.ts +13 -0
  31. package/dist-server/engine/restart-policy.js +52 -0
  32. package/dist-server/engine/restart-policy.js.map +1 -0
  33. package/dist-server/engine/spec-coverage.d.ts +8 -0
  34. package/dist-server/engine/spec-coverage.js +3 -1
  35. package/dist-server/engine/spec-coverage.js.map +1 -1
  36. package/dist-server/engine/twin-engine.d.ts +48 -15
  37. package/dist-server/engine/twin-engine.js +179 -39
  38. package/dist-server/engine/twin-engine.js.map +1 -1
  39. package/dist-server/index.js +1 -1
  40. package/dist-server/index.js.map +1 -1
  41. package/dist-server/service/index.d.ts +1 -1
  42. package/dist-server/service/reference/control-routing.d.ts +14 -0
  43. package/dist-server/service/reference/control-routing.js +64 -0
  44. package/dist-server/service/reference/control-routing.js.map +1 -0
  45. package/dist-server/service/reference/index.d.ts +1 -0
  46. package/dist-server/service/reference/index.js +2 -0
  47. package/dist-server/service/reference/index.js.map +1 -1
  48. package/dist-server/service/reference/reference-adapter.d.ts +67 -0
  49. package/dist-server/service/reference/reference-adapter.js +18 -0
  50. package/dist-server/service/reference/reference-adapter.js.map +1 -1
  51. package/dist-server/service/reference/reference-live.js +3 -3
  52. package/dist-server/service/reference/reference-live.js.map +1 -1
  53. package/dist-server/service/reference/reference-resolver.d.ts +3 -3
  54. package/dist-server/service/reference/reference-resolver.js +35 -18
  55. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  56. package/dist-server/service/twin-audit/command-audit.d.ts +34 -0
  57. package/dist-server/service/twin-audit/command-audit.js +15 -1
  58. package/dist-server/service/twin-audit/command-audit.js.map +1 -1
  59. package/dist-server/service/twin-audit/twin-audit-event.d.ts +3 -0
  60. package/dist-server/service/twin-audit/twin-audit-event.js +28 -2
  61. package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -1
  62. package/dist-server/service/twin-control/twin-control-mutation.d.ts +15 -2
  63. package/dist-server/service/twin-control/twin-control-mutation.js +85 -34
  64. package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
  65. package/dist-server/service/twin-event/twin-event.js +1 -1
  66. package/dist-server/service/twin-event/twin-event.js.map +1 -1
  67. package/dist-server/service/twin-instance/twin-instance.d.ts +1 -1
  68. package/dist-server/service/twin-instance/twin-instance.js +2 -2
  69. package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
  70. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +2 -1
  71. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
  72. package/dist-server/service/twin-model/twin-equipment.js +1 -1
  73. package/dist-server/service/twin-model/twin-equipment.js.map +1 -1
  74. package/dist-server/service/twin-model/twin-location.js +1 -1
  75. package/dist-server/service/twin-model/twin-location.js.map +1 -1
  76. package/dist-server/service/twin-model/twin-model-query.js +1 -1
  77. package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
  78. package/dist-server/service/twin-model/twin-operation.js +1 -1
  79. package/dist-server/service/twin-model/twin-operation.js.map +1 -1
  80. package/package.json +3 -3
  81. package/server/engine/canonical-ingest.ts +78 -14
  82. package/server/engine/declared-stimulus.ts +66 -0
  83. package/server/engine/index.ts +2 -0
  84. package/server/engine/kpi-fold.ts +29 -1
  85. package/server/engine/kpi-query.ts +8 -8
  86. package/server/engine/live-feed-registry.ts +2 -2
  87. package/server/engine/local-declarations.ts +97 -19
  88. package/server/engine/measured-yield.ts +89 -0
  89. package/server/engine/operation-basis.ts +33 -2
  90. package/server/engine/property-effects.ts +17 -0
  91. package/server/engine/restart-policy.ts +55 -0
  92. package/server/engine/spec-coverage.ts +23 -3
  93. package/server/engine/twin-engine.ts +201 -42
  94. package/server/index.ts +1 -1
  95. package/server/service/reference/control-routing.ts +62 -0
  96. package/server/service/reference/index.ts +1 -0
  97. package/server/service/reference/reference-adapter.ts +78 -0
  98. package/server/service/reference/reference-live.ts +3 -3
  99. package/server/service/reference/reference-resolver.ts +46 -7
  100. package/server/service/twin-audit/command-audit.ts +34 -1
  101. package/server/service/twin-audit/twin-audit-event.ts +50 -4
  102. package/server/service/twin-control/twin-control-mutation.ts +75 -28
  103. package/server/service/twin-event/twin-event.ts +10 -2
  104. package/server/service/twin-instance/twin-instance.ts +16 -10
  105. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +2 -1
  106. package/server/service/twin-model/twin-equipment.ts +8 -1
  107. package/server/service/twin-model/twin-location.ts +8 -1
  108. package/server/service/twin-model/twin-model-query.ts +1 -1
  109. package/server/service/twin-model/twin-operation.ts +8 -1
  110. package/test/adopt-structure-live.test.ts +7 -7
  111. package/test/boot-resume.test.ts +3 -3
  112. package/test/column-type-portability.test.ts +122 -0
  113. package/test/control-capability.test.ts +103 -0
  114. package/test/declared-stimulus.test.ts +88 -0
  115. package/test/ingest-running-guard.test.ts +5 -5
  116. package/test/instance-cache-lifecycle.test.ts +1 -1
  117. package/test/kpi-baseline-db.test.ts +1 -1
  118. package/test/kpi-query-bench.test.ts +1 -1
  119. package/test/lineage-survives-restart.test.ts +3 -3
  120. package/test/live-feed-registry.test.ts +6 -6
  121. package/test/local-declarations.test.ts +108 -1
  122. package/test/measured-yield.test.ts +90 -0
  123. package/test/operation-basis.test.ts +28 -1
  124. package/test/operational-vocabulary.test.ts +108 -0
  125. package/test/operations-capability-db.test.ts +4 -4
  126. package/test/project-structure-db.test.ts +1 -1
  127. package/test/projection-reaches-screen.test.ts +1 -1
  128. package/test/property-effects.test.ts +28 -0
  129. package/test/restart-policy.test.ts +111 -0
  130. package/test/resync-origin-site.test.ts +7 -1
  131. package/test/source-outcome-audit.test.ts +104 -0
  132. package/test/structure-revision-db.test.ts +27 -27
  133. package/test/tenant-registry-db.test.ts +2 -2
  134. package/test/twin-model-item-db.test.ts +3 -3
  135. package/test/twin-model-tree-db.test.ts +7 -7
  136. package/test/twin-origin-resync.test.ts +4 -4
  137. package/test/yield-loop.test.ts +144 -0
  138. package/tsconfig.tsbuildinfo +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"property-effects.js","sourceRoot":"","sources":["../../server/engine/property-effects.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 자원 속성의 **효과 등록부** — 이 속성이 무엇을 바꾸나.\n *\n * ── 왜 있나 ─────────────────────────────────────────────────────────────────\n * 속성은 **열려 있다**: 템플릿·마스터가 아무 id 로나 사실을 실어 보낼 수 있고, 커널은 그것을 그대로\n * 나르고 화면은 단위와 함께 보인다(표준이 속성 어휘를 정하지 않으므로 그것이 옳다).\n *\n * 그런데 **효과는 닫혀 있다**: 값이 계산·배정·판정을 바꾸려면 그 규칙을 코드가 알아야 한다. 지금\n * 효과가 있는 것은 둘뿐이다. 문제는 그 사실이 **어디에도 드러나지 않는다**는 것이었다 — 사용자는\n * 속성을 넣으면 무언가 달라질 것으로 기대하는데, 실제로는 표에 한 줄이 늘 뿐이다.\n *\n * 그래서 이 등록부는 **지어내지 않고 드러낸다**: 읽는 코드가 있는 것만 적고, 없는 것은 \"표시만 된다\"\n * 고 화면이 말한다. 새 효과를 코드로 붙이는 사람은 **여기 한 줄을 더한다** — 그러지 않으면 사용자는\n * 그 효과가 있는지 알 수 없다.\n *\n * ⚠️ 이 파일은 등록부이고 판정이 아니다. 값을 실제로 읽는 곳은 각자다(아래 `readBy` 가 그 자리를\n * 가리킨다) — 여기에 계산을 옮겨 오면 규칙이 두 벌이 된다.\n */\n\n/** 효과가 있는 속성 하나. */\nexport interface PropertyEffect {\n /** 속성 id — 선언에 적히는 그 이름(`ResourceProperty.id`). */\n id: string\n /** 어느 축의 속성인가 — 그 축의 항목에만 이 효과를 말한다. */\n axes: string[]\n /** 무엇을 바꾸나 — 화면이 읽는 i18n 키(언어중립: 서버는 사람 말을 만들지 않는다). */\n effect: string\n /** 실제로 읽는 코드 — 지어낸 효과를 등록하지 않기 위한 표식이다(없는 자리를 가리키면 리뷰가 잡는다). */\n readBy: string\n}\n\nexport const PROPERTY_EFFECTS: PropertyEffect[] = [\n {\n id: 'speed',\n axes: ['equipment'],\n effect: 'twin.propeffect.speed',\n readBy: 'headless-twin/server/engine/travel-estimator.ts (buildTravelEstimator)'\n },\n {\n id: 'perBaseUnit',\n axes: ['materialDefinitions'],\n effect: 'twin.propeffect.perBaseUnit',\n readBy: '@operato/twin-kernel conversionFactorOf'\n },\n /*\n * ── 에너지 속성 (2026-08-18) ──────────────────────────────────────────────\n *\n * 이 값들은 **오래전부터 읽히고 있었다** — 커널이 부하·피크·배터리를 그것으로 계산하고, 호스트가\n * 금액을 그것으로 만든다. 그런데 등록부에 없어서 화면은 그 값들에 대해 「표시만 됩니다」라고 말했다.\n * 조용한 거짓말이다: 사용자는 계약전력을 넣고도 「이 값은 아무것도 바꾸지 않는다」는 문장을 읽었다.\n *\n * 효과를 새로 만든 것이 아니라 **있는 효과를 드러낸다.** `readBy` 는 실제로 읽는 자리를 가리킨다.\n */\n {\n id: 'contract.kW',\n axes: ['locations'],\n effect: 'twin.propeffect.contractKW',\n readBy: '@operato/twin-kernel EmsKernel.declaredContractKW (계약 대비 판정·초과 주의)'\n },\n {\n id: 'power.ratedKW',\n axes: ['equipment'],\n effect: 'twin.propeffect.ratedKW',\n readBy: '@operato/twin-kernel EmsKernel.deriveEquipmentLoad (가동 중 부하)'\n },\n {\n id: 'power.standbyKW',\n axes: ['equipment'],\n effect: 'twin.propeffect.standbyKW',\n readBy: '@operato/twin-kernel EmsKernel.deriveEquipmentLoad (대기 중 부하)'\n },\n {\n id: 'storage.capacityKWh',\n axes: ['equipment'],\n effect: 'twin.propeffect.capacityKWh',\n readBy: '@operato/twin-kernel EmsKernel (축전지 방전 가능량)'\n },\n {\n id: 'dispatch.dischargeAboveKW',\n axes: ['equipment'],\n effect: 'twin.propeffect.dischargeAboveKW',\n readBy: '@operato/twin-kernel EmsKernel (방전 시작 문턱)'\n },\n {\n id: 'dispatch.maxDischargeKW',\n axes: ['equipment'],\n effect: 'twin.propeffect.maxDischargeKW',\n readBy: '@operato/twin-kernel EmsKernel (인버터 방전 상한)'\n },\n {\n id: 'dispatch.reserveSoc',\n axes: ['equipment'],\n effect: 'twin.propeffect.reserveSoc',\n readBy: '@operato/twin-kernel EmsKernel (이 아래로는 방전하지 않는다)'\n },\n {\n id: 'storage.initialSoc',\n axes: ['equipment'],\n effect: 'twin.propeffect.initialSoc',\n readBy: '@operato/twin-kernel EmsKernel (시작 충전율 — 예측·what-if 의 씨앗)'\n },\n {\n id: 'generation.ratedKW',\n axes: ['equipment'],\n effect: 'twin.propeffect.genRatedKW',\n readBy: '@operato/twin-kernel EmsKernel.deriveGeneration (정격 발전)'\n },\n {\n id: 'generation.dailyProfile',\n axes: ['equipment'],\n effect: 'twin.propeffect.genDailyProfile',\n readBy: '@operato/twin-kernel generationFractionAt (시각별 발전 비율)'\n },\n /* 요금은 커널이 읽지 않는다 — 금액은 저널을 접는 호스트가 만든다. readBy 가 그 사실을 말한다. */\n {\n id: 'tariff.demandChargePerKW',\n axes: ['locations'],\n effect: 'twin.propeffect.demandChargePerKW',\n readBy: 'headless-twin/server/engine/kpi-query.ts (수요요금 계산)'\n },\n {\n id: 'tariff.energyChargePerKWh',\n axes: ['locations'],\n effect: 'twin.propeffect.energyChargePerKWh',\n readBy: 'headless-twin/server/engine/kpi-query.ts (전력량 요금 계산)'\n },\n /* 판정 기준 — 값이 경보의 유무를 바꾼다(가장 눈에 보이는 효과다). */\n {\n id: 'attention.congestionRatio',\n axes: ['locations'],\n effect: 'twin.propeffect.congestionRatio',\n readBy: '@operato/twin-kernel deriveAttentions (병목 판정 기준)'\n },\n {\n id: 'attention.scrapRate',\n axes: ['equipment'],\n effect: 'twin.propeffect.scrapRate',\n readBy: '@operato/twin-kernel deriveAttentions (불량 과다 판정 기준)'\n },\n {\n id: 'attention.scrapMinSamples',\n axes: ['equipment'],\n effect: 'twin.propeffect.scrapMinSamples',\n readBy: '@operato/twin-kernel deriveAttentions (불량률을 재기 시작할 표본 수)'\n },\n /*\n * ── 공정 소요시간 (2026-08-18) ────────────────────────────────────────────\n *\n * 실측: 운영 중인 트윈 35개 중 **창고·야드는 공정 명세 행이 0개**였다 — 그 트윈들의 시간은 전부 커널\n * 상수에서 왔고, 예측은 「같은 조건에서의 상대 비교」에 머물렀다(절대 시각 주장 불가). 이 선언이\n * 그 사다리를 한 칸 올린다: 실측이 없어도 **현장이 정한 시간**으로 굴러간다.\n *\n * 실측(이력에서 배운 값)은 여전히 이 선언을 이긴다 — 화면이 그 사실을 함께 말한다.\n */\n {\n id: 'operation.duration',\n axes: ['operations'],\n effect: 'twin.propeffect.operationDuration',\n readBy: '@operato/twin-kernel FlowEngine.durationOf ← declareDurations (engine/local-declarations.ts 가 실어 보낸다)'\n },\n {\n id: 'tariff.currency',\n axes: ['locations'],\n effect: 'twin.propeffect.currency',\n readBy: 'headless-twin/server/engine/kpi-query.ts (금액 표기 통화)'\n }\n]\n\n/**\n * 이 축의 속성 효과 — `속성 id → i18n 키`.\n *\n * 없는 속성은 **여기 없다**(빈 값으로 채우지 않는다) — 화면이 \"등록된 효과가 없다\" 를 스스로 말한다.\n */\nexport function propertyEffectsOf(axis: string): Record<string, string> {\n const out: Record<string, string> = {}\n for (const e of PROPERTY_EFFECTS) if (e.axes.includes(axis)) out[e.id] = e.effect\n return out\n}\n\n/** 등록부의 축 이름 → 선언이 겨누는 대상 이름. 대상이 아닌 축(자재 정의 등)은 여기 없다. */\nconst AXIS_TO_TARGET: Record<string, string> = { locations: 'location', equipment: 'equipment', operations: 'operation' }\n\n/**\n * 이 속성을 **어느 대상에 얹을 수 있나** — 등록부의 축이 그대로 답한다.\n *\n * ── 왜 필요한가 (2026-08-18) ────────────────────────────────────────────────\n * 지게차에 계약전력을 선언할 수 있었다. 문은 받아 주고 화면은 「넣었습니다」라고 말하는데, 그 값을 읽는\n * 코드는 자리(수전)만 본다 — 아무 일도 일어나지 않고 그 사실이 어디에도 나오지 않았다. 효과가 어느 축의\n * 것인지는 이미 등록부가 알고 있으므로, 그 지식을 검사에 쓴다(두 곳에 적지 않는다).\n *\n * 등록부에 없는 속성은 **비어 있는 목록이 아니라 `undefined`** 다: 「어디에도 못 얹는다」와 「우리가\n * 제한을 모른다」는 다르다(후자는 받아 준다 — 속성은 열려 있다는 규약을 지킨다).\n */\nexport function declarationTargetsOf(propertyId: string): string[] | undefined {\n const e = PROPERTY_EFFECTS.find(x => x.id === propertyId)\n if (!e) return undefined\n const targets = e.axes.map(a => AXIS_TO_TARGET[a]).filter((t): t is string => !!t)\n return targets.length ? targets : undefined\n}\n"]}
1
+ {"version":3,"file":"property-effects.js","sourceRoot":"","sources":["../../server/engine/property-effects.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 자원 속성의 **효과 등록부** — 이 속성이 무엇을 바꾸나.\n *\n * ── 왜 있나 ─────────────────────────────────────────────────────────────────\n * 속성은 **열려 있다**: 템플릿·마스터가 아무 id 로나 사실을 실어 보낼 수 있고, 커널은 그것을 그대로\n * 나르고 화면은 단위와 함께 보인다(표준이 속성 어휘를 정하지 않으므로 그것이 옳다).\n *\n * 그런데 **효과는 닫혀 있다**: 값이 계산·배정·판정을 바꾸려면 그 규칙을 코드가 알아야 한다. 지금\n * 효과가 있는 것은 둘뿐이다. 문제는 그 사실이 **어디에도 드러나지 않는다**는 것이었다 — 사용자는\n * 속성을 넣으면 무언가 달라질 것으로 기대하는데, 실제로는 표에 한 줄이 늘 뿐이다.\n *\n * 그래서 이 등록부는 **지어내지 않고 드러낸다**: 읽는 코드가 있는 것만 적고, 없는 것은 \"표시만 된다\"\n * 고 화면이 말한다. 새 효과를 코드로 붙이는 사람은 **여기 한 줄을 더한다** — 그러지 않으면 사용자는\n * 그 효과가 있는지 알 수 없다.\n *\n * ⚠️ 이 파일은 등록부이고 판정이 아니다. 값을 실제로 읽는 곳은 각자다(아래 `readBy` 가 그 자리를\n * 가리킨다) — 여기에 계산을 옮겨 오면 규칙이 두 벌이 된다.\n */\n\n/** 효과가 있는 속성 하나. */\nexport interface PropertyEffect {\n /** 속성 id — 선언에 적히는 그 이름(`ResourceProperty.id`). */\n id: string\n /** 어느 축의 속성인가 — 그 축의 항목에만 이 효과를 말한다. */\n axes: string[]\n /** 무엇을 바꾸나 — 화면이 읽는 i18n 키(언어중립: 서버는 사람 말을 만들지 않는다). */\n effect: string\n /** 실제로 읽는 코드 — 지어낸 효과를 등록하지 않기 위한 표식이다(없는 자리를 가리키면 리뷰가 잡는다). */\n readBy: string\n}\n\nexport const PROPERTY_EFFECTS: PropertyEffect[] = [\n {\n id: 'speed',\n axes: ['equipment'],\n effect: 'twin.propeffect.speed',\n readBy: 'headless-twin/server/engine/travel-estimator.ts (buildTravelEstimator)'\n },\n {\n id: 'perBaseUnit',\n axes: ['materialDefinitions'],\n effect: 'twin.propeffect.perBaseUnit',\n readBy: '@operato/twin-kernel conversionFactorOf'\n },\n /*\n * ── 에너지 속성 (2026-08-18) ──────────────────────────────────────────────\n *\n * 이 값들은 **오래전부터 읽히고 있었다** — 커널이 부하·피크·배터리를 그것으로 계산하고, 호스트가\n * 금액을 그것으로 만든다. 그런데 등록부에 없어서 화면은 그 값들에 대해 「표시만 됩니다」라고 말했다.\n * 조용한 거짓말이다: 사용자는 계약전력을 넣고도 「이 값은 아무것도 바꾸지 않는다」는 문장을 읽었다.\n *\n * 효과를 새로 만든 것이 아니라 **있는 효과를 드러낸다.** `readBy` 는 실제로 읽는 자리를 가리킨다.\n */\n {\n id: 'contract.kW',\n axes: ['locations'],\n effect: 'twin.propeffect.contractKW',\n readBy: '@operato/twin-kernel EmsKernel.declaredContractKW (계약 대비 판정·초과 주의)'\n },\n {\n id: 'power.ratedKW',\n axes: ['equipment'],\n effect: 'twin.propeffect.ratedKW',\n readBy: '@operato/twin-kernel EmsKernel.deriveEquipmentLoad (가동 중 부하)'\n },\n {\n id: 'power.standbyKW',\n axes: ['equipment'],\n effect: 'twin.propeffect.standbyKW',\n readBy: '@operato/twin-kernel EmsKernel.deriveEquipmentLoad (대기 중 부하)'\n },\n {\n id: 'storage.capacityKWh',\n axes: ['equipment'],\n effect: 'twin.propeffect.capacityKWh',\n readBy: '@operato/twin-kernel EmsKernel (축전지 방전 가능량)'\n },\n {\n id: 'dispatch.dischargeAboveKW',\n axes: ['equipment'],\n effect: 'twin.propeffect.dischargeAboveKW',\n readBy: '@operato/twin-kernel EmsKernel (방전 시작 문턱)'\n },\n {\n id: 'dispatch.maxDischargeKW',\n axes: ['equipment'],\n effect: 'twin.propeffect.maxDischargeKW',\n readBy: '@operato/twin-kernel EmsKernel (인버터 방전 상한)'\n },\n {\n id: 'dispatch.reserveSoc',\n axes: ['equipment'],\n effect: 'twin.propeffect.reserveSoc',\n readBy: '@operato/twin-kernel EmsKernel (이 아래로는 방전하지 않는다)'\n },\n {\n id: 'storage.initialSoc',\n axes: ['equipment'],\n effect: 'twin.propeffect.initialSoc',\n readBy: '@operato/twin-kernel EmsKernel (시작 충전율 — 예측·what-if 의 씨앗)'\n },\n {\n id: 'generation.ratedKW',\n axes: ['equipment'],\n effect: 'twin.propeffect.genRatedKW',\n readBy: '@operato/twin-kernel EmsKernel.deriveGeneration (정격 발전)'\n },\n {\n id: 'generation.dailyProfile',\n axes: ['equipment'],\n effect: 'twin.propeffect.genDailyProfile',\n readBy: '@operato/twin-kernel generationFractionAt (시각별 발전 비율)'\n },\n /* 요금은 커널이 읽지 않는다 — 금액은 저널을 접는 호스트가 만든다. readBy 가 그 사실을 말한다. */\n {\n id: 'tariff.demandChargePerKW',\n axes: ['locations'],\n effect: 'twin.propeffect.demandChargePerKW',\n readBy: 'headless-twin/server/engine/kpi-query.ts (수요요금 계산)'\n },\n {\n id: 'tariff.energyChargePerKWh',\n axes: ['locations'],\n effect: 'twin.propeffect.energyChargePerKWh',\n readBy: 'headless-twin/server/engine/kpi-query.ts (전력량 요금 계산)'\n },\n /* 판정 기준 — 값이 경보의 유무를 바꾼다(가장 눈에 보이는 효과다). */\n {\n id: 'attention.congestionRatio',\n axes: ['locations'],\n effect: 'twin.propeffect.congestionRatio',\n readBy: '@operato/twin-kernel deriveAttentions (병목 판정 기준)'\n },\n {\n id: 'attention.scrapRate',\n axes: ['equipment'],\n effect: 'twin.propeffect.scrapRate',\n readBy: '@operato/twin-kernel deriveAttentions (불량 과다 판정 기준)'\n },\n {\n id: 'attention.scrapMinSamples',\n axes: ['equipment'],\n effect: 'twin.propeffect.scrapMinSamples',\n readBy: '@operato/twin-kernel deriveAttentions (불량률을 재기 시작할 표본 수)'\n },\n /*\n * ── 공정 소요시간 (2026-08-18) ────────────────────────────────────────────\n *\n * 실측: 운영 중인 트윈 35개 중 **창고·야드는 공정 명세 행이 0개**였다 — 그 트윈들의 시간은 전부 커널\n * 상수에서 왔고, 예측은 「같은 조건에서의 상대 비교」에 머물렀다(절대 시각 주장 불가). 이 선언이\n * 그 사다리를 한 칸 올린다: 실측이 없어도 **현장이 정한 시간**으로 굴러간다.\n *\n * 실측(이력에서 배운 값)은 여전히 이 선언을 이긴다 — 화면이 그 사실을 함께 말한다.\n */\n {\n id: 'operation.duration',\n axes: ['operations'],\n effect: 'twin.propeffect.operationDuration',\n readBy: '@operato/twin-kernel FlowEngine.durationOf ← declareDurations (engine/local-declarations.ts 가 실어 보낸다)'\n },\n /*\n * ── 공정 모수 (2026-08-19) ────────────────────────────────────────────────\n * 양품률의 기본값은 커널 소스의 상수(0.8)였고, **그 상수가 불량 과다 주목 신호까지 만들었다** — 초기\n * 라인이든 성숙한 라인이든 같은 수로 판정된다. 셋업도 상수였다. 둘 다 현장이 말할 수 있어야 한다.\n */\n {\n id: 'operation.yield',\n axes: ['operations'],\n effect: 'twin.propeffect.operationYield',\n readBy: '@operato/twin-kernel MesKernel(OP_PARAM.yield) ← declareParameters (양품/불량 판정과 산출량)'\n },\n {\n id: 'operation.setupDuration',\n axes: ['operations'],\n effect: 'twin.propeffect.operationSetup',\n readBy: '@operato/twin-kernel MesKernel(OP_PARAM.setupDuration) ← declareParameters (가동 앞에 붙는 셋업)'\n },\n {\n id: 'tariff.currency',\n axes: ['locations'],\n effect: 'twin.propeffect.currency',\n readBy: 'headless-twin/server/engine/kpi-query.ts (금액 표기 통화)'\n }\n]\n\n/**\n * 이 축의 속성 효과 — `속성 id → i18n 키`.\n *\n * 없는 속성은 **여기 없다**(빈 값으로 채우지 않는다) — 화면이 \"등록된 효과가 없다\" 를 스스로 말한다.\n */\nexport function propertyEffectsOf(axis: string): Record<string, string> {\n const out: Record<string, string> = {}\n for (const e of PROPERTY_EFFECTS) if (e.axes.includes(axis)) out[e.id] = e.effect\n return out\n}\n\n/** 등록부의 축 이름 → 선언이 겨누는 대상 이름. 대상이 아닌 축(자재 정의 등)은 여기 없다. */\nconst AXIS_TO_TARGET: Record<string, string> = { locations: 'location', equipment: 'equipment', operations: 'operation' }\n\n/**\n * 이 속성을 **어느 대상에 얹을 수 있나** — 등록부의 축이 그대로 답한다.\n *\n * ── 왜 필요한가 (2026-08-18) ────────────────────────────────────────────────\n * 지게차에 계약전력을 선언할 수 있었다. 문은 받아 주고 화면은 「넣었습니다」라고 말하는데, 그 값을 읽는\n * 코드는 자리(수전)만 본다 — 아무 일도 일어나지 않고 그 사실이 어디에도 나오지 않았다. 효과가 어느 축의\n * 것인지는 이미 등록부가 알고 있으므로, 그 지식을 검사에 쓴다(두 곳에 적지 않는다).\n *\n * 등록부에 없는 속성은 **비어 있는 목록이 아니라 `undefined`** 다: 「어디에도 못 얹는다」와 「우리가\n * 제한을 모른다」는 다르다(후자는 받아 준다 — 속성은 열려 있다는 규약을 지킨다).\n */\nexport function declarationTargetsOf(propertyId: string): string[] | undefined {\n const e = PROPERTY_EFFECTS.find(x => x.id === propertyId)\n if (!e) return undefined\n const targets = e.axes.map(a => AXIS_TO_TARGET[a]).filter((t): t is string => !!t)\n return targets.length ? targets : undefined\n}\n"]}
@@ -0,0 +1,13 @@
1
+ /** 재기동 정책 — 셋뿐이다. */
2
+ export type RestartPolicy = 'resync' | 'resume' | 'reset';
3
+ export declare const RESTART_POLICIES: readonly RestartPolicy[];
4
+ /**
5
+ * 저장된 값을 정책으로 읽는다 — **모르면 던진다**(기본값으로 메우지 않는다).
6
+ *
7
+ * 옛 어휘(`mirror`·`sim-world`·`sim-experiment`)를 여기서 번역하지 않는다: 번역을 두면 옛 값이 살아남고,
8
+ * 그 값이 다시 어딘가에 저장된다. 저장된 값은 **한 번 옮기고**(데이터 이관) 코드는 새 어휘만 안다.
9
+ * 그래서 이 함수는 옛 값에 대해서도 던지며, 무엇으로 옮겨야 하는지 문장으로 말한다.
10
+ */
11
+ export declare function readRestartPolicy(raw: unknown, where: string): RestartPolicy;
12
+ /** 이 값이 정책인가 — 던지지 않고 묻는 자리(화면·검사)를 위해. */
13
+ export declare function isRestartPolicy(raw: unknown): raw is RestartPolicy;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ /*
3
+ * **재기동 정책** — 이 트윈은 다시 세워질 때 자기 과거를 어떻게 대하나.
4
+ *
5
+ * ── 왜 이 이름인가 (ADR-0029 · plans/simulator-as-source.md §2) ─────────────
6
+ * 옛 이름은 `realityMode` 였고 값은 `mirror`·`sim-world`·`sim-experiment` 였다. 그 축은 「현실이 어디서
7
+ * 오나」를 말하는 것처럼 보였지만, 코드가 그것으로 가른 것은 **재기동 거동 하나**였다. 그리고 「현실이
8
+ * 어디서 오나」는 ADR-0029 가 **원본의 종류**로 옮긴 물음이다(`TwinReference.adapterType`) — 트윈은 한
9
+ * 종류이고 언제나 원본을 반영한다.
10
+ *
11
+ * 그래서 이 축은 사라지지 않고 **트윈에 남아 이름을 바꿨다.** 남는 이유: 「자기 저널을 잇는가」는 원본의
12
+ * 성질이 아니라 **트윈과 자기 과거의 관계**다. 같은 시뮬레이터 원본을 보는 두 트윈이 하나는 이어가고
13
+ * 하나는 매번 처음부터 재현할 수 있다 — 원본은 그 둘을 구별하지 못한다.
14
+ *
15
+ * resync — 외부 실물에서 다시 읽는다(원본이 진실이므로 우리 과거를 이어 붙이지 않는다).
16
+ * resume — 저널을 이어 세운다(이 트윈이 낳은 타임라인이 곧 사실이다).
17
+ * reset — 저널을 비우고 씨앗부터 다시 돌린다(재현 가능한 실험).
18
+ *
19
+ * ── 기본값을 두지 않는다 ────────────────────────────────────────────────────
20
+ * 예전에는 미선언이 조용히 `sim-experiment`(=저널 초기화)로 떨어졌다. 그 관용은 값이 하나 어긋나는
21
+ * 순간 **저널을 지우는 길**이 된다 — 개명 뒤 옛 값이 남아 있는 행을 그 길로 읽으면 재기동 한 번에
22
+ * 이력이 사라진다. 그래서 모르는 값은 **던진다.** 부르는 쪽이 선언하게 만드는 것이 이 축의 요점이다.
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.RESTART_POLICIES = void 0;
26
+ exports.readRestartPolicy = readRestartPolicy;
27
+ exports.isRestartPolicy = isRestartPolicy;
28
+ exports.RESTART_POLICIES = ['resync', 'resume', 'reset'];
29
+ /**
30
+ * 저장된 값을 정책으로 읽는다 — **모르면 던진다**(기본값으로 메우지 않는다).
31
+ *
32
+ * 옛 어휘(`mirror`·`sim-world`·`sim-experiment`)를 여기서 번역하지 않는다: 번역을 두면 옛 값이 살아남고,
33
+ * 그 값이 다시 어딘가에 저장된다. 저장된 값은 **한 번 옮기고**(데이터 이관) 코드는 새 어휘만 안다.
34
+ * 그래서 이 함수는 옛 값에 대해서도 던지며, 무엇으로 옮겨야 하는지 문장으로 말한다.
35
+ */
36
+ function readRestartPolicy(raw, where) {
37
+ const value = typeof raw === 'string' ? raw.trim() : '';
38
+ if (exports.RESTART_POLICIES.includes(value))
39
+ return value;
40
+ const legacy = { mirror: 'resync', 'sim-world': 'resume', 'sim-experiment': 'reset' };
41
+ if (legacy[value]) {
42
+ throw new Error(`${where}: restart policy "${value}" is the old vocabulary — move the stored value to "${legacy[value]}" ` +
43
+ '(ADR-0029 · plans/simulator-as-source.md §2). Nothing is translated at read time on purpose: a translation would keep the old value alive.');
44
+ }
45
+ throw new Error(`${where}: restart policy ${value ? `"${value}"` : 'is missing'} — declare one of ${exports.RESTART_POLICIES.join(' | ')}. ` +
46
+ 'There is no default: an unspecified policy used to fall through to "reset", which erases the journal on the next reboot.');
47
+ }
48
+ /** 이 값이 정책인가 — 던지지 않고 묻는 자리(화면·검사)를 위해. */
49
+ function isRestartPolicy(raw) {
50
+ return typeof raw === 'string' && exports.RESTART_POLICIES.includes(raw);
51
+ }
52
+ //# sourceMappingURL=restart-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restart-policy.js","sourceRoot":"","sources":["../../server/engine/restart-policy.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;;;AAcH,8CAcC;AAGD,0CAEC;AA5BY,QAAA,gBAAgB,GAA6B,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAU,CAAA;AAEhG;;;;;;GAMG;AACH,SAAgB,iBAAiB,CAAC,GAAY,EAAE,KAAa;IAC3D,MAAM,KAAK,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IACvD,IAAK,wBAAsC,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAsB,CAAA;IAC1F,MAAM,MAAM,GAAkC,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,gBAAgB,EAAE,OAAO,EAAE,CAAA;IACpH,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CACb,GAAG,KAAK,qBAAqB,KAAK,uDAAuD,MAAM,CAAC,KAAK,CAAC,IAAI;YACxG,4IAA4I,CAC/I,CAAA;IACH,CAAC;IACD,MAAM,IAAI,KAAK,CACb,GAAG,KAAK,oBAAoB,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,YAAY,qBAAqB,wBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI;QAClH,0HAA0H,CAC7H,CAAA;AACH,CAAC;AAED,2CAA2C;AAC3C,SAAgB,eAAe,CAAC,GAAY;IAC1C,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAK,wBAAsC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;AACzF,CAAC","sourcesContent":["/*\n * **재기동 정책** — 이 트윈은 다시 세워질 때 자기 과거를 어떻게 대하나.\n *\n * ── 왜 이 이름인가 (ADR-0029 · plans/simulator-as-source.md §2) ─────────────\n * 옛 이름은 `realityMode` 였고 값은 `mirror`·`sim-world`·`sim-experiment` 였다. 그 축은 「현실이 어디서\n * 오나」를 말하는 것처럼 보였지만, 코드가 그것으로 가른 것은 **재기동 거동 하나**였다. 그리고 「현실이\n * 어디서 오나」는 ADR-0029 가 **원본의 종류**로 옮긴 물음이다(`TwinReference.adapterType`) — 트윈은 한\n * 종류이고 언제나 원본을 반영한다.\n *\n * 그래서 이 축은 사라지지 않고 **트윈에 남아 이름을 바꿨다.** 남는 이유: 「자기 저널을 잇는가」는 원본의\n * 성질이 아니라 **트윈과 자기 과거의 관계**다. 같은 시뮬레이터 원본을 보는 두 트윈이 하나는 이어가고\n * 하나는 매번 처음부터 재현할 수 있다 — 원본은 그 둘을 구별하지 못한다.\n *\n * resync — 외부 실물에서 다시 읽는다(원본이 진실이므로 우리 과거를 이어 붙이지 않는다).\n * resume — 저널을 이어 세운다(이 트윈이 낳은 타임라인이 곧 사실이다).\n * reset — 저널을 비우고 씨앗부터 다시 돌린다(재현 가능한 실험).\n *\n * ── 기본값을 두지 않는다 ────────────────────────────────────────────────────\n * 예전에는 미선언이 조용히 `sim-experiment`(=저널 초기화)로 떨어졌다. 그 관용은 값이 하나 어긋나는\n * 순간 **저널을 지우는 길**이 된다 — 개명 뒤 옛 값이 남아 있는 행을 그 길로 읽으면 재기동 한 번에\n * 이력이 사라진다. 그래서 모르는 값은 **던진다.** 부르는 쪽이 선언하게 만드는 것이 이 축의 요점이다.\n */\n\n/** 재기동 정책 — 셋뿐이다. */\nexport type RestartPolicy = 'resync' | 'resume' | 'reset'\n\nexport const RESTART_POLICIES: readonly RestartPolicy[] = ['resync', 'resume', 'reset'] as const\n\n/**\n * 저장된 값을 정책으로 읽는다 — **모르면 던진다**(기본값으로 메우지 않는다).\n *\n * 옛 어휘(`mirror`·`sim-world`·`sim-experiment`)를 여기서 번역하지 않는다: 번역을 두면 옛 값이 살아남고,\n * 그 값이 다시 어딘가에 저장된다. 저장된 값은 **한 번 옮기고**(데이터 이관) 코드는 새 어휘만 안다.\n * 그래서 이 함수는 옛 값에 대해서도 던지며, 무엇으로 옮겨야 하는지 문장으로 말한다.\n */\nexport function readRestartPolicy(raw: unknown, where: string): RestartPolicy {\n const value = typeof raw === 'string' ? raw.trim() : ''\n if ((RESTART_POLICIES as readonly string[]).includes(value)) return value as RestartPolicy\n const legacy: Record<string, RestartPolicy> = { mirror: 'resync', 'sim-world': 'resume', 'sim-experiment': 'reset' }\n if (legacy[value]) {\n throw new Error(\n `${where}: restart policy \"${value}\" is the old vocabulary — move the stored value to \"${legacy[value]}\" ` +\n '(ADR-0029 · plans/simulator-as-source.md §2). Nothing is translated at read time on purpose: a translation would keep the old value alive.'\n )\n }\n throw new Error(\n `${where}: restart policy ${value ? `\"${value}\"` : 'is missing'} — declare one of ${RESTART_POLICIES.join(' | ')}. ` +\n 'There is no default: an unspecified policy used to fall through to \"reset\", which erases the journal on the next reboot.'\n )\n}\n\n/** 이 값이 정책인가 — 던지지 않고 묻는 자리(화면·검사)를 위해. */\nexport function isRestartPolicy(raw: unknown): raw is RestartPolicy {\n return typeof raw === 'string' && (RESTART_POLICIES as readonly string[]).includes(raw)\n}\n"]}
@@ -17,6 +17,13 @@ export interface SpecCoverageReport {
17
17
  duration: 'measured' | 'declared' | 'default';
18
18
  variability?: string;
19
19
  parameters: string[];
20
+ /**
21
+ * 그 모수를 **무엇으로** 읽었나(`id → measured|declared`) — 없는 id 는 코드 상수를 썼다는 뜻이다.
22
+ *
23
+ * 소요시간이 근거를 밝히듯 모수도 밝힌다(2026-08-19): 수율이 이력에서 온 값인지 사람이 적은 값인지
24
+ * 상수인지에 따라 「불량이 늘었다」는 화면 문장의 무게가 다르다.
25
+ */
26
+ parameterBasis?: Record<string, 'measured' | 'declared'>;
20
27
  }[];
21
28
  /** 이력·계산에서 온 소요(추정기) — 선언값보다 강한 근거. */
22
29
  measuredDurations: number;
@@ -36,6 +43,7 @@ export declare function classifySpecCoverage(cov: {
36
43
  duration: 'measured' | 'declared' | 'default';
37
44
  variability?: string;
38
45
  parameters?: string[];
46
+ parameterBasis?: Record<string, 'measured' | 'declared'>;
39
47
  }[];
40
48
  measuredDurations?: number;
41
49
  declaredDurations?: number;
@@ -25,7 +25,9 @@ function classifySpecCoverage(cov) {
25
25
  kind: o.kind,
26
26
  duration: o.duration,
27
27
  ...(o.variability ? { variability: o.variability } : {}),
28
- parameters: o.parameters ?? []
28
+ parameters: o.parameters ?? [],
29
+ /* 근거는 커널이 말한 것만 나른다 — 없으면 필드를 만들지 않는다(상수를 썼다는 뜻이 빈칸이다). */
30
+ ...(o.parameterBasis && Object.keys(o.parameterBasis).length ? { parameterBasis: o.parameterBasis } : {})
29
31
  }));
30
32
  const measuredDurations = cov.measuredDurations ?? operations.filter(o => o.duration === 'measured').length;
31
33
  const declaredDurations = cov.declaredDurations ?? operations.filter(o => o.duration === 'declared').length;
@@ -1 +1 @@
1
- {"version":3,"file":"spec-coverage.js","sourceRoot":"","sources":["../../server/engine/spec-coverage.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;AA+BH,oDA6BC;AAOD,4CAOC;AA/CD;;;GAGG;AACH,SAAgB,oBAAoB,CAAC,GAKpC;IACC,MAAM,GAAG,GAAG,GAAG,EAAE,UAAU,CAAA;IAC3B,IAAI,CAAC,GAAG,EAAE,MAAM;QAAE,OAAO,SAAS,CAAA;IAClC,MAAM,UAAU,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/B,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,UAAU,EAAE,CAAC,CAAC,UAAU,IAAI,EAAE;KAC/B,CAAC,CAAC,CAAA;IACH,MAAM,iBAAiB,GAAG,GAAG,CAAC,iBAAiB,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,MAAM,CAAA;IAC3G,MAAM,iBAAiB,GAAG,GAAG,CAAC,iBAAiB,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,MAAM,CAAA;IAC3G,MAAM,gBAAgB,GAAG,GAAG,CAAC,gBAAgB,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,MAAM,CAAA;IACxG,MAAM,gBAAgB,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,WAAW,KAAK,UAAU,CAAC,CAAC,MAAM,CAAA;IACrG,iGAAiG;IACjG,MAAM,QAAQ,GAAG,iBAAiB,GAAG,iBAAiB,CAAA;IACtD,MAAM,aAAa,GACjB,QAAQ,KAAK,CAAC;QACZ,CAAC,CAAC,UAAU;QACZ,CAAC,CAAC,gBAAgB,GAAG,CAAC;YACpB,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,iBAAiB,KAAK,CAAC;gBACvB,CAAC,CAAC,YAAY;gBACd,CAAC,CAAC,kBAAkB,CAAA;IAC5B,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAE,CAAA;AAChH,CAAC;AAED;;;;GAIG;AACH,SAAgB,gBAAgB,CAAC,MAAW;IAC1C,IAAI,OAAO,MAAM,EAAE,YAAY,KAAK,UAAU;QAAE,OAAO,SAAS,CAAA;IAChE,IAAI,CAAC;QACH,OAAO,oBAAoB,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC,CAAA;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC","sourcesContent":["/*\n * 시뮬 명세 자기보고 — **\"이 예측으로 무엇을 말할 수 있나\" 를 예측과 함께 내보낸다.**\n *\n * 배경(operato-twin design/plans/simulation-spec.md): 공정 소요·수율이 커널 소스의 상수였다. 이제\n * 마스터가 명세를 실어 오면 커널이 그것으로 실행하고, `specCoverage()` 가 무엇을 선언값으로 무엇을\n * 기본값으로 썼는지 보고한다. 그 보고를 **예측 응답에 붙이지 않으면** 받는 쪽은 상수로 계산한 숫자와\n * 현장 명세로 계산한 숫자를 구별할 수 없다 — 똑같이 \"p50 168\" 로 보인다.\n *\n * 언어 중립: 여기서는 **코드와 수치만** 낸다(`qualification` + 카운트). 사람이 읽는 문장은 표현\n * 계층(클라 i18next / AI 답변)이 만든다 — attention 과 같은 규율.\n */\n\n/** 예측 자격 — 선언된 명세가 얼마나 덮는지에 따라 이 예측으로 정당하게 말할 수 있는 범위가 달라진다. */\nexport type ForecastQualification =\n /** 모든 소요가 커널 기본 상수 → **상대 비교만**(\"자원 2대 더 넣으면 나아지나\"). 절대 시각 주장 불가. */\n | 'relative'\n /** 일부만 선언·실측 → 그 구간은 접지, 나머지는 상수. 혼합. */\n | 'partial'\n /** 소요 전부 선언 → 절대 예측 시도 가능. 단 **정확도는 이력이 확인한다**(선언 != 검증). */\n | 'absolute-capable'\n /**\n * 소요 전부가 **이 현장의 이력에서 온 값**(추정기) → 가장 강한 근거. 선언은 사람이 적은 값이고\n * 이것은 실제로 일어난 값이다. 그래도 \"정확하다\" 가 아니라 \"관측에 접지됐다\" 는 뜻이다.\n */\n | 'calibrated'\n\nexport interface SpecCoverageReport {\n operations: { kind: string; duration: 'measured' | 'declared' | 'default'; variability?: string; parameters: string[] }[]\n /** 이력·계산에서 온 소요(추정기) — 선언값보다 강한 근거. */\n measuredDurations: number\n declaredDurations: number\n defaultDurations: number\n /** 변동(분포)을 가진 작업 종류 수. **0 이면 모든 소요가 고정값**이라 대기·병목이 과소평가된다. */\n varyingDurations: number\n qualification: ForecastQualification\n}\n\n/**\n * 명세 자격 판정(순수) — 커널 보고를 받아 분류한다. 커널을 모르므로 테스트가 쉽다.\n * 관측된 작업이 하나도 없으면(`operations` 빈 배열) 판정할 근거가 없으므로 **undefined**(모른다).\n */\nexport function classifySpecCoverage(cov: {\n operations?: { kind: string; duration: 'measured' | 'declared' | 'default'; variability?: string; parameters?: string[] }[]\n measuredDurations?: number\n declaredDurations?: number\n defaultDurations?: number\n}): SpecCoverageReport | undefined {\n const ops = cov?.operations\n if (!ops?.length) return undefined\n const operations = ops.map(o => ({\n kind: o.kind,\n duration: o.duration,\n ...(o.variability ? { variability: o.variability } : {}),\n parameters: o.parameters ?? []\n }))\n const measuredDurations = cov.measuredDurations ?? operations.filter(o => o.duration === 'measured').length\n const declaredDurations = cov.declaredDurations ?? operations.filter(o => o.duration === 'declared').length\n const defaultDurations = cov.defaultDurations ?? operations.filter(o => o.duration === 'default').length\n const varyingDurations = operations.filter(o => o.variability && o.variability !== 'constant').length\n /* 사다리: 전부 이력 → calibrated · 상수 없음 → absolute-capable · 접지가 하나도 없음 → relative · 나머지 → partial. */\n const grounded = measuredDurations + declaredDurations\n const qualification: ForecastQualification =\n grounded === 0\n ? 'relative'\n : defaultDurations > 0\n ? 'partial'\n : declaredDurations === 0\n ? 'calibrated'\n : 'absolute-capable'\n return { operations, measuredDurations, declaredDurations, defaultDurations, varyingDurations, qualification }\n}\n\n/**\n * 커널에서 읽는다 — **아직 이 API 가 없는 커널(발행 이전)이면 undefined.**\n * 그때 `undefined` 는 \"명세가 없다\" 가 아니라 \"모른다\" 다. 소비처가 그 둘을 섞지 않도록 주석을 남긴다:\n * 없는 것을 \"전부 기본값\" 이라고 단정하지 않는다(단정하려면 커널이 말해 줘야 한다).\n */\nexport function readSpecCoverage(kernel: any): SpecCoverageReport | undefined {\n if (typeof kernel?.specCoverage !== 'function') return undefined\n try {\n return classifySpecCoverage(kernel.specCoverage())\n } catch {\n return undefined\n }\n}\n"]}
1
+ {"version":3,"file":"spec-coverage.js","sourceRoot":"","sources":["../../server/engine/spec-coverage.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;AA2CH,oDAqCC;AAOD,4CAOC;AAvDD;;;GAGG;AACH,SAAgB,oBAAoB,CAAC,GAWpC;IACC,MAAM,GAAG,GAAG,GAAG,EAAE,UAAU,CAAA;IAC3B,IAAI,CAAC,GAAG,EAAE,MAAM;QAAE,OAAO,SAAS,CAAA;IAClC,MAAM,UAAU,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/B,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,UAAU,EAAE,CAAC,CAAC,UAAU,IAAI,EAAE;QAC9B,2DAA2D;QAC3D,GAAG,CAAC,CAAC,CAAC,cAAc,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1G,CAAC,CAAC,CAAA;IACH,MAAM,iBAAiB,GAAG,GAAG,CAAC,iBAAiB,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,MAAM,CAAA;IAC3G,MAAM,iBAAiB,GAAG,GAAG,CAAC,iBAAiB,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,MAAM,CAAA;IAC3G,MAAM,gBAAgB,GAAG,GAAG,CAAC,gBAAgB,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,MAAM,CAAA;IACxG,MAAM,gBAAgB,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,WAAW,KAAK,UAAU,CAAC,CAAC,MAAM,CAAA;IACrG,iGAAiG;IACjG,MAAM,QAAQ,GAAG,iBAAiB,GAAG,iBAAiB,CAAA;IACtD,MAAM,aAAa,GACjB,QAAQ,KAAK,CAAC;QACZ,CAAC,CAAC,UAAU;QACZ,CAAC,CAAC,gBAAgB,GAAG,CAAC;YACpB,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,iBAAiB,KAAK,CAAC;gBACvB,CAAC,CAAC,YAAY;gBACd,CAAC,CAAC,kBAAkB,CAAA;IAC5B,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAE,CAAA;AAChH,CAAC;AAED;;;;GAIG;AACH,SAAgB,gBAAgB,CAAC,MAAW;IAC1C,IAAI,OAAO,MAAM,EAAE,YAAY,KAAK,UAAU;QAAE,OAAO,SAAS,CAAA;IAChE,IAAI,CAAC;QACH,OAAO,oBAAoB,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC,CAAA;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC","sourcesContent":["/*\n * 시뮬 명세 자기보고 — **\"이 예측으로 무엇을 말할 수 있나\" 를 예측과 함께 내보낸다.**\n *\n * 배경(operato-twin design/plans/simulation-spec.md): 공정 소요·수율이 커널 소스의 상수였다. 이제\n * 마스터가 명세를 실어 오면 커널이 그것으로 실행하고, `specCoverage()` 가 무엇을 선언값으로 무엇을\n * 기본값으로 썼는지 보고한다. 그 보고를 **예측 응답에 붙이지 않으면** 받는 쪽은 상수로 계산한 숫자와\n * 현장 명세로 계산한 숫자를 구별할 수 없다 — 똑같이 \"p50 168\" 로 보인다.\n *\n * 언어 중립: 여기서는 **코드와 수치만** 낸다(`qualification` + 카운트). 사람이 읽는 문장은 표현\n * 계층(클라 i18next / AI 답변)이 만든다 — attention 과 같은 규율.\n */\n\n/** 예측 자격 — 선언된 명세가 얼마나 덮는지에 따라 이 예측으로 정당하게 말할 수 있는 범위가 달라진다. */\nexport type ForecastQualification =\n /** 모든 소요가 커널 기본 상수 → **상대 비교만**(\"자원 2대 더 넣으면 나아지나\"). 절대 시각 주장 불가. */\n | 'relative'\n /** 일부만 선언·실측 → 그 구간은 접지, 나머지는 상수. 혼합. */\n | 'partial'\n /** 소요 전부 선언 → 절대 예측 시도 가능. 단 **정확도는 이력이 확인한다**(선언 != 검증). */\n | 'absolute-capable'\n /**\n * 소요 전부가 **이 현장의 이력에서 온 값**(추정기) → 가장 강한 근거. 선언은 사람이 적은 값이고\n * 이것은 실제로 일어난 값이다. 그래도 \"정확하다\" 가 아니라 \"관측에 접지됐다\" 는 뜻이다.\n */\n | 'calibrated'\n\nexport interface SpecCoverageReport {\n operations: {\n kind: string\n duration: 'measured' | 'declared' | 'default'\n variability?: string\n parameters: string[]\n /**\n * 그 모수를 **무엇으로** 읽었나(`id → measured|declared`) — 없는 id 는 코드 상수를 썼다는 뜻이다.\n *\n * 소요시간이 근거를 밝히듯 모수도 밝힌다(2026-08-19): 수율이 이력에서 온 값인지 사람이 적은 값인지\n * 상수인지에 따라 「불량이 늘었다」는 화면 문장의 무게가 다르다.\n */\n parameterBasis?: Record<string, 'measured' | 'declared'>\n }[]\n /** 이력·계산에서 온 소요(추정기) — 선언값보다 강한 근거. */\n measuredDurations: number\n declaredDurations: number\n defaultDurations: number\n /** 변동(분포)을 가진 작업 종류 수. **0 이면 모든 소요가 고정값**이라 대기·병목이 과소평가된다. */\n varyingDurations: number\n qualification: ForecastQualification\n}\n\n/**\n * 명세 자격 판정(순수) — 커널 보고를 받아 분류한다. 커널을 모르므로 테스트가 쉽다.\n * 관측된 작업이 하나도 없으면(`operations` 빈 배열) 판정할 근거가 없으므로 **undefined**(모른다).\n */\nexport function classifySpecCoverage(cov: {\n operations?: {\n kind: string\n duration: 'measured' | 'declared' | 'default'\n variability?: string\n parameters?: string[]\n parameterBasis?: Record<string, 'measured' | 'declared'>\n }[]\n measuredDurations?: number\n declaredDurations?: number\n defaultDurations?: number\n}): SpecCoverageReport | undefined {\n const ops = cov?.operations\n if (!ops?.length) return undefined\n const operations = ops.map(o => ({\n kind: o.kind,\n duration: o.duration,\n ...(o.variability ? { variability: o.variability } : {}),\n parameters: o.parameters ?? [],\n /* 근거는 커널이 말한 것만 나른다 — 없으면 필드를 만들지 않는다(상수를 썼다는 뜻이 빈칸이다). */\n ...(o.parameterBasis && Object.keys(o.parameterBasis).length ? { parameterBasis: o.parameterBasis } : {})\n }))\n const measuredDurations = cov.measuredDurations ?? operations.filter(o => o.duration === 'measured').length\n const declaredDurations = cov.declaredDurations ?? operations.filter(o => o.duration === 'declared').length\n const defaultDurations = cov.defaultDurations ?? operations.filter(o => o.duration === 'default').length\n const varyingDurations = operations.filter(o => o.variability && o.variability !== 'constant').length\n /* 사다리: 전부 이력 → calibrated · 상수 없음 → absolute-capable · 접지가 하나도 없음 → relative · 나머지 → partial. */\n const grounded = measuredDurations + declaredDurations\n const qualification: ForecastQualification =\n grounded === 0\n ? 'relative'\n : defaultDurations > 0\n ? 'partial'\n : declaredDurations === 0\n ? 'calibrated'\n : 'absolute-capable'\n return { operations, measuredDurations, declaredDurations, defaultDurations, varyingDurations, qualification }\n}\n\n/**\n * 커널에서 읽는다 — **아직 이 API 가 없는 커널(발행 이전)이면 undefined.**\n * 그때 `undefined` 는 \"명세가 없다\" 가 아니라 \"모른다\" 다. 소비처가 그 둘을 섞지 않도록 주석을 남긴다:\n * 없는 것을 \"전부 기본값\" 이라고 단정하지 않는다(단정하려면 커널이 말해 줘야 한다).\n */\nexport function readSpecCoverage(kernel: any): SpecCoverageReport | undefined {\n if (typeof kernel?.specCoverage !== 'function') return undefined\n try {\n return classifySpecCoverage(kernel.specCoverage())\n } catch {\n return undefined\n }\n}\n"]}
@@ -2,6 +2,7 @@ import type { ReducerCheckpoint } from '@operato/twin-kernel';
2
2
  import type { OeeCheckpoint } from './oee-accumulator.js';
3
3
  import { type ReferenceMaster } from '../service/reference/reference-master.js';
4
4
  import { type IngestWarning } from '../service/reference/reference-master.js';
5
+ import { type RestartPolicy } from './restart-policy.js';
5
6
  import { type ModelBasis } from './model-basis.js';
6
7
  import { type LoadMeter, type LoadPhase } from './load-meter.js';
7
8
  import { type StructureDiff } from './structure-diff.js';
@@ -20,13 +21,12 @@ interface TwinMetrics {
20
21
  _accJournal: number;
21
22
  _windowStartMs: number;
22
23
  }
23
- export type RealityMode = 'mirror' | 'sim-world' | 'sim-experiment';
24
- export declare const DEFAULT_REALITY_MODE: RealityMode;
24
+ export type { RestartPolicy } from './restart-policy.js';
25
25
  interface InstanceRuntime {
26
26
  /** 부하 계기판 — 작업별 소요. 시뮬·라이브 모두 붙는다(누가 루프를 점유하는지 보려면 둘 다 필요하다). */
27
27
  load?: LoadMeter;
28
28
  /** 현실 출처 선언(§0 프레임 ①). 부팅 거동(reset/resume/resync)의 근거. */
29
- realityMode?: RealityMode;
29
+ restartPolicy?: RestartPolicy;
30
30
  id: string;
31
31
  domainId: string;
32
32
  domain?: any;
@@ -164,7 +164,7 @@ export declare class TwinEngine {
164
164
  *
165
165
  * ── 모드를 지어내지 않는다 ──────────────────────────────────────────────────
166
166
  * 미러였던 트윈을 시뮬로 되살리면 **없던 움직임을 만들어 낸다**(관측 트윈이 스스로 물건을 옮긴다).
167
- * 그래서 선언된 `realityMode` 그대로 되살린다 — 미러는 관측 구동으로, 시뮬은 시뮬로.
167
+ * 그래서 선언된 `restartPolicy` 그대로 되살린다 — 미러는 관측 구동으로, 시뮬은 시뮬로.
168
168
  *
169
169
  * ── 되살릴 수 없으면 그렇게 적는다 ──────────────────────────────────────────
170
170
  * 실패를 삼키면 등록부가 계속 `running` 이라 말한다 — 우리가 고치려던 그 거짓말이다. 그래서 실패한
@@ -249,7 +249,31 @@ export declare class TwinEngine {
249
249
  * 정상 규모에서 서로 밀어내며 캐시가 무의미해진다(그게 더 나쁘다: 조용히 느려진다).
250
250
  */
251
251
  private static readonly MEASURED_MAX;
252
+ /** 이 트윈이 이력에서 배운 양품률 — 소요와 **같은 폴드·같은 캐시**에서 온다(저널을 두 번 접지 않는다). */
253
+ private static measuredYield;
252
254
  private static measuredEstimator;
255
+ /**
256
+ * **원본이 선언한 자극을 싣는다** — 재기동해도 살아 있게 (2026-08-19).
257
+ *
258
+ * ── 무엇이 났나 ────────────────────────────────────────────────────────────
259
+ * 데모의 시나리오는 시드 코드가 메모리에만 실었다. 그래서 트윈을 재기동하면 자극이 사라지고 구조만
260
+ * 서 있는 트윈이 남았다(작업 0·오더 0) — 「살아 있는 데모」가 **첫 재기동까지만** 살았다.
261
+ *
262
+ * 자극의 집은 **원본**이다(`TwinReference.connectionConfig.scenario`, ADR-0029 ·
263
+ * `plans/simulator-as-source.md` §4): 무엇이 들어오고 무슨 주문이 나는지는 시뮬레이터가 정하는 사실이고
264
+ * 트윈은 반영한다. 트윈에 새 축을 만들면 ADR-0029 가 걷어내야 할 표면이 하나 늘어난다.
265
+ *
266
+ * 판정은 순수 함수가 한다(`planStimulus`) — 미러에 싣지 않고, 검사를 통과하지 못한 선언은 태우지 않고
267
+ * (그 이력이 있다: 잘못된 선언 하나가 다음 틱에서 서버를 내렸다), 못 실은 이유는 **말한다.**
268
+ */
269
+ private static installStimulus;
270
+ /**
271
+ * 자극을 **원본에 선언한다** — 프로비저닝·데모 시드가 부르는 문.
272
+ *
273
+ * 트윈이 아니라 원본에 적는 이유는 위와 같다(ADR-0029). 원본 행이 없으면 만들지 않는다 — 어떤 원본에서
274
+ * 온 트윈인지 모르는 채 자극을 지어 붙이면, 그 자극이 어디서 왔는지 아무도 되짚을 수 없다.
275
+ */
276
+ static declareStimulus(domainId: string, instanceId: string, scenario: any): Promise<boolean>;
253
277
  /**
254
278
  * 이 트윈이 **이력에서 시간을 배운 작업 종류들** — 재기동에도 남는 근거.
255
279
  *
@@ -313,7 +337,7 @@ export declare class TwinEngine {
313
337
  * 커널이 그 문을 열어 두지 않았으면 그 사실도 말한다: 그 트윈은 재기동마다 열린 구간을 잃는다.
314
338
  */
315
339
  private static seedLiveContinuity;
316
- static start(id: string, domainId: string, kind: string, model: TwinModelDef, realityMode?: RealityMode, purpose?: string, resumeFrom?: number): InstanceRuntime;
340
+ static start(id: string, domainId: string, kind: string, model: TwinModelDef, restartPolicy?: RestartPolicy, purpose?: string, resumeFrom?: number): InstanceRuntime;
317
341
  /**
318
342
  * live-모드 기동 — **관측 모드 커널**로 실 이벤트를 미러한다(통합 P2).
319
343
  *
@@ -410,7 +434,7 @@ export declare class TwinEngine {
410
434
  /** live 저널 배치 기록 — 모아둔 CanonicalEnvelope 들에 startRevision+1.. 을 부여해 벌크 저장(coalescer tick 당 1회). */
411
435
  static persistBatch(domainId: string, instanceId: string, envelopes: any[], startRevision: number): Promise<void>;
412
436
  /** 레지스트리 upsert(도메인+instanceId 유니크). status 인자로 provision(stopped)/start(running) 공용. */
413
- static register(domainId: string, instanceId: string, kind: string, model: TwinModelDef, status?: 'running' | 'stopped', realityMode?: RealityMode, origin?: any, meta?: {
437
+ static register(domainId: string, instanceId: string, kind: string, model: TwinModelDef, status?: 'running' | 'stopped', restartPolicy?: RestartPolicy, origin?: any, meta?: {
414
438
  name?: string;
415
439
  description?: string;
416
440
  actor?: {
@@ -455,7 +479,15 @@ export declare class TwinEngine {
455
479
  static adoptStructure(domainId: string, instanceId: string, model: TwinModelDef, comment?: string, origin?: any): Promise<{
456
480
  rev: number;
457
481
  } & StructureShift>;
458
- static provision(domainId: string, instanceId: string, kind: string, model: TwinModelDef, comment?: string, origin?: any,
482
+ static provision(domainId: string, instanceId: string, kind: string, model: TwinModelDef,
483
+ /**
484
+ * **재기동 정책은 생성 시점의 선언이다**(ADR-0029 §2) — 트윈은 정책 없이 존재할 수 없다.
485
+ *
486
+ * 예전에는 이 자리가 없었고 `register` 가 기본값(`sim-experiment` = 저널 초기화)을 각인했다. 그래서
487
+ * 선언을 빠뜨린 프로비저닝이 **재기동마다 이력을 지우는 트윈**을 조용히 만들었다. 이제 만드는 쪽이
488
+ * 말해야 한다: 이 트윈이 자기 과거를 어떻게 대하는지는 만드는 사람이 아는 사실이다.
489
+ */
490
+ restartPolicy: RestartPolicy, comment?: string, origin?: any,
459
491
  /** 이름·저자 — 업무키를 나눠 둔 대가로 이름을 함께 남긴다(`register` 의 `meta` 그대로). */
460
492
  meta?: {
461
493
  name?: string;
@@ -497,18 +529,18 @@ export declare class TwinEngine {
497
529
  private static structureFingerprint;
498
530
  private static structureSignature;
499
531
  /** 레지스트리 model 로 기동(프로비전된 인스턴스 start). model 인자 없이 저장된 구조로 재기동. */
500
- static startFromRegistry(domainId: string, instanceId: string, realityMode?: RealityMode): Promise<InstanceRuntime>;
532
+ static startFromRegistry(domainId: string, instanceId: string, restartPolicy?: RestartPolicy): Promise<InstanceRuntime>;
501
533
  /**
502
- * 선언 기반 부팅(§0 프레임 ① → 부팅 거동 매핑) — 트윈이 선언한 realityMode 에 따라 재기동 방식을 구분한다.
534
+ * 선언 기반 부팅(§0 프레임 ① → 부팅 거동 매핑) — 트윈이 선언한 restartPolicy 에 따라 재기동 방식을 구분한다.
503
535
  * 부팅 경로를 한 곳에 모아 "런타임 ≠ 현실" 범주오류를 코드로 강제한다.
504
- * - 'mirror' : 현실=외부 실물 → startLive(재동기). 이벤트는 어댑터 ingest 로 유입, 커널 tick 없음.
505
- * - 'sim-world' : 생성 타임라인 지속 → 저널 보존 + resume(현실 이어감).
536
+ * - 'resync' : 현실=외부 실물 → startLive(재동기). 이벤트는 어댑터 ingest 로 유입, 커널 tick 없음.
537
+ * - 'resume' : 생성 타임라인 지속 → 저널 보존 + resume(현실 이어감).
506
538
  * ⚠ GATE(§8 ②③): 커널 상태 직렬화/재개가 아직 없어 진짜 resume 불가 → 실 필요·실부하 시 구현.
507
539
  * 그때까지는 저널을 보존하되 revision 충돌을 피하려 fresh 로 기동하고, 선언과 구현의 간극을 크게 경고한다.
508
- * - 'sim-experiment' : seed 재현 → resetJournal + fresh 기동(백지 시작이 버그가 아니라 선언된 거동).
540
+ * - 'reset' : seed 재현 → resetJournal + fresh 기동(백지 시작이 버그가 아니라 선언된 거동).
509
541
  * 반환: 기동된 InstanceRuntime.
510
542
  */
511
- static bootDeclared(domainId: string, instanceId: string, mode?: RealityMode): Promise<InstanceRuntime>;
543
+ static bootDeclared(domainId: string, instanceId: string, mode: RestartPolicy): Promise<InstanceRuntime>;
512
544
  /**
513
545
  * 저널 초기화 — 인스턴스 이벤트 저널 purge(레지스트리·상태는 유지).
514
546
  * 데모 fresh-start 용: 매 부팅 seed 부터 새로 실행하는 데모에서, 복구본 폐기 후 revision 0 부터 재실행하면
@@ -571,7 +603,9 @@ export declare class TwinEngine {
571
603
  * 기존 공간으로 들어갈 수 있다. 아래 병합은 원래 그 경우를 위해 있었는데(N:1) 만드는 흐름에서
572
604
  * 공간을 고를 방법이 없었다 — 마스터가 파생한 id 만 쓰였다. 판정은 `resolveIngestSpace`(순수).
573
605
  */
574
- static ingestMaster(domainId: string, master: ReferenceMaster, into?: {
606
+ static ingestMaster(domainId: string, master: ReferenceMaster,
607
+ /** 만들어지는 트윈의 재기동 정책 — 원본이 정하는 것이 아니라 **만드는 쪽의 선언**이다(ADR-0029 §2). */
608
+ restartPolicy: RestartPolicy, into?: {
575
609
  spaceId?: string;
576
610
  },
577
611
  /** 누가 인제스트했나 — 사람이 없는 경로(부팅)는 주지 않는다(감사 기록을 지어내지 않는다). */
@@ -749,4 +783,3 @@ export declare class TwinEngine {
749
783
  /** 현재 전체 스냅샷 — 라이브 우선, 없으면 저널 복구 캐시. */
750
784
  static snapshot(domainId: string, id: string): any;
751
785
  }
752
- export {};