@things-factory/headless-twin 10.1.141 → 10.1.143

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 (28) hide show
  1. package/dist-server/engine/model-refusal.d.ts +3 -0
  2. package/dist-server/engine/model-refusal.js +37 -0
  3. package/dist-server/engine/model-refusal.js.map +1 -0
  4. package/dist-server/engine/twin-engine.js +8 -7
  5. package/dist-server/engine/twin-engine.js.map +1 -1
  6. package/dist-server/service/reference/reference-resolver.js +4 -1
  7. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  8. package/dist-server/service/twin-forecast/twin-forecast-query.js +2 -1
  9. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
  10. package/dist-server/service/twin-journal/twin-journal-query.js +2 -1
  11. package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
  12. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +2 -1
  13. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
  14. package/dist-server/service/twin-model/twin-model-item-query.js +2 -1
  15. package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
  16. package/dist-server/service/twin-model/twin-model-query.js +2 -1
  17. package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
  18. package/package.json +6 -6
  19. package/server/engine/model-refusal.ts +31 -0
  20. package/server/engine/twin-engine.ts +12 -7
  21. package/server/service/reference/reference-resolver.ts +5 -2
  22. package/server/service/twin-forecast/twin-forecast-query.ts +3 -2
  23. package/server/service/twin-journal/twin-journal-query.ts +3 -2
  24. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +3 -2
  25. package/server/service/twin-model/twin-model-item-query.ts +3 -2
  26. package/server/service/twin-model/twin-model-query.ts +3 -2
  27. package/test/model-refusal.test.ts +75 -0
  28. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,3 @@
1
+ export declare function asModelRefusal(error: unknown, instanceId: string): unknown;
2
+ /** Run a step that loads or replays a model; a kernel refusal comes out as a `Refusal`. */
3
+ export declare function refusingModel<T>(instanceId: string, step: () => T): T;
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.asModelRefusal = asModelRefusal;
4
+ exports.refusingModel = refusingModel;
5
+ const shell_1 = require("@things-factory/shell");
6
+ /*
7
+ * **A twin model the kernel refuses is said by name** (ADR-0054).
8
+ *
9
+ * ── What went wrong (2026-10-10) ─────────────────────────────────────────────
10
+ * The MES kernel refuses a model without a production declaration. The engine let that error through
11
+ * as it was, so `twinReplay(miratek)` reached the screen as INTERNAL_SERVER_ERROR with the kernel's
12
+ * English sentence in the shell's notice. The model was what was wrong, not the server.
13
+ *
14
+ * ── The rule ─────────────────────────────────────────────────────────────────
15
+ * The kernel names its refusals: an error that carries a string `code` and an object `params`. The
16
+ * kernel cannot reach the shell, so the host turns that into a shell `Refusal` here, adding which twin
17
+ * it was. The sentence is made where the reader's language is known (`SpeakRefusals`, or the import
18
+ * result's `refusal()`), under `error.<code>`. Anything without a name passes through untouched.
19
+ */
20
+ function asModelRefusal(error, instanceId) {
21
+ if (error instanceof shell_1.Refusal)
22
+ return error;
23
+ const e = error;
24
+ if (!e || typeof e.code !== 'string' || !e.code || !e.params || typeof e.params !== 'object')
25
+ return error;
26
+ return new shell_1.Refusal(e.code, { ...e.params, instanceId }, String(e.message ?? e.code));
27
+ }
28
+ /** Run a step that loads or replays a model; a kernel refusal comes out as a `Refusal`. */
29
+ function refusingModel(instanceId, step) {
30
+ try {
31
+ return step();
32
+ }
33
+ catch (error) {
34
+ throw asModelRefusal(error, instanceId);
35
+ }
36
+ }
37
+ //# sourceMappingURL=model-refusal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-refusal.js","sourceRoot":"","sources":["../../server/engine/model-refusal.ts"],"names":[],"mappings":";;AAgBA,wCAKC;AAGD,sCAMC;AA9BD,iDAA+C;AAE/C;;;;;;;;;;;;;GAaG;AACH,SAAgB,cAAc,CAAC,KAAc,EAAE,UAAkB;IAC/D,IAAI,KAAK,YAAY,eAAO;QAAE,OAAO,KAAK,CAAA;IAC1C,MAAM,CAAC,GAAG,KAAuE,CAAA;IACjF,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,IAAI,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAC1G,OAAO,IAAI,eAAO,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,GAAI,CAAC,CAAC,MAA8B,EAAE,UAAU,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;AAC/G,CAAC;AAED,2FAA2F;AAC3F,SAAgB,aAAa,CAAI,UAAkB,EAAE,IAAa;IAChE,IAAI,CAAC;QACH,OAAO,IAAI,EAAE,CAAA;IACf,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,cAAc,CAAC,KAAK,EAAE,UAAU,CAAC,CAAA;IACzC,CAAC;AACH,CAAC","sourcesContent":["import { Refusal } from '@things-factory/shell'\n\n/*\n * **A twin model the kernel refuses is said by name** (ADR-0054).\n *\n * ── What went wrong (2026-10-10) ─────────────────────────────────────────────\n * The MES kernel refuses a model without a production declaration. The engine let that error through\n * as it was, so `twinReplay(miratek)` reached the screen as INTERNAL_SERVER_ERROR with the kernel's\n * English sentence in the shell's notice. The model was what was wrong, not the server.\n *\n * ── The rule ─────────────────────────────────────────────────────────────────\n * The kernel names its refusals: an error that carries a string `code` and an object `params`. The\n * kernel cannot reach the shell, so the host turns that into a shell `Refusal` here, adding which twin\n * it was. The sentence is made where the reader's language is known (`SpeakRefusals`, or the import\n * result's `refusal()`), under `error.<code>`. Anything without a name passes through untouched.\n */\nexport function asModelRefusal(error: unknown, instanceId: string): unknown {\n if (error instanceof Refusal) return error\n const e = error as { code?: unknown; params?: unknown; message?: unknown } | null\n if (!e || typeof e.code !== 'string' || !e.code || !e.params || typeof e.params !== 'object') return error\n return new Refusal(e.code, { ...(e.params as Record<string, any>), instanceId }, String(e.message ?? e.code))\n}\n\n/** Run a step that loads or replays a model; a kernel refusal comes out as a `Refusal`. */\nexport function refusingModel<T>(instanceId: string, step: () => T): T {\n try {\n return step()\n } catch (error) {\n throw asModelRefusal(error, instanceId)\n }\n}\n"]}
@@ -24,6 +24,7 @@ const local_declarations_js_1 = require("./local-declarations.js");
24
24
  const runtime_key_js_1 = require("./runtime-key.js");
25
25
  const twin_state_channel_js_1 = require("./twin-state-channel.js");
26
26
  const producer_guard_js_1 = require("./producer-guard.js");
27
+ const model_refusal_js_1 = require("./model-refusal.js");
27
28
  const command_routing_js_1 = require("./command-routing.js");
28
29
  const twin_instance_js_1 = require("../service/twin-instance/twin-instance.js");
29
30
  /* 자극의 집은 원본이다(ADR-0029) — 그 행을 읽고 쓴다. */
@@ -1387,7 +1388,7 @@ class TwinEngine {
1387
1388
  const Kernel = kernelFor(kind);
1388
1389
  const kernel = new Kernel(domainId, undefined, this.productionSpecOf(model));
1389
1390
  kernel.scopeId = id;
1390
- kernel.loadTwinModel(model); // 구조만. 상태는 아래 웜스타트가 주입한다.
1391
+ (0, model_refusal_js_1.refusingModel)(id, () => kernel.loadTwinModel(model)); // 구조만. 상태는 아래 웜스타트가 주입한다.
1391
1392
  this.applyOperations(kernel, model, id); // 시간·수율 명세(있으면) — 없으면 커널 기본값
1392
1393
  /* 추정기는 DB 조회를 포함해 비동기 — 기동을 막지 않고 붙는다(붙기 전 작업은 명세·상수로 산출). */
1393
1394
  this.installEstimators(kernel, domainId, id, model).catch(err => (0, log_js_1.twinWarn)('[twin-engine] estimator install failed', err?.message));
@@ -1597,7 +1598,7 @@ class TwinEngine {
1597
1598
  const Kernel = kernelFor(kind);
1598
1599
  const kernel = new Kernel(domainId, undefined, this.productionSpecOf(model));
1599
1600
  kernel.scopeId = id;
1600
- kernel.loadTwinModel(model);
1601
+ (0, model_refusal_js_1.refusingModel)(id, () => kernel.loadTwinModel(model));
1601
1602
  /* **세우는 쪽이 아는 사실은 세울 때 말한다.** 예전에는 첫 이벤트가 도착해야 커널이 스스로를
1602
1603
  관측 구동으로 여겼고, 그래서 아직 아무것도 못 받은 미러는 시뮬레이션 취급을 받았다. */
1603
1604
  declareObserved(kernel, `live twin '${id}'`);
@@ -3703,20 +3704,20 @@ class TwinEngine {
3703
3704
  */
3704
3705
  if (resume?.fold?.reducer && segments.length <= 1) {
3705
3706
  const model = (segments[0]?.model ?? (asOfNow && newest) ?? reg.model);
3706
- const out = replayFrom(model, resume.fold.reducer, segments[0]?.events ?? [], replayOptions(reg, domainId));
3707
+ const out = (0, model_refusal_js_1.refusingModel)(instanceId, () => replayFrom(model, resume.fold.reducer, segments[0]?.events ?? [], replayOptions(reg, domainId)));
3707
3708
  return keep(withNow(out.state), { reducer: out.checkpoint, oee: oee.serialize() });
3708
3709
  }
3709
3710
  if (!segments.length) {
3710
3711
  const model = ((asOfNow && newest) || reg.model);
3711
- const out = replayWithCheckpoint(model, [], replayOptions(reg, domainId));
3712
+ const out = (0, model_refusal_js_1.refusingModel)(instanceId, () => replayWithCheckpoint(model, [], replayOptions(reg, domainId)));
3712
3713
  return keep(withNow(out.state), { reducer: out.checkpoint, oee: oee.serialize() });
3713
3714
  }
3714
3715
  if (segments.length === 1) {
3715
- const out = replayWithCheckpoint(segments[0].model, segments[0].events, replayOptions(reg, domainId));
3716
+ const out = (0, model_refusal_js_1.refusingModel)(instanceId, () => replayWithCheckpoint(segments[0].model, segments[0].events, replayOptions(reg, domainId)));
3716
3717
  return keep(withNow(out.state), { reducer: out.checkpoint, oee: oee.serialize() });
3717
3718
  }
3718
3719
  /* 커널 계약도 `model` 이다(0.6.14) — 경계에서 어휘를 되돌려 담던 브릿지가 사라졌다. */
3719
- const { state, shifts } = replaySegments(segments, replayOptions(reg, domainId));
3720
+ const { state, shifts } = (0, model_refusal_js_1.refusingModel)(instanceId, () => replaySegments(segments, replayOptions(reg, domainId)));
3720
3721
  /* 경계에서 사라진 것을 알리지 않고 넘기지 않는다 — 수가 줄어든 이유를 어딘가에는 남겨야 한다. */
3721
3722
  for (const sh of shifts)
3722
3723
  if (sh.equipmentDropped || sh.locationsDropped || sh.personsDropped || sh.assetsDropped)
@@ -4515,7 +4516,7 @@ class TwinEngine {
4515
4516
  return null;
4516
4517
  const Kernel = kernelFor(reg.kind);
4517
4518
  const k = new Kernel(domainId, undefined, this.productionSpecOf(reg.model));
4518
- k.loadTwinModel(reg.model);
4519
+ (0, model_refusal_js_1.refusingModel)(instanceId, () => k.loadTwinModel(reg.model));
4519
4520
  this.applyOperations(k, reg.model, instanceId); // 예측도 같은 명세로 굴러야 한다(화면과 다른 숫자 금지)
4520
4521
  await this.installEstimators(k, domainId, instanceId, reg.model); // 예측은 기다린다 — 실측을 놓치면 예측이 상수로 계산된다
4521
4522
  const state = await this.recover(domainId, instanceId, undefined, untilTime).catch(() => null);