@things-factory/headless-twin 10.0.8 → 10.0.10
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/attention-digest.d.ts +52 -0
- package/dist-server/engine/attention-digest.js +76 -0
- package/dist-server/engine/attention-digest.js.map +1 -0
- package/dist-server/engine/canonical-ingest.d.ts +2 -2
- package/dist-server/engine/canonical-ingest.js +24 -2
- package/dist-server/engine/canonical-ingest.js.map +1 -1
- package/dist-server/engine/command-routing.d.ts +33 -0
- package/dist-server/engine/command-routing.js +53 -0
- package/dist-server/engine/command-routing.js.map +1 -0
- package/dist-server/engine/energy-topology.d.ts +86 -0
- package/dist-server/engine/energy-topology.js +144 -0
- package/dist-server/engine/energy-topology.js.map +1 -0
- package/dist-server/engine/index.d.ts +11 -0
- package/dist-server/engine/index.js +17 -0
- package/dist-server/engine/index.js.map +1 -1
- package/dist-server/engine/kpi-baseline.d.ts +78 -0
- package/dist-server/engine/kpi-baseline.js +123 -0
- package/dist-server/engine/kpi-baseline.js.map +1 -0
- package/dist-server/engine/kpi-fold.d.ts +137 -1
- package/dist-server/engine/kpi-fold.js +225 -0
- package/dist-server/engine/kpi-fold.js.map +1 -1
- package/dist-server/engine/kpi-query.d.ts +32 -1
- package/dist-server/engine/kpi-query.js +262 -20
- package/dist-server/engine/kpi-query.js.map +1 -1
- package/dist-server/engine/kpi-target.d.ts +17 -0
- package/dist-server/engine/kpi-target.js +24 -2
- package/dist-server/engine/kpi-target.js.map +1 -1
- package/dist-server/engine/live-attentions.d.ts +1 -0
- package/dist-server/engine/live-attentions.js +7 -1
- package/dist-server/engine/live-attentions.js.map +1 -1
- package/dist-server/engine/live-feed-registry.d.ts +25 -0
- package/dist-server/engine/live-feed-registry.js +51 -0
- package/dist-server/engine/live-feed-registry.js.map +1 -0
- package/dist-server/engine/load-meter.d.ts +181 -0
- package/dist-server/engine/load-meter.js +267 -0
- package/dist-server/engine/load-meter.js.map +1 -0
- package/dist-server/engine/local-declarations.d.ts +262 -0
- package/dist-server/engine/local-declarations.js +528 -0
- package/dist-server/engine/local-declarations.js.map +1 -0
- package/dist-server/engine/model-basis.d.ts +39 -6
- package/dist-server/engine/model-basis.js +69 -9
- package/dist-server/engine/model-basis.js.map +1 -1
- package/dist-server/engine/model-vocabulary.d.ts +17 -0
- package/dist-server/engine/model-vocabulary.js +81 -0
- package/dist-server/engine/model-vocabulary.js.map +1 -0
- package/dist-server/engine/oee-accumulator.d.ts +28 -0
- package/dist-server/engine/oee-accumulator.js +26 -1
- package/dist-server/engine/oee-accumulator.js.map +1 -1
- package/dist-server/engine/operation-basis.d.ts +32 -0
- package/dist-server/engine/operation-basis.js +87 -0
- package/dist-server/engine/operation-basis.js.map +1 -0
- package/dist-server/engine/property-effects.d.ts +30 -0
- package/dist-server/engine/property-effects.js +192 -0
- package/dist-server/engine/property-effects.js.map +1 -0
- package/dist-server/engine/runtime-key.d.ts +15 -0
- package/dist-server/engine/runtime-key.js +64 -0
- package/dist-server/engine/runtime-key.js.map +1 -0
- package/dist-server/engine/spec-coverage.js +3 -3
- package/dist-server/engine/spec-coverage.js.map +1 -1
- package/dist-server/engine/state-axes.d.ts +21 -0
- package/dist-server/engine/state-axes.js +59 -0
- package/dist-server/engine/state-axes.js.map +1 -0
- package/dist-server/engine/structure-diff.js +1 -1
- package/dist-server/engine/structure-diff.js.map +1 -1
- package/dist-server/engine/travel-estimator.d.ts +9 -2
- package/dist-server/engine/travel-estimator.js +11 -6
- package/dist-server/engine/travel-estimator.js.map +1 -1
- package/dist-server/engine/twin-engine.d.ts +418 -57
- package/dist-server/engine/twin-engine.js +1599 -306
- package/dist-server/engine/twin-engine.js.map +1 -1
- package/dist-server/engine/warm-start.d.ts +93 -13
- package/dist-server/engine/warm-start.js +114 -14
- package/dist-server/engine/warm-start.js.map +1 -1
- package/dist-server/index.d.ts +1 -0
- package/dist-server/index.js +17 -11
- package/dist-server/index.js.map +1 -1
- package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.d.ts +5 -0
- package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js +48 -0
- package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +1 -0
- package/dist-server/migrations/index.d.ts +2 -0
- package/dist-server/migrations/index.js +10 -0
- package/dist-server/migrations/index.js.map +1 -0
- package/dist-server/service/index.d.ts +3 -2
- package/dist-server/service/index.js +25 -17
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/reference/discovery-result.d.ts +34 -0
- package/dist-server/service/reference/discovery-result.js +84 -0
- package/dist-server/service/reference/discovery-result.js.map +1 -0
- package/dist-server/service/reference/ingest-space.d.ts +53 -0
- package/dist-server/service/reference/ingest-space.js +79 -0
- package/dist-server/service/reference/ingest-space.js.map +1 -0
- package/dist-server/service/reference/knob-defaults.d.ts +20 -0
- package/dist-server/service/reference/knob-defaults.js +59 -0
- package/dist-server/service/reference/knob-defaults.js.map +1 -0
- package/dist-server/service/reference/reference-live.d.ts +23 -0
- package/dist-server/service/reference/reference-live.js +123 -6
- package/dist-server/service/reference/reference-live.js.map +1 -1
- package/dist-server/service/reference/reference-master.d.ts +227 -11
- package/dist-server/service/reference/reference-master.js +206 -25
- package/dist-server/service/reference/reference-master.js.map +1 -1
- package/dist-server/service/reference/reference-resolver.d.ts +33 -3
- package/dist-server/service/reference/reference-resolver.js +290 -23
- package/dist-server/service/reference/reference-resolver.js.map +1 -1
- package/dist-server/service/reference/template-registry.d.ts +19 -3
- package/dist-server/service/reference/template-registry.js.map +1 -1
- package/dist-server/service/twin-attention/twin-attention-query.d.ts +8 -1
- package/dist-server/service/twin-attention/twin-attention-query.js +39 -8
- package/dist-server/service/twin-attention/twin-attention-query.js.map +1 -1
- package/dist-server/service/twin-audit/command-audit.d.ts +37 -0
- package/dist-server/service/twin-audit/command-audit.js +53 -0
- package/dist-server/service/twin-audit/command-audit.js.map +1 -0
- package/dist-server/service/twin-audit/index.d.ts +4 -0
- package/dist-server/service/twin-audit/index.js +8 -0
- package/dist-server/service/twin-audit/index.js.map +1 -0
- package/dist-server/service/twin-audit/twin-audit-event.d.ts +24 -0
- package/dist-server/service/twin-audit/twin-audit-event.js +125 -0
- package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -0
- package/dist-server/service/twin-audit/twin-audit-query.d.ts +4 -0
- package/dist-server/service/twin-audit/twin-audit-query.js +76 -0
- package/dist-server/service/twin-audit/twin-audit-query.js.map +1 -0
- package/dist-server/service/twin-control/twin-control-mutation.d.ts +2 -0
- package/dist-server/service/twin-control/twin-control-mutation.js +50 -11
- package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
- package/dist-server/service/twin-event/twin-event-keys.d.ts +1 -1
- package/dist-server/service/twin-event/twin-event-keys.js +1 -1
- package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
- package/dist-server/service/twin-event/twin-event.d.ts +14 -5
- package/dist-server/service/twin-event/twin-event.js +46 -14
- package/dist-server/service/twin-event/twin-event.js.map +1 -1
- package/dist-server/service/twin-forecast/forecast-metrics.d.ts +13 -0
- package/dist-server/service/twin-forecast/forecast-metrics.js +57 -0
- package/dist-server/service/twin-forecast/forecast-metrics.js.map +1 -0
- package/dist-server/service/twin-forecast/forecast-tuning.d.ts +12 -0
- package/dist-server/service/twin-forecast/forecast-tuning.js +44 -0
- package/dist-server/service/twin-forecast/forecast-tuning.js.map +1 -0
- package/dist-server/service/twin-forecast/gap-analytics.d.ts +28 -2
- package/dist-server/service/twin-forecast/gap-analytics.js +43 -17
- package/dist-server/service/twin-forecast/gap-analytics.js.map +1 -1
- package/dist-server/service/twin-forecast/twin-forecast-query.js +219 -39
- package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
- package/dist-server/service/twin-instance/twin-instance.d.ts +38 -5
- package/dist-server/service/twin-instance/twin-instance.js +66 -18
- package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
- package/dist-server/service/twin-journal/twin-journal-query.d.ts +11 -4
- package/dist-server/service/twin-journal/twin-journal-query.js +95 -15
- package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +11 -5
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +46 -27
- 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 +63 -1
- package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
- package/dist-server/service/twin-model/epcis-coverage.d.ts +14 -0
- package/dist-server/service/twin-model/epcis-coverage.js +174 -0
- package/dist-server/service/twin-model/epcis-coverage.js.map +1 -0
- package/dist-server/service/twin-model/iec61850-coverage.d.ts +14 -0
- package/dist-server/service/twin-model/iec61850-coverage.js +98 -0
- package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -0
- package/dist-server/service/twin-model/index.d.ts +13 -0
- package/dist-server/service/twin-model/index.js +29 -0
- package/dist-server/service/twin-model/index.js.map +1 -0
- package/dist-server/service/twin-model/isa95-coverage.d.ts +48 -0
- package/dist-server/service/twin-model/isa95-coverage.js +155 -0
- package/dist-server/service/twin-model/isa95-coverage.js.map +1 -0
- package/dist-server/service/twin-model/project-structure.d.ts +16 -0
- package/dist-server/service/twin-model/project-structure.js +173 -0
- package/dist-server/service/twin-model/project-structure.js.map +1 -0
- package/dist-server/service/twin-model/standard-coverage.d.ts +9 -0
- package/dist-server/service/twin-model/standard-coverage.js +37 -0
- package/dist-server/service/twin-model/standard-coverage.js.map +1 -0
- package/dist-server/service/twin-model/twin-equipment.d.ts +64 -0
- package/dist-server/service/twin-model/twin-equipment.js +134 -0
- package/dist-server/service/twin-model/twin-equipment.js.map +1 -0
- package/dist-server/service/twin-model/twin-lineage-query.d.ts +3 -0
- package/dist-server/service/twin-model/twin-lineage-query.js +206 -0
- package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -0
- package/dist-server/service/twin-model/twin-location.d.ts +58 -0
- package/dist-server/service/twin-model/twin-location.js +130 -0
- package/dist-server/service/twin-model/twin-location.js.map +1 -0
- package/dist-server/service/twin-model/twin-model-item-query.d.ts +141 -0
- package/dist-server/service/twin-model/twin-model-item-query.js +852 -0
- package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -0
- package/dist-server/service/twin-model/twin-model-mutation.d.ts +57 -0
- package/dist-server/service/twin-model/twin-model-mutation.js +490 -0
- package/dist-server/service/twin-model/twin-model-mutation.js.map +1 -0
- package/dist-server/service/twin-model/twin-model-query.d.ts +43 -0
- package/dist-server/service/twin-model/twin-model-query.js +551 -0
- package/dist-server/service/twin-model/twin-model-query.js.map +1 -0
- package/dist-server/service/twin-model/twin-model-tree-query.d.ts +3 -0
- package/dist-server/service/twin-model/twin-model-tree-query.js +158 -0
- package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -0
- package/dist-server/service/twin-model/twin-operation.d.ts +61 -0
- package/dist-server/service/twin-model/twin-operation.js +137 -0
- package/dist-server/service/twin-model/twin-operation.js.map +1 -0
- package/dist-server/service/twin-space/move-space.d.ts +65 -0
- package/dist-server/service/twin-space/move-space.js +166 -0
- package/dist-server/service/twin-space/move-space.js.map +1 -0
- package/dist-server/service/twin-space/space-integrity.d.ts +97 -0
- package/dist-server/service/twin-space/space-integrity.js +182 -0
- package/dist-server/service/twin-space/space-integrity.js.map +1 -0
- package/dist-server/service/twin-space/twin-space-area.js +4 -4
- package/dist-server/service/twin-space/twin-space-area.js.map +1 -1
- package/dist-server/service/twin-space/twin-space-representation.js +6 -6
- package/dist-server/service/twin-space/twin-space-representation.js.map +1 -1
- package/dist-server/service/twin-space/twin-space-resolver.d.ts +63 -1
- package/dist-server/service/twin-space/twin-space-resolver.js +365 -18
- package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
- package/dist-server/service/twin-space/twin-space.d.ts +17 -1
- package/dist-server/service/twin-space/twin-space.js +15 -5
- package/dist-server/service/twin-space/twin-space.js.map +1 -1
- package/dist-server/service/twin-state/twin-state-subscription.js +1 -1
- package/dist-server/service/twin-state/twin-state-subscription.js.map +1 -1
- package/dist-server/service/twin-structure/twin-structure.d.ts +1 -1
- package/dist-server/service/twin-structure/twin-structure.js +8 -7
- package/dist-server/service/twin-structure/twin-structure.js.map +1 -1
- package/dist-server/service/twin-target/twin-target-resolver.js +21 -4
- package/dist-server/service/twin-target/twin-target-resolver.js.map +1 -1
- package/dist-shared/axis-read.d.ts +12 -0
- package/dist-shared/axis-read.js +32 -0
- package/dist-shared/axis-read.js.map +1 -0
- package/dist-shared/entity-delta.d.ts +29 -0
- package/dist-shared/entity-delta.js +287 -0
- package/dist-shared/entity-delta.js.map +1 -0
- package/dist-shared/kpi-broadcast.d.ts +4 -0
- package/dist-shared/kpi-broadcast.js +16 -0
- package/dist-shared/kpi-broadcast.js.map +1 -0
- package/dist-shared/twin-level.d.ts +23 -0
- package/dist-shared/twin-level.js +52 -0
- package/dist-shared/twin-level.js.map +1 -0
- package/package.json +14 -12
- package/server/engine/attention-digest.ts +102 -0
- package/server/engine/canonical-ingest.ts +36 -4
- package/server/engine/command-routing.ts +67 -0
- package/server/engine/energy-topology.ts +189 -0
- package/server/engine/index.ts +17 -0
- package/server/engine/kpi-baseline.ts +202 -0
- package/server/engine/kpi-fold.ts +389 -1
- package/server/engine/kpi-query.ts +295 -22
- package/server/engine/kpi-target.ts +24 -2
- package/server/engine/live-attentions.ts +7 -2
- package/server/engine/live-feed-registry.ts +58 -0
- package/server/engine/load-meter.ts +384 -0
- package/server/engine/local-declarations.ts +700 -0
- package/server/engine/model-basis.ts +78 -10
- package/server/engine/model-vocabulary.ts +82 -0
- package/server/engine/oee-accumulator.ts +34 -1
- package/server/engine/operation-basis.ts +100 -0
- package/server/engine/property-effects.ts +199 -0
- package/server/engine/runtime-key.ts +58 -0
- package/server/engine/spec-coverage.ts +3 -3
- package/server/engine/state-axes.ts +60 -0
- package/server/engine/structure-diff.ts +1 -1
- package/server/engine/travel-estimator.ts +11 -6
- package/server/engine/twin-engine.ts +1710 -297
- package/server/engine/warm-start.ts +207 -22
- package/server/index.ts +13 -10
- package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +43 -0
- package/server/migrations/index.ts +7 -0
- package/server/service/index.ts +9 -1
- package/server/service/reference/discovery-result.ts +95 -0
- package/server/service/reference/ingest-space.ts +104 -0
- package/server/service/reference/knob-defaults.ts +59 -0
- package/server/service/reference/reference-live.ts +121 -6
- package/server/service/reference/reference-master.ts +362 -35
- package/server/service/reference/reference-resolver.ts +307 -23
- package/server/service/reference/template-registry.ts +20 -5
- package/server/service/twin-attention/twin-attention-query.ts +43 -6
- package/server/service/twin-audit/command-audit.ts +81 -0
- package/server/service/twin-audit/index.ts +5 -0
- package/server/service/twin-audit/twin-audit-event.ts +112 -0
- package/server/service/twin-audit/twin-audit-query.ts +72 -0
- package/server/service/twin-control/twin-control-mutation.ts +53 -13
- package/server/service/twin-event/twin-event-keys.ts +1 -1
- package/server/service/twin-event/twin-event.ts +52 -15
- package/server/service/twin-forecast/forecast-metrics.ts +60 -0
- package/server/service/twin-forecast/forecast-tuning.ts +40 -0
- package/server/service/twin-forecast/gap-analytics.ts +48 -9
- package/server/service/twin-forecast/twin-forecast-query.ts +211 -38
- package/server/service/twin-instance/twin-instance.ts +127 -22
- package/server/service/twin-journal/twin-journal-query.ts +108 -14
- package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +40 -23
- package/server/service/twin-metrics/twin-metrics-query.ts +60 -3
- package/server/service/twin-model/epcis-coverage.ts +195 -0
- package/server/service/twin-model/iec61850-coverage.ts +117 -0
- package/server/service/twin-model/index.ts +25 -0
- package/server/service/twin-model/isa95-coverage.ts +190 -0
- package/server/service/twin-model/project-structure.ts +199 -0
- package/server/service/twin-model/standard-coverage.ts +35 -0
- package/server/service/twin-model/twin-equipment.ts +150 -0
- package/server/service/twin-model/twin-lineage-query.ts +193 -0
- package/server/service/twin-model/twin-location.ts +140 -0
- package/server/service/twin-model/twin-model-item-query.ts +848 -0
- package/server/service/twin-model/twin-model-mutation.ts +503 -0
- package/server/service/twin-model/twin-model-query.ts +537 -0
- package/server/service/twin-model/twin-model-tree-query.ts +180 -0
- package/server/service/twin-model/twin-operation.ts +150 -0
- package/server/service/twin-space/move-space.ts +262 -0
- package/server/service/twin-space/space-integrity.ts +293 -0
- package/server/service/twin-space/twin-space-area.ts +4 -4
- package/server/service/twin-space/twin-space-representation.ts +6 -6
- package/server/service/twin-space/twin-space-resolver.ts +365 -22
- package/server/service/twin-space/twin-space.ts +40 -6
- package/server/service/twin-state/twin-state-subscription.ts +1 -1
- package/server/service/twin-structure/twin-structure.ts +10 -7
- package/server/service/twin-target/twin-target-resolver.ts +22 -4
- package/shared/axis-read.ts +28 -0
- package/shared/entity-delta.ts +286 -0
- package/shared/kpi-broadcast.ts +13 -0
- package/shared/twin-level.ts +48 -0
- package/test/adopt-structure-live.test.ts +133 -0
- package/test/attention-digest.test.ts +135 -0
- package/test/axis-read.test.ts +87 -0
- package/test/boot-resume.test.ts +205 -0
- package/test/canonical-ingest-vocabularies.test.ts +118 -0
- package/test/capability-mapping.test.ts +5 -5
- package/test/command-routing.test.ts +61 -0
- package/test/declaration-reaches-model.test.ts +178 -0
- package/test/discovery-result.test.ts +75 -0
- package/test/energy-topology.test.ts +113 -0
- package/test/entity-delta.test.ts +195 -25
- package/test/event-time-column.test.ts +67 -0
- package/test/forecast-metrics.test.ts +71 -0
- package/test/forecast-tuning.test.ts +42 -0
- package/test/gap-analytics.test.ts +34 -7
- package/test/ingest-bench.test.ts +9 -9
- package/test/ingest-running-guard.test.ts +136 -0
- package/test/ingest-space.test.ts +78 -0
- package/test/instance-cache-lifecycle.test.ts +110 -0
- package/test/kernel-kind-guard.test.ts +96 -0
- package/test/knob-defaults.test.ts +72 -0
- package/test/kpi-baseline-db.test.ts +216 -0
- package/test/kpi-baseline.test.ts +196 -0
- package/test/kpi-fold.test.ts +393 -2
- package/test/kpi-query-bench.test.ts +130 -0
- package/test/lineage-survives-restart.test.ts +203 -0
- package/test/live-feed-registry.test.ts +67 -0
- package/test/live-kernel-facts.test.ts +161 -0
- package/test/live-mirror-parity.test.ts +54 -14
- package/test/load-meter.test.ts +314 -0
- package/test/local-declarations.test.ts +554 -0
- package/test/master-to-twin.test.ts +128 -36
- package/test/model-basis.test.ts +56 -1
- package/test/model-vocabulary.test.ts +114 -0
- package/test/move-space.test.ts +149 -0
- package/test/mutation-gate.test.ts +122 -0
- package/test/oee-accumulator.test.ts +90 -7
- package/test/operation-basis.test.ts +100 -0
- package/test/operations-capability-db.test.ts +165 -0
- package/test/project-structure-db.test.ts +226 -0
- package/test/projection-reaches-screen.test.ts +272 -0
- package/test/property-effects.test.ts +96 -0
- package/test/registry-key-guard.test.ts +80 -0
- package/test/resync-origin-site.test.ts +77 -0
- package/test/runtime-key.test.ts +66 -0
- package/test/scale-twin-bench.test.ts +7 -7
- package/test/snapshot-freshness.test.ts +60 -0
- package/test/space-integrity.test.ts +223 -0
- package/test/standard-coverage.test.ts +94 -0
- package/test/state-axes.test.ts +74 -0
- package/test/streamline-e2e.test.ts +16 -16
- package/test/structure-revision-db.test.ts +16 -15
- package/test/tenant-registry-db.test.ts +149 -0
- package/test/twin-audit.test.ts +83 -0
- package/test/twin-model-item-db.test.ts +275 -0
- package/test/twin-model-tree-db.test.ts +176 -0
- package/test/twin-origin-resync.test.ts +114 -0
- package/test/warm-start-seam.test.ts +140 -0
- package/test/warm-start.test.ts +223 -4
- package/tsconfig.json +6 -1
- package/tsconfig.shared.json +23 -0
- package/tsconfig.shared.tsbuildinfo +1 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/dist-server/engine/entity-delta.d.ts +0 -19
- package/dist-server/engine/entity-delta.js +0 -161
- package/dist-server/engine/entity-delta.js.map +0 -1
- package/dist-server/service/twin-event/backfill-keys.d.ts +0 -11
- package/dist-server/service/twin-event/backfill-keys.js +0 -63
- package/dist-server/service/twin-event/backfill-keys.js.map +0 -1
- package/dist-server/tsconfig.tsbuildinfo +0 -1
- package/server/engine/entity-delta.ts +0 -169
- package/server/service/twin-event/backfill-keys.ts +0 -72
|
@@ -1,12 +1,22 @@
|
|
|
1
|
-
import { Arg, Ctx, Mutation, Query, Resolver } from 'type-graphql'
|
|
1
|
+
import { Arg, Ctx, Mutation, Query, Resolver, Directive} from 'type-graphql'
|
|
2
2
|
|
|
3
3
|
import { ScalarObject, getRepository } from '@things-factory/shell'
|
|
4
4
|
|
|
5
5
|
import { TwinEngine } from '../../engine/index.js'
|
|
6
|
+
import { readBoardEquipment } from '@operato/twin-kernel'
|
|
7
|
+
|
|
8
|
+
import { applyKnobDefaults, filledKnobKeys } from './knob-defaults.js'
|
|
9
|
+
import type { KnobDef } from './template-registry.js'
|
|
10
|
+
import { knobDefaultsFilled, pickOriginSite, siteEquipmentAdopted } from './reference-master.js'
|
|
6
11
|
import { TwinInstance } from '../twin-instance/twin-instance.js'
|
|
7
12
|
import { TwinReference } from './twin-reference.js'
|
|
8
13
|
import { listReferences, getReference } from './reference-registry.js'
|
|
9
14
|
import { getAdapter, listAdapterTypes, listAdapters } from './reference-adapter.js'
|
|
15
|
+
import {
|
|
16
|
+
discovered, failed, detailOf,
|
|
17
|
+
referenceNotFound, adapterUnknown, discoveryFailed, discoveryTimedOut
|
|
18
|
+
} from './discovery-result.js'
|
|
19
|
+
import { refuseImportSpace } from './ingest-space.js'
|
|
10
20
|
import { startReferenceLiveFeed } from './reference-live.js'
|
|
11
21
|
import { listTemplates, getTemplate } from './template-registry.js'
|
|
12
22
|
|
|
@@ -14,6 +24,97 @@ import { listTemplates, getTemplate } from './template-registry.js'
|
|
|
14
24
|
* Face2 레퍼런스 인제스트 채널(ADR-0018) — 등록된 레퍼런스 소스를 열거하고, 골라서 트윈으로 인제스트.
|
|
15
25
|
* "연결하고 내 것을 불러온다" — 엔티티는 레퍼런스에서 오지 손배선/발명이 아니다.
|
|
16
26
|
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 발견 조회의 시간 상한 — 15초.
|
|
30
|
+
*
|
|
31
|
+
* 어댑터 계약에 시간 제한이 없다. 주소가 틀린 엔드포인트는 답하지 않고, 그러면 화면이 무한히 로딩된다 —
|
|
32
|
+
* 사용자에게는 "실패" 보다 못한 상태다(무엇을 기다리는지 모른 채 기다린다). 실 시스템 조회가 몇 초
|
|
33
|
+
* 걸리는 것은 정상이므로 넉넉히 두되, 끝은 있어야 한다.
|
|
34
|
+
*/
|
|
35
|
+
const DISCOVER_TIMEOUT_MS = 15_000
|
|
36
|
+
const TIMEOUT = 'discover-timeout'
|
|
37
|
+
|
|
38
|
+
function withTimeout<T>(work: Promise<T>, ms: number): Promise<T> {
|
|
39
|
+
return new Promise<T>((resolve, reject) => {
|
|
40
|
+
const timer = setTimeout(() => reject(Object.assign(new Error('timeout'), { code: TIMEOUT })), ms)
|
|
41
|
+
work.then(
|
|
42
|
+
v => { clearTimeout(timer); resolve(v) },
|
|
43
|
+
e => { clearTimeout(timer); reject(e) }
|
|
44
|
+
)
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 거절을 **사용자 언어로 옮겨 보낸다** — 번역은 한 곳에서 한다.
|
|
50
|
+
*
|
|
51
|
+
* ── 왜 서버인가 (2026-08-14) ────────────────────────────────────────────────
|
|
52
|
+
* 실측: 실행 중인 트윈을 다시 프로비저닝하면 「instance "order-check" is running — stop before
|
|
53
|
+
* re-provisioning」 이라는 **영어 한 줄이 토스트로** 떴다(다섯 언어 제품에서).
|
|
54
|
+
*
|
|
55
|
+
* 서버와 앱은 **같은 사전**을 쓰고, 사용자의 현재 언어가 백엔드에 반영된다. 그래서 이 문구는 서버가
|
|
56
|
+
* 옮기는 것이 맞다 — 프레임워크가 이미 같은 규약이다(`authenticate-401-middleware` 가
|
|
57
|
+
* `context.t('error.' + errorCode, detail)` 로 옮긴다).
|
|
58
|
+
*
|
|
59
|
+
* ── 그러면 코드는 왜 함께 보내나 ────────────────────────────────────────────
|
|
60
|
+
* 화면이 **구조로** 다뤄야 하는 거절이 따로 있다(정합성 표의 행·심각도·수리 버튼 — 그것들은
|
|
61
|
+
* `twin.compose.chk.*` 처럼 화면이 옮긴다). 그리고 브라우저가 아닌 소비자(AI·스크립트·API)에게는
|
|
62
|
+
* 코드가 계약이다. 문장과 코드는 서로를 대신하지 않는다.
|
|
63
|
+
*
|
|
64
|
+
* **두 곳에서 같은 코드를 옮기지 않는다** — 문장이 두 벌이 되면 반드시 어긋난다.
|
|
65
|
+
*/
|
|
66
|
+
function refusal(context: ResolverContext, e: any, fallback: string): Record<string, unknown> {
|
|
67
|
+
const code = typeof e?.code === 'string' ? e.code : undefined
|
|
68
|
+
const params = e?.params && typeof e.params === 'object' ? e.params : undefined
|
|
69
|
+
/* 영어 canonical 은 항상 남긴다 — 번역 키가 없으면 그것이 보이고, 로그에는 그것이 남는다. */
|
|
70
|
+
const canonical = e?.message || fallback
|
|
71
|
+
const message =
|
|
72
|
+
code && typeof context.t === 'function'
|
|
73
|
+
? String(context.t(`twin.provision.err.${code}`, { ...(params ?? {}), defaultValue: canonical }))
|
|
74
|
+
: canonical
|
|
75
|
+
return { ok: false, error: message, ...(code ? { errorCode: code } : {}), ...(params ? { errorParams: params } : {}) }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* `siteEquipmentIds` 노브를 이 현장의 설비로 채운다 — **비어 있을 때만.**
|
|
80
|
+
*
|
|
81
|
+
* 사용자가 목록을 직접 준 경우에는 손대지 않는다(고른 것이 정본이다). 현장이 없으면 채울 것도 없다.
|
|
82
|
+
* 등록부의 모델에서 읽는다(런타임이 아니다) — 트윈이 도는 중이 아니어도 그 현장의 설비는 사실이다.
|
|
83
|
+
*/
|
|
84
|
+
async function resolveSiteEquipmentKnobs(
|
|
85
|
+
knobs: readonly KnobDef[] | undefined,
|
|
86
|
+
effective: Record<string, any>,
|
|
87
|
+
domainId: string,
|
|
88
|
+
spaceId?: string
|
|
89
|
+
): Promise<{ key: string; count: number }[]> {
|
|
90
|
+
const targets = (knobs ?? []).filter(k => k?.type === 'siteEquipment')
|
|
91
|
+
if (!targets.length) return []
|
|
92
|
+
const out: { key: string; count: number }[] = []
|
|
93
|
+
let ids: { id: string; kind: string }[] | undefined
|
|
94
|
+
for (const k of targets) {
|
|
95
|
+
const given = effective[k.key]
|
|
96
|
+
if (Array.isArray(given) && given.length) continue // 사용자가 고른 것이 정본이다
|
|
97
|
+
if (!spaceId) {
|
|
98
|
+
effective[k.key] = []
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
if (ids === undefined) {
|
|
102
|
+
const rows = await getRepository(TwinInstance).find({ where: { domain: { id: domainId }, spaceId } })
|
|
103
|
+
const seen = new Map<string, { id: string; kind: string }>()
|
|
104
|
+
for (const r of rows) {
|
|
105
|
+
for (const e of readBoardEquipment((r.model as any) ?? {})) {
|
|
106
|
+
if (!e?.id || seen.has(String(e.id))) continue
|
|
107
|
+
seen.set(String(e.id), { id: String(e.id), kind: String((e as any).kind ?? '') })
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
ids = [...seen.values()]
|
|
111
|
+
}
|
|
112
|
+
effective[k.key] = ids
|
|
113
|
+
out.push({ key: k.key, count: ids.length })
|
|
114
|
+
}
|
|
115
|
+
return out
|
|
116
|
+
}
|
|
117
|
+
|
|
17
118
|
@Resolver()
|
|
18
119
|
export class TwinReferenceResolver {
|
|
19
120
|
/** 등록된 레퍼런스 소스 목록(요약 + 이미 인제스트됐는지). */
|
|
@@ -31,13 +132,14 @@ export class TwinReferenceResolver {
|
|
|
31
132
|
areaCount: m.locations.filter(l => l.role === 'area' || l.role === 'both').length,
|
|
32
133
|
equipmentCount: (m.equipment ?? []).length,
|
|
33
134
|
imported: !!reg,
|
|
34
|
-
running:
|
|
135
|
+
running: TwinEngine.owns(domainId, m.source)
|
|
35
136
|
})
|
|
36
137
|
}
|
|
37
138
|
return out
|
|
38
139
|
}
|
|
39
140
|
|
|
40
141
|
/** 소스를 인제스트 → 공간 + 인스턴스 생성(provision). start 면 기동까지. */
|
|
142
|
+
@Directive('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
41
143
|
@Mutation(returns => ScalarObject, { description: 'Ingest a registered reference source into a twin (space + instance). Optionally start it.' })
|
|
42
144
|
async importFromReference(
|
|
43
145
|
@Arg('source') source: string,
|
|
@@ -48,14 +150,121 @@ export class TwinReferenceResolver {
|
|
|
48
150
|
if (!master) return { ok: false, error: `unknown reference source: ${source}` }
|
|
49
151
|
const domainId = context.state.domain.id
|
|
50
152
|
try {
|
|
51
|
-
|
|
153
|
+
/* 어디서 왔는지 남긴다 — 이것이 없으면 이 트윈은 두 번 다시 읽을 수 없다.
|
|
154
|
+
이 경로는 **메모리에 등록된 마스터 하나**를 그대로 쓰므로 사이트 서술자가 없다(사이트 정체를
|
|
155
|
+
적을 것이 없다). 다시 읽기도 같은 등록본을 집으므로 사이트를 찾을 필요가 없다. */
|
|
156
|
+
const res = await TwinEngine.ingestMaster(domainId, { ...master, origin: { kind: 'reference', source } }, undefined, context.state.user)
|
|
52
157
|
if (start) await TwinEngine.startFromRegistry(domainId, res.instanceId)
|
|
53
158
|
return { ok: true, instanceId: res.instanceId, spaceId: res.spaceId, warnings: res.warnings }
|
|
54
159
|
} catch (e: any) {
|
|
55
|
-
return
|
|
160
|
+
return refusal(context, e, 'ingest failed')
|
|
56
161
|
}
|
|
57
162
|
}
|
|
58
163
|
|
|
164
|
+
/**
|
|
165
|
+
* **원본에서 다시 읽는다** — 이 트윈의 구조를 지금의 원본 모습으로 갱신한다.
|
|
166
|
+
*
|
|
167
|
+
* ── 왜 진입점이 하나여야 하나 ──────────────────────────────────────────────
|
|
168
|
+
* 지금까지 다시 읽기는 **만드는 경로에 얹혀 있었다**(`importFromReference`·`createFromTemplate`).
|
|
169
|
+
* 그래서 이미 있는 트윈을 갱신하려면 만들 때 무엇을 골랐는지 사람이 기억해 같은 화면을 다시
|
|
170
|
+
* 거쳐야 했고, 템플릿으로 세운 트윈은 노브를 어디에도 안 남겨서 **아예 갱신할 방법이 없었다.**
|
|
171
|
+
* 원천이 무엇이든 트윈 자신이 기억하게 하고(`TwinInstance.origin`), 여기 한 곳에서 그것을 따른다.
|
|
172
|
+
*
|
|
173
|
+
* ── 무엇이 일어나나 ────────────────────────────────────────────────────────
|
|
174
|
+
* 원본에서 마스터를 다시 얻어 인제스트에 그대로 태운다. 그다음은 인제스트가 아는 일이다 —
|
|
175
|
+
* 구조가 달라졌으면 새 리비전이 남고, 구조 행이 다시 그려지고, **실행 중인 미러는 멈추지 않고
|
|
176
|
+
* 전환한다.** 실행 중인 시뮬레이션은 거절된다(조건이 바뀐 실험은 다른 실험이다).
|
|
177
|
+
*
|
|
178
|
+
* ── 못 할 때는 못 한다고 한다 ──────────────────────────────────────────────
|
|
179
|
+
* `origin` 이 없는 트윈(이 자리가 생기기 전에 만들어진 것)은 다시 읽을 수 없다. 짐작으로
|
|
180
|
+
* 템플릿을 고르거나 첫 레퍼런스를 집지 않는다 — 엉뚱한 원본으로 현장을 덮는 것이 훨씬 나쁘다.
|
|
181
|
+
*/
|
|
182
|
+
@Directive('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
183
|
+
@Mutation(returns => ScalarObject, {
|
|
184
|
+
description:
|
|
185
|
+
"Re-read a twin's structure from the source it remembers (TwinInstance.origin). Works for both reference- and template-born twins. A running mirror adopts the new structure without stopping; a running simulation is refused."
|
|
186
|
+
})
|
|
187
|
+
async resyncTwin(@Arg('instanceId') instanceId: string, @Ctx() context: ResolverContext): Promise<any> {
|
|
188
|
+
const domainId = context.state.domain.id
|
|
189
|
+
const inst = await getRepository(TwinInstance).findOne({ where: { domain: { id: domainId }, instanceId } })
|
|
190
|
+
if (!inst) return { ok: false, error: `unknown twin: ${instanceId}` }
|
|
191
|
+
|
|
192
|
+
const origin = inst.origin as any
|
|
193
|
+
if (!origin?.kind)
|
|
194
|
+
return {
|
|
195
|
+
ok: false,
|
|
196
|
+
error: `twin "${instanceId}" does not remember where it came from — it cannot be re-read. Re-create it from its template or reference so the origin is recorded.`
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
const master = await this.masterFromOrigin(domainId, origin, instanceId)
|
|
201
|
+
/*
|
|
202
|
+
* **어느 현장의 트윈이었나를 지킨다** — 다시 읽기는 구조만 새로 그리는 일이고, 현장 소속은 그때
|
|
203
|
+
* 사람이 고른 것이다. 기억이 없는 옛 트윈은 **지금 있는 현장**을 그대로 쓴다(빈손으로 두면 템플릿
|
|
204
|
+
* 기본 공간으로 옮겨 간다 — 조용히 현장을 떠나고 그 현장의 값도 잃는다).
|
|
205
|
+
*/
|
|
206
|
+
const keepSpace = origin.spaceId || inst.spaceId
|
|
207
|
+
const res = await TwinEngine.ingestMaster(domainId, master, keepSpace ? { spaceId: keepSpace } : undefined, context.state.user)
|
|
208
|
+
return { ok: true, instanceId: res.instanceId, spaceId: res.spaceId, warnings: res.warnings }
|
|
209
|
+
} catch (e: any) {
|
|
210
|
+
return refusal(context, e, 'resync failed')
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** 기억해 둔 원천에서 마스터를 다시 얻는다 — 어댑터든 템플릿이든 이 한 곳에서 갈린다. */
|
|
215
|
+
private async masterFromOrigin(domainId: string, origin: any, instanceId: string): Promise<any> {
|
|
216
|
+
if (origin.kind === 'template') {
|
|
217
|
+
const tpl = getTemplate(origin.templateId)
|
|
218
|
+
if (!tpl) throw new Error(`unknown template: ${origin.templateId} — this twin cannot be rebuilt`)
|
|
219
|
+
/* 저장해 둔 노브 그대로 다시 짓는다. 기본값을 다시 입히지 않는다 — 그 사이 템플릿의 기본값이
|
|
220
|
+
바뀌었다면, 사용자가 고르지 않은 값이 조용히 현장을 바꾸게 된다. */
|
|
221
|
+
const { master } = tpl.build(origin.params ?? {})
|
|
222
|
+
master.origin = origin
|
|
223
|
+
return master
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/* 레퍼런스 — 메모리 등록본이 있으면 그것, 없으면 저장된 어댑터 연결로 다시 가져온다.
|
|
227
|
+
(부팅 뒤에는 메모리 등록본이 비어 있으므로 어댑터 경로가 실제로 쓰이는 길이다.) */
|
|
228
|
+
const inMemory = getReference(origin.source)
|
|
229
|
+
if (inMemory) return { ...inMemory, origin }
|
|
230
|
+
|
|
231
|
+
const ref = await getRepository(TwinReference).findOne({ where: { domain: { id: domainId }, source: origin.source } })
|
|
232
|
+
if (!ref) throw new Error(`reference "${origin.source}" is no longer registered — nothing to re-read from`)
|
|
233
|
+
const adapter = getAdapter(ref.adapterType)
|
|
234
|
+
if (!adapter) throw new Error(`no adapter for type: ${ref.adapterType}`)
|
|
235
|
+
const cfg = ref.connectionConfig ?? {}
|
|
236
|
+
const sites = await adapter.discoverSites(cfg)
|
|
237
|
+
|
|
238
|
+
/*
|
|
239
|
+
* ── 무엇을 무엇과 견주나 (2026-08-18 실측으로 고침) ────────────────────────
|
|
240
|
+
*
|
|
241
|
+
* 예전에는 `master.source === origin.source` 로 사이트를 찾았다. 그런데 `origin.source` 는 **레퍼런스
|
|
242
|
+
* 등록 이름**이고(그 참조의 사이트 전부에 같다) `master.source` 는 **사이트별 인스턴스 id** 다 —
|
|
243
|
+
* 서로 다른 것을 견줬으니 사이트가 멀쩡히 있어도 「더 이상 보고하지 않는다」로 거절했다. 사이트가
|
|
244
|
+
* 여럿인 참조에서 온 트윈은 그래서 **전부** 재동기가 불가했고, 오류 문장은 원인을 참조 쪽으로
|
|
245
|
+
* 잘못 가리켰다.
|
|
246
|
+
*
|
|
247
|
+
* 이제 ① 기억해 둔 사이트로 곧장 간다(`origin.siteId`) ② 기억이 없는 옛 트윈은 사이트를 살펴
|
|
248
|
+
* **마스터의 정체(=이 인스턴스 id)** 와 견준다. 어느 쪽도 못 찾으면 **첫 사이트로 때우지 않는다** —
|
|
249
|
+
* 다른 현장의 구조를 이 트윈에 덮는 것은 오류가 아니라 조용히 틀린 공장으로 나타난다.
|
|
250
|
+
*/
|
|
251
|
+
const picked = pickOriginSite(sites, origin)
|
|
252
|
+
if (picked.site) return { ...(await adapter.fetchMaster(cfg, picked.site)), origin }
|
|
253
|
+
if (picked.reason === 'site-gone')
|
|
254
|
+
throw new Error(
|
|
255
|
+
`reference "${origin.source}" no longer reports site "${origin.siteId}" — refusing to overwrite this twin with a different site`
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
for (const site of sites) {
|
|
259
|
+
const master = await adapter.fetchMaster(cfg, site)
|
|
260
|
+
if (master.source === instanceId) return { ...master, origin: { ...origin, siteId: site.siteId } }
|
|
261
|
+
}
|
|
262
|
+
throw new Error(
|
|
263
|
+
`reference "${origin.source}" does not report a site that rebuilds twin "${instanceId}" ` +
|
|
264
|
+
`(this twin was imported before the site identity was recorded, and no site's master carries its id) — refusing to overwrite it with a different site`
|
|
265
|
+
)
|
|
266
|
+
}
|
|
267
|
+
|
|
59
268
|
/** 등록된 트윈 템플릿(파라미터화 아키타입) 목록 — 노브 스키마 포함(클라이언트 폼용). */
|
|
60
269
|
@Query(returns => [ScalarObject], { description: 'List registered twin templates (parameterized archetypes) with their knob schema.' })
|
|
61
270
|
async twinTemplates(@Ctx() _context: ResolverContext): Promise<any[]> {
|
|
@@ -63,32 +272,63 @@ export class TwinReferenceResolver {
|
|
|
63
272
|
}
|
|
64
273
|
|
|
65
274
|
/** 템플릿 + 노브 → 합성 마스터 → 인제스트(+기동+시나리오). 레퍼런스 import 와 같은 경로, 원천만 합성. */
|
|
275
|
+
@Directive('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
66
276
|
@Mutation(returns => ScalarObject, { description: 'Instantiate a twin template with params → synthetic master → ingest (+start+scenario). Returns {ok, instanceId, spaceId}.' })
|
|
67
277
|
async createFromTemplate(
|
|
68
278
|
@Arg('templateId') templateId: string,
|
|
69
279
|
@Arg('params', type => ScalarObject) params: any,
|
|
70
280
|
@Ctx() context: ResolverContext,
|
|
71
|
-
@Arg('start', { nullable: true }) start?: boolean
|
|
281
|
+
@Arg('start', { nullable: true }) start?: boolean,
|
|
282
|
+
@Arg('spaceId', { nullable: true, description: 'Existing space to co-locate this twin into (add a lens to a site). Omit to create its own space.' }) spaceId?: string
|
|
72
283
|
): Promise<any> {
|
|
73
284
|
const tpl = getTemplate(templateId)
|
|
74
285
|
if (!tpl) return { ok: false, error: `unknown template: ${templateId}` }
|
|
75
286
|
const domainId = context.state.domain.id
|
|
76
287
|
try {
|
|
77
|
-
|
|
78
|
-
|
|
288
|
+
/*
|
|
289
|
+
* **빠진 노브를 서버가 채운다.** 기본값을 아는 곳은 노브 스키마(계약)인데 그것을 적용하는 곳이
|
|
290
|
+
* 위자드뿐이었다 — 그래서 화면을 거치지 않는 호출자(AI·스크립트·API)가 파라미터를 일부만 주면
|
|
291
|
+
* 템플릿 안에서 발생률이 0 이 되고 **아무것도 하지 않는 트윈**이 조용히 만들어졌다.
|
|
292
|
+
* 채운 노브 이름은 경고로 함께 돌려준다 — 조용히 채우는 것도 조용히 비우는 것만큼 위험하다.
|
|
293
|
+
*/
|
|
294
|
+
const filled = filledKnobKeys(tpl.knobs, params)
|
|
295
|
+
const effective = applyKnobDefaults(tpl.knobs, params)
|
|
296
|
+
/*
|
|
297
|
+
* **이 현장에 이미 있는 설비를 노브 값으로 확정한다** (`siteEquipmentIds`).
|
|
298
|
+
*
|
|
299
|
+
* 에너지 트윈이 「공장이 돌면 전기가 오른다」를 말하려면 부하를 **이웃 트윈의 설비 id 로**
|
|
300
|
+
* 선언해야 한다(설비 하나에 사실 둘). 그 목록을 빌드할 때마다 현장에서 다시 읽으면, 사이에
|
|
301
|
+
* 설비가 늘거나 준 만큼 **다시 지을 때 다른 트윈**이 된다. 그래서 여기서 한 번 해석하고
|
|
302
|
+
* 그 결과를 노브 값으로 남긴다(아래 `origin.params` 에 그대로 저장된다).
|
|
303
|
+
*/
|
|
304
|
+
const adopted = await resolveSiteEquipmentKnobs(tpl.knobs, effective, domainId, spaceId)
|
|
305
|
+
const { master, scenario } = tpl.build(effective)
|
|
306
|
+
/* **무엇으로 지었는지 남긴다.** 합성은 결정적이므로 같은 노브면 같은 마스터가 다시 나온다 —
|
|
307
|
+
그래서 템플릿 트윈도 어댑터 트윈과 똑같이 다시 읽을 수 있다. 남기지 않으면 그 트윈은
|
|
308
|
+
첫날의 모습에 영원히 머문다(원본을 잃은 트윈). */
|
|
309
|
+
/* 고른 현장도 남긴다 — 없으면 다시 읽기가 트윈을 템플릿 기본 공간으로 옮겨 버린다(현장 값도 잃는다). */
|
|
310
|
+
master.origin = { kind: 'template', templateId, params: effective, ...(spaceId ? { spaceId } : {}) }
|
|
311
|
+
/* 공간 선택은 템플릿을 거치지 않는다 — 템플릿마다 이 노브를 알고 있어야 한다면 새 템플릿이
|
|
312
|
+
조용히 무시하게 된다. 합성된 마스터에 한 곳에서 얹는다(레퍼런스 인제스트와 같은 경로). */
|
|
313
|
+
const res = await TwinEngine.ingestMaster(domainId, master, { spaceId }, context.state.user)
|
|
79
314
|
if (start !== false) {
|
|
80
315
|
await TwinEngine.startFromRegistry(domainId, res.instanceId)
|
|
81
316
|
if (scenario) {
|
|
82
|
-
const runtime = TwinEngine.runtime(res.instanceId)
|
|
317
|
+
const runtime = TwinEngine.runtime(domainId, res.instanceId)
|
|
83
318
|
if (runtime) {
|
|
84
319
|
runtime.scenario.load(scenario)
|
|
85
320
|
runtime.scenario.start()
|
|
86
321
|
}
|
|
87
322
|
}
|
|
88
323
|
}
|
|
89
|
-
|
|
324
|
+
const warnings = [
|
|
325
|
+
...(filled.length ? [knobDefaultsFilled(filled)] : []),
|
|
326
|
+
...adopted.filter(a => a.count > 0).map(a => siteEquipmentAdopted(a.key, a.count)),
|
|
327
|
+
...res.warnings
|
|
328
|
+
]
|
|
329
|
+
return { ok: true, instanceId: res.instanceId, spaceId: res.spaceId, warnings }
|
|
90
330
|
} catch (e: any) {
|
|
91
|
-
return
|
|
331
|
+
return refusal(context, e, 'template instantiation failed')
|
|
92
332
|
}
|
|
93
333
|
}
|
|
94
334
|
|
|
@@ -155,7 +395,7 @@ export class TwinReferenceResolver {
|
|
|
155
395
|
.filter(p => instMap.has(p.instanceId))
|
|
156
396
|
.map(p => {
|
|
157
397
|
const inst = instMap.get(p.instanceId) as any
|
|
158
|
-
return { instanceId: p.instanceId, spaceId: p.spaceId, siteId: p.siteId, kind: inst.kind, status: inst.status, running:
|
|
398
|
+
return { instanceId: p.instanceId, spaceId: p.spaceId, siteId: p.siteId, kind: inst.kind, status: inst.status, running: TwinEngine.owns(domainId, p.instanceId) }
|
|
159
399
|
})
|
|
160
400
|
return {
|
|
161
401
|
source: r.source,
|
|
@@ -188,6 +428,7 @@ export class TwinReferenceResolver {
|
|
|
188
428
|
return listAdapters().map(a => ({ type: a.type, ...(a.meta ?? {}) }))
|
|
189
429
|
}
|
|
190
430
|
|
|
431
|
+
@Directive('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
191
432
|
@Mutation(returns => ScalarObject, { description: 'Create or update a tenant-scoped twin reference (persisted).' })
|
|
192
433
|
async saveTwinReference(
|
|
193
434
|
@Arg('source') source: string,
|
|
@@ -219,6 +460,7 @@ export class TwinReferenceResolver {
|
|
|
219
460
|
}
|
|
220
461
|
|
|
221
462
|
/** 연결 해제 — removeTwins=true 면 이 레퍼런스가 만든 트윈(provenance)도 함께 제거(라이프사이클 연쇄). 기본은 트윈 보존(분리). */
|
|
463
|
+
@Directive('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
222
464
|
@Mutation(returns => Boolean, { description: 'Delete a tenant-scoped reference. If removeTwins, also remove the twin instances it produced (provenance cascade); otherwise twins are kept (detached).' })
|
|
223
465
|
async deleteTwinReference(
|
|
224
466
|
@Arg('source') source: string,
|
|
@@ -241,6 +483,7 @@ export class TwinReferenceResolver {
|
|
|
241
483
|
return true
|
|
242
484
|
}
|
|
243
485
|
|
|
486
|
+
@Directive('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
244
487
|
@Mutation(returns => ScalarObject, { description: 'Test a reference connection via its adapter; updates status.' })
|
|
245
488
|
async testReferenceConnection(@Arg('source') source: string, @Ctx() context: ResolverContext): Promise<any> {
|
|
246
489
|
const repo = getRepository(TwinReference)
|
|
@@ -262,30 +505,54 @@ export class TwinReferenceResolver {
|
|
|
262
505
|
* P1 은 대개 N=1(virtual/단일). start 면 각 인스턴스 기동. 라이브 피드·mode 는 face2-inbound-live 소유.
|
|
263
506
|
*/
|
|
264
507
|
/** 레퍼런스가 노출하는 사이트/공장 발견 — 트윈 생성 시 "어느 공장" 선택용(§2.2). */
|
|
265
|
-
@Query(returns =>
|
|
266
|
-
async discoverReferenceSites(@Arg('source') source: string, @Ctx() context: ResolverContext): Promise<any
|
|
508
|
+
@Query(returns => ScalarObject, { description: 'Discover sites/factories exposed by a reference. Returns { sites, error? } — an empty list with no error means the system genuinely has none, which is different from a failure.' })
|
|
509
|
+
async discoverReferenceSites(@Arg('source') source: string, @Ctx() context: ResolverContext): Promise<any> {
|
|
510
|
+
/*
|
|
511
|
+
* **"못 찾았다" 와 "없다" 를 구분한다.** 예전에는 넷을 빈 배열 하나로 접었고(레퍼런스 없음·어댑터
|
|
512
|
+
* 미상·조회 실패·정말 0건), 화면은 그 모두를 "사이트를 찾지 못했습니다" 라고만 말했다. 실 시스템
|
|
513
|
+
* 연결에서 가장 자주 막히는 자리인데 자격이 틀렸는지 주소가 틀렸는지 알 수 없었다.
|
|
514
|
+
* 판정과 문장 형태는 `discovery-result`(순수)가 갖는다.
|
|
515
|
+
*/
|
|
267
516
|
const ref = await getRepository(TwinReference).findOne({ where: { domain: { id: context.state.domain.id }, source } })
|
|
268
|
-
if (!ref) return
|
|
517
|
+
if (!ref) return failed(referenceNotFound(source))
|
|
269
518
|
const adapter = getAdapter(ref.adapterType)
|
|
270
|
-
if (!adapter) return
|
|
519
|
+
if (!adapter) return failed(adapterUnknown(ref.adapterType, source))
|
|
271
520
|
try {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
521
|
+
/*
|
|
522
|
+
* **기다려도 답이 없으면 사유를 준다.** 어댑터 계약에 시간 제한이 없어서, 주소가 틀린 엔드포인트는
|
|
523
|
+
* 화면을 무한히 돌게 만들었다 — 무한히 도는 화면은 조용한 실패의 다른 얼굴이다.
|
|
524
|
+
* 상한은 여기서 정한다(어댑터마다 다르게 두면 사용자가 어느 것을 기다리는지 알 수 없다).
|
|
525
|
+
*/
|
|
526
|
+
const sites = await withTimeout(adapter.discoverSites(ref.connectionConfig ?? {}), DISCOVER_TIMEOUT_MS)
|
|
527
|
+
return discovered(sites)
|
|
528
|
+
} catch (e: any) {
|
|
529
|
+
if (e?.code === TIMEOUT) return failed(discoveryTimedOut(source, DISCOVER_TIMEOUT_MS / 1000))
|
|
530
|
+
return failed(discoveryFailed(source, detailOf(e)))
|
|
275
531
|
}
|
|
276
532
|
}
|
|
277
533
|
|
|
278
534
|
/**
|
|
279
535
|
* 트윈 생성 — 레퍼런스에서 사이트 발견 → (siteIds 있으면 그 공장만) → fetchMaster → provision. 사이트별 resilient.
|
|
280
536
|
* 여러 공장을 관할하는 소스는 siteIds 로 "어느 공장"을 골라 생성(1:N, §2.1·§2.2).
|
|
537
|
+
*
|
|
538
|
+
* ── 현장 선택이 이 경로에도 있다 (2026-08-13) ────────────────────────────
|
|
539
|
+
* 한 현실을 여러 렌즈가 비춘다(WMS·MES·YMS). 그런데 **그 선택이 템플릿 경로에만** 있어서
|
|
540
|
+
* (`createFromTemplate(spaceId)`), 실 시스템에서 가져온 트윈은 어댑터가 파생한 공간 id 가 우연히
|
|
541
|
+
* 일치할 때만 한 현장에 모였다 — 사람이 "이 MES 와 같은 현장에 넣어라" 를 말할 방법이 없었다.
|
|
542
|
+
* 데이터로는 가능한 일이 저작으로는 불가능한, `ingest-space` 가 이미 지적한 그 비대칭이다.
|
|
543
|
+
*
|
|
544
|
+
* 판정은 그 순수 모듈이 갖는다: 없는 공간은 거절(`resolveIngestSpace`), 사이트 여럿을 한 공간에
|
|
545
|
+
* 넣는 것도 거절(`refuseImportSpace`) — 서로 다른 현장이 조용히 합쳐지는 것을 막는다.
|
|
281
546
|
*/
|
|
282
|
-
@
|
|
547
|
+
@Directive('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
548
|
+
@Mutation(returns => ScalarObject, { description: 'Create twins from a reference: discover sites → filter by siteIds (if given) → fetch master(s) → provision. spaceId co-locates the twin into an existing space (one site only). start=false to only provision; live=true to run as a mirror (adapter openLiveFeed → live projector) instead of sim.' })
|
|
283
549
|
async importTwinReference(
|
|
284
550
|
@Arg('source') source: string,
|
|
285
551
|
@Ctx() context: ResolverContext,
|
|
286
552
|
@Arg('siteIds', type => [String], { nullable: true }) siteIds?: string[],
|
|
287
553
|
@Arg('start', { nullable: true }) start?: boolean,
|
|
288
|
-
@Arg('live', { nullable: true }) live?: boolean
|
|
554
|
+
@Arg('live', { nullable: true }) live?: boolean,
|
|
555
|
+
@Arg('spaceId', { nullable: true, description: 'Existing space to co-locate this twin into (add a lens to a site). Only when importing a single site. Omit to give each site its own space.' }) spaceId?: string
|
|
289
556
|
): Promise<any> {
|
|
290
557
|
const domainId = context.state.domain.id
|
|
291
558
|
const repo = getRepository(TwinReference)
|
|
@@ -297,12 +564,22 @@ export class TwinReferenceResolver {
|
|
|
297
564
|
try {
|
|
298
565
|
let sites = await adapter.discoverSites(cfg)
|
|
299
566
|
if (siteIds?.length) sites = sites.filter(s => siteIds.includes(s.siteId))
|
|
567
|
+
/* 아무것도 쓰기 전에 거절한다 — 한 사이트를 넘으면 "이 현장에 더한다" 가 성립하지 않는다. */
|
|
568
|
+
const refusal = refuseImportSpace(sites.length, spaceId)
|
|
569
|
+
if (refusal) return { ok: false, error: refusal.message, errorCode: refusal.code, errorParams: refusal.params }
|
|
300
570
|
const instances: any[] = []
|
|
301
571
|
const failed: any[] = []
|
|
302
572
|
for (const site of sites) {
|
|
303
573
|
try {
|
|
304
574
|
const master = await adapter.fetchMaster(cfg, site)
|
|
305
|
-
|
|
575
|
+
/* 어느 레퍼런스의 **어느 사이트**에서 왔는지 남긴다 — 다시 읽기가 같은 어댑터·같은 사이트로 돌아오려면
|
|
576
|
+
사이트 정체가 있어야 한다(`source` 는 참조 이름이라 사이트 전부에 같다). */
|
|
577
|
+
const res = await TwinEngine.ingestMaster(
|
|
578
|
+
domainId,
|
|
579
|
+
{ ...master, origin: { kind: 'reference', source, siteId: site.siteId, ...(spaceId ? { spaceId } : {}) } },
|
|
580
|
+
{ spaceId },
|
|
581
|
+
context.state.user
|
|
582
|
+
)
|
|
306
583
|
if (start !== false) {
|
|
307
584
|
// live=true + 어댑터가 openLiveFeed 지원 → mirror(실 이벤트 미러). 아니면 sim 기동(fallback).
|
|
308
585
|
const wentLive = live ? await startReferenceLiveFeed(domainId, ref.adapterType, cfg, site, res.instanceId) : false
|
|
@@ -323,14 +600,21 @@ export class TwinReferenceResolver {
|
|
|
323
600
|
{
|
|
324
601
|
status: 'connected',
|
|
325
602
|
lastSyncedAt: new Date().toISOString(),
|
|
326
|
-
|
|
603
|
+
/*
|
|
604
|
+
* **영어 canonical 로 남긴다.** 이 값은 DB 에 저장되고 연결 화면이 그대로 뿌린다 — 예전에는
|
|
605
|
+
* 한글 산문(`N개 사이트 실패`)이라 다섯 언어 화면에 한글이 새어 나갔다. 어댑터가 주는 사유도
|
|
606
|
+
* 같은 필드에 들어오므로(그것은 원 시스템의 말이다) 키가 아니라 영어 문장으로 통일한다.
|
|
607
|
+
* 어느 사이트가 왜 실패했는지는 `failed[]` 가 답한다 — 화면이 그것을 보여 준다.
|
|
608
|
+
*/
|
|
609
|
+
lastError: failed.length ? `${failed.length} of ${sites.length} site(s) failed: ${failed.map((f: any) => f.siteId).join(', ')}` : null,
|
|
327
610
|
scopeSpec: { ...((ref.scopeSpec as any) ?? {}), produced: [...prev, ...producedNew] }
|
|
328
611
|
} as any
|
|
329
612
|
)
|
|
330
613
|
return { ok: instances.length > 0, instances, failed }
|
|
331
614
|
} catch (e: any) {
|
|
615
|
+
/* 레퍼런스 행에는 **영어 canonical** 을 남긴다 — 저장되는 값은 언어중립이어야 다시 읽을 수 있다. */
|
|
332
616
|
await repo.update({ id: ref.id }, { status: 'error', lastError: e?.message ?? 'ingest failed' } as any)
|
|
333
|
-
return
|
|
617
|
+
return refusal(context, e, 'ingest failed')
|
|
334
618
|
}
|
|
335
619
|
}
|
|
336
620
|
}
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
* 트윈 템플릿(아키타입) 레지스트리 — 파라미터화된 합성 레퍼런스 마스터 빌더.
|
|
3
3
|
* 레퍼런스(reference-registry)가 "고정 마스터"라면, 템플릿은 "노브로 파라미터화된 마스터 빌더".
|
|
4
4
|
* 둘 다 같은 인제스트 경로(ingestMaster)로 흐른다 — 원천이 고정이냐 합성이냐만 다르다(board-authoring §10).
|
|
5
|
-
* 앱(operato-twin)이 bootstrap 에서 자기 아키타입을 registerTemplate 한다. 표현(map/image/
|
|
5
|
+
* 앱(operato-twin)이 bootstrap 에서 자기 아키타입을 registerTemplate 한다. 표현(map/image/model) 무관.
|
|
6
6
|
*/
|
|
7
|
+
import type { DomainSystem } from '@operato/twin-kernel'
|
|
8
|
+
|
|
7
9
|
import type { ReferenceMaster } from './reference-master.js'
|
|
8
10
|
|
|
9
11
|
/** 사용자 노출 파라미터(노브) 정의 — 클라이언트가 이 스키마로 폼을 렌더한다. */
|
|
@@ -11,7 +13,14 @@ export interface KnobDef {
|
|
|
11
13
|
key: string
|
|
12
14
|
/** 'geo' = 위경도 지점(지도에서 클릭 선택). 값은 { lat, lon }. 사용자가 좌표를 몰라도 지도로 지정. */
|
|
13
15
|
label: string
|
|
14
|
-
|
|
16
|
+
/**
|
|
17
|
+
* 'siteEquipment' = **이 현장에 이미 있는 설비들**(`{id, kind}`). 값이 비어 있으면 만들 때 한 번
|
|
18
|
+
* 해석해서 채우고, 채운 결과를 노브 값으로 **남긴다**(`origin.params`). 그래야 나중에 다시 지어도
|
|
19
|
+
* 같은 트윈이 나온다 — 그때그때 현장을 다시 읽으면 사이에 설비가 늘고 준 만큼 다른 트윈이 된다.
|
|
20
|
+
*
|
|
21
|
+
* 종류를 함께 싣는 이유: 무엇을 받아들일지는 **템플릿이 종류로 판단**한다(이름으로 짐작하지 않는다).
|
|
22
|
+
*/
|
|
23
|
+
type: 'number' | 'text' | 'select' | 'geo' | 'siteEquipment'
|
|
15
24
|
default: any
|
|
16
25
|
min?: number
|
|
17
26
|
max?: number
|
|
@@ -40,8 +49,13 @@ export interface TemplateDetail {
|
|
|
40
49
|
/** 완성 라우트 이름(공정 라우트 참조). */
|
|
41
50
|
route?: string
|
|
42
51
|
}
|
|
43
|
-
/**
|
|
44
|
-
|
|
52
|
+
/**
|
|
53
|
+
* 표준 정합 매핑 — 개념이 **어느 표준의 무엇에 대응하나.**
|
|
54
|
+
*
|
|
55
|
+
* 표준 칸이 EPCIS·ISA-95 둘로 굳어 있었다. 에너지 템플릿은 그 둘이 앵커가 아니라 적을 자리가 없었다 —
|
|
56
|
+
* 커널의 `StandardClass` 도 같은 이유로 열었다(`iso50001`·`iec61850`). 대응할 이름이 없으면 **비운다**.
|
|
57
|
+
*/
|
|
58
|
+
standardsMap?: { concept: string; epcis?: string; isa95?: string; iso50001?: string; iec61850?: string }[]
|
|
45
59
|
/** "이 템플릿이 맞나?" 판단 도움 항목. */
|
|
46
60
|
fits?: string[]
|
|
47
61
|
}
|
|
@@ -49,7 +63,8 @@ export interface TemplateDetail {
|
|
|
49
63
|
export interface TwinTemplate {
|
|
50
64
|
id: string
|
|
51
65
|
label: string
|
|
52
|
-
|
|
66
|
+
/** 커널 카탈로그가 정본 — 종류가 늘면 여기 손댈 것이 없다(`DomainSystem`). */
|
|
67
|
+
system: DomainSystem
|
|
53
68
|
description?: string
|
|
54
69
|
/** 상세 설명(선택) — 설명 페이지/패널이 렌더. */
|
|
55
70
|
detail?: TemplateDetail
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { Arg, Ctx, Query, Resolver } from 'type-graphql'
|
|
1
|
+
import { Arg, Ctx, Int, Query, Resolver } from 'type-graphql'
|
|
2
2
|
|
|
3
3
|
import { ScalarObject } from '@things-factory/shell'
|
|
4
4
|
|
|
5
5
|
import { TwinEngine } from '../../engine/index.js'
|
|
6
|
+
import { digestAttentions } from '../../engine/attention-digest.js'
|
|
6
7
|
|
|
7
8
|
/*
|
|
8
9
|
* 주목 신호(Attention) 질의 — 현재 스냅샷의 요약된 "봐야 할 것".
|
|
@@ -16,11 +17,47 @@ export class TwinAttentionQuery {
|
|
|
16
17
|
@Query(returns => [ScalarObject], {
|
|
17
18
|
description: 'Active attention signals (things to look at) derived from the current twin snapshot — severity (ISA-18.2), lifecycle state (OPC UA A&C), space anchor, and suggested action.'
|
|
18
19
|
})
|
|
19
|
-
async twinAttentions(
|
|
20
|
-
|
|
20
|
+
async twinAttentions(
|
|
21
|
+
@Ctx() context: ResolverContext,
|
|
22
|
+
@Arg('instanceId', { nullable: true }) instanceId?: string,
|
|
23
|
+
/*
|
|
24
|
+
* **공간으로 물으면 그 현장의 모든 렌즈에서 모아 답한다** — 한 현실을 WMS·MES·YMS 가 함께 비추므로
|
|
25
|
+
* "이 현장에서 봐야 할 것" 은 렌즈 하나로 답할 수 없다. 예전에는 화면이 인스턴스마다 물어 손으로
|
|
26
|
+
* 합쳤고, 그러면 정렬 규칙이 화면마다 갈린다.
|
|
27
|
+
*
|
|
28
|
+
* 경고는 **합집합이 그대로 뜻이 있는 유일한 렌즈**다. 예측은 합칠 수 없다(P50 두 개를 더할 수 없다) —
|
|
29
|
+
* 그래서 예측은 여전히 인스턴스별로 답하고 화면이 나란히 놓는다.
|
|
30
|
+
*/
|
|
31
|
+
@Arg('spaceId', { nullable: true }) spaceId?: string,
|
|
32
|
+
/** 상위 몇 건만 — 말하지 않으면 자르지 않는다. 전체 개수는 `twinAttentionDigest` 가 함께 답한다. */
|
|
33
|
+
@Arg('limit', type => Int, { nullable: true }) limit?: number
|
|
34
|
+
): Promise<any[]> {
|
|
35
|
+
if (spaceId) return (await TwinEngine.attentionsOfSpace(context.state.domain.id, spaceId, limit ?? undefined)).attentions
|
|
36
|
+
if (!instanceId || !TwinEngine.owns(context.state.domain.id, instanceId)) return [] // 테넌트 격리
|
|
21
37
|
// 라이브 커널 스냅샷(fresh) 우선 — snapshot()=resync() 는 캐시라 커맨드 델타 지연(recover 와 동일 원천 사용).
|
|
22
|
-
const kernel = TwinEngine.kernel(instanceId)
|
|
23
|
-
const snap = kernel?.getSnapshot?.() ?? TwinEngine.snapshot(instanceId)
|
|
24
|
-
|
|
38
|
+
const kernel = TwinEngine.kernel(context.state.domain.id, instanceId)
|
|
39
|
+
const snap = kernel?.getSnapshot?.() ?? TwinEngine.snapshot(context.state.domain.id, instanceId)
|
|
40
|
+
const list = snap?.attentions ?? []
|
|
41
|
+
return limit && limit > 0 ? digestAttentions(list, limit).attentions : list
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 잘랐다면 **몇 건을 잘랐는지**와 자리 색 요약까지 — 목록만 주면 화면이 "N건 더" 를 말할 수 없다.
|
|
46
|
+
* 자리별 최고 심각도는 **전량을 근거로** 접는다(상위 N 으로 접으면 20위 밖 신호를 가진 자리가 색을 잃는다).
|
|
47
|
+
*/
|
|
48
|
+
@Query(returns => ScalarObject, {
|
|
49
|
+
description: 'Attention signals for a space (all its lenses) or one twin, with the total count and the per-location highest severity folded from all of them.'
|
|
50
|
+
})
|
|
51
|
+
async twinAttentionDigest(
|
|
52
|
+
@Ctx() context: ResolverContext,
|
|
53
|
+
@Arg('spaceId', { nullable: true }) spaceId?: string,
|
|
54
|
+
@Arg('instanceId', { nullable: true }) instanceId?: string,
|
|
55
|
+
@Arg('limit', type => Int, { nullable: true }) limit?: number
|
|
56
|
+
): Promise<any> {
|
|
57
|
+
if (spaceId) return TwinEngine.attentionsOfSpace(context.state.domain.id, spaceId, limit ?? undefined)
|
|
58
|
+
if (!instanceId || !TwinEngine.owns(context.state.domain.id, instanceId)) return { attentions: [], attentionTotal: 0, severityByLocation: {} }
|
|
59
|
+
const kernel = TwinEngine.kernel(context.state.domain.id, instanceId)
|
|
60
|
+
const snap = kernel?.getSnapshot?.() ?? TwinEngine.snapshot(context.state.domain.id, instanceId)
|
|
61
|
+
return digestAttentions((snap?.attentions ?? []).map((a: any) => ({ ...a, instanceId })), limit ?? undefined)
|
|
25
62
|
}
|
|
26
63
|
}
|