@things-factory/headless-twin 10.0.12 → 10.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-server/engine/canonical-ingest.d.ts +57 -2
- package/dist-server/engine/canonical-ingest.js +43 -2
- package/dist-server/engine/canonical-ingest.js.map +1 -1
- package/dist-server/engine/command-routing.js +1 -1
- package/dist-server/engine/command-routing.js.map +1 -1
- package/dist-server/engine/index.d.ts +3 -0
- package/dist-server/engine/index.js +4 -0
- package/dist-server/engine/index.js.map +1 -1
- package/dist-server/engine/ingest-health.d.ts +335 -0
- package/dist-server/engine/ingest-health.js +434 -0
- package/dist-server/engine/ingest-health.js.map +1 -0
- package/dist-server/engine/integration-coverage.d.ts +76 -0
- package/dist-server/engine/integration-coverage.js +73 -0
- package/dist-server/engine/integration-coverage.js.map +1 -0
- package/dist-server/engine/integration-probes.d.ts +65 -0
- package/dist-server/engine/integration-probes.js +100 -0
- package/dist-server/engine/integration-probes.js.map +1 -0
- package/dist-server/engine/integration-runner.d.ts +45 -0
- package/dist-server/engine/integration-runner.js +59 -0
- package/dist-server/engine/integration-runner.js.map +1 -0
- package/dist-server/engine/integration-target-profile.d.ts +57 -0
- package/dist-server/engine/integration-target-profile.js +79 -0
- package/dist-server/engine/integration-target-profile.js.map +1 -0
- package/dist-server/engine/kpi-fold.d.ts +27 -0
- package/dist-server/engine/kpi-fold.js +47 -4
- package/dist-server/engine/kpi-fold.js.map +1 -1
- package/dist-server/engine/kpi-query.js +128 -14
- package/dist-server/engine/kpi-query.js.map +1 -1
- package/dist-server/engine/live-feed-registry.js +2 -16
- package/dist-server/engine/live-feed-registry.js.map +1 -1
- package/dist-server/engine/load-meter.d.ts +1 -1
- package/dist-server/engine/load-meter.js +12 -3
- package/dist-server/engine/load-meter.js.map +1 -1
- package/dist-server/engine/log.d.ts +18 -0
- package/dist-server/engine/log.js +80 -0
- package/dist-server/engine/log.js.map +1 -0
- package/dist-server/engine/loop-lag.d.ts +54 -0
- package/dist-server/engine/loop-lag.js +87 -0
- package/dist-server/engine/loop-lag.js.map +1 -0
- package/dist-server/engine/model-gap.d.ts +108 -0
- package/dist-server/engine/model-gap.js +95 -0
- package/dist-server/engine/model-gap.js.map +1 -0
- package/dist-server/engine/restart-policy.d.ts +3 -3
- package/dist-server/engine/restart-policy.js +4 -4
- package/dist-server/engine/restart-policy.js.map +1 -1
- package/dist-server/engine/runtime-key.js +1 -1
- package/dist-server/engine/runtime-key.js.map +1 -1
- package/dist-server/engine/stage-path.d.ts +83 -0
- package/dist-server/engine/stage-path.js +118 -0
- package/dist-server/engine/stage-path.js.map +1 -0
- package/dist-server/engine/twin-engine.d.ts +352 -16
- package/dist-server/engine/twin-engine.js +1037 -166
- package/dist-server/engine/twin-engine.js.map +1 -1
- package/dist-server/index.js +34 -2
- package/dist-server/index.js.map +1 -1
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.d.ts +5 -0
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js +58 -0
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +1 -0
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.d.ts +5 -0
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js +55 -0
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +1 -0
- package/dist-server/migrations/index.js +7 -1
- package/dist-server/migrations/index.js.map +1 -1
- package/dist-server/service/index.d.ts +5 -2
- package/dist-server/service/index.js +18 -7
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/reference/discovery-result.d.ts +1 -1
- package/dist-server/service/reference/discovery-result.js +1 -1
- package/dist-server/service/reference/discovery-result.js.map +1 -1
- package/dist-server/service/reference/index.d.ts +4 -1
- package/dist-server/service/reference/index.js +6 -1
- package/dist-server/service/reference/index.js.map +1 -1
- package/dist-server/service/reference/reference-adapter.d.ts +184 -3
- package/dist-server/service/reference/reference-adapter.js +28 -4
- package/dist-server/service/reference/reference-adapter.js.map +1 -1
- package/dist-server/service/reference/reference-assessment.d.ts +68 -0
- package/dist-server/service/reference/reference-assessment.js +136 -0
- package/dist-server/service/reference/reference-assessment.js.map +1 -0
- package/dist-server/service/reference/reference-live.d.ts +12 -1
- package/dist-server/service/reference/reference-live.js +116 -11
- package/dist-server/service/reference/reference-live.js.map +1 -1
- package/dist-server/service/reference/reference-master.d.ts +47 -1
- package/dist-server/service/reference/reference-master.js +34 -4
- package/dist-server/service/reference/reference-master.js.map +1 -1
- package/dist-server/service/reference/reference-probe.d.ts +92 -0
- package/dist-server/service/reference/reference-probe.js +186 -0
- package/dist-server/service/reference/reference-probe.js.map +1 -0
- package/dist-server/service/reference/reference-progress-subscription.d.ts +17 -0
- package/dist-server/service/reference/reference-progress-subscription.js +94 -0
- package/dist-server/service/reference/reference-progress-subscription.js.map +1 -0
- package/dist-server/service/reference/reference-progress.d.ts +38 -0
- package/dist-server/service/reference/reference-progress.js +71 -0
- package/dist-server/service/reference/reference-progress.js.map +1 -0
- package/dist-server/service/reference/reference-resolver.js +210 -43
- package/dist-server/service/reference/reference-resolver.js.map +1 -1
- package/dist-server/service/reference/twin-reference.d.ts +32 -0
- package/dist-server/service/reference/twin-reference.js +10 -0
- package/dist-server/service/reference/twin-reference.js.map +1 -1
- package/dist-server/service/twin-control/twin-control-mutation.js +4 -3
- package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
- package/dist-server/service/twin-event/journal-count.d.ts +21 -0
- package/dist-server/service/twin-event/journal-count.js +26 -0
- package/dist-server/service/twin-event/journal-count.js.map +1 -0
- package/dist-server/service/twin-event/twin-event-keys.d.ts +9 -0
- package/dist-server/service/twin-event/twin-event-keys.js +15 -18
- package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
- package/dist-server/service/twin-event/twin-event-type.d.ts +3 -1
- package/dist-server/service/twin-event/twin-event-type.js +18 -1
- package/dist-server/service/twin-event/twin-event-type.js.map +1 -1
- package/dist-server/service/twin-event/twin-event.d.ts +1 -0
- package/dist-server/service/twin-event/twin-event.js +9 -1
- package/dist-server/service/twin-event/twin-event.js.map +1 -1
- package/dist-server/service/twin-forecast/twin-forecast-query.js +2 -1
- package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
- package/dist-server/service/twin-ingest-window/index.d.ts +5 -0
- package/dist-server/service/twin-ingest-window/index.js +10 -0
- package/dist-server/service/twin-ingest-window/index.js.map +1 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.d.ts +12 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js +123 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +1 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.d.ts +1 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js +51 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +1 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window.d.ts +16 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window.js +108 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window.js.map +1 -0
- package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
- package/dist-server/service/twin-journal/twin-journal-query.js +58 -5
- package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +14 -0
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +72 -2
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
- package/dist-server/service/twin-metrics/twin-metrics-query.d.ts +1 -0
- package/dist-server/service/twin-metrics/twin-metrics-query.js +78 -3
- package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
- package/dist-server/service/twin-model/axis-journal-evidence.d.ts +2 -0
- package/dist-server/service/twin-model/axis-journal-evidence.js +89 -0
- package/dist-server/service/twin-model/axis-journal-evidence.js.map +1 -0
- package/dist-server/service/twin-model/epcis-coverage.d.ts +1 -1
- package/dist-server/service/twin-model/epcis-coverage.js +19 -7
- package/dist-server/service/twin-model/epcis-coverage.js.map +1 -1
- package/dist-server/service/twin-model/iec61850-coverage.d.ts +1 -1
- package/dist-server/service/twin-model/iec61850-coverage.js +18 -7
- package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -1
- package/dist-server/service/twin-model/isa95-coverage.d.ts +14 -0
- package/dist-server/service/twin-model/isa95-coverage.js +23 -9
- package/dist-server/service/twin-model/isa95-coverage.js.map +1 -1
- package/dist-server/service/twin-model/item-ref.d.ts +24 -0
- package/dist-server/service/twin-model/item-ref.js +88 -0
- package/dist-server/service/twin-model/item-ref.js.map +1 -0
- package/dist-server/service/twin-model/name-index.d.ts +36 -0
- package/dist-server/service/twin-model/name-index.js +116 -0
- package/dist-server/service/twin-model/name-index.js.map +1 -0
- package/dist-server/service/twin-model/project-structure.js +1 -1
- package/dist-server/service/twin-model/project-structure.js.map +1 -1
- package/dist-server/service/twin-model/status-tally.d.ts +9 -0
- package/dist-server/service/twin-model/status-tally.js +37 -0
- package/dist-server/service/twin-model/status-tally.js.map +1 -0
- package/dist-server/service/twin-model/twin-lineage-query.js +40 -12
- package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-item-query.js +38 -39
- package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-query.js +156 -4
- package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-tree-query.js +7 -0
- package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
- package/dist-server/service/twin-readiness/index.d.ts +2 -0
- package/dist-server/service/twin-readiness/index.js +6 -0
- package/dist-server/service/twin-readiness/index.js.map +1 -0
- package/dist-server/service/twin-readiness/twin-readiness-query.d.ts +3 -0
- package/dist-server/service/twin-readiness/twin-readiness-query.js +103 -0
- package/dist-server/service/twin-readiness/twin-readiness-query.js.map +1 -0
- package/dist-server/service/twin-space/twin-space-resolver.d.ts +2 -2
- package/dist-server/service/twin-space/twin-space-resolver.js +5 -4
- package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
- package/dist-shared/entity-delta.d.ts +23 -3
- package/dist-shared/entity-delta.js +12 -8
- package/dist-shared/entity-delta.js.map +1 -1
- package/dist-shared/kpi-broadcast.js +1 -1
- package/dist-shared/kpi-broadcast.js.map +1 -1
- package/dist-shared/touched-items.d.ts +7 -0
- package/dist-shared/touched-items.js +80 -0
- package/dist-shared/touched-items.js.map +1 -0
- package/package.json +7 -7
- package/server/engine/canonical-ingest.ts +92 -7
- package/server/engine/command-routing.ts +1 -1
- package/server/engine/index.ts +4 -0
- package/server/engine/ingest-health.ts +704 -0
- package/server/engine/integration-coverage.ts +147 -0
- package/server/engine/integration-probes.ts +144 -0
- package/server/engine/integration-runner.ts +95 -0
- package/server/engine/integration-target-profile.ts +103 -0
- package/server/engine/kpi-fold.ts +72 -4
- package/server/engine/kpi-query.ts +129 -15
- package/server/engine/live-feed-registry.ts +2 -1
- package/server/engine/load-meter.ts +12 -3
- package/server/engine/log.ts +72 -0
- package/server/engine/loop-lag.ts +120 -0
- package/server/engine/model-gap.ts +168 -0
- package/server/engine/restart-policy.ts +4 -4
- package/server/engine/runtime-key.ts +1 -1
- package/server/engine/stage-path.ts +172 -0
- package/server/engine/twin-engine.ts +1135 -162
- package/server/index.ts +35 -2
- package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +54 -0
- package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +53 -0
- package/server/migrations/index.ts +7 -1
- package/server/service/index.ts +11 -0
- package/server/service/reference/discovery-result.ts +1 -1
- package/server/service/reference/index.ts +6 -1
- package/server/service/reference/reference-adapter.ts +197 -5
- package/server/service/reference/reference-assessment.ts +215 -0
- package/server/service/reference/reference-live.ts +123 -11
- package/server/service/reference/reference-master.ts +64 -4
- package/server/service/reference/reference-probe.ts +264 -0
- package/server/service/reference/reference-progress-subscription.ts +73 -0
- package/server/service/reference/reference-progress.ts +95 -0
- package/server/service/reference/reference-resolver.ts +200 -12
- package/server/service/reference/twin-reference.ts +39 -1
- package/server/service/twin-control/twin-control-mutation.ts +4 -3
- package/server/service/twin-event/journal-count.ts +53 -0
- package/server/service/twin-event/twin-event-keys.ts +16 -1
- package/server/service/twin-event/twin-event-type.ts +26 -2
- package/server/service/twin-event/twin-event.ts +7 -0
- package/server/service/twin-forecast/twin-forecast-query.ts +2 -1
- package/server/service/twin-ingest-window/index.ts +7 -0
- package/server/service/twin-ingest-window/twin-ingest-window-query.ts +125 -0
- package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +57 -0
- package/server/service/twin-ingest-window/twin-ingest-window.ts +113 -0
- package/server/service/twin-instance/twin-instance.ts +1 -1
- package/server/service/twin-journal/twin-journal-query.ts +59 -5
- package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +77 -3
- package/server/service/twin-metrics/twin-metrics-query.ts +81 -3
- package/server/service/twin-model/axis-journal-evidence.ts +57 -0
- package/server/service/twin-model/epcis-coverage.ts +2 -8
- package/server/service/twin-model/iec61850-coverage.ts +2 -8
- package/server/service/twin-model/isa95-coverage.ts +22 -9
- package/server/service/twin-model/item-ref.ts +80 -0
- package/server/service/twin-model/name-index.ts +94 -0
- package/server/service/twin-model/project-structure.ts +1 -1
- package/server/service/twin-model/status-tally.ts +37 -0
- package/server/service/twin-model/twin-lineage-query.ts +38 -9
- package/server/service/twin-model/twin-model-item-query.ts +26 -27
- package/server/service/twin-model/twin-model-query.ts +155 -4
- package/server/service/twin-model/twin-model-tree-query.ts +7 -0
- package/server/service/twin-readiness/index.ts +3 -0
- package/server/service/twin-readiness/twin-readiness-query.ts +96 -0
- package/server/service/twin-space/twin-space-resolver.ts +5 -4
- package/shared/entity-delta.ts +31 -7
- package/shared/kpi-broadcast.ts +1 -1
- package/shared/touched-items.ts +73 -0
- package/test/axis-journal-evidence.test.ts +71 -0
- package/test/axis-read.test.ts +101 -1
- package/test/boot-resume.test.ts +92 -26
- package/test/broadcast-cost-baseline.test.ts +200 -0
- package/test/broadcast-period.test.ts +58 -0
- package/test/canonical-ingest-vocabularies.test.ts +36 -1
- package/test/canonical-quantity-door.test.ts +61 -0
- package/test/command-routing.test.ts +1 -1
- package/test/declared-location-types.test.ts +89 -0
- package/test/discovery-result.test.ts +1 -1
- package/test/duration-estimators.test.ts +1 -1
- package/test/entity-delta.test.ts +2 -2
- package/test/ingest-bench.test.ts +3 -3
- package/test/ingest-health-engine.test.ts +248 -0
- package/test/ingest-health-wiring.test.ts +119 -0
- package/test/ingest-health.test.ts +306 -0
- package/test/ingest-history.test.ts +247 -0
- package/test/integration-probes.test.ts +103 -0
- package/test/integration-runner.test.ts +95 -0
- package/test/item-ref.test.ts +78 -0
- package/test/journal-read-discipline.test.ts +177 -0
- package/test/journal-retention.test.ts +133 -0
- package/test/journal-sort-axis.test.ts +142 -0
- package/test/journal-write-door.test.ts +110 -0
- package/test/journal-write-trend.test.ts +115 -0
- package/test/kernel-kind-guard.test.ts +4 -4
- package/test/kpi-fold.test.ts +90 -3
- package/test/kpi-query-bench.test.ts +2 -2
- package/test/lineage-survives-restart.test.ts +17 -1
- package/test/live-cursor-wiring.test.ts +87 -0
- package/test/live-feed-registry.test.ts +9 -1
- package/test/live-kernel-facts.test.ts +7 -7
- package/test/live-mirror-parity.test.ts +6 -0
- package/test/load-meter.test.ts +29 -15
- package/test/local-declarations.test.ts +1 -1
- package/test/log-stamp.test.ts +59 -0
- package/test/loop-lag.test.ts +82 -0
- package/test/model-gap.test.ts +153 -0
- package/test/oee-accumulator.test.ts +4 -0
- package/test/operational-vocabulary.test.ts +4 -4
- package/test/read-failure-visible.test.ts +145 -0
- package/test/reference-grounding.test.ts +70 -0
- package/test/resolve-ts-siblings.mjs +52 -0
- package/test/restart-policy.test.ts +7 -7
- package/test/revision-axis.test.ts +93 -0
- package/test/runtime-key.test.ts +2 -2
- package/test/scale-twin-bench.test.ts +2 -2
- package/test/spec-coverage.test.ts +1 -1
- package/test/stage-path.test.ts +95 -0
- package/test/standard-coverage.test.ts +15 -3
- package/test/status-tally.test.ts +55 -0
- package/test/structure-revision-db.test.ts +4 -0
- package/test/tenant-registry-db.test.ts +1 -1
- package/test/time-range.test.ts +152 -0
- package/test/touched-items.test.ts +61 -0
- package/test/twin-event-keys.test.ts +10 -2
- package/test/twin-model-item-db.test.ts +2 -0
- package/test/warm-start-seam.test.ts +4 -0
- package/test/yield-loop.test.ts +57 -2
- package/tsconfig.shared.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
package/server/index.ts
CHANGED
|
@@ -3,10 +3,12 @@ export * from './service/index.js'
|
|
|
3
3
|
/* 스키마 이력 — 프레임워크가 `migrations` export 를 모아 실행한다(`run migration`). */
|
|
4
4
|
export { migrations } from './migrations/index.js'
|
|
5
5
|
|
|
6
|
+
import { twinLog, twinError } from './engine/log.js'
|
|
6
7
|
import './routes.js'
|
|
7
8
|
|
|
8
9
|
import { TwinEngine } from './engine/index.js'
|
|
9
10
|
import { resumeReferenceLiveFeeds } from './service/reference/reference-live.js'
|
|
11
|
+
import { installIngestWindowWriter } from './service/twin-ingest-window/index.js'
|
|
10
12
|
|
|
11
13
|
/*
|
|
12
14
|
* 모듈 부팅 — **도는 트윈을 되살린다.**
|
|
@@ -19,10 +21,41 @@ import { resumeReferenceLiveFeeds } from './service/reference/reference-live.js'
|
|
|
19
21
|
*/
|
|
20
22
|
process.on('bootstrap-module-start' as any, async ({ app, config, client }: any) => {
|
|
21
23
|
try {
|
|
24
|
+
/*
|
|
25
|
+
* 닫힌 인제스트 창을 표로 옮기는 문을 먼저 연다 — 트윈을 되살리기 **전에** 등록해야 첫 창부터
|
|
26
|
+
* 남는다. 커널 층은 저장 계층을 모르므로 이 등록이 그 둘을 잇는 유일한 자리다.
|
|
27
|
+
*/
|
|
28
|
+
installIngestWindowWriter()
|
|
29
|
+
/*
|
|
30
|
+
* **저널 보존 기간은 선언에서 온다** — 코드에 박아 두지 않는다(2026-08-22).
|
|
31
|
+
*
|
|
32
|
+
* 실측: 개발 저널이 시간당 581,794행 · 0.85GB(하루 20GB) 로 자랐고 지우는 것이 없었다. 그런데
|
|
33
|
+
* 「며칠까지 남기나」는 **배포마다 갈리는 사실**이다 — 개발은 짧아도 되고 실증은 길어야 한다.
|
|
34
|
+
*
|
|
35
|
+
* 선언이 없으면 **아무것도 지우지 않는다.** 조용히 지우는 편이 조용히 쌓는 것보다 나쁘다.
|
|
36
|
+
* 값은 스냅샷 TTL(7일)보다 짧을 수 없다 — 엔진이 그것을 올려 주고 그 사실을 말한다.
|
|
37
|
+
*/
|
|
38
|
+
/*
|
|
39
|
+
* 읽는 순서: **환경변수 → 설정 → 없음.**
|
|
40
|
+
*
|
|
41
|
+
* 설정(`config.twin.journalRetentionDays`)이 배포의 선언이고, 환경변수
|
|
42
|
+
* (`TWIN_JOURNAL_RETENTION_DAYS`)는 그것을 **그 자리에서** 덮는다. 컨테이너로 도는 배포에서는
|
|
43
|
+
* 파일을 고치지 않고 값을 바꿀 수 있어야 하고, 개발에서도 그 편이 빠르다.
|
|
44
|
+
*
|
|
45
|
+
* 둘 다 없으면 **아무것도 지우지 않는다.**
|
|
46
|
+
*/
|
|
47
|
+
const fromEnv = Number(process.env.TWIN_JOURNAL_RETENTION_DAYS ?? 0)
|
|
48
|
+
const fromConfig = Number(config?.get?.('twin')?.journalRetentionDays ?? 0)
|
|
49
|
+
const retentionDays = fromEnv > 0 ? fromEnv : fromConfig
|
|
50
|
+
if (retentionDays > 0) {
|
|
51
|
+
TwinEngine.JOURNAL_RETENTION_DAYS = retentionDays
|
|
52
|
+
twinLog(`[twin] journal retention ${retentionDays}일 (${fromEnv > 0 ? '환경변수' : '설정'})`)
|
|
53
|
+
}
|
|
54
|
+
|
|
22
55
|
await TwinEngine.bootstrap()
|
|
23
56
|
await resumeReferenceLiveFeeds()
|
|
24
|
-
|
|
57
|
+
twinLog('Headless Twin host has started.')
|
|
25
58
|
} catch (ex) {
|
|
26
|
-
|
|
59
|
+
twinError('Headless Twin host failed to start.', ex)
|
|
27
60
|
}
|
|
28
61
|
})
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner, TableColumn, TableIndex } from 'typeorm'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 두 계측을 위한 컬럼 둘.
|
|
5
|
+
*
|
|
6
|
+
* ① `twin_events.action` — EPCIS 행위(ADD/OBSERVE/DELETE) 승격.
|
|
7
|
+
* 되풀어 읽는 스냅샷은 원리적으로 `DELETE` 를 못 낸다. 그래서 「이 트윈이 반출을 한 번이라도
|
|
8
|
+
* 접었는가」가 진짜 delta 피드의 증거인데, 값이 `payload`(simple-json = TEXT) 안에 있는 동안에는
|
|
9
|
+
* 5개 드라이버 어디에서도 그 질문을 할 수 없었다(DB별 JSON 연산자 금지).
|
|
10
|
+
*
|
|
11
|
+
* ② `twin_references.last_warning_count` — 마지막 동기에서 못 옮긴 값의 수.
|
|
12
|
+
* 세지 않은 구조 인제스트는 도크를 랙으로 바꿔 놓고도 조용히 성공한다.
|
|
13
|
+
*
|
|
14
|
+
* **둘 다 nullable, 기본값 없음.** 기본값 0 을 깔면 「없었다」와 「세지 않았다」가 같아지고, 그
|
|
15
|
+
* 구분이 단계 판정의 전부다(옛 행은 모르는 것이 맞다 — 채워 넣지 않는다).
|
|
16
|
+
*
|
|
17
|
+
* 인덱스 `ix_twin_event_6` 는 저널이 큰 환경에서 생성에 시간이 걸린다. 그래도 만드는 이유:
|
|
18
|
+
* 없으면 「반출을 접었나」가 그 인스턴스 저널 전량 주사가 되고, 반출이 **없는** 경우(초기 상태이자
|
|
19
|
+
* 가장 흔한 경우)가 항상 최악이다.
|
|
20
|
+
*
|
|
21
|
+
* 멱등: 이미 있으면 아무것도 하지 않는다. TypeORM API 만 쓴다(raw SQL 금지 — 드라이버 5종).
|
|
22
|
+
*/
|
|
23
|
+
const EVENTS = 'twin_events'
|
|
24
|
+
const REFS = 'twin_references'
|
|
25
|
+
const ACTION = 'action'
|
|
26
|
+
const WARN = 'last_warning_count'
|
|
27
|
+
const IX = 'ix_twin_event_6'
|
|
28
|
+
|
|
29
|
+
export class PromoteEventActionAndSyncWarnings1786100000000 implements MigrationInterface {
|
|
30
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
31
|
+
const events = await queryRunner.getTable(EVENTS)
|
|
32
|
+
if (events && !events.columns.some(c => c.name === ACTION)) {
|
|
33
|
+
await queryRunner.addColumn(events, new TableColumn({ name: ACTION, type: 'varchar', length: '16', isNullable: true }))
|
|
34
|
+
}
|
|
35
|
+
const withColumn = await queryRunner.getTable(EVENTS)
|
|
36
|
+
if (withColumn && withColumn.columns.some(c => c.name === ACTION) && !withColumn.indices.some(i => i.name === IX)) {
|
|
37
|
+
await queryRunner.createIndex(withColumn, new TableIndex({ name: IX, columnNames: ['domain_id', 'instance_id', ACTION] }))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const refs = await queryRunner.getTable(REFS)
|
|
41
|
+
if (refs && !refs.columns.some(c => c.name === WARN)) {
|
|
42
|
+
await queryRunner.addColumn(refs, new TableColumn({ name: WARN, type: 'int', isNullable: true }))
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
47
|
+
const events = await queryRunner.getTable(EVENTS)
|
|
48
|
+
if (events?.indices.some(i => i.name === IX)) await queryRunner.dropIndex(events, IX)
|
|
49
|
+
const events2 = await queryRunner.getTable(EVENTS)
|
|
50
|
+
if (events2?.columns.some(c => c.name === ACTION)) await queryRunner.dropColumn(events2, ACTION)
|
|
51
|
+
const refs = await queryRunner.getTable(REFS)
|
|
52
|
+
if (refs?.columns.some(c => c.name === WARN)) await queryRunner.dropColumn(refs, WARN)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `twin_references.live_cursor` — **어디까지 읽었나**를 재기동 너머로 잇는다.
|
|
5
|
+
*
|
|
6
|
+
* ── 무엇이 조용히 사라지고 있었나 (2026-08-23 실측) ─────────────────────────
|
|
7
|
+
* 라이브 어댑터가 붙을 때마다 `Date.now() − 되돌아볼 날수` 로 창을 새로 만들었다. 그래서 **재기동마다
|
|
8
|
+
* 미러의 과거가 잘렸다** — 작업 2,855 → 2,820(그 사이 8시간이 흐른 만큼). 미러가 아는 것이 「원본의
|
|
9
|
+
* 사실」이 아니라 「창의 함수」였고, 화면은 그 수를 보이면서 그 사실을 말하지 않았다.
|
|
10
|
+
*
|
|
11
|
+
* 같은 원본에서 잰 규모가 그 값을 말한다: 작업지시 77,594건 중 창이 읽는 것이 3,039건(3.9%)이다.
|
|
12
|
+
*
|
|
13
|
+
* ── 왜 표인가 (캐시가 아니라) ───────────────────────────────────────────────
|
|
14
|
+
* 커널이 갈림을 이미 적어 두었다(§`hydrateContinuity`): 「원천이 **애초에 다시 말해 주지 않는 축**」은
|
|
15
|
+
* 재기동 연속성으로 이어받는다. 「우리가 어디까지 읽었나」가 정확히 그 성질이다.
|
|
16
|
+
*
|
|
17
|
+
* 그리고 스냅샷 체크포인트와 성질이 다르다: 그쪽은 잃어도 원천이 정정해 준다(그래서 TTL 캐시로 족하다).
|
|
18
|
+
* **이 값은 잃으면 원천에 묻지 않게 된다** — 창이 다시 미끄러지고, 그 손실은 오류 없이 조용하다.
|
|
19
|
+
* 만료되는 자리에 둘 수 없다.
|
|
20
|
+
*
|
|
21
|
+
* ── 모양 ────────────────────────────────────────────────────────────────────
|
|
22
|
+
* `{ streams: { [흐름]: { since?, seen: string[] } }, firstAttachedAt? }`
|
|
23
|
+
*
|
|
24
|
+
* **흐름 이름은 어댑터가 정한다** — 원본마다 흐름 수와 뜻이 다르다(첫 실 원본은 재고·오더·로트·투입 넷).
|
|
25
|
+
* 이 층은 열쇠로만 다룬다. 계약이 이름을 닫으면 새 원본마다 계약을 고치게 된다.
|
|
26
|
+
*
|
|
27
|
+
* `firstAttachedAt` 은 「언제부터 아는가」다 — 커널은 상한만 안다(`nowTime`). 둘이 함께 「이 트윈이
|
|
28
|
+
* 아는 구간」이고, 화면이 수를 보일 때 그 구간을 말해야 한다.
|
|
29
|
+
*
|
|
30
|
+
* **nullable, 기본값 없음.** `null` = 한 번도 붙지 않았다는 사실이고, 그때만 되돌아볼 날수로 첫 창을
|
|
31
|
+
* 만든다. 빈 객체를 기본으로 깔면 「첫 붙음」과 「이어 붙음」이 같아지고, 그 구분이 이 컬럼의 전부다.
|
|
32
|
+
*
|
|
33
|
+
* `simple-json` = TEXT 이므로 드라이버 다섯을 그대로 지난다(DB별 JSON 타입·연산자 금지).
|
|
34
|
+
* 멱등: 이미 있으면 아무것도 하지 않는다. TypeORM API 만 쓴다(raw SQL 금지).
|
|
35
|
+
*/
|
|
36
|
+
const REFS = 'twin_references'
|
|
37
|
+
const CURSOR = 'live_cursor'
|
|
38
|
+
|
|
39
|
+
export class CarryLiveFeedCursor1786200000000 implements MigrationInterface {
|
|
40
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
41
|
+
const refs = await queryRunner.getTable(REFS)
|
|
42
|
+
if (refs && !refs.columns.some(c => c.name === CURSOR)) {
|
|
43
|
+
await queryRunner.addColumn(refs, new TableColumn({ name: CURSOR, type: 'text', isNullable: true }))
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
48
|
+
const refs = await queryRunner.getTable(REFS)
|
|
49
|
+
if (refs && refs.columns.some(c => c.name === CURSOR)) {
|
|
50
|
+
await queryRunner.dropColumn(refs, CURSOR)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -3,5 +3,11 @@
|
|
|
3
3
|
* 파일마다 하나씩 export 하고 여기서 모아 `server/index.ts` 가 내보낸다.
|
|
4
4
|
*/
|
|
5
5
|
import { RenameTwinInstanceBoardToModel1786000000000 } from './1786000000000-RenameTwinInstanceBoardToModel.js'
|
|
6
|
+
import { PromoteEventActionAndSyncWarnings1786100000000 } from './1786100000000-PromoteEventActionAndSyncWarnings.js'
|
|
7
|
+
import { CarryLiveFeedCursor1786200000000 } from './1786200000000-CarryLiveFeedCursor.js'
|
|
6
8
|
|
|
7
|
-
export const migrations = [
|
|
9
|
+
export const migrations = [
|
|
10
|
+
RenameTwinInstanceBoardToModel1786000000000,
|
|
11
|
+
PromoteEventActionAndSyncWarnings1786100000000,
|
|
12
|
+
CarryLiveFeedCursor1786200000000
|
|
13
|
+
]
|
package/server/service/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* EXPORT ENTITY TYPES */
|
|
2
2
|
export * from './twin-event/twin-event.js'
|
|
3
3
|
export * from './twin-audit/twin-audit-event.js'
|
|
4
|
+
export * from './twin-ingest-window/twin-ingest-window.js'
|
|
4
5
|
export * from './twin-instance/twin-instance.js'
|
|
5
6
|
export * from './twin-space/twin-space.js'
|
|
6
7
|
export * from './twin-space/twin-space-representation.js'
|
|
@@ -14,6 +15,10 @@ export * from './reference/reference-adapter.js'
|
|
|
14
15
|
export * from './reference/reference-live.js'
|
|
15
16
|
export * from './reference/twin-reference.js'
|
|
16
17
|
export * from './reference/template-registry.js'
|
|
18
|
+
/* 커넥터 점검 하네스 — 실 원본에 붙여 무엇이 채워지고 무엇이 비는지 센다(앱을 띄우지 않는다). */
|
|
19
|
+
export * from './reference/reference-probe.js'
|
|
20
|
+
/* 읽은 뒤의 총평(사실 층) — 화면이 표로 그리고, 더 알고 싶을 때 AI 가 이 사실만 근거로 서술한다. */
|
|
21
|
+
export * from './reference/reference-assessment.js'
|
|
17
22
|
/* 도메인 어휘 카탈로그 조합(커널 SSOT 노출) — 서버측 소비자(board-ai 도구 등)가 같은 형태를 쓰도록. */
|
|
18
23
|
export * from './twin-lifecycle/domain-catalog.js'
|
|
19
24
|
|
|
@@ -28,15 +33,19 @@ import { entities as TwinReferenceEntities, resolvers as TwinReferenceResolvers
|
|
|
28
33
|
import { resolvers as TwinStateResolvers } from './twin-state/index.js'
|
|
29
34
|
import { resolvers as TwinControlResolvers } from './twin-control/index.js'
|
|
30
35
|
import { entities as TwinModelEntities, resolvers as TwinModelResolvers } from './twin-model/index.js'
|
|
36
|
+
/* 인제스트 창 — 동기화 추이를 재기동 뒤에도 이어 보기 위한 행(리졸버 없음: 메모리 장부가 조회를 답한다). */
|
|
37
|
+
import { entities as TwinIngestWindowEntities, resolvers as TwinIngestWindowResolvers } from './twin-ingest-window/index.js'
|
|
31
38
|
import { resolvers as TwinLifecycleResolvers } from './twin-lifecycle/index.js'
|
|
32
39
|
import { resolvers as TwinJournalResolvers } from './twin-journal/index.js'
|
|
33
40
|
import { resolvers as TwinForecastResolvers } from './twin-forecast/index.js'
|
|
34
41
|
import { resolvers as TwinAttentionResolvers } from './twin-attention/index.js'
|
|
35
42
|
import { resolvers as TwinMetricsResolvers } from './twin-metrics/index.js'
|
|
43
|
+
import { resolvers as TwinReadinessResolvers } from './twin-readiness/index.js'
|
|
36
44
|
|
|
37
45
|
export const entities = [
|
|
38
46
|
/* ENTITIES */
|
|
39
47
|
...TwinEventEntities,
|
|
48
|
+
...TwinIngestWindowEntities,
|
|
40
49
|
...TwinAuditEntities,
|
|
41
50
|
...TwinInstanceEntities,
|
|
42
51
|
...TwinStructureEntities,
|
|
@@ -61,6 +70,8 @@ export const schema = {
|
|
|
61
70
|
...TwinForecastResolvers,
|
|
62
71
|
...TwinAttentionResolvers,
|
|
63
72
|
...TwinMetricsResolvers,
|
|
73
|
+
...TwinReadinessResolvers,
|
|
74
|
+
...TwinIngestWindowResolvers,
|
|
64
75
|
...TwinModelResolvers,
|
|
65
76
|
...TwinSpaceResolvers,
|
|
66
77
|
...TwinTargetResolvers,
|
|
@@ -86,7 +86,7 @@ export function failed<S>(error: DiscoveryError): DiscoveryResult<S> {
|
|
|
86
86
|
return { sites: [], error }
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
/** 어댑터가
|
|
89
|
+
/** 어댑터가 오류를 낸 것에서 사람이 읽을 사유를 뽑는다 — 형태를 가정하지 않는다. */
|
|
90
90
|
export function detailOf(e: unknown): string {
|
|
91
91
|
if (e instanceof Error && e.message) return e.message
|
|
92
92
|
if (typeof e === 'string' && e) return e
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { TwinReferenceResolver } from './reference-resolver.js'
|
|
2
|
+
import { TwinReferenceProgressSubscription } from './reference-progress-subscription.js'
|
|
2
3
|
import { TwinReference } from './twin-reference.js'
|
|
3
4
|
|
|
4
5
|
export const entities = [TwinReference]
|
|
5
|
-
export const resolvers = [TwinReferenceResolver]
|
|
6
|
+
export const resolvers = [TwinReferenceResolver, TwinReferenceProgressSubscription]
|
|
6
7
|
export * from './control-routing.js'
|
|
8
|
+
/* 진행 단계 어휘 — **모든 커넥터가 같은 낱말로 말한다.** 화면은 단계마다 번역 하나만 두면 된다. */
|
|
9
|
+
export * from './reference-progress.js'
|
|
10
|
+
/* 커넥터 점검 하네스 — 실 원본에 붙여 무엇이 채워지고 무엇이 비는지 센다(앱을 띄우지 않는다). */
|
|
11
|
+
export * from './reference-probe.js'
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ReferenceStep } from './reference-progress.js'
|
|
1
2
|
import type { ReferenceMaster } from './reference-master.js'
|
|
2
3
|
|
|
3
4
|
/*
|
|
@@ -102,6 +103,59 @@ export interface ControlResult {
|
|
|
102
103
|
detail?: string
|
|
103
104
|
}
|
|
104
105
|
|
|
106
|
+
/**
|
|
107
|
+
* 한 흐름의 읽기 커서 — **어디까지 읽었나.**
|
|
108
|
+
*
|
|
109
|
+
* `since` 와 `seen` 이 **함께** 있어야 한다. 이어 읽기는 경계 시각을 겹쳐 읽고(`gte`), 그 겹침을
|
|
110
|
+
* `seen` 이 걸러 낸다. `gt` 로 좁히면 같은 밀리초의 다른 행이 사라진다 — 조용한 누락이다.
|
|
111
|
+
*
|
|
112
|
+
* `seen` 이 예상보다 커지면(한 시각에 몰린 행 수가 늘면) 그것은 원본 쪽 신호다. 어댑터가 그 크기를
|
|
113
|
+
* 말해야 한다 — 조용히 커지게 두면 이어 읽기가 막히는 순간까지 아무도 모른다.
|
|
114
|
+
*/
|
|
115
|
+
export interface LiveFeedCursor {
|
|
116
|
+
/** 그 흐름에서 마지막으로 본 시각(원본의 시각, ISO). */
|
|
117
|
+
since?: string
|
|
118
|
+
/** 그 시각에 이미 소비한 행 id — 겹쳐 읽은 것을 걸러 낸다. 저장되는 모양이므로 배열이다. */
|
|
119
|
+
seen: string[]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 재기동을 넘어 이어지는 읽기 상태 — 참조 계층이 소유하고 어댑터에 건넨다(§`openLiveFeed`).
|
|
124
|
+
*
|
|
125
|
+
* 흐름 열쇠는 **어댑터가 정한다**(`Record`). 계약이 이름을 닫으면 새 원본마다 계약을 고치게 된다.
|
|
126
|
+
*/
|
|
127
|
+
export interface LiveFeedContinuity {
|
|
128
|
+
/** 지난번에 어디까지 읽었나 — 없으면 첫 붙음이다(그때만 되돌아볼 날수로 창을 만든다). */
|
|
129
|
+
cursor?: { streams?: Record<string, LiveFeedCursor>; firstAttachedAt?: string }
|
|
130
|
+
/**
|
|
131
|
+
* 커서가 움직였다고 알린다 — 이 층이 저장한다.
|
|
132
|
+
*
|
|
133
|
+
* 어댑터가 **매 행마다 부르지 않는다**: 저장은 이 층의 몫이고, 잦으면 이 층이 눌린다. 흐름 하나가
|
|
134
|
+
* 한 묶음을 소비한 뒤 한 번이면 충분하다.
|
|
135
|
+
*/
|
|
136
|
+
onCursor?: (cursor: { streams: Record<string, LiveFeedCursor>; firstAttachedAt?: string }) => void
|
|
137
|
+
/**
|
|
138
|
+
* **원본에 닿지 못했다** — 어댑터가 읽기 실패를 알린다(2026-08-23).
|
|
139
|
+
*
|
|
140
|
+
* ── 무엇이 조용했나 ─────────────────────────────────────────────────────────
|
|
141
|
+
* 실 원본이 끊겼을 때(접속 시간 초과) 트윈의 **조회 가능한 상태 어디에도** 그 사실이 없었다.
|
|
142
|
+
* `onRecords` 가 불리지 않으면 유입 장부에 아무 일도 일어나지 않고, `TwinReference.lastError` 는
|
|
143
|
+
* 마스터 동기·접속 시험에만 적힌다. 그래서 화면이 볼 수 있는 것은 「새 사실이 없다」뿐이었고,
|
|
144
|
+
* 그것은 **「원본이 조용하다」와 구별되지 않는다.**
|
|
145
|
+
*
|
|
146
|
+
* 어댑터는 로그로 말하고 있었다(재시도 경고). 그러나 **로그는 사람이 볼 때만 값이 있다** — 화면이
|
|
147
|
+
* 말하려면 상태에 있어야 한다. 이 통로가 그 자리다.
|
|
148
|
+
*
|
|
149
|
+
* ── 무엇을 알리고 무엇을 알리지 않나 ────────────────────────────────────────
|
|
150
|
+
* **재시도를 다 쓰고 그 주기를 포기했을 때** 부른다 — 재시도마다 부르면 한 번의 흔들림이 단절로 보인다.
|
|
151
|
+
* 흐름 여럿을 읽는 원본은 흐름 이름을 함께 준다(어느 표가 막혔나가 원인 찾기의 절반이다).
|
|
152
|
+
*
|
|
153
|
+
* **빈 읽기는 실패가 아니다** — 원본이 「새 것이 없다」고 답한 것은 닿았다는 뜻이므로 `onRecords([])`
|
|
154
|
+
* 로 알린다. 그 둘을 섞으면 조용한 원본이 끊긴 원본으로 보인다(고치려던 것의 반대 방향으로 틀린다).
|
|
155
|
+
*/
|
|
156
|
+
onReadFailure?: (info: { reason: string; stream?: string }) => void
|
|
157
|
+
}
|
|
158
|
+
|
|
105
159
|
export interface ReferenceAdapter {
|
|
106
160
|
/** 레지스트리 키 (예: 'virtual' | 'sap-ewm' | 'custom-rest'). */
|
|
107
161
|
type: string
|
|
@@ -112,9 +166,65 @@ export interface ReferenceAdapter {
|
|
|
112
166
|
/** N 발견 — 사이트/공장 열거. 단일 소스는 길이 1 반환. */
|
|
113
167
|
discoverSites(cfg: ConnectionConfig): Promise<SiteDescriptor[]>
|
|
114
168
|
/** 사이트별 마스터(구조) — 기존 masterToTwin 이 소비. */
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
169
|
+
/**
|
|
170
|
+
* 이 어댑터가 마스터를 읽으며 **보낼 단계를 미리 선언한다** — 그것이 진행률의 분모다.
|
|
171
|
+
*
|
|
172
|
+
* 표준 낱말만 쓴다(`REFERENCE_STEPS`). 선언하지 않으면 화면은 퍼센트를 보이지 않고 단계 이름만
|
|
173
|
+
* 보인다 — **모르는 진행률을 지어내지 않는다.**
|
|
174
|
+
*
|
|
175
|
+
* 원본에 없는 자리는 선언하지 않는다. 그러면 「이 원본으로 채울 수 있는 만큼」이 곧 분모가 된다.
|
|
176
|
+
*/
|
|
177
|
+
masterSteps?: readonly ReferenceStep[]
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* @param onStep 진행을 알리는 통로 — **선택이다.** 주지 않으면 예전과 같이 동작한다.
|
|
181
|
+
*
|
|
182
|
+
* 마스터 읽기는 원본에 여러 질의를 보내는 긴 작업이고(창고·자리·설비·공정·경로·품목·BOM), 화면은 그
|
|
183
|
+
* 사이에 아무것도 알 수 없었다. 어댑터만 자기 단계를 아므로 어댑터가 말해야 한다 — 호스트가 짐작하면
|
|
184
|
+
* 그것은 지어낸 진행률이다.
|
|
185
|
+
*
|
|
186
|
+
* `total` 은 **그 어댑터가 보낼 단계 수**다. 모르면 주지 않는다(화면이 퍼센트를 만들지 않는다).
|
|
187
|
+
*/
|
|
188
|
+
fetchMaster(
|
|
189
|
+
cfg: ConnectionConfig,
|
|
190
|
+
site: SiteDescriptor,
|
|
191
|
+
onStep?: (step: { key: ReferenceStep; done: number; total?: number; detail?: string }) => void
|
|
192
|
+
): Promise<ReferenceMaster>
|
|
193
|
+
/**
|
|
194
|
+
* (선택) 사이트별 실 이벤트 스트림 — 없으면 sim. onRecords 로 레코드 push, unsubscribe 반환.
|
|
195
|
+
*
|
|
196
|
+
* ── 네 번째 인자: **읽기 커서** (2026-08-23) ────────────────────────────────
|
|
197
|
+
*
|
|
198
|
+
* 왜 이 층이 커서를 드나: 커널이 그 갈림을 이미 적어 두었다(§`hydrateContinuity`) — 「원천이 **애초에
|
|
199
|
+
* 다시 말해 주지 않는 축**」은 재기동 연속성으로 이어받는다. **「우리가 어디까지 읽었나」가 정확히 그
|
|
200
|
+
* 성질이다.** 원본은 그것을 되풀어 주지 않는다.
|
|
201
|
+
*
|
|
202
|
+
* 커서가 없던 동안 어댑터는 붙을 때마다 `Date.now() − 되돌아볼 날수` 로 창을 새로 만들었다. 그래서
|
|
203
|
+
* **재기동마다 미러의 과거가 잘렸다** — 실측으로 작업 2,855 → 2,820(8시간 흐른 만큼). 미러가 아는
|
|
204
|
+
* 것이 「원본의 사실」이 아니라 「창의 함수」였다.
|
|
205
|
+
*
|
|
206
|
+
* 그런데 미러의 상태는 **원천으로 서야 한다**(같은 절: 「미러의 진실은 원천이다 … 심으면 떠난 물건이
|
|
207
|
+
* 되살아난다」). 그러니 저널을 접어 상태를 세우는 것으로 고칠 일이 아니다 — 그 선언의 **전제**가
|
|
208
|
+
* 「원천이 다시 말해 준다」이고, 창이 좁아지면 그 전제가 깨진다. 창을 고치는 것이 그 원칙을 지키는 길이다.
|
|
209
|
+
*
|
|
210
|
+
* ── 계약의 모양 ─────────────────────────────────────────────────────────────
|
|
211
|
+
* · **흐름 열쇠는 어댑터가 정한다.** 원본마다 흐름 수와 뜻이 다르다(이 원본은 재고·오더·로트·투입 넷).
|
|
212
|
+
* 계약이 이름을 닫으면 새 원본마다 계약을 고치게 된다 — 원본의 스키마가 계약을 끌고 가는 그 모양이다.
|
|
213
|
+
* · **저장되는 모양으로 넘긴다**(`seen` 은 배열). 이 층은 저장·복원만 하고 안을 해석하지 않는다.
|
|
214
|
+
* `Set` 을 받으면 이 층이 직렬화를 알게 되고, 커서 모양이 바뀔 때 두 곳을 고친다.
|
|
215
|
+
* · `firstAttachedAt` — **첫 창을 만든 시각.** 이후 재기동에서 바뀌지 않는다. 커널은 아는 구간의
|
|
216
|
+
* 상한만 안다(`nowTime` = 마지막으로 들은 시각). 이 값이 하한이고, 둘이 「이 트윈이 아는 구간」이다.
|
|
217
|
+
* 화면이 수를 보일 때 그 구간을 함께 말해야 한다 — 말하지 않으면 조용히 자르는 것이 된다.
|
|
218
|
+
* · **커서가 없을 때만** 되돌아볼 날수를 쓴다. 그 순간이 `firstAttachedAt` 이다.
|
|
219
|
+
*
|
|
220
|
+
* 주지 않는 호출자와도 함께 선다(선택 인자) — 커서를 모르는 배포는 예전처럼 동작한다.
|
|
221
|
+
*/
|
|
222
|
+
openLiveFeed?(
|
|
223
|
+
cfg: ConnectionConfig,
|
|
224
|
+
site: SiteDescriptor,
|
|
225
|
+
onRecords: (records: unknown[]) => void,
|
|
226
|
+
continuity?: LiveFeedContinuity
|
|
227
|
+
): () => void
|
|
118
228
|
/**
|
|
119
229
|
* 이 원본이 **선언하는 능력** — 없으면 「구조만 준다」는 뜻이다(ADR-0029 §8).
|
|
120
230
|
*
|
|
@@ -122,6 +232,28 @@ export interface ReferenceAdapter {
|
|
|
122
232
|
* 구현이 없으면 그 능력은 **없는 것으로 읽는다**(선언만 있는 능력은 사용자에게 거짓말이 된다).
|
|
123
233
|
*/
|
|
124
234
|
capabilities?: ReferenceCapability[]
|
|
235
|
+
/**
|
|
236
|
+
* 이 커넥터가 **무엇에 대고 확인됐나** — 능력과 다른 축이다(2026-08-23).
|
|
237
|
+
*
|
|
238
|
+
* ── 왜 필요한가 ─────────────────────────────────────────────────────────────
|
|
239
|
+
* `capabilities` 는 「무엇을 할 수 있다고 선언하나」이고, 이 자리는 **「그 선언이 무엇으로 확인됐나」**다.
|
|
240
|
+
* 둘이 갈리는 것이 지금 상태다: 등록된 커넥터 열넷 중 실 시스템에 붙어 본 것은 **하나**이고, 나머지는
|
|
241
|
+
* 목(mock)에 대고 만들었다. 그런데 화면의 커넥터 목록에 그 구분이 없어서 **목으로 확인한 것이 확인으로
|
|
242
|
+
* 보인다.**
|
|
243
|
+
*
|
|
244
|
+
* 그것은 이 저장소가 다른 자리에서 계속 거절하는 모양이다 — 「선언만 있는 능력은 사용자에게
|
|
245
|
+
* 거짓말이 된다」(§`capabilitiesOf`). 능력에 세운 그 규율을 근거에도 세운다.
|
|
246
|
+
*
|
|
247
|
+
* ── 왜 계약이고 배포 노트가 아닌가 ──────────────────────────────────────────
|
|
248
|
+
* 노트는 다음 사람이 보지 않는다. 계약에 있으면 새 커넥터가 그 값을 **빠뜨릴 수 없고**, 빠뜨리면
|
|
249
|
+
* `groundingOf` 가 `unknown` 으로 답해 화면이 그 사실을 말한다.
|
|
250
|
+
*
|
|
251
|
+
* ── 자동으로 알 수 없는 값이다 ──────────────────────────────────────────────
|
|
252
|
+
* 능력은 구현이 있는지 보면 알 수 있다. 「실 시스템에 붙여 봤나」는 **볼 수 없다** — 사람이 아는 사실이다.
|
|
253
|
+
* 그래서 이 선언은 낡을 수 있고, 그것을 막는 것이 `verifiedAgainst` 다: **무엇에 대고 확인했는지 적지
|
|
254
|
+
* 않으면 그 주장은 주장이 아니다**(`groundingOf` 가 `unknown` 으로 내린다).
|
|
255
|
+
*/
|
|
256
|
+
grounding?: ReferenceGrounding
|
|
125
257
|
/**
|
|
126
258
|
* (선택) 구동 — 자극·속도·정지·초기화. **`capabilities` 에 `control` 을 선언한 원본만** 가진다.
|
|
127
259
|
*
|
|
@@ -136,6 +268,59 @@ export interface ReferenceAdapter {
|
|
|
136
268
|
* 선언만 있고 구현이 없으면 화면이 「할 수 있다」고 말한 뒤 아무 일도 일어나지 않는다. 반대로 구현이
|
|
137
269
|
* 있는데 선언이 없으면 그 능력은 **의도적으로 닫아 둔 것**이므로 열지 않는다 — 선언이 권위다.
|
|
138
270
|
*/
|
|
271
|
+
/**
|
|
272
|
+
* 근거 수준 — **무엇에 대고 확인했나.** 넷이고 닫혀 있다.
|
|
273
|
+
*
|
|
274
|
+
* `synthetic` 실 시스템이 아니다 — 스스로 만들어 낸다(가상 원본·시뮬레이터). 한계가 아니라 성질이다.
|
|
275
|
+
* `mock` 목·목 REST 에 대고만 확인했다. **실 시스템에 붙어 본 적이 없다.**
|
|
276
|
+
* `instance` 실 시스템 **한 배포**에 붙여 확인했다. 그 배포의 성질과 제품의 성질을 아직 가르지 못했다.
|
|
277
|
+
* `product` 실 시스템 **여러 배포**에 붙여 확인했다 — 그때 비로소 제품의 성질이라 말할 수 있다.
|
|
278
|
+
*
|
|
279
|
+
* `instance` 와 `product` 를 가르는 이유: 첫 실 연동에서 그 배포만의 성질이 넷 드러났고(관계 리솔버가
|
|
280
|
+
* 다른 테넌트의 행을 답한다 · 모르는 필터를 조용히 무시한다 · 지워진 품목의 BOM 이 남아 있다 ·
|
|
281
|
+
* 사실상 무한을 뜻하는 표식) **그것이 그 제품의 성질인지 그 인스턴스의 성질인지 아무도 모른다.**
|
|
282
|
+
* 두 번째 배포가 붙는 날 갈린다. 그 사실을 어휘가 담는다.
|
|
283
|
+
*/
|
|
284
|
+
export type ReferenceGroundingLevel = 'synthetic' | 'mock' | 'instance' | 'product'
|
|
285
|
+
|
|
286
|
+
export interface ReferenceGrounding {
|
|
287
|
+
level: ReferenceGroundingLevel
|
|
288
|
+
/**
|
|
289
|
+
* **무엇에 대고 확인했나** — 사람이 읽는 한 줄(원본의 판·인스턴스·목의 이름).
|
|
290
|
+
*
|
|
291
|
+
* `instance`·`product` 를 주장하면서 이것이 비어 있으면 `groundingOf` 가 `unknown` 으로 내린다 —
|
|
292
|
+
* 가리키는 대상이 없는 주장은 주장이 아니고, 그런 주장은 낡아도 아무도 알 수 없다.
|
|
293
|
+
*/
|
|
294
|
+
verifiedAgainst?: string
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* 이 커넥터의 근거 — **주장을 그대로 믿지 않는다**(`capabilitiesOf` 와 같은 규율).
|
|
299
|
+
*
|
|
300
|
+
* 능력은 구현이 있는지 보아 선언을 검증할 수 있다. 근거는 **볼 수 없다** — 사람이 아는 사실이다.
|
|
301
|
+
* 그래서 검증할 수 있는 것 하나만 검증한다: **실 시스템을 주장하면 무엇에 대고인지 말해야 한다.**
|
|
302
|
+
*
|
|
303
|
+
* 선언이 없으면 `unknown` 이다 — 「목이다」가 아니라 **「모른다」**다. 둘을 같게 두면 아직 선언하지 않은
|
|
304
|
+
* 새 커넥터가 목으로 낙인찍히고, 그것도 없는 사실이다.
|
|
305
|
+
*/
|
|
306
|
+
export function groundingOf(adapter: ReferenceAdapter | undefined): {
|
|
307
|
+
level: ReferenceGroundingLevel | 'unknown'
|
|
308
|
+
verifiedAgainst?: string
|
|
309
|
+
/** 주장이 내려갔으면 그 이유 — 화면이 그대로 옮긴다. */
|
|
310
|
+
downgraded?: string
|
|
311
|
+
} {
|
|
312
|
+
const g = adapter?.grounding
|
|
313
|
+
if (!g?.level) return { level: 'unknown' }
|
|
314
|
+
const against = g.verifiedAgainst?.trim()
|
|
315
|
+
if ((g.level === 'instance' || g.level === 'product') && !against) {
|
|
316
|
+
return {
|
|
317
|
+
level: 'unknown',
|
|
318
|
+
downgraded: `claims "${g.level}" but does not say what it was verified against`
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return { level: g.level, ...(against ? { verifiedAgainst: against } : {}) }
|
|
322
|
+
}
|
|
323
|
+
|
|
139
324
|
export function capabilitiesOf(adapter: ReferenceAdapter | undefined): ReferenceCapability[] {
|
|
140
325
|
if (!adapter) return []
|
|
141
326
|
const declared = new Set(adapter.capabilities ?? [])
|
|
@@ -158,6 +343,13 @@ export function listAdapterTypes(): string[] {
|
|
|
158
343
|
return [...adapters.keys()]
|
|
159
344
|
}
|
|
160
345
|
/** 커넥터 메타데이터 목록(picker·연결 폼용). */
|
|
161
|
-
|
|
162
|
-
|
|
346
|
+
/**
|
|
347
|
+
* 등록된 어댑터 목록 — **선언한 능력도 함께 낸다.**
|
|
348
|
+
*
|
|
349
|
+
* 예전에는 `{ type, meta }` 만 냈습니다. 그런데 소비처(어댑터 목록 질의)가 `capabilities` 를 함께
|
|
350
|
+
* 내보내고 있어 타입검사가 막혔고, 그 상태로는 개발 서버 빌드가 실패합니다. 능력은 이 목록을 읽는
|
|
351
|
+
* 화면이 「이 원본에 무엇을 요구할 수 있나」를 판단하는 근거이므로, 빼지 않고 여기서 함께 냅니다.
|
|
352
|
+
*/
|
|
353
|
+
export function listAdapters(): { type: string; meta?: AdapterMeta; capabilities?: ReferenceCapability[] }[] {
|
|
354
|
+
return [...adapters.values()].map(a => ({ type: a.type, meta: a.meta, capabilities: a.capabilities }))
|
|
163
355
|
}
|