@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
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* **원본의 표면 중 얼마를 분류했나** — 순수(import 없음).
|
|
4
|
+
*
|
|
5
|
+
* ── 왜 이 축이 생겼나 (2026-08-20) ──────────────────────────────────────────
|
|
6
|
+
* 탐침 열한 개는 「이 단계가 되나」를 묻는다. `live-ingest` 는 **레코드 한 건이 통과하면 초록**이다.
|
|
7
|
+
* 그래서 재고만 읽는 커넥터에도 「라이브 통과」가 나왔고, 그 사이 원본의 `production_input`(무엇으로
|
|
8
|
+
* 무엇을 만들었나 · 실제 투입량 · 선언된 필요량)과 오더 네 종류와 팔레트가 읽히지 않고 있었다.
|
|
9
|
+
*
|
|
10
|
+
* 그것들은 대화에서 하나씩 「아차」로 드러났다. 그 방식은 반복된다 — 빠진 것을 기억으로 찾기 때문이다.
|
|
11
|
+
* 그래서 축을 하나 더 둔다: **원본이 내놓는 표면을 세고, 그 각각을 커넥터가 무엇으로 처리하는지 적는다.**
|
|
12
|
+
*
|
|
13
|
+
* ── 규율 ────────────────────────────────────────────────────────────────────
|
|
14
|
+
* 표면 목록은 **원본에서 얻는다**(GraphQL 인트로스펙션 등). 커넥터가 손으로 적은 목록으로 세면 같은
|
|
15
|
+
* 눈먼 자리를 그대로 물려받는다 — 내가 잊은 표는 목록에도 없다.
|
|
16
|
+
*
|
|
17
|
+
* 「읽지 않는다」는 결론은 **정당할 수 있다.** 파생 표(집계·추적)나 우리 어휘에 자리가 없는 것을 읽지
|
|
18
|
+
* 않는 것은 결함이 아니다. 결함은 **분류하지 않은 것**이다 — 그것은 생각한 적이 없다는 뜻이다.
|
|
19
|
+
*
|
|
20
|
+
* 그래서 판정은 「전부 읽었나」가 아니라 **「전부 판단했나」** 다.
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.assessCoverage = assessCoverage;
|
|
24
|
+
exports.coverageLine = coverageLine;
|
|
25
|
+
const DISPOSITIONS = ['read', 'derived', 'duplicate', 'no-home', 'out-of-scope', 'blocked', 'todo'];
|
|
26
|
+
function assessCoverage(input) {
|
|
27
|
+
const surface = [...new Set(input.sourceSurface)].sort();
|
|
28
|
+
const byDisposition = DISPOSITIONS.reduce((acc, d) => ((acc[d] = []), acc), {});
|
|
29
|
+
const unclassified = [];
|
|
30
|
+
for (const name of surface) {
|
|
31
|
+
const r = input.rulings[name];
|
|
32
|
+
/* 이유가 없으면 분류로 세지 않는다 — 낱말만 채운 것을 판단으로 세면 이 축이 무의미해진다. */
|
|
33
|
+
if (!r || !DISPOSITIONS.includes(r.disposition) || !r.why?.trim()) {
|
|
34
|
+
unclassified.push(name);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
byDisposition[r.disposition].push(name);
|
|
38
|
+
}
|
|
39
|
+
const known = new Set(surface);
|
|
40
|
+
const stale = Object.keys(input.rulings)
|
|
41
|
+
.filter(k => !known.has(k))
|
|
42
|
+
.sort();
|
|
43
|
+
return {
|
|
44
|
+
total: surface.length,
|
|
45
|
+
classified: surface.length - unclassified.length,
|
|
46
|
+
unclassified,
|
|
47
|
+
stale,
|
|
48
|
+
byDisposition,
|
|
49
|
+
todo: byDisposition.todo,
|
|
50
|
+
blocked: byDisposition.blocked
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 한 줄 요약 — 화면과 로그가 같은 문장을 쓰도록.
|
|
55
|
+
*
|
|
56
|
+
* **「전부 읽었다」고 말하지 않는다.** 말하는 것은 「전부 판단했다」다. 그리고 분류하지 않은 것이 남아
|
|
57
|
+
* 있으면 그 수를 앞세운다 — 그것이 이 축의 유일한 목적이다.
|
|
58
|
+
*/
|
|
59
|
+
function coverageLine(r) {
|
|
60
|
+
if (r.unclassified.length) {
|
|
61
|
+
const head = r.unclassified.slice(0, 5).join(', ');
|
|
62
|
+
const more = r.unclassified.length > 5 ? ` +${r.unclassified.length - 5}` : '';
|
|
63
|
+
return `${r.unclassified.length}/${r.total} surface(s) not yet judged — ${head}${more}`;
|
|
64
|
+
}
|
|
65
|
+
const read = r.byDisposition.read.length;
|
|
66
|
+
const parts = [`${read} read`];
|
|
67
|
+
if (r.todo.length)
|
|
68
|
+
parts.push(`${r.todo.length} still to carry`);
|
|
69
|
+
if (r.blocked.length)
|
|
70
|
+
parts.push(`${r.blocked.length} blocked at the source`);
|
|
71
|
+
return `all ${r.total} surface(s) judged; ${parts.join(', ')}`;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=integration-coverage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration-coverage.js","sourceRoot":"","sources":["../../server/engine/integration-coverage.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * **원본의 표면 중 얼마를 분류했나** — 순수(import 없음).\n *\n * ── 왜 이 축이 생겼나 (2026-08-20) ──────────────────────────────────────────\n * 탐침 열한 개는 「이 단계가 되나」를 묻는다. `live-ingest` 는 **레코드 한 건이 통과하면 초록**이다.\n * 그래서 재고만 읽는 커넥터에도 「라이브 통과」가 나왔고, 그 사이 원본의 `production_input`(무엇으로\n * 무엇을 만들었나 · 실제 투입량 · 선언된 필요량)과 오더 네 종류와 팔레트가 읽히지 않고 있었다.\n *\n * 그것들은 대화에서 하나씩 「아차」로 드러났다. 그 방식은 반복된다 — 빠진 것을 기억으로 찾기 때문이다.\n * 그래서 축을 하나 더 둔다: **원본이 내놓는 표면을 세고, 그 각각을 커넥터가 무엇으로 처리하는지 적는다.**\n *\n * ── 규율 ────────────────────────────────────────────────────────────────────\n * 표면 목록은 **원본에서 얻는다**(GraphQL 인트로스펙션 등). 커넥터가 손으로 적은 목록으로 세면 같은\n * 눈먼 자리를 그대로 물려받는다 — 내가 잊은 표는 목록에도 없다.\n *\n * 「읽지 않는다」는 결론은 **정당할 수 있다.** 파생 표(집계·추적)나 우리 어휘에 자리가 없는 것을 읽지\n * 않는 것은 결함이 아니다. 결함은 **분류하지 않은 것**이다 — 그것은 생각한 적이 없다는 뜻이다.\n *\n * 그래서 판정은 「전부 읽었나」가 아니라 **「전부 판단했나」** 다.\n */\n\n/**\n * 표면 하나를 커넥터가 무엇으로 처리하는가 — **각 낱말에 적용 가능한 기준과 조치할 사람이 있다.**\n * 기준이 없으면 분류가 느낌이 되고, 조치자가 없으면 표를 읽어도 누가 움직일지 알 수 없다.\n *\n * · `read` — **지금 커넥터가 질의한다.** 기준: 커넥터 코드에 그 질의 이름이 있다(확인 가능).\n * 조치자: 없음(완료분).\n *\n * · `todo` — **실려야 하는 사실인데 아직 안 읽는다.** 기준 셋을 다 만족: ① 원본에 읽을 길이\n * 있다 ② 우리 어휘에 담을 자리가 있다 ③ 아직 안 읽는다. 조치자: **커넥터**.\n *\n * · `blocked` — **실려야 하고 담을 자리도 있는데 원본이 읽을 길을 주지 않는다.** 기준: 목록 질의가\n * 없거나, 있어도 필수 인자 때문에 전역으로 읽을 수 없다. 조치자: **원본 쪽**.\n * `todo` 와 한 칸에 두지 않는 이유가 이것이다 — 움직여야 하는 사람이 다르다.\n *\n * · `no-home` — **원본에 길은 있는데 그 사실이 상태로 남을 자리가 없다.** 기준은 어휘가 아니라\n * **상태에 반영하는 것**이다 — 이벤트 어휘에 통과할 자리가 있어도 상태에 반영되지\n * 않으면 사실은 남지\n * 않는다(오히려 더 나쁘다: 검증을 통과하므로 실은 줄 알고 잃는다).\n * 조치자: **커널·아키텍처** — 반영할 것인지 판단해야 한다.\n * 냉장 온도가 그 예다. EPCIS `SensorElement`·`sensorReport` 는 이미 있고 검증기도\n * 본다. **소비하는 곳이 0 이다.** 그러므로 「어휘에 자리가 없다」가 아니라 「상태에\n * 반영하는 곳이 없다」가 정확하고, 할 일도 자리 만들기가 아니라 그 반영이다.\n * **「중요하지 않다」가 아니다.** 오히려 중요한데 못 담는다는 뜻이고, 그래서 판단이\n * 필요하다. 냉장 온도가 그 예다 — 식품에서 일급 사실인데 계량 어휘가 에너지 전용이다.\n *\n * · `derived` — **원본이 다른 표에서 계산해 내놓는 값.** 기준: 그 질의의 결과가 우리가 읽는(또는\n * 읽을) 사실로부터 계산된다. 읽으면 **같은 수를 두 곳에서 만들게 되고**, 두 수가\n * 갈리면 어느 것을 믿을지 알 수 없다. 조치자: 없음 — 읽지 않는 것이 정답.\n *\n * · `duplicate` — **같은 사실을 다른 문으로 내놓는 것.** 기준: 목록 질의로 이미 읽는(또는 읽을) 그\n * 사실의 단건 드릴다운이다. 계산된 값이 아니므로 `derived` 와 다르고, 관심 밖이\n * 아니므로 `out-of-scope` 와도 다르다. 조치자: 없음.\n *\n * · `out-of-scope` — **공장의 상태나 활동이 아니다.** 기준: 그 사실이 자리·물건·설비·사람·작업·\n * 오더 어디에도 해당하지 않는다(사용자 계정·세무·거래처 마스터). 조치자: 없음.\n *\n * 「읽지 않는다」는 결론은 정당할 수 있다(`derived`·`duplicate`·`out-of-scope`). 결함은 **분류하지\n * 않은 것**이다 — 그것은 생각한 적이 없다는 뜻이다. 그래서 판정은 「전부 읽었나」가 아니라\n * **「전부 판단했나」** 다.\n */\nexport type Disposition = 'read' | 'derived' | 'duplicate' | 'no-home' | 'out-of-scope' | 'blocked' | 'todo'\n\n/** 표면 하나에 대한 판단 — **이유가 필수다.** 이유 없는 분류는 분류한 척이다. */\nexport interface SurfaceRuling {\n disposition: Disposition\n /** 왜 그렇게 정했나. 사람이 읽는 한 문장. */\n why: string\n}\n\nexport interface CoverageInput {\n /** 원본이 실제로 내놓는 표면 — **원본에서 얻은 것.** 커넥터가 적은 목록이 아니다. */\n sourceSurface: readonly string[]\n /** 커넥터의 판단표. */\n rulings: Readonly<Record<string, SurfaceRuling>>\n}\n\nexport interface CoverageReport {\n /** 원본 표면 수. */\n total: number\n /** 분류된 수. */\n classified: number\n /** **분류하지 않은 표면** — 생각한 적 없는 것. 이 목록이 비어야 한다. */\n unclassified: string[]\n /** 판단표에는 있는데 원본에 없는 것 — 원본이 바뀌었거나 이름을 잘못 적었다. */\n stale: string[]\n byDisposition: Record<Disposition, string[]>\n /** 실어야 하는데 아직 아닌 것. 초록을 막지는 않지만 **세어 보여야** 한다. */\n todo: string[]\n /** 원본이 길을 주지 않아 막힌 것 — 이 목록이 곧 **원본 쪽 선결 과제**다. */\n blocked: string[]\n}\n\nconst DISPOSITIONS: Disposition[] = ['read', 'derived', 'duplicate', 'no-home', 'out-of-scope', 'blocked', 'todo']\n\nexport function assessCoverage(input: CoverageInput): CoverageReport {\n const surface = [...new Set(input.sourceSurface)].sort()\n const byDisposition = DISPOSITIONS.reduce(\n (acc, d) => ((acc[d] = []), acc),\n {} as Record<Disposition, string[]>\n )\n const unclassified: string[] = []\n\n for (const name of surface) {\n const r = input.rulings[name]\n /* 이유가 없으면 분류로 세지 않는다 — 낱말만 채운 것을 판단으로 세면 이 축이 무의미해진다. */\n if (!r || !DISPOSITIONS.includes(r.disposition) || !r.why?.trim()) {\n unclassified.push(name)\n continue\n }\n byDisposition[r.disposition].push(name)\n }\n\n const known = new Set(surface)\n const stale = Object.keys(input.rulings)\n .filter(k => !known.has(k))\n .sort()\n\n return {\n total: surface.length,\n classified: surface.length - unclassified.length,\n unclassified,\n stale,\n byDisposition,\n todo: byDisposition.todo,\n blocked: byDisposition.blocked\n }\n}\n\n/**\n * 한 줄 요약 — 화면과 로그가 같은 문장을 쓰도록.\n *\n * **「전부 읽었다」고 말하지 않는다.** 말하는 것은 「전부 판단했다」다. 그리고 분류하지 않은 것이 남아\n * 있으면 그 수를 앞세운다 — 그것이 이 축의 유일한 목적이다.\n */\nexport function coverageLine(r: CoverageReport): string {\n if (r.unclassified.length) {\n const head = r.unclassified.slice(0, 5).join(', ')\n const more = r.unclassified.length > 5 ? ` +${r.unclassified.length - 5}` : ''\n return `${r.unclassified.length}/${r.total} surface(s) not yet judged — ${head}${more}`\n }\n const read = r.byDisposition.read.length\n const parts = [`${read} read`]\n if (r.todo.length) parts.push(`${r.todo.length} still to carry`)\n if (r.blocked.length) parts.push(`${r.blocked.length} blocked at the source`)\n return `all ${r.total} surface(s) judged; ${parts.join(', ')}`\n}\n"]}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { StageEvidence, StageId } from './stage-path.ts';
|
|
2
|
+
export type ProbeId = 'connect' | 'discover' | 'master' | 'master-warnings' | 'items' | 'live-ingest' | 'live-removal' | 'declarations' | 'forecast' | 'actuate-declared' | 'actuate-effect';
|
|
3
|
+
export interface ProbeSpec {
|
|
4
|
+
id: ProbeId;
|
|
5
|
+
/** 이 프로브가 증거를 대는 단계. */
|
|
6
|
+
stage: StageId;
|
|
7
|
+
/** **무엇을 증명하나** — 언어중립 키(사람 말은 표현계층이 렌더). 「무엇을 호출하나」가 아니다. */
|
|
8
|
+
proves: string;
|
|
9
|
+
/** 대상 시스템을 바꾸는가. 참이면 사람이 켜야 돈다. */
|
|
10
|
+
writes: boolean;
|
|
11
|
+
/** 이 프로브가 실패하면 뒤 프로브를 돌릴 의미가 없다. */
|
|
12
|
+
gating: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 시퀀스 — 순서가 곧 방법론이다. 앞이 막히면 뒤는 돌리지 않는다(돌려도 답이 무의미하다).
|
|
16
|
+
*
|
|
17
|
+
* `master-warnings` 를 **별개 프로브**로 둔 이유: 구조가 들어오는 것과 못 옮긴 값을 세는 것은 다른
|
|
18
|
+
* 사실이고, 세지 않은 인제스트는 도크를 랙으로 바꿔 놓고도 성공으로 보인다.
|
|
19
|
+
*/
|
|
20
|
+
export declare const PROBE_SEQUENCE: readonly ProbeSpec[];
|
|
21
|
+
/** 프로브 한 건의 결과. `ran: false` = 돌리지 않았다(실패가 아니다). */
|
|
22
|
+
export interface ProbeResult {
|
|
23
|
+
id: ProbeId;
|
|
24
|
+
ran: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* 이 대상에 **해당하지 않는다**. 통과도 실패도 미측정도 아닌 네 번째 값이다.
|
|
27
|
+
*
|
|
28
|
+
* 시뮬레이션 트윈에 라이브 프로브를 들이대면 빨간 칸이 생기는데 그건 결함이 아니다 — 시뮬은 원천에서
|
|
29
|
+
* 받는 것이 없다. 레퍼런스 없이 템플릿으로 세운 트윈에는 옮겨 온 마스터가 없으므로 「못 옮긴 값」도 없다.
|
|
30
|
+
* 이걸 통과로 접으면 없는 능력을 주장하고, 미측정으로 접으면 영원히 배선을 기다린다.
|
|
31
|
+
*/
|
|
32
|
+
applicable?: boolean;
|
|
33
|
+
/** 돌렸을 때의 판정. 돌리지 않았으면 무의미하다. */
|
|
34
|
+
passed?: boolean;
|
|
35
|
+
/** 센 값(자리 수·반출 건수·선언 수 등). 세지 않았으면 없다. */
|
|
36
|
+
count?: number;
|
|
37
|
+
/** 선언 적재량 프로브만 — `declared`/`total`. */
|
|
38
|
+
coverage?: {
|
|
39
|
+
declared: number;
|
|
40
|
+
total: number;
|
|
41
|
+
};
|
|
42
|
+
/** 왜 실패했나·왜 건너뛰었나. 언어중립 코드 또는 원문 오류. */
|
|
43
|
+
detail?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 프로브 결과를 **증거**로 접는다.
|
|
47
|
+
*
|
|
48
|
+
* 접는 규칙의 정수: **돌리지 않은 프로브는 `null`** 로 간다. 그러면 판정이 그 단계를 달성으로
|
|
49
|
+
* 만들지 않고 `unmeasured` 로 말한다 — 스위트를 절반만 돌리고 초록을 주장할 길이 없다.
|
|
50
|
+
*/
|
|
51
|
+
export declare function foldProbeResults(results: readonly ProbeResult[]): StageEvidence;
|
|
52
|
+
/**
|
|
53
|
+
* 지금 돌릴 프로브 — 게이팅과 쓰기 동의를 지킨다.
|
|
54
|
+
*
|
|
55
|
+
* 앞의 게이팅 프로브가 실패했으면 **거기서 멈춘다.** 뒤를 돌려 빨간 칸을 늘리는 것은 정보를 주지
|
|
56
|
+
* 않는다(원인은 이미 앞에 있다). 쓰기 프로브는 `allowWrites` 없이는 목록에 들어오지 않는다.
|
|
57
|
+
*/
|
|
58
|
+
export declare function plannedProbes(results: readonly ProbeResult[], opts?: {
|
|
59
|
+
allowWrites?: boolean;
|
|
60
|
+
}): ProbeId[];
|
|
61
|
+
/** 해당 없음으로 판정된 프로브 — 화면이 「왜 회색인가」를 말할 수 있게 사유와 함께 낸다. */
|
|
62
|
+
export declare function notApplicable(results: readonly ProbeResult[]): {
|
|
63
|
+
id: ProbeId;
|
|
64
|
+
detail?: string;
|
|
65
|
+
}[];
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* 연결 적합성 하네스 — **통합을 시험 스위트처럼 돌린다.**
|
|
4
|
+
*
|
|
5
|
+
* 선언 적합성 하네스 세 벌과 같은 규율을 **연결**에 적용한다: 사실이 조용히 사라지는 것을 막는다.
|
|
6
|
+
* 다른 점은 대상이 우리 선언이 아니라 **남의 시스템**이라는 것이고, 그래서 두 규칙이 더 붙는다.
|
|
7
|
+
*
|
|
8
|
+
* ① **돌리지 않은 프로브는 「실패」가 아니라 「모른다」다.** 실패로 접으면 사람이 그 빨간 칸을
|
|
9
|
+
* 없애려고 무엇이든 하게 되고, 모른다로 접으면 **무엇을 아직 모르는지**가 남는다. 판정
|
|
10
|
+
* (`assessStagePath`)이 「모른다」를 달성으로 바꾸지 않으므로 이 구분이 끝까지 살아 있다.
|
|
11
|
+
* ② **쓰기 프로브는 명시 동의 없이 돌지 않는다.** 이 스위트는 고객의 운영 시스템을 두드린다.
|
|
12
|
+
* 읽기는 기본으로 돌리고, 남의 공장에 명령을 넣는 프로브는 사람이 켜야 돈다 — 켜지 않으면
|
|
13
|
+
* 폐루프 칸은 「검증되지 않음」으로 남는다(그게 사실이다).
|
|
14
|
+
*
|
|
15
|
+
* 순수 모듈이다(전송·DB 무의존). 실행기는 이 선언을 읽어 프로브를 돌리고 `ProbeResult[]` 를 낸다.
|
|
16
|
+
* 화면은 그 결과와 `assessStagePath` 의 판정을 함께 보인다 — 초록/빨강과 **그 초록이 무엇을 증명하나**.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.PROBE_SEQUENCE = void 0;
|
|
20
|
+
exports.foldProbeResults = foldProbeResults;
|
|
21
|
+
exports.plannedProbes = plannedProbes;
|
|
22
|
+
exports.notApplicable = notApplicable;
|
|
23
|
+
/**
|
|
24
|
+
* 시퀀스 — 순서가 곧 방법론이다. 앞이 막히면 뒤는 돌리지 않는다(돌려도 답이 무의미하다).
|
|
25
|
+
*
|
|
26
|
+
* `master-warnings` 를 **별개 프로브**로 둔 이유: 구조가 들어오는 것과 못 옮긴 값을 세는 것은 다른
|
|
27
|
+
* 사실이고, 세지 않은 인제스트는 도크를 랙으로 바꿔 놓고도 성공으로 보인다.
|
|
28
|
+
*/
|
|
29
|
+
exports.PROBE_SEQUENCE = [
|
|
30
|
+
{ id: 'connect', stage: 'structure', proves: 'probe.connect', writes: false, gating: true },
|
|
31
|
+
{ id: 'discover', stage: 'structure', proves: 'probe.discover', writes: false, gating: true },
|
|
32
|
+
{ id: 'master', stage: 'structure', proves: 'probe.master', writes: false, gating: true },
|
|
33
|
+
{ id: 'master-warnings', stage: 'structure', proves: 'probe.masterWarnings', writes: false, gating: false },
|
|
34
|
+
{ id: 'items', stage: 'structure', proves: 'probe.items', writes: false, gating: false },
|
|
35
|
+
{ id: 'live-ingest', stage: 'live', proves: 'probe.liveIngest', writes: false, gating: true },
|
|
36
|
+
{ id: 'live-removal', stage: 'live', proves: 'probe.liveRemoval', writes: false, gating: false },
|
|
37
|
+
{ id: 'declarations', stage: 'forecast', proves: 'probe.declarations', writes: false, gating: false },
|
|
38
|
+
{ id: 'forecast', stage: 'forecast', proves: 'probe.forecast', writes: false, gating: false },
|
|
39
|
+
{ id: 'actuate-declared', stage: 'closed-loop', proves: 'probe.actuateDeclared', writes: false, gating: true },
|
|
40
|
+
{ id: 'actuate-effect', stage: 'closed-loop', proves: 'probe.actuateEffect', writes: true, gating: false }
|
|
41
|
+
];
|
|
42
|
+
const by = (rs, id) => rs.find(r => r.id === id);
|
|
43
|
+
/** 돌렸고 셌으면 그 수, 아니면 `null`(모른다). 실패한 프로브의 수를 0 으로 읽지 않는다. */
|
|
44
|
+
/**
|
|
45
|
+
* 센 값은 **판정과 무관한 사실**이다 — `passed` 를 요구하면 「세어서 0 을 얻었다」가 「모른다」로 접힌다.
|
|
46
|
+
* 그 구분이 이 하네스의 전부이므로 여기서 잃으면 나머지가 무의미하다. 해당 없음만 `null` 이다.
|
|
47
|
+
*/
|
|
48
|
+
const counted = (r) => r?.applicable === false ? null : r?.ran && r.count !== undefined ? r.count : null;
|
|
49
|
+
/** 돌렸고 셌으면 그 수, 아니면 0. **존재 여부**를 묻는 자리에만 쓴다(모름과 0 을 구별할 필요가 없는 축). */
|
|
50
|
+
const countedOrZero = (r) => counted(r) ?? 0;
|
|
51
|
+
/**
|
|
52
|
+
* 프로브 결과를 **증거**로 접는다.
|
|
53
|
+
*
|
|
54
|
+
* 접는 규칙의 정수: **돌리지 않은 프로브는 `null`** 로 간다. 그러면 판정이 그 단계를 달성으로
|
|
55
|
+
* 만들지 않고 `unmeasured` 로 말한다 — 스위트를 절반만 돌리고 초록을 주장할 길이 없다.
|
|
56
|
+
*/
|
|
57
|
+
function foldProbeResults(results) {
|
|
58
|
+
return {
|
|
59
|
+
locationCount: countedOrZero(by(results, 'master')),
|
|
60
|
+
itemCount: countedOrZero(by(results, 'items')),
|
|
61
|
+
masterWarnings: counted(by(results, 'master-warnings')),
|
|
62
|
+
/* 해당 없음은 **미측정이 아니다** — 물을 대상이 아닌 축을 판정에서 뺀다. */
|
|
63
|
+
masterWarningsApplicable: by(results, 'master-warnings')?.applicable !== false,
|
|
64
|
+
liveIngestTotal: countedOrZero(by(results, 'live-ingest')),
|
|
65
|
+
removalsObserved: counted(by(results, 'live-removal')),
|
|
66
|
+
forecastRan: by(results, 'forecast')?.ran === true && by(results, 'forecast')?.passed === true,
|
|
67
|
+
specCoverage: (() => {
|
|
68
|
+
const r = by(results, 'declarations');
|
|
69
|
+
return r?.ran && r.passed && r.coverage ? r.coverage : null;
|
|
70
|
+
})(),
|
|
71
|
+
actuateCapability: by(results, 'actuate-declared')?.passed === true,
|
|
72
|
+
correlatedEffectObserved: counted(by(results, 'actuate-effect'))
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* 지금 돌릴 프로브 — 게이팅과 쓰기 동의를 지킨다.
|
|
77
|
+
*
|
|
78
|
+
* 앞의 게이팅 프로브가 실패했으면 **거기서 멈춘다.** 뒤를 돌려 빨간 칸을 늘리는 것은 정보를 주지
|
|
79
|
+
* 않는다(원인은 이미 앞에 있다). 쓰기 프로브는 `allowWrites` 없이는 목록에 들어오지 않는다.
|
|
80
|
+
*/
|
|
81
|
+
function plannedProbes(results, opts = {}) {
|
|
82
|
+
const out = [];
|
|
83
|
+
for (const spec of exports.PROBE_SEQUENCE) {
|
|
84
|
+
if (spec.writes && !opts.allowWrites)
|
|
85
|
+
continue;
|
|
86
|
+
const prior = by(results, spec.id);
|
|
87
|
+
if (prior?.applicable === false)
|
|
88
|
+
continue; // 해당 없는 프로브는 계획하지 않는다
|
|
89
|
+
if (!prior?.ran)
|
|
90
|
+
out.push(spec.id);
|
|
91
|
+
if (spec.gating && prior?.ran && prior.passed === false)
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
/** 해당 없음으로 판정된 프로브 — 화면이 「왜 회색인가」를 말할 수 있게 사유와 함께 낸다. */
|
|
97
|
+
function notApplicable(results) {
|
|
98
|
+
return results.filter(r => r.applicable === false).map(r => ({ id: r.id, ...(r.detail ? { detail: r.detail } : {}) }));
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=integration-probes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration-probes.js","sourceRoot":"","sources":["../../server/engine/integration-probes.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 연결 적합성 하네스 — **통합을 시험 스위트처럼 돌린다.**\n *\n * 선언 적합성 하네스 세 벌과 같은 규율을 **연결**에 적용한다: 사실이 조용히 사라지는 것을 막는다.\n * 다른 점은 대상이 우리 선언이 아니라 **남의 시스템**이라는 것이고, 그래서 두 규칙이 더 붙는다.\n *\n * ① **돌리지 않은 프로브는 「실패」가 아니라 「모른다」다.** 실패로 접으면 사람이 그 빨간 칸을\n * 없애려고 무엇이든 하게 되고, 모른다로 접으면 **무엇을 아직 모르는지**가 남는다. 판정\n * (`assessStagePath`)이 「모른다」를 달성으로 바꾸지 않으므로 이 구분이 끝까지 살아 있다.\n * ② **쓰기 프로브는 명시 동의 없이 돌지 않는다.** 이 스위트는 고객의 운영 시스템을 두드린다.\n * 읽기는 기본으로 돌리고, 남의 공장에 명령을 넣는 프로브는 사람이 켜야 돈다 — 켜지 않으면\n * 폐루프 칸은 「검증되지 않음」으로 남는다(그게 사실이다).\n *\n * 순수 모듈이다(전송·DB 무의존). 실행기는 이 선언을 읽어 프로브를 돌리고 `ProbeResult[]` 를 낸다.\n * 화면은 그 결과와 `assessStagePath` 의 판정을 함께 보인다 — 초록/빨강과 **그 초록이 무엇을 증명하나**.\n */\n\nimport type { StageEvidence, StageId } from './stage-path.ts'\n\nexport type ProbeId =\n | 'connect' // 붙는다\n | 'discover' // 사이트가 열거된다\n | 'master' // 구조가 들어온다\n | 'master-warnings' // 못 옮긴 값을 세었다\n | 'items' // 자리에 물품이 놓인다(비직렬 수량 포함)\n | 'live-ingest' // 이벤트가 들어온다\n | 'live-removal' // **반출이 관측된다** — 진짜 delta 의 증거\n | 'declarations' // 공정 선언이 실려 있다\n | 'forecast' // 예측이 돈다\n | 'actuate-declared' // 액추에이션 능력이 선언∩구현이다\n | 'actuate-effect' // 명령의 효과가 상관되어 돌아온다 (쓰기)\n\nexport interface ProbeSpec {\n id: ProbeId\n /** 이 프로브가 증거를 대는 단계. */\n stage: StageId\n /** **무엇을 증명하나** — 언어중립 키(사람 말은 표현계층이 렌더). 「무엇을 호출하나」가 아니다. */\n proves: string\n /** 대상 시스템을 바꾸는가. 참이면 사람이 켜야 돈다. */\n writes: boolean\n /** 이 프로브가 실패하면 뒤 프로브를 돌릴 의미가 없다. */\n gating: boolean\n}\n\n/**\n * 시퀀스 — 순서가 곧 방법론이다. 앞이 막히면 뒤는 돌리지 않는다(돌려도 답이 무의미하다).\n *\n * `master-warnings` 를 **별개 프로브**로 둔 이유: 구조가 들어오는 것과 못 옮긴 값을 세는 것은 다른\n * 사실이고, 세지 않은 인제스트는 도크를 랙으로 바꿔 놓고도 성공으로 보인다.\n */\nexport const PROBE_SEQUENCE: readonly ProbeSpec[] = [\n { id: 'connect', stage: 'structure', proves: 'probe.connect', writes: false, gating: true },\n { id: 'discover', stage: 'structure', proves: 'probe.discover', writes: false, gating: true },\n { id: 'master', stage: 'structure', proves: 'probe.master', writes: false, gating: true },\n { id: 'master-warnings', stage: 'structure', proves: 'probe.masterWarnings', writes: false, gating: false },\n { id: 'items', stage: 'structure', proves: 'probe.items', writes: false, gating: false },\n { id: 'live-ingest', stage: 'live', proves: 'probe.liveIngest', writes: false, gating: true },\n { id: 'live-removal', stage: 'live', proves: 'probe.liveRemoval', writes: false, gating: false },\n { id: 'declarations', stage: 'forecast', proves: 'probe.declarations', writes: false, gating: false },\n { id: 'forecast', stage: 'forecast', proves: 'probe.forecast', writes: false, gating: false },\n { id: 'actuate-declared', stage: 'closed-loop', proves: 'probe.actuateDeclared', writes: false, gating: true },\n { id: 'actuate-effect', stage: 'closed-loop', proves: 'probe.actuateEffect', writes: true, gating: false }\n]\n\n/** 프로브 한 건의 결과. `ran: false` = 돌리지 않았다(실패가 아니다). */\nexport interface ProbeResult {\n id: ProbeId\n ran: boolean\n /**\n * 이 대상에 **해당하지 않는다**. 통과도 실패도 미측정도 아닌 네 번째 값이다.\n *\n * 시뮬레이션 트윈에 라이브 프로브를 들이대면 빨간 칸이 생기는데 그건 결함이 아니다 — 시뮬은 원천에서\n * 받는 것이 없다. 레퍼런스 없이 템플릿으로 세운 트윈에는 옮겨 온 마스터가 없으므로 「못 옮긴 값」도 없다.\n * 이걸 통과로 접으면 없는 능력을 주장하고, 미측정으로 접으면 영원히 배선을 기다린다.\n */\n applicable?: boolean\n /** 돌렸을 때의 판정. 돌리지 않았으면 무의미하다. */\n passed?: boolean\n /** 센 값(자리 수·반출 건수·선언 수 등). 세지 않았으면 없다. */\n count?: number\n /** 선언 적재량 프로브만 — `declared`/`total`. */\n coverage?: { declared: number; total: number }\n /** 왜 실패했나·왜 건너뛰었나. 언어중립 코드 또는 원문 오류. */\n detail?: string\n}\n\nconst by = (rs: readonly ProbeResult[], id: ProbeId) => rs.find(r => r.id === id)\n/** 돌렸고 셌으면 그 수, 아니면 `null`(모른다). 실패한 프로브의 수를 0 으로 읽지 않는다. */\n/**\n * 센 값은 **판정과 무관한 사실**이다 — `passed` 를 요구하면 「세어서 0 을 얻었다」가 「모른다」로 접힌다.\n * 그 구분이 이 하네스의 전부이므로 여기서 잃으면 나머지가 무의미하다. 해당 없음만 `null` 이다.\n */\nconst counted = (r: ProbeResult | undefined): number | null =>\n r?.applicable === false ? null : r?.ran && r.count !== undefined ? r.count : null\n/** 돌렸고 셌으면 그 수, 아니면 0. **존재 여부**를 묻는 자리에만 쓴다(모름과 0 을 구별할 필요가 없는 축). */\nconst countedOrZero = (r: ProbeResult | undefined): number => counted(r) ?? 0\n\n/**\n * 프로브 결과를 **증거**로 접는다.\n *\n * 접는 규칙의 정수: **돌리지 않은 프로브는 `null`** 로 간다. 그러면 판정이 그 단계를 달성으로\n * 만들지 않고 `unmeasured` 로 말한다 — 스위트를 절반만 돌리고 초록을 주장할 길이 없다.\n */\nexport function foldProbeResults(results: readonly ProbeResult[]): StageEvidence {\n return {\n locationCount: countedOrZero(by(results, 'master')),\n itemCount: countedOrZero(by(results, 'items')),\n masterWarnings: counted(by(results, 'master-warnings')),\n /* 해당 없음은 **미측정이 아니다** — 물을 대상이 아닌 축을 판정에서 뺀다. */\n masterWarningsApplicable: by(results, 'master-warnings')?.applicable !== false,\n liveIngestTotal: countedOrZero(by(results, 'live-ingest')),\n removalsObserved: counted(by(results, 'live-removal')),\n forecastRan: by(results, 'forecast')?.ran === true && by(results, 'forecast')?.passed === true,\n specCoverage: (() => {\n const r = by(results, 'declarations')\n return r?.ran && r.passed && r.coverage ? r.coverage : null\n })(),\n actuateCapability: by(results, 'actuate-declared')?.passed === true,\n correlatedEffectObserved: counted(by(results, 'actuate-effect'))\n }\n}\n\n/**\n * 지금 돌릴 프로브 — 게이팅과 쓰기 동의를 지킨다.\n *\n * 앞의 게이팅 프로브가 실패했으면 **거기서 멈춘다.** 뒤를 돌려 빨간 칸을 늘리는 것은 정보를 주지\n * 않는다(원인은 이미 앞에 있다). 쓰기 프로브는 `allowWrites` 없이는 목록에 들어오지 않는다.\n */\nexport function plannedProbes(results: readonly ProbeResult[], opts: { allowWrites?: boolean } = {}): ProbeId[] {\n const out: ProbeId[] = []\n for (const spec of PROBE_SEQUENCE) {\n if (spec.writes && !opts.allowWrites) continue\n const prior = by(results, spec.id)\n if (prior?.applicable === false) continue // 해당 없는 프로브는 계획하지 않는다\n if (!prior?.ran) out.push(spec.id)\n if (spec.gating && prior?.ran && prior.passed === false) break\n }\n return out\n}\n\n/** 해당 없음으로 판정된 프로브 — 화면이 「왜 회색인가」를 말할 수 있게 사유와 함께 낸다. */\nexport function notApplicable(results: readonly ProbeResult[]): { id: ProbeId; detail?: string }[] {\n return results.filter(r => r.applicable === false).map(r => ({ id: r.id, ...(r.detail ? { detail: r.detail } : {}) }))\n}\n"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type ProbeId, type ProbeResult } from './integration-probes.ts';
|
|
2
|
+
import { type StagePath } from './stage-path.ts';
|
|
3
|
+
import { type FeedKind, type TargetProfile } from './integration-target-profile.ts';
|
|
4
|
+
/** 호출자가 모아 오는 사실. 전부 `null` 이 「모른다」다 — 0 이나 false 로 채우지 않는다. */
|
|
5
|
+
export interface SuiteFacts {
|
|
6
|
+
/** 이 트윈이 무엇인가. 시뮬은 원천에서 받는 것이 없다(라이브 프로브는 해당 없음). */
|
|
7
|
+
subject: 'live' | 'sim' | 'stopped' | 'unknown';
|
|
8
|
+
/** 레퍼런스(원천 어댑터)에서 왔나. 템플릿으로 세운 트윈은 옮겨 온 마스터가 없다. */
|
|
9
|
+
fromReference: boolean;
|
|
10
|
+
/** `capabilitiesOf(adapter)` — 선언∩구현. */
|
|
11
|
+
adapterCapabilities: readonly string[];
|
|
12
|
+
connectOk: boolean | null;
|
|
13
|
+
siteCount: number | null;
|
|
14
|
+
locationCount: number | null;
|
|
15
|
+
itemCount: number | null;
|
|
16
|
+
/** 마지막 동기의 못 옮긴 값 수(`TwinReference.lastWarningCount`). */
|
|
17
|
+
lastWarningCount: number | null;
|
|
18
|
+
/** 저널 누적 — 재시작에도 남는다(인메모리 계기와 다르다). */
|
|
19
|
+
journalTotal: number | null;
|
|
20
|
+
/** 저널의 `action='DELETE'` 수 — **반출 관측**. 승격 전 행은 세어지지 않으므로 `null` 이 정직하다. */
|
|
21
|
+
removalsJournaled: number | null;
|
|
22
|
+
specCoverage: {
|
|
23
|
+
declared: number;
|
|
24
|
+
total: number;
|
|
25
|
+
} | null;
|
|
26
|
+
forecastRan: boolean | null;
|
|
27
|
+
feed: FeedKind;
|
|
28
|
+
}
|
|
29
|
+
export interface SuiteAssessment {
|
|
30
|
+
results: ProbeResult[];
|
|
31
|
+
profile: TargetProfile;
|
|
32
|
+
path: StagePath;
|
|
33
|
+
notApplicable: {
|
|
34
|
+
id: ProbeId;
|
|
35
|
+
detail?: string;
|
|
36
|
+
}[];
|
|
37
|
+
forbiddenClaims: string[];
|
|
38
|
+
/** 다음에 돌릴 프로브 — 쓰기 동의 없이는 쓰기 프로브가 빠진다. */
|
|
39
|
+
planned: ProbeId[];
|
|
40
|
+
}
|
|
41
|
+
export declare function assessSuite(f: SuiteFacts, opts?: {
|
|
42
|
+
allowWrites?: boolean;
|
|
43
|
+
}): SuiteAssessment;
|
|
44
|
+
/** 시퀀스 명세 — 화면이 회색 칸의 이유(해당 없음·미실행)를 나란히 보이도록 함께 낸다. */
|
|
45
|
+
export declare const SUITE_SPEC: readonly import("./integration-probes.ts").ProbeSpec[];
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SUITE_SPEC = void 0;
|
|
4
|
+
exports.assessSuite = assessSuite;
|
|
5
|
+
/*
|
|
6
|
+
* 실행기 — 모은 사실을 프로브 결과로 옮기고, 단계 판정과 프로파일까지 한 번에 낸다.
|
|
7
|
+
*
|
|
8
|
+
* I/O 는 여기 없다(호출자가 모은다). 그래서 판정 전체가 테스트된다.
|
|
9
|
+
*/
|
|
10
|
+
const integration_probes_ts_1 = require("./integration-probes.js");
|
|
11
|
+
const stage_path_ts_1 = require("./stage-path.js");
|
|
12
|
+
const integration_target_profile_ts_1 = require("./integration-target-profile.js");
|
|
13
|
+
const r = (id, o) => ({ id, ran: false, ...o });
|
|
14
|
+
/** 값이 있으면 돌린 것으로 보고 판정한다. `null` = 안 돌렸다(실패 아님). */
|
|
15
|
+
const fromNumber = (id, v, min = 1) => v === null ? r(id, { ran: false }) : r(id, { ran: true, passed: v >= min, count: v });
|
|
16
|
+
function assessSuite(f, opts = {}) {
|
|
17
|
+
const liveApplies = f.subject !== 'sim';
|
|
18
|
+
const results = [
|
|
19
|
+
f.connectOk === null ? r('connect', { ran: false }) : r('connect', { ran: true, passed: f.connectOk }),
|
|
20
|
+
fromNumber('discover', f.siteCount),
|
|
21
|
+
fromNumber('master', f.locationCount),
|
|
22
|
+
/* 레퍼런스 없이 세운 트윈에는 옮겨 온 마스터가 없다 — 「못 옮긴 값」을 물을 대상이 아니다. */
|
|
23
|
+
f.fromReference
|
|
24
|
+
? fromNumber('master-warnings', f.lastWarningCount, 0)
|
|
25
|
+
: r('master-warnings', { ran: false, applicable: false, detail: 'not-from-reference' }),
|
|
26
|
+
fromNumber('items', f.itemCount),
|
|
27
|
+
liveApplies ? fromNumber('live-ingest', f.journalTotal) : r('live-ingest', { ran: false, applicable: false, detail: 'subject-is-simulation' }),
|
|
28
|
+
liveApplies ? fromNumber('live-removal', f.removalsJournaled, 1) : r('live-removal', { ran: false, applicable: false, detail: 'subject-is-simulation' }),
|
|
29
|
+
f.specCoverage === null
|
|
30
|
+
? r('declarations', { ran: false })
|
|
31
|
+
: r('declarations', { ran: true, passed: f.specCoverage.declared > 0, coverage: f.specCoverage }),
|
|
32
|
+
f.forecastRan === null ? r('forecast', { ran: false }) : r('forecast', { ran: true, passed: f.forecastRan }),
|
|
33
|
+
r('actuate-declared', { ran: true, passed: f.adapterCapabilities.includes('actuate') }),
|
|
34
|
+
/* 액추에이션은 계약에 아직 없다(ADR-0036). 측정을 만들면 없는 것을 있는 척한다. */
|
|
35
|
+
r('actuate-effect', { ran: false, detail: 'actuation-not-wired' })
|
|
36
|
+
];
|
|
37
|
+
/* 반출을 셌는데 0 이면 「세지 않음」과 구별해 프로브를 실패로 남긴다 — count 0 은 사실이다. */
|
|
38
|
+
const removal = results.find(x => x.id === 'live-removal');
|
|
39
|
+
if (removal.ran && f.removalsJournaled === 0)
|
|
40
|
+
removal.count = 0;
|
|
41
|
+
const profile = (0, integration_target_profile_ts_1.narrowProfile)((0, integration_target_profile_ts_1.unknownProfile)(), {
|
|
42
|
+
feed: f.feed,
|
|
43
|
+
multiSite: f.siteCount === null ? null : f.siteCount > 1,
|
|
44
|
+
write: f.adapterCapabilities.includes('actuate') ? 'command' : 'unknown'
|
|
45
|
+
});
|
|
46
|
+
const evidence = { ...(0, integration_probes_ts_1.foldProbeResults)(results), subject: liveApplies ? 'live' : 'sim', feed: f.feed };
|
|
47
|
+
const path = (0, stage_path_ts_1.assessStagePath)(evidence);
|
|
48
|
+
return {
|
|
49
|
+
results,
|
|
50
|
+
profile,
|
|
51
|
+
path,
|
|
52
|
+
notApplicable: (0, integration_probes_ts_1.notApplicable)(results),
|
|
53
|
+
forbiddenClaims: (0, stage_path_ts_1.forbiddenClaims)(path),
|
|
54
|
+
planned: (0, integration_probes_ts_1.plannedProbes)(results, opts)
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** 시퀀스 명세 — 화면이 회색 칸의 이유(해당 없음·미실행)를 나란히 보이도록 함께 낸다. */
|
|
58
|
+
exports.SUITE_SPEC = integration_probes_ts_1.PROBE_SEQUENCE;
|
|
59
|
+
//# sourceMappingURL=integration-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration-runner.js","sourceRoot":"","sources":["../../server/engine/integration-runner.ts"],"names":[],"mappings":";;;AAgDA,kCA2CC;AA3FD;;;;GAIG;AACH,mEAAwI;AACxI,mDAAkF;AAClF,mFAAkH;AAmClH,MAAM,CAAC,GAAG,CAAC,EAAW,EAAE,CAAuB,EAAe,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC,CAAA;AAE3F,oDAAoD;AACpD,MAAM,UAAU,GAAG,CAAC,EAAW,EAAE,CAAgB,EAAE,GAAG,GAAG,CAAC,EAAe,EAAE,CACzE,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,IAAI,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAA;AAEvF,SAAgB,WAAW,CAAC,CAAa,EAAE,OAAkC,EAAE;IAC7E,MAAM,WAAW,GAAG,CAAC,CAAC,OAAO,KAAK,KAAK,CAAA;IACvC,MAAM,OAAO,GAAkB;QAC7B,CAAC,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC;QACtG,UAAU,CAAC,UAAU,EAAE,CAAC,CAAC,SAAS,CAAC;QACnC,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC,aAAa,CAAC;QACrC,0DAA0D;QAC1D,CAAC,CAAC,aAAa;YACb,CAAC,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC,gBAAgB,EAAE,CAAC,CAAC;YACtD,CAAC,CAAC,CAAC,CAAC,iBAAiB,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAC;QACzF,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,SAAS,CAAC;QAChC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,aAAa,EAAE,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,uBAAuB,EAAE,CAAC;QAC9I,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,cAAc,EAAE,CAAC,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,uBAAuB,EAAE,CAAC;QACxJ,CAAC,CAAC,YAAY,KAAK,IAAI;YACrB,CAAC,CAAC,CAAC,CAAC,cAAc,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;YACnC,CAAC,CAAC,CAAC,CAAC,cAAc,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,YAAY,CAAC,QAAQ,GAAG,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,YAAY,EAAE,CAAC;QACnG,CAAC,CAAC,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QAC5G,CAAC,CAAC,kBAAkB,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,mBAAmB,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACvF,uDAAuD;QACvD,CAAC,CAAC,gBAAgB,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAC;KACnE,CAAA;IAED,8DAA8D;IAC9D,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAE,CAAA;IAC3D,IAAI,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,iBAAiB,KAAK,CAAC;QAAE,OAAO,CAAC,KAAK,GAAG,CAAC,CAAA;IAE/D,MAAM,OAAO,GAAG,IAAA,6CAAa,EAAC,IAAA,8CAAc,GAAE,EAAE;QAC9C,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,SAAS,EAAE,CAAC,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC;QACxD,KAAK,EAAE,CAAC,CAAC,mBAAmB,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;KACzE,CAAC,CAAA;IAEF,MAAM,QAAQ,GAAG,EAAE,GAAG,IAAA,wCAAgB,EAAC,OAAO,CAAC,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC,CAAE,MAAgB,CAAC,CAAC,CAAE,KAAe,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IAC5H,MAAM,IAAI,GAAG,IAAA,+BAAe,EAAC,QAAQ,CAAC,CAAA;IAEtC,OAAO;QACL,OAAO;QACP,OAAO;QACP,IAAI;QACJ,aAAa,EAAE,IAAA,qCAAa,EAAC,OAAO,CAAC;QACrC,eAAe,EAAE,IAAA,+BAAe,EAAC,IAAI,CAAC;QACtC,OAAO,EAAE,IAAA,qCAAa,EAAC,OAAO,EAAE,IAAI,CAAC;KACtC,CAAA;AACH,CAAC;AAED,wDAAwD;AAC3C,QAAA,UAAU,GAAG,sCAAc,CAAA","sourcesContent":["/*\n * 실행기 — 모은 사실을 프로브 결과로 옮기고, 단계 판정과 프로파일까지 한 번에 낸다.\n *\n * I/O 는 여기 없다(호출자가 모은다). 그래서 판정 전체가 테스트된다.\n */\nimport { PROBE_SEQUENCE, foldProbeResults, notApplicable, plannedProbes, type ProbeId, type ProbeResult } from './integration-probes.ts'\nimport { assessStagePath, forbiddenClaims, type StagePath } from './stage-path.ts'\nimport { narrowProfile, unknownProfile, type FeedKind, type TargetProfile } from './integration-target-profile.ts'\n\n/** 호출자가 모아 오는 사실. 전부 `null` 이 「모른다」다 — 0 이나 false 로 채우지 않는다. */\nexport interface SuiteFacts {\n /** 이 트윈이 무엇인가. 시뮬은 원천에서 받는 것이 없다(라이브 프로브는 해당 없음). */\n subject: 'live' | 'sim' | 'stopped' | 'unknown'\n /** 레퍼런스(원천 어댑터)에서 왔나. 템플릿으로 세운 트윈은 옮겨 온 마스터가 없다. */\n fromReference: boolean\n /** `capabilitiesOf(adapter)` — 선언∩구현. */\n adapterCapabilities: readonly string[]\n connectOk: boolean | null\n siteCount: number | null\n locationCount: number | null\n itemCount: number | null\n /** 마지막 동기의 못 옮긴 값 수(`TwinReference.lastWarningCount`). */\n lastWarningCount: number | null\n /** 저널 누적 — 재시작에도 남는다(인메모리 계기와 다르다). */\n journalTotal: number | null\n /** 저널의 `action='DELETE'` 수 — **반출 관측**. 승격 전 행은 세어지지 않으므로 `null` 이 정직하다. */\n removalsJournaled: number | null\n specCoverage: { declared: number; total: number } | null\n forecastRan: boolean | null\n feed: FeedKind\n}\n\nexport interface SuiteAssessment {\n results: ProbeResult[]\n profile: TargetProfile\n path: StagePath\n notApplicable: { id: ProbeId; detail?: string }[]\n forbiddenClaims: string[]\n /** 다음에 돌릴 프로브 — 쓰기 동의 없이는 쓰기 프로브가 빠진다. */\n planned: ProbeId[]\n}\n\nconst r = (id: ProbeId, o: Partial<ProbeResult>): ProbeResult => ({ id, ran: false, ...o })\n\n/** 값이 있으면 돌린 것으로 보고 판정한다. `null` = 안 돌렸다(실패 아님). */\nconst fromNumber = (id: ProbeId, v: number | null, min = 1): ProbeResult =>\n v === null ? r(id, { ran: false }) : r(id, { ran: true, passed: v >= min, count: v })\n\nexport function assessSuite(f: SuiteFacts, opts: { allowWrites?: boolean } = {}): SuiteAssessment {\n const liveApplies = f.subject !== 'sim'\n const results: ProbeResult[] = [\n f.connectOk === null ? r('connect', { ran: false }) : r('connect', { ran: true, passed: f.connectOk }),\n fromNumber('discover', f.siteCount),\n fromNumber('master', f.locationCount),\n /* 레퍼런스 없이 세운 트윈에는 옮겨 온 마스터가 없다 — 「못 옮긴 값」을 물을 대상이 아니다. */\n f.fromReference\n ? fromNumber('master-warnings', f.lastWarningCount, 0)\n : r('master-warnings', { ran: false, applicable: false, detail: 'not-from-reference' }),\n fromNumber('items', f.itemCount),\n liveApplies ? fromNumber('live-ingest', f.journalTotal) : r('live-ingest', { ran: false, applicable: false, detail: 'subject-is-simulation' }),\n liveApplies ? fromNumber('live-removal', f.removalsJournaled, 1) : r('live-removal', { ran: false, applicable: false, detail: 'subject-is-simulation' }),\n f.specCoverage === null\n ? r('declarations', { ran: false })\n : r('declarations', { ran: true, passed: f.specCoverage.declared > 0, coverage: f.specCoverage }),\n f.forecastRan === null ? r('forecast', { ran: false }) : r('forecast', { ran: true, passed: f.forecastRan }),\n r('actuate-declared', { ran: true, passed: f.adapterCapabilities.includes('actuate') }),\n /* 액추에이션은 계약에 아직 없다(ADR-0036). 측정을 만들면 없는 것을 있는 척한다. */\n r('actuate-effect', { ran: false, detail: 'actuation-not-wired' })\n ]\n\n /* 반출을 셌는데 0 이면 「세지 않음」과 구별해 프로브를 실패로 남긴다 — count 0 은 사실이다. */\n const removal = results.find(x => x.id === 'live-removal')!\n if (removal.ran && f.removalsJournaled === 0) removal.count = 0\n\n const profile = narrowProfile(unknownProfile(), {\n feed: f.feed,\n multiSite: f.siteCount === null ? null : f.siteCount > 1,\n write: f.adapterCapabilities.includes('actuate') ? 'command' : 'unknown'\n })\n\n const evidence = { ...foldProbeResults(results), subject: liveApplies ? ('live' as const) : ('sim' as const), feed: f.feed }\n const path = assessStagePath(evidence)\n\n return {\n results,\n profile,\n path,\n notApplicable: notApplicable(results),\n forbiddenClaims: forbiddenClaims(path),\n planned: plannedProbes(results, opts)\n }\n}\n\n/** 시퀀스 명세 — 화면이 회색 칸의 이유(해당 없음·미실행)를 나란히 보이도록 함께 낸다. */\nexport const SUITE_SPEC = PROBE_SEQUENCE\n"]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** 무엇으로 붙나 — 페이지·상한·질의 형태가 여기서 갈린다. */
|
|
2
|
+
export type Transport = 'rest' | 'graphql' | 'db' | 'file' | 'mqtt' | 'opc' | 'unknown';
|
|
3
|
+
/**
|
|
4
|
+
* 변화를 어떻게 아나 — **라이브 단계의 성격이 통째로 이 축에 달려 있다.**
|
|
5
|
+
*
|
|
6
|
+
* · `push` 원천이 밀어 준다(구독·웹훅·큐·CDC). 커서가 필요 없다.
|
|
7
|
+
* · `delta-poll` 거래 이력을 커서로 읽는다. **커서 정합이 급소다.**
|
|
8
|
+
* · `snapshot-poll` 현재 상태를 되풀어 읽는다. **반출을 원리적으로 낼 수 없다** — 라이브 단계는
|
|
9
|
+
* 「아직」이 아니라 **구조적으로 불가**이고, 그 사실을 그대로 말한다.
|
|
10
|
+
* · `none` 변화를 알 길이 없다(구조 전용 소스).
|
|
11
|
+
*/
|
|
12
|
+
export type FeedKind = 'push' | 'delta-poll' | 'snapshot-poll' | 'none' | 'unknown';
|
|
13
|
+
/** 물건을 무엇이라 부르나 — 발명해야 하는지가 여기서 갈린다. */
|
|
14
|
+
export type IdentityKind = 'gs1' | 'local' | 'none' | 'unknown';
|
|
15
|
+
/**
|
|
16
|
+
* 양을 어떻게 세나 — **개수냐 연속량이냐.**
|
|
17
|
+
*
|
|
18
|
+
* 식품·화학·제약은 KG·L 로 오고, 조립 제조는 대수로 온다. 커널·미러는 둘 다 다루지만(비직렬
|
|
19
|
+
* `MaterialSubLot`), **요구하는 프로브가 다르다**: 연속량이면 단위 어휘와 환산을 반드시 묻고,
|
|
20
|
+
* 직렬이면 그 프로브는 해당 없음이다.
|
|
21
|
+
*/
|
|
22
|
+
export type Quantization = 'serial' | 'continuous' | 'mixed' | 'unknown';
|
|
23
|
+
/** 이 원천이 겸하는 버티컬 — 둘 이상이면 트윈이 여럿이고 **핸드오버 경계**를 물어야 한다. */
|
|
24
|
+
export type Vertical = 'wms' | 'mes' | 'yms' | 'ems';
|
|
25
|
+
/** 밖으로 나가는 문 — 폐루프 프로브의 적용 여부. */
|
|
26
|
+
export type WriteSurface = 'none' | 'command' | 'unknown';
|
|
27
|
+
export interface TargetProfile {
|
|
28
|
+
transport: Transport;
|
|
29
|
+
feed: FeedKind;
|
|
30
|
+
multiSite: boolean | null;
|
|
31
|
+
identity: IdentityKind;
|
|
32
|
+
quantization: Quantization;
|
|
33
|
+
/** 빈 배열 = 아직 모른다(없다는 뜻이 아니다). */
|
|
34
|
+
verticals: readonly Vertical[];
|
|
35
|
+
write: WriteSurface;
|
|
36
|
+
}
|
|
37
|
+
/** 아무것도 모르는 상태 — 스위트는 여기서 시작한다. */
|
|
38
|
+
export declare function unknownProfile(): TargetProfile;
|
|
39
|
+
/** 이 축을 아직 모르나 — 모르는 축으로는 라우팅하지 않는다. */
|
|
40
|
+
export declare function isUnknown(p: TargetProfile, axis: keyof TargetProfile): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* 프로브가 알아낸 것을 프로파일에 반영한다 — **좁히기만 한다.**
|
|
43
|
+
*
|
|
44
|
+
* 이미 알아낸 축을 나중 프로브가 `'unknown'` 으로 덮지 못하게 한다. 스위트는 여러 번 돌고, 뒤에
|
|
45
|
+
* 돌린 프로브가 그 축을 다루지 않는다는 이유로 앞의 발견을 지우면 라우팅이 왕복한다.
|
|
46
|
+
*/
|
|
47
|
+
export declare function narrowProfile(base: TargetProfile, found: Partial<TargetProfile>): TargetProfile;
|
|
48
|
+
/**
|
|
49
|
+
* 이 프로파일에서 **라이브가 구조적으로 가능한가.**
|
|
50
|
+
*
|
|
51
|
+
* `snapshot-poll` 은 불가다 — 사라진 것은 그냥 없어지고 `OBSERVE` 만 남는다. 이걸 「아직 반출을 못
|
|
52
|
+
* 봤다」로 적으면 사람이 더 기다리게 되는데, 더 기다려도 오지 않는다. 방식을 바꿔야 한다는 것이 답이다.
|
|
53
|
+
*/
|
|
54
|
+
export declare function liveFeasible(p: TargetProfile): {
|
|
55
|
+
ok: boolean;
|
|
56
|
+
reason?: string;
|
|
57
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* 대상 프로파일 — **스위트가 갈리는 근거.**
|
|
4
|
+
*
|
|
5
|
+
* ── 왜 프로파일이 먼저인가 ──────────────────────────────────────────────────
|
|
6
|
+
* 처음 만든 시퀀스는 직선이었다. 모든 대상에게 같은 순서를 강요했고, 그래서 두 방향으로 거짓말을
|
|
7
|
+
* 했다: push 소스에 커서 프로브를 들이대 빨간 칸을 만들고(결함이 아닌데), 반대로 스냅샷 소스에는
|
|
8
|
+
* 반출을 못 본 이유를 「아직 안 돌렸다」로 적었다(구조적으로 못 내는 것인데).
|
|
9
|
+
*
|
|
10
|
+
* 갈림의 근거는 **대상의 성격**이고, 그 성격은 **우리가 정하는 것이 아니라 발견하는 것**이다.
|
|
11
|
+
* 그래서 프로파일의 모든 축은 `'unknown'` 에서 시작해 프로브가 좁힌다. 모르는 축으로는 라우팅하지
|
|
12
|
+
* 않는다 — 모르면 그 축에 걸린 프로브는 「아직 판단할 수 없음」이고, 짐작으로 건너뛰지 않는다.
|
|
13
|
+
*
|
|
14
|
+
* ── 축을 고른 기준 ─────────────────────────────────────────────────────────
|
|
15
|
+
* 실제로 **다른 프로브를 요구하는 것만** 축이 된다. 「벤더 이름」은 축이 아니다(같은 벤더가 REST 도
|
|
16
|
+
* 파일도 준다). 아래 일곱은 각각 적어도 한 프로브의 적용 여부를 바꾼다.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.unknownProfile = unknownProfile;
|
|
20
|
+
exports.isUnknown = isUnknown;
|
|
21
|
+
exports.narrowProfile = narrowProfile;
|
|
22
|
+
exports.liveFeasible = liveFeasible;
|
|
23
|
+
/** 아무것도 모르는 상태 — 스위트는 여기서 시작한다. */
|
|
24
|
+
function unknownProfile() {
|
|
25
|
+
return { transport: 'unknown', feed: 'unknown', multiSite: null, identity: 'unknown', quantization: 'unknown', verticals: [], write: 'unknown' };
|
|
26
|
+
}
|
|
27
|
+
/** 이 축을 아직 모르나 — 모르는 축으로는 라우팅하지 않는다. */
|
|
28
|
+
function isUnknown(p, axis) {
|
|
29
|
+
const v = p[axis];
|
|
30
|
+
if (axis === 'multiSite')
|
|
31
|
+
return v === null;
|
|
32
|
+
if (axis === 'verticals')
|
|
33
|
+
return v.length === 0;
|
|
34
|
+
return v === 'unknown';
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 프로브가 알아낸 것을 프로파일에 반영한다 — **좁히기만 한다.**
|
|
38
|
+
*
|
|
39
|
+
* 이미 알아낸 축을 나중 프로브가 `'unknown'` 으로 덮지 못하게 한다. 스위트는 여러 번 돌고, 뒤에
|
|
40
|
+
* 돌린 프로브가 그 축을 다루지 않는다는 이유로 앞의 발견을 지우면 라우팅이 왕복한다.
|
|
41
|
+
*/
|
|
42
|
+
function narrowProfile(base, found) {
|
|
43
|
+
const out = { ...base };
|
|
44
|
+
for (const k of Object.keys(found)) {
|
|
45
|
+
const v = found[k];
|
|
46
|
+
if (v === undefined)
|
|
47
|
+
continue;
|
|
48
|
+
if (k === 'multiSite') {
|
|
49
|
+
if (v !== null)
|
|
50
|
+
out.multiSite = v;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (k === 'verticals') {
|
|
54
|
+
const list = v;
|
|
55
|
+
if (list.length)
|
|
56
|
+
out.verticals = [...new Set([...out.verticals, ...list])];
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (v !== 'unknown')
|
|
60
|
+
out[k] = v;
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 이 프로파일에서 **라이브가 구조적으로 가능한가.**
|
|
66
|
+
*
|
|
67
|
+
* `snapshot-poll` 은 불가다 — 사라진 것은 그냥 없어지고 `OBSERVE` 만 남는다. 이걸 「아직 반출을 못
|
|
68
|
+
* 봤다」로 적으면 사람이 더 기다리게 되는데, 더 기다려도 오지 않는다. 방식을 바꿔야 한다는 것이 답이다.
|
|
69
|
+
*/
|
|
70
|
+
function liveFeasible(p) {
|
|
71
|
+
if (p.feed === 'unknown')
|
|
72
|
+
return { ok: false, reason: 'feed-unknown' };
|
|
73
|
+
if (p.feed === 'none')
|
|
74
|
+
return { ok: false, reason: 'no-feed' };
|
|
75
|
+
if (p.feed === 'snapshot-poll')
|
|
76
|
+
return { ok: false, reason: 'snapshot-only-feed' };
|
|
77
|
+
return { ok: true };
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=integration-target-profile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration-target-profile.js","sourceRoot":"","sources":["../../server/engine/integration-target-profile.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;AA8CH,wCAEC;AAGD,8BAKC;AAQD,sCAUC;AAQD,oCAKC;AA1CD,mCAAmC;AACnC,SAAgB,cAAc;IAC5B,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAA;AAClJ,CAAC;AAED,wCAAwC;AACxC,SAAgB,SAAS,CAAC,CAAgB,EAAE,IAAyB;IACnE,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAA;IACjB,IAAI,IAAI,KAAK,WAAW;QAAE,OAAO,CAAC,KAAK,IAAI,CAAA;IAC3C,IAAI,IAAI,KAAK,WAAW;QAAE,OAAQ,CAAyB,CAAC,MAAM,KAAK,CAAC,CAAA;IACxE,OAAO,CAAC,KAAK,SAAS,CAAA;AACxB,CAAC;AAED;;;;;GAKG;AACH,SAAgB,aAAa,CAAC,IAAmB,EAAE,KAA6B;IAC9E,MAAM,GAAG,GAAkB,EAAE,GAAG,IAAI,EAAE,CAAA;IACtC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAA4B,EAAE,CAAC;QAC9D,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;QAClB,IAAI,CAAC,KAAK,SAAS;YAAE,SAAQ;QAC7B,IAAI,CAAC,KAAK,WAAW,EAAE,CAAC;YAAC,IAAI,CAAC,KAAK,IAAI;gBAAE,GAAG,CAAC,SAAS,GAAG,CAAY,CAAC;YAAC,SAAQ;QAAC,CAAC;QACjF,IAAI,CAAC,KAAK,WAAW,EAAE,CAAC;YAAC,MAAM,IAAI,GAAG,CAAwB,CAAC;YAAC,IAAI,IAAI,CAAC,MAAM;gBAAE,GAAG,CAAC,SAAS,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YAAC,SAAQ;QAAC,CAAC;QACtJ,IAAI,CAAC,KAAK,SAAS;YAAG,GAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IAC1C,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;;GAKG;AACH,SAAgB,YAAY,CAAC,CAAgB;IAC3C,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,CAAA;IACtE,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAA;IAC9D,IAAI,CAAC,CAAC,IAAI,KAAK,eAAe;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAA;IAClF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;AACrB,CAAC","sourcesContent":["/*\n * 대상 프로파일 — **스위트가 갈리는 근거.**\n *\n * ── 왜 프로파일이 먼저인가 ──────────────────────────────────────────────────\n * 처음 만든 시퀀스는 직선이었다. 모든 대상에게 같은 순서를 강요했고, 그래서 두 방향으로 거짓말을\n * 했다: push 소스에 커서 프로브를 들이대 빨간 칸을 만들고(결함이 아닌데), 반대로 스냅샷 소스에는\n * 반출을 못 본 이유를 「아직 안 돌렸다」로 적었다(구조적으로 못 내는 것인데).\n *\n * 갈림의 근거는 **대상의 성격**이고, 그 성격은 **우리가 정하는 것이 아니라 발견하는 것**이다.\n * 그래서 프로파일의 모든 축은 `'unknown'` 에서 시작해 프로브가 좁힌다. 모르는 축으로는 라우팅하지\n * 않는다 — 모르면 그 축에 걸린 프로브는 「아직 판단할 수 없음」이고, 짐작으로 건너뛰지 않는다.\n *\n * ── 축을 고른 기준 ─────────────────────────────────────────────────────────\n * 실제로 **다른 프로브를 요구하는 것만** 축이 된다. 「벤더 이름」은 축이 아니다(같은 벤더가 REST 도\n * 파일도 준다). 아래 일곱은 각각 적어도 한 프로브의 적용 여부를 바꾼다.\n */\n\n/** 무엇으로 붙나 — 페이지·상한·질의 형태가 여기서 갈린다. */\nexport type Transport = 'rest' | 'graphql' | 'db' | 'file' | 'mqtt' | 'opc' | 'unknown'\n\n/**\n * 변화를 어떻게 아나 — **라이브 단계의 성격이 통째로 이 축에 달려 있다.**\n *\n * · `push` 원천이 밀어 준다(구독·웹훅·큐·CDC). 커서가 필요 없다.\n * · `delta-poll` 거래 이력을 커서로 읽는다. **커서 정합이 급소다.**\n * · `snapshot-poll` 현재 상태를 되풀어 읽는다. **반출을 원리적으로 낼 수 없다** — 라이브 단계는\n * 「아직」이 아니라 **구조적으로 불가**이고, 그 사실을 그대로 말한다.\n * · `none` 변화를 알 길이 없다(구조 전용 소스).\n */\nexport type FeedKind = 'push' | 'delta-poll' | 'snapshot-poll' | 'none' | 'unknown'\n\n/** 물건을 무엇이라 부르나 — 발명해야 하는지가 여기서 갈린다. */\nexport type IdentityKind = 'gs1' | 'local' | 'none' | 'unknown'\n\n/**\n * 양을 어떻게 세나 — **개수냐 연속량이냐.**\n *\n * 식품·화학·제약은 KG·L 로 오고, 조립 제조는 대수로 온다. 커널·미러는 둘 다 다루지만(비직렬\n * `MaterialSubLot`), **요구하는 프로브가 다르다**: 연속량이면 단위 어휘와 환산을 반드시 묻고,\n * 직렬이면 그 프로브는 해당 없음이다.\n */\nexport type Quantization = 'serial' | 'continuous' | 'mixed' | 'unknown'\n\n/** 이 원천이 겸하는 버티컬 — 둘 이상이면 트윈이 여럿이고 **핸드오버 경계**를 물어야 한다. */\nexport type Vertical = 'wms' | 'mes' | 'yms' | 'ems'\n\n/** 밖으로 나가는 문 — 폐루프 프로브의 적용 여부. */\nexport type WriteSurface = 'none' | 'command' | 'unknown'\n\nexport interface TargetProfile {\n transport: Transport\n feed: FeedKind\n multiSite: boolean | null\n identity: IdentityKind\n quantization: Quantization\n /** 빈 배열 = 아직 모른다(없다는 뜻이 아니다). */\n verticals: readonly Vertical[]\n write: WriteSurface\n}\n\n/** 아무것도 모르는 상태 — 스위트는 여기서 시작한다. */\nexport function unknownProfile(): TargetProfile {\n return { transport: 'unknown', feed: 'unknown', multiSite: null, identity: 'unknown', quantization: 'unknown', verticals: [], write: 'unknown' }\n}\n\n/** 이 축을 아직 모르나 — 모르는 축으로는 라우팅하지 않는다. */\nexport function isUnknown(p: TargetProfile, axis: keyof TargetProfile): boolean {\n const v = p[axis]\n if (axis === 'multiSite') return v === null\n if (axis === 'verticals') return (v as readonly Vertical[]).length === 0\n return v === 'unknown'\n}\n\n/**\n * 프로브가 알아낸 것을 프로파일에 반영한다 — **좁히기만 한다.**\n *\n * 이미 알아낸 축을 나중 프로브가 `'unknown'` 으로 덮지 못하게 한다. 스위트는 여러 번 돌고, 뒤에\n * 돌린 프로브가 그 축을 다루지 않는다는 이유로 앞의 발견을 지우면 라우팅이 왕복한다.\n */\nexport function narrowProfile(base: TargetProfile, found: Partial<TargetProfile>): TargetProfile {\n const out: TargetProfile = { ...base }\n for (const k of Object.keys(found) as (keyof TargetProfile)[]) {\n const v = found[k]\n if (v === undefined) continue\n if (k === 'multiSite') { if (v !== null) out.multiSite = v as boolean; continue }\n if (k === 'verticals') { const list = v as readonly Vertical[]; if (list.length) out.verticals = [...new Set([...out.verticals, ...list])]; continue }\n if (v !== 'unknown') (out as any)[k] = v\n }\n return out\n}\n\n/**\n * 이 프로파일에서 **라이브가 구조적으로 가능한가.**\n *\n * `snapshot-poll` 은 불가다 — 사라진 것은 그냥 없어지고 `OBSERVE` 만 남는다. 이걸 「아직 반출을 못\n * 봤다」로 적으면 사람이 더 기다리게 되는데, 더 기다려도 오지 않는다. 방식을 바꿔야 한다는 것이 답이다.\n */\nexport function liveFeasible(p: TargetProfile): { ok: boolean; reason?: string } {\n if (p.feed === 'unknown') return { ok: false, reason: 'feed-unknown' }\n if (p.feed === 'none') return { ok: false, reason: 'no-feed' }\n if (p.feed === 'snapshot-poll') return { ok: false, reason: 'snapshot-only-feed' }\n return { ok: true }\n}\n"]}
|
|
@@ -199,6 +199,33 @@ export interface KpiResult {
|
|
|
199
199
|
leadTime: number;
|
|
200
200
|
workTime: number;
|
|
201
201
|
};
|
|
202
|
+
/**
|
|
203
|
+
* **완료로 세지 않은 오더 상태 낱말의 인구조사** — 낱말 → 창 안에서 그 낱말로 본 건수.
|
|
204
|
+
*
|
|
205
|
+
* 「못 읽었다」가 **아니다.** `picking` 처럼 완료가 아닌 낱말이 여기 오는 것은 정상이다. 이 값은
|
|
206
|
+
* 판정이 아니라 **재료**다: 완료가 0 인데 여기 `{ finished: 2780 }` 이 있으면 원인이 곧 보인다.
|
|
207
|
+
*
|
|
208
|
+
* ── 왜 필요한가, 그리고 무엇의 대체가 아닌가 ────────────────────────────────
|
|
209
|
+
* 이 폴드는 완료를 **동의어 목록**으로 판정한다(`completed`·`fulfilled`·`done`·`finished`). 그 방식은
|
|
210
|
+
* 원본의 낱말에서 뜻을 **추론**하는 것이고, 이 저장소가 다른 자리에서 거절하는 그것이다
|
|
211
|
+
* (「선언이 있으면 변환이지만 없으면 발명이다」).
|
|
212
|
+
*
|
|
213
|
+
* 실제로 그 대가를 치렀다: `finished` 가 목록에 없어 첫 실 시스템의 완료 오더가 전부 0 이었다.
|
|
214
|
+
* 그런데 **빈 자리는 이 목록이 아니라 커넥터였다** — 그 커넥터에 상태 표(`WORK_ORDER_STATE`)가
|
|
215
|
+
* 정의돼 있으면서 오더 경로에서 쓰이지 않았고(로트 경로는 썼다), 원본 낱말이 그대로 흘렀다.
|
|
216
|
+
* 목록에 `finished` 를 더한 것은 그 사실을 가린 땜빵이었다.
|
|
217
|
+
*
|
|
218
|
+
* **정석은 커넥터가 자기 표로 옮기고, 옮길 수 없는 낱말은 세고 넣지 않는 것이다**(재고 처분이 이미
|
|
219
|
+
* 그 규율로 선다: 「표현할 길이 없는 것은 넣지 않는다」). 그러면 이 폴드는 추론하지 않아도 된다.
|
|
220
|
+
*
|
|
221
|
+
* 그러므로 이 값은 **동의어 목록을 늘리는 근거가 아니다.** 목록은 커넥터들이 옮기기 시작하면
|
|
222
|
+
* 닫힌 어휘로 좁혀야 하고, 그때까지 이 값이 어긋남을 말해 준다. 순서가 중요하다 — 목록을 먼저
|
|
223
|
+
* 좁히면 아직 옮기지 않는 원본의 완료가 그 순간 0 이 된다.
|
|
224
|
+
*
|
|
225
|
+
* 상태를 말하지 않은 사건은 여기 오지 않는다. 그것은 「모르는 낱말」이 아니라 다른 결함이고
|
|
226
|
+
* (유입 문이 상태를 필수로 받는다) 그 진단은 그 문의 몫이다.
|
|
227
|
+
*/
|
|
228
|
+
orderStatusNotCounted: Record<string, number>;
|
|
202
229
|
/**
|
|
203
230
|
* 소비한 이벤트 수 — **넘겨받은 배열 전체**(창 앞 lookback 포함). 진단용이다.
|
|
204
231
|
*
|