@things-factory/headless-twin 10.1.277 → 10.1.279
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/package.json +21 -7
- package/dist-server/engine/attention-digest.js.map +0 -1
- package/dist-server/engine/canonical-ingest.js.map +0 -1
- package/dist-server/engine/checkpoint-fold.js.map +0 -1
- package/dist-server/engine/command-routing.js.map +0 -1
- package/dist-server/engine/declared-stimulus.js.map +0 -1
- package/dist-server/engine/energy-topology.js.map +0 -1
- package/dist-server/engine/event-time.js.map +0 -1
- package/dist-server/engine/index.js.map +0 -1
- package/dist-server/engine/ingest-dedupe.js.map +0 -1
- package/dist-server/engine/ingest-health.js.map +0 -1
- package/dist-server/engine/integration-coverage.js.map +0 -1
- package/dist-server/engine/integration-probes.js.map +0 -1
- package/dist-server/engine/integration-runner.js.map +0 -1
- package/dist-server/engine/integration-target-profile.js.map +0 -1
- package/dist-server/engine/kpi-baseline.js.map +0 -1
- package/dist-server/engine/kpi-fold.js.map +0 -1
- package/dist-server/engine/kpi-query.js.map +0 -1
- package/dist-server/engine/kpi-target.js.map +0 -1
- package/dist-server/engine/live-attentions.js.map +0 -1
- package/dist-server/engine/live-feed-registry.js.map +0 -1
- package/dist-server/engine/load-meter.js.map +0 -1
- package/dist-server/engine/local-declarations.js.map +0 -1
- package/dist-server/engine/log.js.map +0 -1
- package/dist-server/engine/loop-lag.js.map +0 -1
- package/dist-server/engine/measured-estimator.js.map +0 -1
- package/dist-server/engine/measured-yield.js.map +0 -1
- package/dist-server/engine/model-basis.js.map +0 -1
- package/dist-server/engine/model-gap.js.map +0 -1
- package/dist-server/engine/model-refusal.js.map +0 -1
- package/dist-server/engine/model-vocabulary.js.map +0 -1
- package/dist-server/engine/oee-accumulator.js.map +0 -1
- package/dist-server/engine/operation-basis.js.map +0 -1
- package/dist-server/engine/producer-guard.js.map +0 -1
- package/dist-server/engine/property-effects.js.map +0 -1
- package/dist-server/engine/read-time.js.map +0 -1
- package/dist-server/engine/restart-policy.js.map +0 -1
- package/dist-server/engine/runtime-key.js.map +0 -1
- package/dist-server/engine/spec-coverage.js.map +0 -1
- package/dist-server/engine/stage-path.js.map +0 -1
- package/dist-server/engine/state-axes.js.map +0 -1
- package/dist-server/engine/structure-diff.js.map +0 -1
- package/dist-server/engine/travel-estimator.js.map +0 -1
- package/dist-server/engine/twin-engine.js.map +0 -1
- package/dist-server/engine/twin-state-channel.js.map +0 -1
- package/dist-server/engine/warm-start.js.map +0 -1
- package/dist-server/index.js.map +0 -1
- package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +0 -1
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +0 -1
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +0 -1
- package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js.map +0 -1
- package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js.map +0 -1
- package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js.map +0 -1
- package/dist-server/migrations/1786600000000-RenameOperatoMesAdapterType.js.map +0 -1
- package/dist-server/migrations/1786700000000-AddDispatchRetryColumns.js.map +0 -1
- package/dist-server/migrations/1786800000000-CreateTwinReferenceReconciliations.js.map +0 -1
- package/dist-server/migrations/index.js.map +0 -1
- package/dist-server/routes.js.map +0 -1
- package/dist-server/service/actuation/actuation-advisor.js.map +0 -1
- package/dist-server/service/actuation/actuation-outcome.js.map +0 -1
- package/dist-server/service/actuation/actuation-rule-resolver.js.map +0 -1
- package/dist-server/service/actuation/actuation-rule.js.map +0 -1
- package/dist-server/service/actuation/approval-gateway.js.map +0 -1
- package/dist-server/service/actuation/catch-up-effects.js.map +0 -1
- package/dist-server/service/actuation/command-dispatcher.js.map +0 -1
- package/dist-server/service/actuation/command-store.js.map +0 -1
- package/dist-server/service/actuation/dispatch-after-approval.js.map +0 -1
- package/dist-server/service/actuation/effect-match.js.map +0 -1
- package/dist-server/service/actuation/index.js.map +0 -1
- package/dist-server/service/actuation/retry-plan.js.map +0 -1
- package/dist-server/service/actuation/rule-evaluate.js.map +0 -1
- package/dist-server/service/actuation/rule-loop.js.map +0 -1
- package/dist-server/service/actuation/rule-runner.js.map +0 -1
- package/dist-server/service/actuation/rule-wiring.js.map +0 -1
- package/dist-server/service/actuation/settle-effects.js.map +0 -1
- package/dist-server/service/actuation/sweep-dispatch.js.map +0 -1
- package/dist-server/service/actuation/twin-command-resolver.js.map +0 -1
- package/dist-server/service/actuation/twin-command.js.map +0 -1
- package/dist-server/service/actuation/twin-now.js.map +0 -1
- package/dist-server/service/index.js.map +0 -1
- package/dist-server/service/reference/actuation-routing.js.map +0 -1
- package/dist-server/service/reference/actuation-target.js.map +0 -1
- package/dist-server/service/reference/actuation-verification.js.map +0 -1
- package/dist-server/service/reference/attention-lane.js.map +0 -1
- package/dist-server/service/reference/connection-portability-resolver.js.map +0 -1
- package/dist-server/service/reference/connection-portability.js.map +0 -1
- package/dist-server/service/reference/control-routing.js.map +0 -1
- package/dist-server/service/reference/discovery-result.js.map +0 -1
- package/dist-server/service/reference/fill-cadence.js.map +0 -1
- package/dist-server/service/reference/fill-dedupe.js.map +0 -1
- package/dist-server/service/reference/fill-loop.js.map +0 -1
- package/dist-server/service/reference/fill-span.js.map +0 -1
- package/dist-server/service/reference/hook-contract.js.map +0 -1
- package/dist-server/service/reference/hook-rejected.js.map +0 -1
- package/dist-server/service/reference/hook-store.js.map +0 -1
- package/dist-server/service/reference/index.js.map +0 -1
- package/dist-server/service/reference/ingest-space.js.map +0 -1
- package/dist-server/service/reference/knob-defaults.js.map +0 -1
- package/dist-server/service/reference/live-cadence.js.map +0 -1
- package/dist-server/service/reference/live-feed-lease.js.map +0 -1
- package/dist-server/service/reference/live-ingest.js.map +0 -1
- package/dist-server/service/reference/reference-adapter.js.map +0 -1
- package/dist-server/service/reference/reference-assessment.js.map +0 -1
- package/dist-server/service/reference/reference-fill-run.js.map +0 -1
- package/dist-server/service/reference/reference-fill.js.map +0 -1
- package/dist-server/service/reference/reference-hook.js.map +0 -1
- package/dist-server/service/reference/reference-link-groups.js.map +0 -1
- package/dist-server/service/reference/reference-links.js.map +0 -1
- package/dist-server/service/reference/reference-live.js.map +0 -1
- package/dist-server/service/reference/reference-master.js.map +0 -1
- package/dist-server/service/reference/reference-probe.js.map +0 -1
- package/dist-server/service/reference/reference-progress-channel.js.map +0 -1
- package/dist-server/service/reference/reference-progress-subscription.js.map +0 -1
- package/dist-server/service/reference/reference-progress.js.map +0 -1
- package/dist-server/service/reference/reference-reconciliation-query.js.map +0 -1
- package/dist-server/service/reference/reference-reconciliation-writer.js.map +0 -1
- package/dist-server/service/reference/reference-reconciliation.js.map +0 -1
- package/dist-server/service/reference/reference-registry.js.map +0 -1
- package/dist-server/service/reference/reference-resolver.js.map +0 -1
- package/dist-server/service/reference/template-registry.js.map +0 -1
- package/dist-server/service/reference/twin-reference-reconciliation.js.map +0 -1
- package/dist-server/service/reference/twin-reference.js.map +0 -1
- package/dist-server/service/twin-attention/index.js.map +0 -1
- package/dist-server/service/twin-attention/twin-attention-query.js.map +0 -1
- package/dist-server/service/twin-audit/command-audit.js.map +0 -1
- package/dist-server/service/twin-audit/index.js.map +0 -1
- package/dist-server/service/twin-audit/twin-audit-event.js.map +0 -1
- package/dist-server/service/twin-audit/twin-audit-query.js.map +0 -1
- package/dist-server/service/twin-backfill/backfill-period-facts.js.map +0 -1
- package/dist-server/service/twin-backfill/backfill-shape.js.map +0 -1
- package/dist-server/service/twin-backfill/index.js.map +0 -1
- package/dist-server/service/twin-backfill/twin-backfill-resolver.js.map +0 -1
- package/dist-server/service/twin-control/index.js.map +0 -1
- package/dist-server/service/twin-control/twin-control-mutation.js.map +0 -1
- package/dist-server/service/twin-event/index.js.map +0 -1
- package/dist-server/service/twin-event/journal-count.js.map +0 -1
- package/dist-server/service/twin-event/twin-event-keys.js.map +0 -1
- package/dist-server/service/twin-event/twin-event-type.js.map +0 -1
- package/dist-server/service/twin-event/twin-event.js.map +0 -1
- package/dist-server/service/twin-forecast/forecast-metrics.js.map +0 -1
- package/dist-server/service/twin-forecast/forecast-tuning.js.map +0 -1
- package/dist-server/service/twin-forecast/gap-analytics.js.map +0 -1
- package/dist-server/service/twin-forecast/index.js.map +0 -1
- package/dist-server/service/twin-forecast/twin-forecast-query.js.map +0 -1
- package/dist-server/service/twin-ingest-window/index.js.map +0 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +0 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +0 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window.js.map +0 -1
- package/dist-server/service/twin-instance/index.js.map +0 -1
- package/dist-server/service/twin-instance/twin-instance.js.map +0 -1
- package/dist-server/service/twin-journal/event-type-split-shape.js.map +0 -1
- package/dist-server/service/twin-journal/event-type-split.js.map +0 -1
- package/dist-server/service/twin-journal/index.js.map +0 -1
- package/dist-server/service/twin-journal/journal-order.js.map +0 -1
- package/dist-server/service/twin-journal/twin-journal-query.js.map +0 -1
- package/dist-server/service/twin-lifecycle/domain-catalog.js.map +0 -1
- package/dist-server/service/twin-lifecycle/index.js.map +0 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +0 -1
- package/dist-server/service/twin-metrics/index.js.map +0 -1
- package/dist-server/service/twin-metrics/twin-metrics-query.js.map +0 -1
- package/dist-server/service/twin-model/axis-journal-evidence.js.map +0 -1
- package/dist-server/service/twin-model/choose-recipe.js.map +0 -1
- package/dist-server/service/twin-model/epcis-coverage.js.map +0 -1
- package/dist-server/service/twin-model/external-resolver.js.map +0 -1
- package/dist-server/service/twin-model/iec61850-coverage.js.map +0 -1
- package/dist-server/service/twin-model/index.js.map +0 -1
- package/dist-server/service/twin-model/isa95-coverage.js.map +0 -1
- package/dist-server/service/twin-model/item-ref.js.map +0 -1
- package/dist-server/service/twin-model/name-index.js.map +0 -1
- package/dist-server/service/twin-model/project-structure.js.map +0 -1
- package/dist-server/service/twin-model/standard-coverage.js.map +0 -1
- package/dist-server/service/twin-model/status-tally.js.map +0 -1
- package/dist-server/service/twin-model/twin-equipment.js.map +0 -1
- package/dist-server/service/twin-model/twin-lineage-query.js.map +0 -1
- package/dist-server/service/twin-model/twin-location.js.map +0 -1
- package/dist-server/service/twin-model/twin-model-item-query.js.map +0 -1
- package/dist-server/service/twin-model/twin-model-mutation.js.map +0 -1
- package/dist-server/service/twin-model/twin-model-query.js.map +0 -1
- package/dist-server/service/twin-model/twin-model-tree-query.js.map +0 -1
- package/dist-server/service/twin-model/twin-operation.js.map +0 -1
- package/dist-server/service/twin-readiness/index.js.map +0 -1
- package/dist-server/service/twin-readiness/twin-readiness-query.js.map +0 -1
- package/dist-server/service/twin-space/area-resolver.js.map +0 -1
- package/dist-server/service/twin-space/index.js.map +0 -1
- package/dist-server/service/twin-space/move-space.js.map +0 -1
- package/dist-server/service/twin-space/representation-role.js.map +0 -1
- package/dist-server/service/twin-space/space-integrity.js.map +0 -1
- package/dist-server/service/twin-space/twin-area.js.map +0 -1
- package/dist-server/service/twin-space/twin-space-area.js.map +0 -1
- package/dist-server/service/twin-space/twin-space-representation.js.map +0 -1
- package/dist-server/service/twin-space/twin-space-resolver.js.map +0 -1
- package/dist-server/service/twin-space/twin-space.js.map +0 -1
- package/dist-server/service/twin-state/index.js.map +0 -1
- package/dist-server/service/twin-state/twin-state-subscription.js.map +0 -1
- package/dist-server/service/twin-structure/index.js.map +0 -1
- package/dist-server/service/twin-structure/twin-structure.js.map +0 -1
- package/dist-server/service/twin-subject/event-subjects.js.map +0 -1
- package/dist-server/service/twin-subject/index.js.map +0 -1
- package/dist-server/service/twin-subject/item-resolver.js.map +0 -1
- package/dist-server/service/twin-subject/subject-query-shape.js.map +0 -1
- package/dist-server/service/twin-subject/twin-subject-backfill.js.map +0 -1
- package/dist-server/service/twin-subject/twin-subject-event.js.map +0 -1
- package/dist-server/service/twin-subject/twin-subject-fast-path.js.map +0 -1
- package/dist-server/service/twin-subject/twin-subject-query.js.map +0 -1
- package/dist-server/service/twin-subject/twin-subject-writer.js.map +0 -1
- package/dist-server/service/twin-target/index.js.map +0 -1
- package/dist-server/service/twin-target/twin-target-resolver.js.map +0 -1
- package/dist-server/service/twin-target/twin-target.js.map +0 -1
- package/dist-shared/axis-read.js.map +0 -1
- package/dist-shared/entity-delta.js.map +0 -1
- package/dist-shared/kpi-broadcast.js.map +0 -1
- package/dist-shared/touched-items.js.map +0 -1
- package/dist-shared/twin-level.js.map +0 -1
- package/server/engine/attention-digest.ts +0 -102
- package/server/engine/canonical-ingest.ts +0 -511
- package/server/engine/checkpoint-fold.ts +0 -12
- package/server/engine/command-routing.ts +0 -67
- package/server/engine/declared-stimulus.ts +0 -66
- package/server/engine/energy-topology.ts +0 -189
- package/server/engine/event-time.ts +0 -96
- package/server/engine/index.ts +0 -51
- package/server/engine/ingest-dedupe.ts +0 -24
- package/server/engine/ingest-health.ts +0 -24
- package/server/engine/integration-coverage.ts +0 -147
- package/server/engine/integration-probes.ts +0 -144
- package/server/engine/integration-runner.ts +0 -95
- package/server/engine/integration-target-profile.ts +0 -103
- package/server/engine/kpi-baseline.ts +0 -202
- package/server/engine/kpi-fold.ts +0 -1516
- package/server/engine/kpi-query.ts +0 -1210
- package/server/engine/kpi-target.ts +0 -248
- package/server/engine/live-attentions.ts +0 -30
- package/server/engine/live-feed-registry.ts +0 -59
- package/server/engine/load-meter.ts +0 -393
- package/server/engine/local-declarations.ts +0 -779
- package/server/engine/log.ts +0 -72
- package/server/engine/loop-lag.ts +0 -120
- package/server/engine/measured-estimator.ts +0 -91
- package/server/engine/measured-yield.ts +0 -89
- package/server/engine/model-basis.ts +0 -162
- package/server/engine/model-gap.ts +0 -168
- package/server/engine/model-refusal.ts +0 -31
- package/server/engine/model-vocabulary.ts +0 -82
- package/server/engine/oee-accumulator.ts +0 -195
- package/server/engine/operation-basis.ts +0 -131
- package/server/engine/producer-guard.ts +0 -64
- package/server/engine/property-effects.ts +0 -216
- package/server/engine/read-time.ts +0 -77
- package/server/engine/restart-policy.ts +0 -55
- package/server/engine/runtime-key.ts +0 -58
- package/server/engine/spec-coverage.ts +0 -105
- package/server/engine/stage-path.ts +0 -172
- package/server/engine/state-axes.ts +0 -60
- package/server/engine/structure-diff.ts +0 -88
- package/server/engine/travel-estimator.ts +0 -138
- package/server/engine/twin-engine.ts +0 -4979
- package/server/engine/twin-state-channel.ts +0 -39
- package/server/engine/warm-start.ts +0 -265
- package/server/index.ts +0 -90
- package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +0 -43
- package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +0 -54
- package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +0 -53
- package/server/migrations/1786300000000-IndexEventTypeByTime.ts +0 -48
- package/server/migrations/1786400000000-IndexEventCreatedAt.ts +0 -38
- package/server/migrations/1786500000000-CreateTwinSubjectEvents.ts +0 -93
- package/server/migrations/1786600000000-RenameOperatoMesAdapterType.ts +0 -66
- package/server/migrations/1786700000000-AddDispatchRetryColumns.ts +0 -82
- package/server/migrations/1786800000000-CreateTwinReferenceReconciliations.ts +0 -39
- package/server/migrations/index.ts +0 -25
- package/server/routes.ts +0 -75
- package/server/service/actuation/actuation-advisor.ts +0 -131
- package/server/service/actuation/actuation-outcome.ts +0 -97
- package/server/service/actuation/actuation-rule-resolver.ts +0 -147
- package/server/service/actuation/actuation-rule.ts +0 -139
- package/server/service/actuation/approval-gateway.ts +0 -44
- package/server/service/actuation/catch-up-effects.ts +0 -152
- package/server/service/actuation/command-dispatcher.ts +0 -474
- package/server/service/actuation/command-store.ts +0 -236
- package/server/service/actuation/dispatch-after-approval.ts +0 -71
- package/server/service/actuation/effect-match.ts +0 -124
- package/server/service/actuation/index.ts +0 -27
- package/server/service/actuation/retry-plan.ts +0 -145
- package/server/service/actuation/rule-evaluate.ts +0 -352
- package/server/service/actuation/rule-loop.ts +0 -160
- package/server/service/actuation/rule-runner.ts +0 -216
- package/server/service/actuation/rule-wiring.ts +0 -84
- package/server/service/actuation/settle-effects.ts +0 -115
- package/server/service/actuation/sweep-dispatch.ts +0 -179
- package/server/service/actuation/twin-command-resolver.ts +0 -387
- package/server/service/actuation/twin-command.ts +0 -232
- package/server/service/actuation/twin-now.ts +0 -97
- package/server/service/index.ts +0 -100
- package/server/service/reference/actuation-routing.ts +0 -192
- package/server/service/reference/actuation-target.ts +0 -163
- package/server/service/reference/actuation-verification.ts +0 -85
- package/server/service/reference/attention-lane.ts +0 -21
- package/server/service/reference/connection-portability-resolver.ts +0 -171
- package/server/service/reference/connection-portability.ts +0 -329
- package/server/service/reference/control-routing.ts +0 -62
- package/server/service/reference/discovery-result.ts +0 -95
- package/server/service/reference/fill-cadence.ts +0 -29
- package/server/service/reference/fill-dedupe.ts +0 -66
- package/server/service/reference/fill-loop.ts +0 -153
- package/server/service/reference/fill-span.ts +0 -31
- package/server/service/reference/hook-contract.ts +0 -364
- package/server/service/reference/hook-rejected.ts +0 -53
- package/server/service/reference/hook-store.ts +0 -28
- package/server/service/reference/index.ts +0 -18
- package/server/service/reference/ingest-space.ts +0 -104
- package/server/service/reference/knob-defaults.ts +0 -59
- package/server/service/reference/live-cadence.ts +0 -129
- package/server/service/reference/live-feed-lease.ts +0 -56
- package/server/service/reference/live-ingest.ts +0 -94
- package/server/service/reference/reference-adapter.ts +0 -985
- package/server/service/reference/reference-assessment.ts +0 -250
- package/server/service/reference/reference-fill-run.ts +0 -179
- package/server/service/reference/reference-fill.ts +0 -223
- package/server/service/reference/reference-hook.ts +0 -479
- package/server/service/reference/reference-link-groups.ts +0 -21
- package/server/service/reference/reference-links.ts +0 -77
- package/server/service/reference/reference-live.ts +0 -420
- package/server/service/reference/reference-master.ts +0 -999
- package/server/service/reference/reference-probe.ts +0 -264
- package/server/service/reference/reference-progress-channel.ts +0 -26
- package/server/service/reference/reference-progress-subscription.ts +0 -78
- package/server/service/reference/reference-progress.ts +0 -95
- package/server/service/reference/reference-reconciliation-query.ts +0 -28
- package/server/service/reference/reference-reconciliation-writer.ts +0 -38
- package/server/service/reference/reference-reconciliation.ts +0 -55
- package/server/service/reference/reference-registry.ts +0 -17
- package/server/service/reference/reference-resolver.ts +0 -969
- package/server/service/reference/template-registry.ts +0 -85
- package/server/service/reference/twin-reference-reconciliation.ts +0 -27
- package/server/service/reference/twin-reference.ts +0 -190
- package/server/service/twin-attention/index.ts +0 -3
- package/server/service/twin-attention/twin-attention-query.ts +0 -70
- package/server/service/twin-audit/command-audit.ts +0 -114
- package/server/service/twin-audit/index.ts +0 -5
- package/server/service/twin-audit/twin-audit-event.ts +0 -158
- package/server/service/twin-audit/twin-audit-query.ts +0 -72
- package/server/service/twin-backfill/backfill-period-facts.ts +0 -168
- package/server/service/twin-backfill/backfill-shape.ts +0 -86
- package/server/service/twin-backfill/index.ts +0 -3
- package/server/service/twin-backfill/twin-backfill-resolver.ts +0 -35
- package/server/service/twin-control/index.ts +0 -3
- package/server/service/twin-control/twin-control-mutation.ts +0 -150
- package/server/service/twin-event/index.ts +0 -3
- package/server/service/twin-event/journal-count.ts +0 -53
- package/server/service/twin-event/twin-event-keys.ts +0 -215
- package/server/service/twin-event/twin-event-type.ts +0 -68
- package/server/service/twin-event/twin-event.ts +0 -250
- package/server/service/twin-forecast/forecast-metrics.ts +0 -106
- package/server/service/twin-forecast/forecast-tuning.ts +0 -40
- package/server/service/twin-forecast/gap-analytics.ts +0 -139
- package/server/service/twin-forecast/index.ts +0 -3
- package/server/service/twin-forecast/twin-forecast-query.ts +0 -486
- package/server/service/twin-ingest-window/index.ts +0 -7
- package/server/service/twin-ingest-window/twin-ingest-window-query.ts +0 -125
- package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +0 -57
- package/server/service/twin-ingest-window/twin-ingest-window.ts +0 -113
- package/server/service/twin-instance/index.ts +0 -3
- package/server/service/twin-instance/twin-instance.ts +0 -199
- package/server/service/twin-journal/event-type-split-shape.ts +0 -112
- package/server/service/twin-journal/event-type-split.ts +0 -144
- package/server/service/twin-journal/index.ts +0 -3
- package/server/service/twin-journal/journal-order.ts +0 -20
- package/server/service/twin-journal/twin-journal-query.ts +0 -615
- package/server/service/twin-lifecycle/domain-catalog.ts +0 -51
- package/server/service/twin-lifecycle/index.ts +0 -3
- package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +0 -285
- package/server/service/twin-metrics/index.ts +0 -3
- package/server/service/twin-metrics/twin-metrics-query.ts +0 -172
- package/server/service/twin-model/axis-journal-evidence.ts +0 -57
- package/server/service/twin-model/choose-recipe.ts +0 -31
- package/server/service/twin-model/epcis-coverage.ts +0 -251
- package/server/service/twin-model/external-resolver.ts +0 -173
- package/server/service/twin-model/iec61850-coverage.ts +0 -111
- package/server/service/twin-model/index.ts +0 -25
- package/server/service/twin-model/isa95-coverage.ts +0 -320
- package/server/service/twin-model/item-ref.ts +0 -80
- package/server/service/twin-model/name-index.ts +0 -94
- package/server/service/twin-model/project-structure.ts +0 -213
- package/server/service/twin-model/standard-coverage.ts +0 -35
- package/server/service/twin-model/status-tally.ts +0 -37
- package/server/service/twin-model/twin-equipment.ts +0 -191
- package/server/service/twin-model/twin-lineage-query.ts +0 -327
- package/server/service/twin-model/twin-location.ts +0 -147
- package/server/service/twin-model/twin-model-item-query.ts +0 -1029
- package/server/service/twin-model/twin-model-mutation.ts +0 -503
- package/server/service/twin-model/twin-model-query.ts +0 -792
- package/server/service/twin-model/twin-model-tree-query.ts +0 -211
- package/server/service/twin-model/twin-operation.ts +0 -157
- package/server/service/twin-readiness/index.ts +0 -3
- package/server/service/twin-readiness/twin-readiness-query.ts +0 -96
- package/server/service/twin-space/area-resolver.ts +0 -75
- package/server/service/twin-space/index.ts +0 -8
- package/server/service/twin-space/move-space.ts +0 -262
- package/server/service/twin-space/representation-role.ts +0 -15
- package/server/service/twin-space/space-integrity.ts +0 -293
- package/server/service/twin-space/twin-area.ts +0 -63
- package/server/service/twin-space/twin-space-area.ts +0 -66
- package/server/service/twin-space/twin-space-representation.ts +0 -99
- package/server/service/twin-space/twin-space-resolver.ts +0 -582
- package/server/service/twin-space/twin-space.ts +0 -148
- package/server/service/twin-state/index.ts +0 -3
- package/server/service/twin-state/twin-state-subscription.ts +0 -62
- package/server/service/twin-structure/index.ts +0 -3
- package/server/service/twin-structure/twin-structure.ts +0 -96
- package/server/service/twin-subject/event-subjects.ts +0 -162
- package/server/service/twin-subject/index.ts +0 -10
- package/server/service/twin-subject/item-resolver.ts +0 -92
- package/server/service/twin-subject/subject-query-shape.ts +0 -84
- package/server/service/twin-subject/twin-subject-backfill.ts +0 -196
- package/server/service/twin-subject/twin-subject-event.ts +0 -101
- package/server/service/twin-subject/twin-subject-fast-path.ts +0 -346
- package/server/service/twin-subject/twin-subject-query.ts +0 -210
- package/server/service/twin-subject/twin-subject-writer.ts +0 -189
- package/server/service/twin-target/index.ts +0 -5
- package/server/service/twin-target/twin-target-resolver.ts +0 -142
- package/server/service/twin-target/twin-target.ts +0 -97
- package/server/types/global.d.ts +0 -22
- package/shared/axis-read.ts +0 -28
- package/shared/entity-delta.ts +0 -335
- package/shared/kpi-broadcast.ts +0 -13
- package/shared/touched-items.ts +0 -73
- package/shared/twin-level.ts +0 -48
- package/test/ack-shape-alignment.test.ts +0 -97
- package/test/actuation-advisor.test.ts +0 -55
- package/test/actuation-approval-door.test.ts +0 -411
- package/test/actuation-dispatch.test.ts +0 -209
- package/test/actuation-effect.test.ts +0 -162
- package/test/actuation-outcome.test.ts +0 -152
- package/test/actuation-rule-runner.test.ts +0 -281
- package/test/actuation-rule.test.ts +0 -266
- package/test/actuation-seam.test.ts +0 -255
- package/test/actuation-settle.test.ts +0 -164
- package/test/actuation-target.test.ts +0 -145
- package/test/actuation-verification-state.test.ts +0 -139
- package/test/adopt-structure-live.test.ts +0 -133
- package/test/aggregate-not-findone.test.ts +0 -112
- package/test/area-resolver-db.test.ts +0 -110
- package/test/attention-digest.test.ts +0 -161
- package/test/axis-declaration-passthrough-db.test.ts +0 -152
- package/test/axis-journal-evidence.test.ts +0 -71
- package/test/axis-read.test.ts +0 -188
- package/test/backfill-shape.test.ts +0 -89
- package/test/boot-resume.test.ts +0 -287
- package/test/broadcast-cost-baseline.test.ts +0 -201
- package/test/broadcast-period.test.ts +0 -58
- package/test/broadcast-queue-overflow.test.ts +0 -244
- package/test/canonical-ingest-vocabularies.test.ts +0 -451
- package/test/canonical-quantity-door.test.ts +0 -61
- package/test/capability-mapping.test.ts +0 -89
- package/test/checkpoint-refuses-empty.test.ts +0 -103
- package/test/choose-recipe.test.ts +0 -117
- package/test/column-type-portability.test.ts +0 -122
- package/test/command-routing.test.ts +0 -61
- package/test/command-store-writes-columns.test.ts +0 -126
- package/test/connection-portability-doors.test.ts +0 -87
- package/test/connector-capability-declaration.test.ts +0 -195
- package/test/contract-layer-guard.test.ts +0 -48
- package/test/control-capability.test.ts +0 -98
- package/test/cursor-stall-not-read-failure.test.ts +0 -126
- package/test/declaration-reaches-model.test.ts +0 -226
- package/test/declared-location-types.test.ts +0 -89
- package/test/declared-stimulus.test.ts +0 -88
- package/test/dedupe-transformation.test.ts +0 -112
- package/test/discovery-result.test.ts +0 -75
- package/test/dispatch-after-approval-source.test.ts +0 -81
- package/test/duration-estimators.test.ts +0 -144
- package/test/energy-topology.test.ts +0 -113
- package/test/entity-delta.test.ts +0 -352
- package/test/equipment-identity-reaches.test.ts +0 -57
- package/test/event-subjects.test.ts +0 -148
- package/test/event-time-column.test.ts +0 -67
- package/test/event-type-split.test.ts +0 -178
- package/test/external-resolver.test.ts +0 -177
- package/test/fact-scope-wiring.test.ts +0 -96
- package/test/fill-dedupe.test.ts +0 -59
- package/test/fill-loop.test.ts +0 -35
- package/test/first-envelope.test.ts +0 -153
- package/test/fold-gap-speaks.test.ts +0 -53
- package/test/fold-shift-dst.test.ts +0 -110
- package/test/forecast-metrics.test.ts +0 -157
- package/test/forecast-tuning.test.ts +0 -42
- package/test/gap-analytics.test.ts +0 -113
- package/test/generated-not-counted.test.ts +0 -99
- package/test/guard-roots.ts +0 -76
- package/test/home-in-area.test.ts +0 -90
- package/test/hook-address.test.ts +0 -28
- package/test/hook-rejected-shape.test.ts +0 -86
- package/test/hook-response-sum.test.ts +0 -193
- package/test/hook-sequence.test.ts +0 -333
- package/test/ingest-bench.test.ts +0 -70
- package/test/ingest-dedupe.test.ts +0 -56
- package/test/ingest-expected-quiet.test.ts +0 -111
- package/test/ingest-health-engine.test.ts +0 -248
- package/test/ingest-health-wiring.test.ts +0 -120
- package/test/ingest-health.test.ts +0 -306
- package/test/ingest-history.test.ts +0 -247
- package/test/ingest-idempotent.test.ts +0 -211
- package/test/ingest-reconcile-four-ways.test.ts +0 -97
- package/test/ingest-rules-header.test.ts +0 -100
- package/test/ingest-running-guard.test.ts +0 -136
- package/test/ingest-space.test.ts +0 -78
- package/test/ingest-window-rolls-unobserved.test.ts +0 -72
- package/test/ingest-wiring-guard.test.ts +0 -197
- package/test/instance-cache-lifecycle.test.ts +0 -110
- package/test/intake-mapping-parity.test.ts +0 -175
- package/test/integration-probes.test.ts +0 -103
- package/test/integration-runner.test.ts +0 -95
- package/test/item-ref.test.ts +0 -78
- package/test/journal-order-axis.test.ts +0 -80
- package/test/journal-read-discipline.test.ts +0 -186
- package/test/journal-retention.test.ts +0 -275
- package/test/journal-sort-axis.test.ts +0 -142
- package/test/journal-write-door.test.ts +0 -110
- package/test/journal-write-trend.test.ts +0 -115
- package/test/kernel-kind-guard.test.ts +0 -98
- package/test/knob-defaults.test.ts +0 -72
- package/test/kpi-baseline-db.test.ts +0 -216
- package/test/kpi-baseline.test.ts +0 -196
- package/test/kpi-fold.test.ts +0 -1456
- package/test/kpi-query-bench.test.ts +0 -130
- package/test/lens-own-places.test.ts +0 -50
- package/test/lineage-survives-restart.test.ts +0 -234
- package/test/live-cadence.test.ts +0 -170
- package/test/live-cursor-wiring.test.ts +0 -87
- package/test/live-feed-lease.test.ts +0 -63
- package/test/live-feed-registry.test.ts +0 -75
- package/test/live-kernel-facts.test.ts +0 -161
- package/test/live-mirror-parity.test.ts +0 -286
- package/test/load-meter.test.ts +0 -328
- package/test/local-declarations.test.ts +0 -662
- package/test/log-stamp.test.ts +0 -59
- package/test/loop-lag.test.ts +0 -82
- package/test/master-to-twin.test.ts +0 -279
- package/test/measured-yield.test.ts +0 -90
- package/test/merge-streams.test.ts +0 -70
- package/test/mirror-resumes-from-checkpoint.test.ts +0 -192
- package/test/model-basis.test.ts +0 -141
- package/test/model-gap.test.ts +0 -153
- package/test/model-refusal.test.ts +0 -75
- package/test/model-vocabulary.test.ts +0 -114
- package/test/move-space.test.ts +0 -149
- package/test/mutation-gate.test.ts +0 -122
- package/test/oee-accumulator.test.ts +0 -204
- package/test/oee-site-calendar.test.ts +0 -146
- package/test/open-doors.test.ts +0 -197
- package/test/operation-basis.test.ts +0 -127
- package/test/operational-vocabulary.test.ts +0 -108
- package/test/operations-capability-db.test.ts +0 -165
- package/test/order-and-transaction-split.test.ts +0 -107
- package/test/order-fold-host.test.ts +0 -112
- package/test/producer-guard.test.ts +0 -66
- package/test/project-structure-db.test.ts +0 -226
- package/test/projection-reaches-screen.test.ts +0 -277
- package/test/projection-reads-declared.test.ts +0 -109
- package/test/property-effects.test.ts +0 -128
- package/test/provenance-promotion.test.ts +0 -99
- package/test/read-failure-ledger.test.ts +0 -106
- package/test/read-failure-visible.test.ts +0 -145
- package/test/read-time.test.ts +0 -118
- package/test/reference-fill.test.ts +0 -235
- package/test/reference-grounding.test.ts +0 -70
- package/test/reference-hook.test.ts +0 -177
- package/test/reference-links.test.ts +0 -56
- package/test/reference-reconciliation.test.ts +0 -39
- package/test/registry-key-guard.test.ts +0 -80
- package/test/rejection-carries-event-id.test.ts +0 -101
- package/test/rename-twin.test.ts +0 -89
- package/test/resolve-ts-siblings.mjs +0 -52
- package/test/restart-policy.test.ts +0 -111
- package/test/resync-origin-site.test.ts +0 -83
- package/test/retry-plan.test.ts +0 -139
- package/test/revision-axis.test.ts +0 -110
- package/test/runtime-key.test.ts +0 -66
- package/test/save-intake-collision.test.ts +0 -91
- package/test/scale-twin-bench.test.ts +0 -103
- package/test/secrets-at-rest.test.ts +0 -135
- package/test/snapshot-freshness.test.ts +0 -60
- package/test/source-outcome-audit.test.ts +0 -104
- package/test/space-integrity.test.ts +0 -223
- package/test/space-representation.test.ts +0 -66
- package/test/space-scope-includes-stopped.test.ts +0 -55
- package/test/spec-coverage.test.ts +0 -113
- package/test/stage-path.test.ts +0 -95
- package/test/standard-coverage.test.ts +0 -148
- package/test/state-axes.test.ts +0 -74
- package/test/status-tally.test.ts +0 -55
- package/test/streamline-e2e.test.ts +0 -76
- package/test/structure-revision-db.test.ts +0 -315
- package/test/subject-fast-path.test.ts +0 -148
- package/test/subject-read-model.test.ts +0 -175
- package/test/sweep-dispatch.test.ts +0 -191
- package/test/tenant-registry-db.test.ts +0 -149
- package/test/time-range.test.ts +0 -152
- package/test/touched-items.test.ts +0 -61
- package/test/twin-audit.test.ts +0 -83
- package/test/twin-event-keys.test.ts +0 -193
- package/test/twin-model-item-db.test.ts +0 -319
- package/test/twin-model-parent-relation-db.test.ts +0 -125
- package/test/twin-model-tree-db.test.ts +0 -209
- package/test/twin-origin-resync.test.ts +0 -133
- package/test/twin-state-channel.test.ts +0 -111
- package/test/vocabulary-guard.test.ts +0 -43
- package/test/warm-start-seam.test.ts +0 -162
- package/test/warm-start.test.ts +0 -297
- package/test/withheld-door.test.ts +0 -95
- package/test/yield-loop.test.ts +0 -199
- package/tsconfig.json +0 -15
- package/tsconfig.shared.json +0 -23
- package/tsconfig.shared.tsbuildinfo +0 -1
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"twin-journal-query.js","sourceRoot":"","sources":["../../../server/service/twin-journal/twin-journal-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { twinWarn } from '../../engine/log.js'\nimport { Between, LessThanOrEqual, MoreThanOrEqual } from 'typeorm'\nimport { Arg, Args, Ctx, Float, Int, Query, Resolver, UseMiddleware } from 'type-graphql'\n\nimport { buildNextCursor, cursorSortings, getQueryBuilderFromListParams, getRepository, ListParam, ScalarObject, SpeakRefusals } from '@things-factory/shell'\n\nimport { TwinEvent } from '../twin-event/twin-event.js'\nimport { JOURNAL_COUNT_CAP } from '../twin-event/journal-count.js'\nimport { TwinEventList } from '../twin-event/twin-event-type.js'\nimport { TwinEngine } from '../../engine/index.js'\nimport { pickAxes } from '../../engine/state-axes.js'\nimport { digestAttentions } from '../../engine/attention-digest.js'\nimport { computeTwinKpi, resolveTwinTargets } from '../../engine/kpi-query.js'\nimport { journalOrder } from './journal-order.js'\nimport { readModelPage } from '../twin-subject/twin-subject-fast-path.js'\nimport { eventTypeSplitPage } from './event-type-split.js'\nimport { splitShape } from './event-type-split-shape.js'\n\n/*\n * 저널 읽기 채널 — 상향 query.\n * twinEvents : append-only 이벤트 저널 조회(Ledger/History/Entity360) — instanceId·revision 범위·eventType 필터.\n * twinReplay : 내구 저널을 replay 해 상태 재구성(재부팅 복구·시간여행) — untilRevision 으로 임의 과거.\n * (실시간 스트림은 twinState subscription, 명령은 twin-control mutation. 이건 '질의' 표면.)\n */\n/*\n * 건수 상한은 **저널 세기의 문**에서 온다(`journal-count.ts`) — 값과 규율이 두 곳에 흩어지면 한쪽만\n * 고쳐진다. 근거(2026-08-22 실측): 상한 1만이면 0.04초, 상한 없이 세면 5.32초(저널 358만 행).\n */\nconst COUNT_CAP = JOURNAL_COUNT_CAP\n\n/**\n * 시각 필터 값을 날짜로 — 배열(between)이면 원소마다.\n *\n * 못 읽는 값은 **그대로 둔다.** 여기서 지어내면 사용자가 요청하지 않은 구간을 조회하게 된다.\n */\nfunction toDateValue(v: any): any {\n const one = (x: any) => {\n if (x instanceof Date) return x\n const t = typeof x === 'string' ? Date.parse(x) : NaN\n return Number.isNaN(t) ? x : new Date(t)\n }\n return Array.isArray(v) ? v.map(one) : one(v)\n}\n\n/**\n * 자유 검색어가 펼쳐지는 축 — **목록과 건수가 같은 것을 본다**(2026-09-19).\n *\n * 전부 인덱스 가능한 승격 컬럼이다(payload JSON 안이 아니라). 프레임워크 질의 빌더는 이 목록에 없는\n * 컬럼의 LIKE 를 경고 후 무시한다 — 인덱스 없는 전체 스캔을 막기 위해서다.\n *\n * ── 왜 상수인가 ────────────────────────────────────────────────────────────\n * 목록과 건수가 **다른 빌더**로 만들어진다(빠른 갈래의 건수 probe). 두 자리에 배열을 따로 적으면\n * 한쪽만 늘어나는 날 목록은 걸러지고 건수는 안 걸러진다 — 화면에 「한 건」과 「전체 수」가 나란히 선다.\n *\n * 지금은 그 일이 **일어날 수 없다**: 빠른 갈래는 검색어가 있으면 아예 안 탄다\n * (`event-type-split-shape.ts` — 시험 「자유 검색어」가 그 축을 붙든다). 그래도 같은 상수를 쓴다 —\n * 그 규칙이 언젠가 느슨해질 때 이 자리가 조용히 갈리지 않게.\n */\nexport const JOURNAL_SEARCHABLES = ['epc', 'orderId', 'locationId', 'moverId', 'bizStep', 'eventType', 'correlationId'] as const\n\n@Resolver()\n@UseMiddleware(SpeakRefusals)\nexport class TwinJournalQuery {\n @Query(returns => [TwinEvent], {\n description:\n 'Read the append-only twin event journal (Ledger / History / audit). Newest first by event time; ' +\n 'ordered by revision only when a revision range is given.'\n })\n async twinEvents(\n @Arg('instanceId') instanceId: string,\n @Arg('fromRevision', type => Int, { nullable: true }) fromRevision: number,\n @Arg('toRevision', type => Int, { nullable: true }) toRevision: number,\n @Arg('eventType', { nullable: true }) eventType: string,\n @Arg('limit', type => Int, { nullable: true }) limit: number,\n @Ctx() context: ResolverContext,\n @Arg('untilTime', { nullable: true }) untilTime?: string,\n @Arg('fromTime', { nullable: true }) fromTime?: string\n ): Promise<TwinEvent[]> {\n const where: any = { domain: { id: context.state.domain.id }, instanceId }\n if (eventType) where.eventType = eventType\n /*\n * ── 구간으로 물을 수 있게 한다 (2026-08-25 실측) ────────────────────────────\n *\n * `untilTime` 만 있으면 **끝만 정해진 조회**다. 그러면 위쪽 경계에서 뒤로 걸어오며 종류를 하나씩\n * 확인해야 하고, 찾는 종류가 며칠 전에 멈춘 트윈에서는 그 사이 행을 전부 열어 본다. 실측:\n *\n * 공정 타임라인이 부르는 방식(종류만·200건) 22.0초\n * 같은 조회에 `untilTime` 만 더한 것 9.7초\n *\n * 이유는 계획이 말한다. 종류별 시각순 인덱스가 없어서 `(domain, instanceId, eventTime)` 로 걷고\n * 행마다 테이블을 열어 종류를 본다. 승화푸드에서 작업 사실은 8월 22일에 멈춰 있고 그 뒤로 리비전이\n * 110만 늘었으므로, 200건에 닿기까지 그만큼을 읽고 버린다.\n *\n * **구간을 주면 그 범위만 걷는다**(`event_time>? AND event_time<?`). 화면은 자기가 그리는 구간을\n * 알고 있으므로 그것을 그대로 물으면 된다 — 「전체」가 실제로는 상한에 걸린 6.7시간이었던 문제도\n * 같은 자리에서 없어진다.\n *\n * 컬럼이 날짜라 **날짜로 넘긴다**(문자열로 비교하면 sqlite 에서 오류 없이 빈 결과가 나온다).\n */\n if (untilTime != null && fromTime != null) where.eventTime = Between(new Date(fromTime), new Date(untilTime))\n else if (untilTime != null) where.eventTime = LessThanOrEqual(new Date(untilTime))\n else if (fromTime != null) where.eventTime = MoreThanOrEqual(new Date(fromTime))\n else if (fromRevision != null && toRevision != null) where.revision = Between(fromRevision, toRevision)\n else if (fromRevision != null) where.revision = MoreThanOrEqual(fromRevision)\n else if (toRevision != null) where.revision = LessThanOrEqual(toRevision)\n\n /*\n * 최신순(DESC) 기본 — Ledger/recent 뷰. 범위 필터로 시간여행 구간 조회도 가능.\n *\n * ── 정렬 축을 **거르는 축과 맞춘다** (2026-08-22 실측) ─────────────────────\n * `untilTime` 경로는 `eventTime <= T` 로 거르면서 `revision` 으로 정렬했다. 그러면 sqlite 는 조건에\n * 맞는 행을 **전부 찾아 전부 정렬한 뒤** 상한을 적용한다 — 상한이 정렬 뒤에 걸리므로 200건을 얻는\n * 데도 전량 정렬을 치른다. 실측(인스턴스 하나가 375만 행):\n *\n * ORDER BY revision DESC → 33.4초\n * ORDER BY eventTime DESC, revision DESC → 0.007초 (4,700배)\n *\n * 계획이 그것을 그대로 말한다: 앞은 `USE TEMP B-TREE FOR ORDER BY`(전체 정렬), 뒤는\n * `... FOR LAST TERM OF ORDER BY` — 시각은 인덱스(`ix_twin_event_1`)에서 순서대로 나오고\n * **동시각만** 임시 정렬한다.\n *\n * **`revision` 경로는 그대로 둔다.** 그때는 거르는 축이 `revision` 이고 정렬도 `revision` 이라 이미\n * 맞아 있다(`ix_twin_event_0`). 축이 어긋난 경로만 고친다 — 맞는 자리를 건드리면 그쪽이 어긋난다.\n *\n * 순서의 뜻이 **완전히 같지는 않다**(측정해서 고친 문장): `revision` 순서와 `eventTime` 순서가\n * 어긋나는 자리가 실재한다(426만 행 중 7건 — 미러는 원본의 시각을 싣고 도착 순서로 리비전을 받는다).\n * 이 목록은 사람이 이력을 훑는 화면이고 **시각순으로 보는 것이 그 화면의 뜻**이므로, 그 어긋남은\n * 결함이 아니라 오히려 맞는 순서다. 동시각의 순서를 위해 `revision` 을 둘째 축으로 남긴다.\n */\n /*\n * ── 아무 필터도 없을 때도 시각순으로 낸다 (2026-08-25 실측) ────────────────\n * 이 갈래가 `revision DESC` 였다. 공정 타임라인은 필터 없이 상한만 걸고 부르므로\n * (`loadTwinEvents(id, { eventType: 'task.status', limit })`) 그 갈래로 떨어졌다.\n *\n * `revision` 은 **적힌 순서**다. 연결된 시스템을 되읽는 트윈에서는 그 순서가 일어난 순서와 다르다 —\n * 커넥터가 원본의 갱신 순서를 걷기 때문에, 원본에서 나중에 손댄 행이 4월 사실을 담고 있으면 그\n * 행이 큰 revision 을 받는다. 승화푸드 실측:\n *\n * 작업 축에 있는 가장 늦은 사실 2026-08-23 03:20\n * revision 이 가장 큰 작업 사실 2026-04-15 10:49 ← 상한이 잡던 값\n *\n * 상한 안에 8월 두 달치가 들어오지 않았고, 화면은 4월 사실을 최근 것으로 그렸다.\n *\n * 아래 목록 계약(`twinEventList`)은 이미 시각순을 기본으로 골라 두었다(§`sortings`). 이 함수만\n * 남아 있었다. 성능도 이유가 되지 않는다 — 시각 정렬이 인덱스를 순서대로 타는 것은 아래 실측이\n * 말한다.\n *\n * `revision` 범위로 거를 때는 `revision DESC` 를 그대로 쓴다. 거르는 축과 정렬 축을 맞추는 규칙이고,\n * 그때는 부르는 쪽이 리비전으로 말하고 있으므로 리비전 순서가 그 물음의 답이다.\n */\n const order: any = journalOrder({ untilTime, fromTime, fromRevision, toRevision })\n\n /*\n * ── `find` 이 아니라 질의 빌더로 묻는다 (2026-08-25 실측) ───────────────────\n *\n * `find({ where: { domain: { id } }, order, take })` 는 관계를 가진 엔티티에서 **두 단계 질의**가\n * 된다. 실제로 나간 SQL 이 이렇다:\n *\n * SELECT DISTINCT \"distinctAlias\".\"TwinEvent_id\", …\n * FROM (SELECT … 모든 컬럼 … FROM twin_events WHERE …) \"distinctAlias\"\n * ORDER BY … LIMIT ?\n *\n * **안쪽에 상한이 없다.** 조건에 맞는 행을 전부 만들어 놓고 밖에서 자른다. 승화푸드의 작업 사실은\n * 438,913건이고 `payload` 가 함께 실리므로, 200건을 얻는 데 그 전부를 읽었다.\n *\n * 실측: 같은 조회를 손으로 쓰면 0.024초, 이 함수를 통하면 12초였다. 인덱스를 새로 얹은 뒤에도\n * 그대로였다 — 인덱스는 안쪽 질의를 도와줄 뿐이고, 문제는 안쪽이 전부를 만든다는 것이었다.\n *\n * 관계를 **컬럼으로** 물으면 감싸지 않는다. 이 파일의 다른 자리(`timeRange` · 최종 리비전)가 이미\n * 그 방식이다. `getMany` 는 조인이 없으면 `DISTINCT` 를 붙이지 않는다.\n */\n const qb = getRepository(TwinEvent)\n .createQueryBuilder('e')\n .where('e.domain = :domainId', { domainId: context.state.domain.id })\n .andWhere('e.instanceId = :instanceId', { instanceId })\n if (eventType) qb.andWhere('e.eventType = :eventType', { eventType })\n if (where.eventTime !== undefined) {\n /* 조건은 위에서 이미 정했다 — 같은 판단을 두 벌로 적지 않는다. */\n if (untilTime != null && fromTime != null) {\n qb.andWhere('e.eventTime BETWEEN :fromTime AND :untilTime', { fromTime: new Date(fromTime), untilTime: new Date(untilTime) })\n } else if (untilTime != null) {\n qb.andWhere('e.eventTime <= :untilTime', { untilTime: new Date(untilTime) })\n } else {\n qb.andWhere('e.eventTime >= :fromTime', { fromTime: new Date(fromTime as string) })\n }\n } else if (where.revision !== undefined) {\n if (fromRevision != null && toRevision != null) qb.andWhere('e.revision BETWEEN :from AND :to', { from: fromRevision, to: toRevision })\n else if (fromRevision != null) qb.andWhere('e.revision >= :from', { from: fromRevision })\n else qb.andWhere('e.revision <= :to', { to: toRevision })\n }\n for (const [column, direction] of Object.entries(order)) qb.addOrderBy(`e.${column}`, direction as 'DESC')\n return qb.limit(limit ?? 200).getMany()\n }\n\n /**\n * 지난 기록 목록 — things-factory 표준 목록 계약(`ListParam` → `{ items, total }`).\n *\n * ── 왜 `twinEvents` 와 따로 두는가 ─────────────────────────────────────────\n * `twinEvents` 는 배열만 돌려준다. 화면은 자기가 받은 게 전부인지 잘린 건지 알 수 없었고, 그래서\n * 리스트가 **알리지 않고 잘린 채 \"이게 전부\" 처럼** 보였다(원장 limit 120 이 대표적). 총건수를 함께\n * 주면 \"48 / 12,904\" 라고 말할 수 있고, 사용자는 좁혀야 한다는 사실을 안다.\n * 기존 호출자를 깨지 않으려고 `twinEvents` 는 그대로 두고 목록 계약을 새로 연다.\n *\n * ── 검색이 진짜인 이유 ─────────────────────────────────────────────────────\n * `searchables` 는 전부 **인덱스 가능한 승격 컬럼**이다(payload JSON 안이 아니라). 프레임워크\n * 질의 빌더는 `searchables` 에 없는 컬럼의 LIKE 를 경고 후 무시한다 — 인덱스 없는 전체 스캔을\n * 막기 위해서다. 그 규율에 맞추려고 검색 축을 컬럼으로 승격했다(`twin-event-keys.ts`).\n *\n * 정렬 기본값은 revision DESC(최신순) — 저널의 자연 순서이자 ix_twin_event_0 가 그대로 타는 축.\n */\n\n @Query(returns => TwinEventList, {\n description:\n 'List twin journal events with the standard list contract (filters, pagination, sortings) plus the total match count. ' +\n 'Scope is either one twin (instanceId) or a whole site (spaceId, which folds in every running operational twin co-located there, the same targets twinKpi uses) — one of the two is required. ' +\n 'Searchable fields are the columns promoted out of the payload: epc (item), orderId, locationId, moverId (equipment), bizStep, eventType. ' +\n 'Returns total so a caller can show an honest \"shown of total\" count instead of silently truncating a long journal. ' +\n 'Defaults to newest first (revision descending) and to a page of 100; limit is capped at 500 per page.'\n })\n async twinEventList(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext,\n @Arg('instanceId', { nullable: true, description: 'Read one twin instance journal.' }) instanceId?: string,\n @Arg('spaceId', { nullable: true, description: 'Read every running operational twin co-located in this space, merged on the shared time axis.' }) spaceId?: string\n ): Promise<TwinEventList> {\n const { domain } = context.state\n if (!instanceId && !spaceId) throw new Error('either instanceId or spaceId is required')\n /* 테넌트 격리 — twinKpi 와 같은 규약. 남의 트윈은 빈 결과가 아니라 명시 실패로 알린다\n * (들어온 것이 없는 빈 목록은 \"권한 없음\" 과 \"데이터 없음\" 을 구분할 수 없게 만든다). */\n if (instanceId && !(await TwinEngine.ownsRegistered(domain.id, instanceId))) {\n throw new Error(`twin instance not found in this tenant: ${instanceId}`)\n }\n\n /* 대상 해소는 KPI 와 같은 창구를 쓴다 — 같은 공간을 보면서 화면마다 대상이 달라지면 안 된다. */\n const instanceIds = await resolveTwinTargets(domain.id, instanceId, spaceId)\n if (instanceIds.length === 0) return { items: [], total: 0 }\n\n /* 페이지 상한 — 화면이 실수로(또는 악의로) 저널 전체를 한 번에 달라고 해도 서버가 버틴다. */\n const limit = Math.min(params.pagination?.limit ?? 100, 500)\n const effective: ListParam = {\n ...params,\n /*\n * ── 이어 읽기 지점을 **잃지 않는다** (2026-08-28 실측) ─────────────────────\n *\n * 여기서 페이지 값을 다시 만들면서 `after` 를 빼고 있었다. 그래서 응답은 다음 지점(`nextCursor`)을\n * 내주는데 다음 요청의 그 값은 아무 데도 쓰이지 않았고, 「더 보기」가 **1페이지를 다시 냈다.**\n * 실측: 같은 품목의 30건을 세 번 이어 읽었더니 90건 중 60건이 되풀었다(같은 id).\n *\n * 오류가 나지 않는 결함이라 화면에서는 「이력이 30건뿐」이나 「같은 사건이 계속 나온다」로 보인다.\n */\n pagination: { page: params.pagination?.page ?? 1, limit, after: params.pagination?.after },\n /* 기본 정렬은 시각 내림차순 — 여러 트윈을 합칠 때 공통 축은 revision(트윈별 카운터)이 아니라\n * **시각**이다. revision 으로 섞으면 서로 다른 트윈의 무관한 카운터가 뒤엉킨다. */\n sortings: params.sortings?.length ? params.sortings : [{ name: 'eventTime', desc: true }],\n /*\n * 시각 필터는 **날짜로 바꿔서** 질의 빌더에 넘긴다.\n *\n * 화면은 ISO 문자열(`2026-08-07T11:00:00.000Z`)을 보내는데 eventTime 은 날짜 컬럼이고,\n * sqlite 는 그것을 공백 구분 형식(`2026-08-07 11:00:00.000`)으로 저장한다. 문자열 그대로\n * 비교하면 사전식으로 어긋나 **오류 없이 빈 결과**가 나온다(실측으로 확인했다).\n * 화면이 날짜를 어떻게 적어 보내든 서버가 받아 주는 편이 맞다 — 계약을 좁히는 대신 경계에서 옮긴다.\n */\n filters: (params.filters ?? []).map(f => (f.name === 'eventTime' ? { ...f, value: toDateValue(f.value) } : f))\n }\n\n /*\n * ── 대상 하나의 이력은 **파생 테이블**이 답한다 (2026-08-28 실측) ───────────\n *\n * 360 화면의 이력이 이 문으로 온다. 물음은 「이 품목(오더·자리·설비)의 사건을 최신순으로 30건」\n * 인데, 저널에는 그 모양의 목차가 없다: 축으로 걸러도 시각으로 정렬하는 순간 목차를 못 쓰고,\n * 품목과 로트가 한 문자열에 붙어 있어 품목으로는 아예 찾지 못한다. 실측 **63.998초**였다.\n *\n * 그 물음의 모양대로 만든 테이블이 이미 있다(`twin_subject_events`). 같은 질의가 **0.078초**다.\n * 화면을 고치지 않고 여기서 답을 가져오는 곳만 바꾼다 — 계약도 커서도 그대로다.\n *\n * 맞지 않는 물음(여러 트윈·자유 검색·종류 필터·다른 정렬)이거나 파생 테이블이 그 구간을 말할 수\n * 없으면 `null` 이 오고, 그때는 아래 저널 경로로 간다. 느리지만 온전하다.\n */\n if (instanceIds.length === 1) {\n const fast = await readModelPage(domain.id, instanceIds[0], effective, limit)\n if (fast) return fast\n }\n\n /*\n * ── **여러 종류를 시각순으로 물으면 나눠 묻는다** (2026-08-28 실측) ──────────\n *\n * 에너지 원장이 「이 여덟 종류를 시각 내림차순으로」를 묻는다. 그 모양에서 sqlite 는 종류 색인을\n * 버리고 시각 색인으로 337만 행을 걸으며 행마다 종류를 확인한다 — **37.181초, 결과 0건**.\n * 종류를 정확일치로 하나씩 물으면 색인이 시각 순서까지 주므로 **각 0.003초**다.\n *\n * 새 색인도 새 테이블도 필요 없다. 있는 색인(`ix_twin_event_7`)을 쓰게 하는 것이 전부다.\n * 건수 세기는 이미 빠르다(정렬을 지우므로 같은 색인을 탄다) — 목록만 이 갈래를 탄다.\n */\n const tiebreak: 'revision' | 'id' = instanceIds.length === 1 ? 'revision' : 'id'\n const split = splitShape(effective, effective.sortings ?? [])\n if (split) {\n const page = await eventTypeSplitPage(domain.id, instanceIds, split, limit, tiebreak, effective.pagination?.after)\n if (page) {\n /*\n * 건수는 일반 경로와 같은 방식으로 센다 — 상한까지 키만 읽는다. 이 조건은 정렬이 없어 종류\n * 색인을 그대로 타므로 값이 싸다(실측 0.003초).\n */\n const probe = getQueryBuilderFromListParams({\n repository: getRepository(TwinEvent),\n params: { ...effective, pagination: { page: 1, limit: COUNT_CAP + 1 } },\n domain,\n searchables: [...JOURNAL_SEARCHABLES],\n sortables: ['eventTime', 'revision', 'eventType', 'bizStep'],\n defaultLimit: COUNT_CAP + 1,\n maxLimit: COUNT_CAP + 1\n })\n probe.andWhere(`${probe.alias}.instanceId IN (:...instanceIds)`, { instanceIds })\n const counted = await probe\n .orderBy()\n .select(`${probe.alias}.id`)\n .getRawMany()\n .catch(() => null)\n const capped = !counted || counted.length > COUNT_CAP\n return {\n items: page.items,\n total: capped ? undefined : counted!.length,\n capped,\n countCap: COUNT_CAP,\n nextCursor: page.nextCursor,\n oldestRevision: undefined\n }\n }\n }\n\n const qb = getQueryBuilderFromListParams({\n repository: getRepository(TwinEvent),\n params: effective,\n domain,\n /* 전부 인덱스 가능한 승격 컬럼 — 자유 검색어는 서버가 이 축들로 펼친다. */\n searchables: [...JOURNAL_SEARCHABLES],\n /* 정렬은 인덱스가 받쳐 주는 축으로만 — 큰 저널에서 임의 컬럼 정렬은 전체 정렬 스캔이다. */\n sortables: ['eventTime', 'revision', 'eventType', 'bizStep'],\n defaultLimit: 100,\n maxLimit: 500,\n /*\n * ── 동시각을 가리는 축을 **두 경로가 같게** 쓴다 (2026-08-28 실측) ─────────\n *\n * 커서는 「이 행 다음부터」이므로 시각이 같은 행들 사이에서 무엇이 앞인지 정해야 한다. 기본값은\n * 기본키(`id`)인데, 파생 테이블 경로는 `revision` 으로 가린다(그 테이블에 `id` 가 없다).\n *\n * 두 경로가 한 목록을 나눠 답하면 그 축이 달라지고, 시각이 같은 행이 많은 자리에서 **본 행이\n * 또 나온다.** 실측: 재고 사건 2,665건이 같은 시각을 가진 트윈에서 30건씩 네 번 이어 읽어\n * 120건 중 15건이 되풀었다.\n *\n * 그래서 트윈 하나를 볼 때는 `revision` 으로 가린다 — 그 트윈 안에서 유일하고 빈 값이 없다.\n * 여러 트윈을 합쳐 보는 목록에서는 리비전이 트윈마다 따로 세는 번호라 유일하지 않으므로\n * 기본키를 쓴다.\n */\n cursorTiebreak: tiebreak\n })\n /* 대상 범위는 호출자가 필터로 빼먹을 수 있는 값이 아니다 — 계약상 필수라 여기서 강제한다. */\n qb.andWhere(`${qb.alias}.instanceId IN (:...instanceIds)`, { instanceIds })\n\n /*\n * ── 건수는 **상한까지만** 센다 (2026-08-22 실측) ──────────────────────────\n * 예전에는 `getManyAndCount()` 였다. 그 두 번째 질의가 조건에 맞는 행을 **전부** 세는데, 실측한\n * 개발 저널에서 트윈 하나가 358만 행이라 **한 번에 5.32초**였다. 이력 화면을 열 때마다 그것을\n * 물었고, 그 5초 동안 이벤트 루프가 다른 요청을 처리하지 못했다.\n *\n * 상한을 두면 같은 답이 **0.04초**다(130배). 상한을 넘으면 정확한 수를 모르는 것이 사실이므로\n * 목록 규약의 「서버 상한에 부딪힘(total 모름)」 갈래로 낸다 — 화면은 「처음 N 건만」이라고 말하고\n * 좁히기를 권한다. **알리지 않고 자르지 않는다**: 상한을 넘었다는 사실을 `capped` 로 함께 낸다.\n *\n * 왜 하위 질의로 세지 않나: 5개 드라이버에서 같이 도는 형태여야 한다(이 저장소의 절대 규칙).\n * 그래서 **키 컬럼만 상한까지 읽어 세는** 방식을 쓴다 — 색인만 읽으므로 행 본문을 만들지 않는다.\n */\n const items = await qb.getMany()\n const probe = qb\n .clone()\n .orderBy() /* 세는 데 순서는 필요 없다 — 정렬을 남기면 임의 축에서 전체 정렬이 된다. */\n .skip(0)\n .take(COUNT_CAP + 1)\n .select(`${qb.alias}.id`)\n const counted = await probe.getRawMany()\n const capped = counted.length > COUNT_CAP\n /* 상한을 넘었으면 **전체 수를 모른다** — 상한값을 전체로 적으면 화면이 거짓을 말한다. */\n const total = capped ? undefined : counted.length\n\n /* 다음 커서는 **이번 페이지의 마지막 행**에서 만든다. 페이지가 상한보다 짧으면 뒤가 없다. */\n const axes = cursorSortings(effective.sortings, tiebreak)\n const nextCursor = items.length === limit ? buildNextCursor(items[items.length - 1], axes) : undefined\n\n /*\n * ── **앞부분이 지워졌는지 함께 낸다** (2026-08-27) ─────────────────────────\n *\n * 보관 기간 정리가 오래된 사건을 지운다(기본 7일). 그러면 이 목록을 아무리 내려가도 어느 지점에서\n * 기록이 끝나는데, 화면이 그것을 말하지 않으면 사람은 「이 트윈은 그때 시작했다」로 읽는다.\n * 없었던 것과 지워진 것은 다른 사실이다.\n *\n * 저장하지 않고 그때그때 구한다 — 저장한 값은 실제 데이터와 어긋날 수 있고, 어긋나면 어느 쪽이\n * 사실인지 알 수 없다. 색인 `(domain, instanceId, revision)` 의 첫 행 하나라서 값이 싸다.\n *\n * 트윈 하나를 물었을 때만 낸다. 여러 트윈을 합쳐 보는 목록에서는 리비전이 트윈마다 따로 세는\n * 번호라 하나의 답이 없다.\n */\n let oldestRevision: number | undefined\n if (instanceIds.length === 1) {\n const first = await getRepository(TwinEvent)\n .createQueryBuilder('e')\n .select('e.revision', 'revision')\n .where('e.domain = :domainId', { domainId: domain.id })\n .andWhere('e.instanceId = :instanceId', { instanceId: instanceIds[0] })\n .orderBy('e.revision', 'ASC')\n .limit(1)\n .getRawOne<{ revision: unknown }>()\n .catch(() => null)\n const at = Number(first?.revision)\n if (Number.isFinite(at) && at > 0) oldestRevision = at\n }\n\n return { items, total, capped, countCap: COUNT_CAP, nextCursor, oldestRevision }\n }\n\n /**\n * 업무 KPI — 시간창 처리량·소요시간·자원 점유. 저널을 **계산해서** 만든다(새 계측을 주입하지 않는다).\n *\n * `twinMetrics`(초당 이벤트 수 = 인프라 부하)와 **다른 것**이다: 이건 \"지난 한 시간에 몇 건 처리했고\n * 한 건에 얼마나 걸렸나\" 다. 이름이 비슷해 헷갈리기 쉬우므로 설명에 명시한다.\n *\n * 완료된 작업의 **시작 이벤트가 창 앞에 있을 수 있어** 조회 구간을 창보다 앞으로 넓힌다(lookback).\n * 그래도 못 찾은 것은 결과의 `unpaired` 로 드러난다 — 평균에서 오류 없이 빠지면 지표가 거짓이 된다.\n */\n @Query(returns => ScalarObject, {\n description:\n 'Windowed business KPI folded from the event journal, for one space (every running twin in it, folded together — averaging per-twin percentiles would be wrong) or one twin: throughput (completed tasks / orders), lead / work / wait time distributions (p50·p90), and per-resource busy ratio. Distinct from twinMetrics, which reports infrastructure throughput (events per second). When toTime is omitted the window ends at the twin\\'s own last recorded event time, not wall clock — a simulated twin lives on its own clock, so asking for \\'the last hour\\' in real time would always find nothing. The basis used is reported in window.basis. Pass groupBy (resource | taskKind | location | order | area | shift) to break the same window down by that perspective. The shift axis is **computed, not stored**: the journal records only times, and the shift is derived from each completion\\'s time plus the declared site shifts and time zone — so correcting a shift definition re-counts history correctly instead of leaving it frozen under the old boundaries. Sites that declare no shifts report every group as unknown rather than inventing shift names — per-group numbers use the same rules as the totals, so group counts sum to the total, and groups beyond groupLimit are reported as groups.truncated (how many groups) and groups.truncatedTasks (how much work those groups held) rather than silently dropped. Pass compareTo (previous | yesterday) to fold the same-length window shifted back and get comparison.delta (current minus that window); when the comparison window has no records, comparison.measured is false and no delta is produced rather than reporting zero change. Passing groupBy together with compareTo crosses the two: each group carries prevTasks / deltaTasks / deltaWorkP50Ms, groups absent from the comparison window are flagged isNew (no delta, so growth is not implied), and groups that had work before but none now are returned in groups.disappeared so a stopped area is never silently missing from the table. Area breakdown needs model locations to carry a parent area; when they do not, groups.note says so instead of implying there was no work. When the journal read hits its row cap, eventsCapped is true and every number is a lower bound — narrow the window and ask again rather than reading the smaller figures as a drop in performance. When operational targets are set for the space or instance, `targets` carries the verdicts [{metric, actual, target, direction, met, ratio}] — ratio is normalised so that 1 or more is always good, letting throughput and duration metrics be compared on one scale. A metric with no target, or with no measurement in the window, is absent from `targets` rather than reported as a miss, because a period with no work would otherwise read as a perfect score on duration metrics. Completions are counted in the window they complete; starts are looked up before the window, and any completion whose start was not found is reported in `unpaired` rather than silently dropped. Pass baselinePeriods (1-14, with baselineStride day | week) to fold the same time-of-day window on each of the last N days (or the same weekday on each of the last N weeks) and get `baseline.metrics` — per metric the distribution of those windows (min / median / max) plus where the current window sits: `rank` (1 = smallest, direction-free) and `standing` (best | better-than-typical | typical | worse-than-typical | worst, which does apply the metric direction). This answers what a single comparison cannot: comparing to yesterday alone is useless if yesterday was also bad. Every requested window is returned in `samples` with `measured` true or false — windows without records are kept and counted (measuredCount vs requested) rather than dropped, so \"the last 7 days\" never silently means 3. No position is reported when the current window has no value or fewer than two baseline windows were measured; `noPosition` says which, because a missing distribution drawn as neutral would read as \"typical\". The baseline is produced even when the current window itself has no records, since that is exactly when knowing the usual level matters (a stopped line).'\n })\n async twinKpi(\n @Ctx() context: ResolverContext,\n /* 공간이 사용자의 단위다 — 한 트윈만 보려면 instanceId 를 준다(둘 중 하나 필수). */\n @Arg('spaceId', { nullable: true }) spaceId?: string,\n @Arg('instanceId', { nullable: true }) instanceId?: string,\n /** 창 시작·끝(ISO). 미지정이면 최근 1시간. */\n @Arg('fromTime', { nullable: true }) fromTime?: string,\n @Arg('toTime', { nullable: true }) toTime?: string,\n /** 창 길이(분). fromTime 이 있으면 무시. 기본 60. */\n @Arg('windowMinutes', type => Int, { nullable: true }) windowMinutes?: number,\n /** 추세를 위해 창을 N 등분(1~48). 미지정이면 쪼개지 않는다. */\n @Arg('buckets', type => Int, { nullable: true }) buckets?: number,\n /** 시작 이벤트를 찾기 위해 창보다 앞으로 더 읽는 시간(분). 기본 60. */\n @Arg('lookbackMinutes', type => Int, { nullable: true }) lookbackMinutes?: number,\n /** 관점 축 — 같은 창을 자원별·작업종류별·도착지점별·오더별·구역별로 쪼갠다. */\n @Arg('groupBy', { nullable: true }) groupBy?: string,\n /** 축의 상한(기본 20, 최대 100). 넘치면 잘라내고 잘린 수를 알린다. */\n @Arg('groupLimit', type => Int, { nullable: true }) groupLimit?: number,\n /** 비교 기준 — 같은 길이의 앞 구간(previous)이나 하루 전 같은 시간(yesterday). */\n @Arg('compareTo', { nullable: true }) compareTo?: string,\n /** 여러 구간 동시 비교 — 최근 N 구간의 같은 시간대 분포(1~14). */\n @Arg('baselinePeriods', type => Int, { nullable: true }) baselinePeriods?: number,\n /** 분포의 간격 — day(같은 시간대) | week(같은 요일·시간대). 기본 day. */\n @Arg('baselineStride', { nullable: true }) baselineStride?: string\n ): Promise<any> {\n /* 테넌트 격리 — 트윈 지정이면 소속을 확인한다. 공간 지정이면 대상 해소가 도메인 목록에서\n * 이뤄지므로(computeTwinKpi) 남의 트윈이 섞이지 않는다. 들어온 것이 없는 빈 결과가 아니라 명시 실패. */\n if (instanceId && !(await TwinEngine.ownsRegistered(context.state.domain.id, instanceId))) {\n throw new Error(`twin instance not found in this tenant: ${instanceId}`)\n }\n if (!instanceId && !spaceId) throw new Error('either spaceId or instanceId is required')\n /* 창 해소·조회·폴드는 `computeTwinKpi` 가 소유한다 — 같은 규칙을 AI 도구와 공유해야 한다\n * (두 곳에 적으면 화면과 AI 가 다른 숫자를 말한다). */\n return computeTwinKpi({\n domainId: context.state.domain.id,\n instanceId,\n spaceId,\n fromTime,\n toTime,\n windowMinutes,\n buckets,\n lookbackMinutes,\n groupBy: groupBy as any,\n groupLimit,\n compareTo: compareTo as any,\n ...(baselinePeriods && baselinePeriods > 0\n ? { baseline: { periods: baselinePeriods, stride: (baselineStride as any) === 'week' ? 'week' : 'day' } }\n : {})\n })\n }\n\n @Query(returns => ScalarObject, {\n nullable: true,\n description: 'Reconstruct twin state from the durable journal (DB → replay). untilTime (ISO) for time-travel on the shared clock (preferred); untilRevision for the lens-internal counter; omit both for latest.'\n })\n async twinReplay(\n @Arg('instanceId') instanceId: string,\n @Arg('untilRevision', type => Int, { nullable: true }) untilRevision: number,\n @Arg('untilTime', { nullable: true }) untilTime: string,\n @Ctx() context: ResolverContext,\n /*\n * 필요한 축만 — 지도는 `locations`·`attentions` 만 쓰는데 상태를 통째로 받고 있었다(실 저널로\n * 재 보니 트윈 하나가 최대 710KB, 같은 공간에 셋이면 약 2MB). 화면 전용 질의를 만들지 않고\n * 호출부가 축을 말한다(무방언 — 어휘가 화면 이름이 아니라 상태의 축이다).\n * 말하지 않으면 자르지 않는다(기존 호출부 무영향).\n */\n @Arg('axes', type => [String], { nullable: true }) axes?: string[],\n /*\n * 신호는 몇 건만 필요한가 — 레일은 20건을 그리고 나머지는 \"N건 더\" 라고만 말한다. 그런데 신호가\n * 많은 트윈은 그 전량이 108KB 다(`hatio-yard` 397건). 상위 N 만 보내고 **전체 개수와 자리별\n * 최고 심각도는 전량을 근거로** 함께 보낸다 — 레일은 잘라도 되지만 지도 색은 잘라선 안 된다.\n * 말하지 않으면 자르지 않는다.\n */\n @Arg('attentionLimit', type => Int, { nullable: true }) attentionLimit?: number\n ): Promise<any> {\n const state = await TwinEngine.recover(context.state.domain.id, instanceId, untilRevision ?? undefined, untilTime ?? undefined)\n const picked = pickAxes(state, axes)\n /* 오타를 알리지 않고 넘기지 않는다 — 화면이 빈 채로 뜨는 이유를 아무도 모르게 되는 것이 가장 나쁘다. */\n if (picked.unknown.length) twinWarn(`[twin] twinReplay: unknown axes ignored for \"${instanceId}\": ${picked.unknown.join(', ')}`)\n if (!attentionLimit || !picked.state?.attentions) return picked.state\n const d = digestAttentions(picked.state.attentions, attentionLimit)\n /* 잘랐다는 사실을 값으로 남긴다 — 개수만 줄어들면 화면은 \"괜찮아졌다\" 로 읽는다. */\n return { ...picked.state, attentions: d.attentions, attentionTotal: d.attentionTotal, severityByLocation: d.severityByLocation }\n }\n\n @Query(returns => ScalarObject, {\n nullable: true,\n description:\n 'Steady-state capacity of a twin against a declared demand: per-operation supply vs required across equipment, personnel, physical assets and locations, plus the binding constraint and the daily ceiling. Answers \"can this plant meet the target?\" without running a simulation. Null when the twin is not running.'\n })\n async twinCapacity(\n @Arg('instanceId', { nullable: true }) instanceId: string,\n @Arg('spaceId', { nullable: true }) spaceId: string,\n /** 하루 몇 대를 낼 것인가 — 화면이 보고 있는 목표에서 나온다(선언된 수요). */\n @Arg('unitsPerDay', type => Float) unitsPerDay: number,\n /**\n * 가용 시간을 잴 기준 주의 시작(ISO, 월요일). 공휴일이 없는 평상주여야 한다 —\n * 공휴일은 연간 가용량을 따로 깎지, 이 공장의 평상시 상한을 정하지 않는다.\n */\n @Arg('sampleWeekStart', { nullable: true }) sampleWeekStart: string,\n @Ctx() context: ResolverContext\n ): Promise<any> {\n if (instanceId && !(await TwinEngine.ownsRegistered(context.state.domain.id, instanceId))) {\n throw new Error(`twin instance not found in this tenant: ${instanceId}`)\n }\n if (!instanceId && !spaceId) throw new Error('either spaceId or instanceId is required')\n if (!(unitsPerDay > 0)) throw new Error('unitsPerDay must be greater than zero')\n\n /*\n * 기준 주를 안 주면 **이번 주 월요일**을 쓴다. 그 주에 공휴일이 끼면 상한이 조금 낮게 나오는데,\n * 그것은 들어온 것이 없는 오류가 아니라 **이번 주의 실제 가용 시간**이라 답으로서 정직하다. 평상시 상한을\n * 보려는 호출자는 평상주를 지정한다.\n */\n const base = sampleWeekStart ? Date.parse(sampleWeekStart) : Date.now()\n if (Number.isNaN(base)) throw new Error(`sampleWeekStart is not a valid ISO time: ${sampleWeekStart}`)\n const d = new Date(base)\n const monday = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() - ((d.getUTCDay() + 6) % 7))\n\n return TwinEngine.capacity(context.state.domain.id, { instanceId, spaceId }, unitsPerDay, monday)\n }\n\n @Query(returns => ScalarObject, {\n nullable: true,\n description:\n 'Operations capability of a twin for a time window (ISA-95 Part 4 OperationsCapability): per operation the ceiling for that window split into Committed (already promised), Unattainable (lost to resources that cannot be used — down, held, off-shift, expired qualification) and Available (what is left). Answers \"can we take more work?\" — which the steady-state rate alone cannot. Null when the twin is not running.'\n })\n async twinOperationsCapability(\n @Arg('instanceId', { nullable: true }) instanceId: string,\n @Arg('spaceId', { nullable: true }) spaceId: string,\n @Arg('unitsPerDay', type => Float) unitsPerDay: number,\n /** 능력을 물을 구간 — **구간 없이 능력은 존재하지 않는다**(표준 StartTime/EndTime). */\n @Arg('fromTime') fromTime: string,\n @Arg('toTime') toTime: string,\n @Arg('sampleWeekStart', { nullable: true }) sampleWeekStart: string,\n @Ctx() context: ResolverContext\n ): Promise<any> {\n if (instanceId && !(await TwinEngine.ownsRegistered(context.state.domain.id, instanceId))) {\n throw new Error(`twin instance not found in this tenant: ${instanceId}`)\n }\n if (!instanceId && !spaceId) throw new Error('either spaceId or instanceId is required')\n if (!(unitsPerDay > 0)) throw new Error('unitsPerDay must be greater than zero')\n /* 구간은 **부르는 쪽이 말해야 한다** — 기본값을 두면 화면마다 다른 구간을 보면서 같은 값으로 읽는다. */\n const from = Date.parse(fromTime)\n const to = Date.parse(toTime)\n if (Number.isNaN(from) || Number.isNaN(to)) throw new Error(`window is not a valid ISO time: ${fromTime} ~ ${toTime}`)\n if (to <= from) throw new Error(`window is inverted: ${fromTime} ~ ${toTime}`)\n\n /* 기준 주 규율은 `twinCapacity` 와 같다(같은 계산을 쓰므로 같은 주를 골라야 한다). */\n const base = sampleWeekStart ? Date.parse(sampleWeekStart) : from\n if (Number.isNaN(base)) throw new Error(`sampleWeekStart is not a valid ISO time: ${sampleWeekStart}`)\n const d = new Date(base)\n const monday = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() - ((d.getUTCDay() + 6) % 7))\n\n return TwinEngine.operationsCapability(\n context.state.domain.id,\n { instanceId, spaceId },\n unitsPerDay,\n monday,\n { fromTime, toTime }\n )\n }\n\n @Query(returns => ScalarObject, {\n description:\n \"Structural revisions a twin has been through, newest last: when each took effect, how many journal events happened under it, and what changed from the previous one (counts by location type and equipment kind). The model itself is not returned — a revision holds the whole plant, but what a person wants is the difference.\"\n })\n async twinStructures(@Arg('instanceId') instanceId: string, @Ctx() context: ResolverContext): Promise<any> {\n /* 이력 질문이므로 **등록부**에 소유를 묻는다 — 꺼진 트윈의 이력도 읽을 수 있어야 한다. */\n if (!(await TwinEngine.ownsRegistered(context.state.domain.id, instanceId))) {\n throw new Error(`twin instance not found in this tenant: ${instanceId}`)\n }\n return TwinEngine.structureHistory(context.state.domain.id, instanceId)\n }\n\n @Query(returns => ScalarObject, {\n description: 'Event time range (min/max ISO) across all co-located twins of a space — the history scrubber time axis.'\n })\n async twinTimeRange(\n @Arg('spaceId') spaceId: string,\n @Ctx() context: ResolverContext\n ): Promise<{ minTime: string | null; maxTime: string | null }> {\n return TwinEngine.timeRange(context.state.domain.id, spaceId)\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"domain-catalog.js","sourceRoot":"","sources":["../../../server/service/twin-lifecycle/domain-catalog.ts"],"names":[],"mappings":";;AAkBA,sCASC;AASD,gDAcC;AAlDD;;;;;;GAMG;AACH,yDAAyD;AACzD,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAA;AACxD,MAAM,EAAE,cAAc,EAAE,YAAY,EAAE,mBAAmB,EAAE,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAA;AAE9F;;;;;;GAMG;AACH,SAAgB,aAAa;IAC3B,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,wHAAwH,CAAC,CAAA;IAC3I,CAAC;IACD,OAAO;QACL,GAAG,cAAc;QACjB,GAAG,EAAE,EAAE,GAAG,cAAc,CAAC,GAAG,EAAE,QAAQ,EAAE,YAAY,IAAI,EAAE,EAAE;QAC5D,YAAY,EAAE,YAAY,IAAI,EAAE;KACjC,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,kBAAkB,CAAC,OAAe,EAAE,MAAe;IACjE,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAA;IACvB,IAAI,MAAM,IAAI,cAAc,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC;QACvC,IAAI,CAAC;YACH,OAAO,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,EAAE,CAAA;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,uCAAuC;QACzC,CAAC;IACH,CAAC;IACD,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,cAAc,IAAI,EAAE,CAAU,EAAE,CAAC;QACnE,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,OAAO,CAAC,CAAA;QACpE,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,YAAY,CAAC;YAAE,OAAO,CAAC,CAAC,YAAY,CAAA;IAC3D,CAAC;IACD,OAAO,EAAE,CAAA;AACX,CAAC","sourcesContent":["/*\n * 도메인 어휘 카탈로그 조합 — 커널(@operato/twin-kernel)이 SSOT, 여기서는 **노출 형태만** 만든다.\n *\n * 이 함수가 정본 진입점이다: GraphQL 리졸버(twinDomainCatalog)와 서버측 소비자(예: operato-twin 의\n * board-ai 도구 카테고리)가 같은 조합을 쓰도록 하기 위해 분리했다. 소비자마다 커널을 직접 require 해\n * 조합을 재현하면 프로파일 병합(mes.products)이나 능력 메타 포함 여부가 갈린다(방언).\n */\n/* 커널은 CJS 배포 — ESM import 대신 require(리졸버의 기존 관행과 동일). */\nconst { MES_PRODUCTS } = require('@operato/twin-kernel')\nconst { DOMAIN_CATALOG, CAPABILITIES, capabilitiesForType } = require('@operato/ops-contract')\n\n/**\n * 시스템별(wms/yms/mes) 노드·설비 타입과 각 타입의 능력을 담은 카탈로그.\n * MES 제품 목록을 mes 프로파일에 병합하고 능력 메타(capabilities)를 함께 싣는다.\n *\n * 커널 빌드가 낡아 DOMAIN_CATALOG 가 없으면 명시 throw — 무의미한 null 대신 원인을 알린다.\n * 조치: 커널 재빌드 후 dist/dist-cjs 를 node_modules 에 복사.\n */\nexport function domainCatalog(): any {\n if (!DOMAIN_CATALOG) {\n throw new Error('DOMAIN_CATALOG missing — @operato/twin-kernel build is stale. Rebuild kernel and copy dist/dist-cjs into node_modules.')\n }\n return {\n ...DOMAIN_CATALOG,\n mes: { ...DOMAIN_CATALOG.mes, products: MES_PRODUCTS ?? [] },\n capabilities: CAPABILITIES ?? {}\n }\n}\n\n/**\n * 타입 → 능력. 커널의 `capabilitiesForType` 이 정본이며 여기서는 **시스템 미상** 경우만 보완한다\n * (커널 함수는 system 을 요구). 소비자가 조회 규칙을 각자 재현하지 않도록 이 함수를 쓸 것.\n *\n * @param typeKey 노드 타입 또는 무버 kind\n * @param system wms | yms | mes. 모르면 생략 — 전 프로파일에서 처음 일치하는 타입의 능력을 쓴다.\n */\nexport function capabilitiesOfType(typeKey: string, system?: string): string[] {\n if (!typeKey) return []\n if (system && DOMAIN_CATALOG?.[system]) {\n try {\n return capabilitiesForType(system, typeKey) ?? []\n } catch {\n /* 커널이 모르는 조합 — 아래 전 프로파일 탐색으로 넘어간다. */\n }\n }\n for (const profile of Object.values(DOMAIN_CATALOG ?? {}) as any[]) {\n const t = (profile?.types ?? []).find((x: any) => x.key === typeKey)\n if (Array.isArray(t?.capabilities)) return t.capabilities\n }\n return []\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/twin-lifecycle/index.ts"],"names":[],"mappings":";;;AAAA,6EAAoE;AAEvD,QAAA,SAAS,GAAG,CAAC,kDAAqB,CAAC,CAAA","sourcesContent":["import { TwinLifecycleMutation } from './twin-lifecycle-mutation.js'\n\nexport const resolvers = [TwinLifecycleMutation]\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"twin-lifecycle-mutation.js","sourceRoot":"","sources":["../../../server/service/twin-lifecycle/twin-lifecycle-mutation.ts"],"names":[],"mappings":";;;;AAAA,+CAA4F;AAE5F,iDAAkF;AAElF,oDAAkD;AAClD,mEAAmE;AACnE,sEAAkE;AAClE,wEAAgE;AAChE,+DAAuD;AAIvD;0EAC0E;AAC1E,2DAAmD;AAEnD;;;;;;GAMG;AAGI,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IAChC;;;;;;;;;OASG;IAEH,iEAAiE;IAK3D,AAAN,KAAK,CAAC,iBAAiB,CACF,UAAkB,EAC9B,OAAwB,EACE,IAAa,EACU,KAAW,EAGzB,aAA6C;QAEvF,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAA;QACxC,uDAAuD;QACvD,IAAI,KAAK,EAAE,CAAC;YACV;qFACyE;YACzE,IAAI,CAAC,IAAI;gBAAE,MAAM,IAAI,KAAK,CAAC,sFAAsF,CAAC,CAAA;YAClH,qBAAU,CAAC,KAAK,CAAC,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,qBAAU,CAAC,iBAAiB,CAAC,KAAqB,EAAE,QAAQ,CAAC,EAAE,aAAa,CAAC,CAAA;QAClI,CAAC;;YACI,MAAM,qBAAU,CAAC,iBAAiB,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAA;QAC7D,OAAO,IAAI,CAAA;IACb,CAAC;IAIK,AAAN,KAAK,CAAC,gBAAgB,CAAoB,UAAkB,EAAS,OAAwB;QAC3F,IAAI,CAAC,qBAAU,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,CAAC;YAAE,OAAO,KAAK,CAAA,CAAC,2BAA2B;QACnG,MAAM,qBAAU,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,CAAC,CAAA;QAC1D,OAAO,IAAI,CAAA;IACb,CAAC;IAED;;;;;;;;;;;;OAYG;IAMG,AAAN,KAAK,CAAC,oBAAoB,CACL,UAAkB,EACf,aAAqB,EACpC,OAAwB;QAE/B,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAA;QACxC,mEAAmE;QACnE,MAAM,MAAM,GAAG,IAAA,qCAAiB,EAAC,aAAa,EAAE,SAAS,UAAU,GAAG,CAAC,CAAA;QACvE,MAAM,IAAI,GAAG,IAAA,qBAAa,EAAC,+BAAY,CAAC,CAAA;QACxC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,UAAU,EAAE,EAAE,CAAC,CAAA;QACnF,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,UAAU,wCAAwC,CAAC,CAAA;QAE9F,MAAM,MAAM,GAAG,GAAG,CAAC,aAAa,CAAA;QAChC,IAAI,MAAM,KAAK,MAAM;YAAE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAA;QAE9G,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC,CAAA;QAC5D,0CAA0C;QAC1C,MAAM,OAAO,GAAG,CAAC,CAAC,qBAAU,CAAC,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAA;QAC1D,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,CAAA;IAC5G,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IASG,AAAN,KAAK,CAAC,kBAAkB,CACH,UAAkB,EACxB,IAAY,EAClB,OAAwB;QAE/B,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAA;QACxC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;QACtC,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,UAAU,sBAAsB,CAAC,CAAA;QAE9F,MAAM,IAAI,GAAG,IAAA,qBAAa,EAAC,+BAAY,CAAC,CAAA;QACxC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,UAAU,EAAE,EAAE,CAAC,CAAA;QACnF,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,UAAU,wCAAwC,CAAC,CAAA;QAE9F,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,IAAI,EAAE,CAAA;QAC7B,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAA;QAEtE,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;QAC9E,uDAAuD;QACvD,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,IAAI,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;IAC5E,CAAC;IAED;;;;;;;OAOG;IAMG,AAAN,KAAK,CAAC,mBAAmB,CAAoB,UAAkB,EAAS,OAAwB;QAC9F,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAA;QACxC,MAAM,QAAQ,GAAG,MAAM,IAAA,qBAAa,EAAC,+BAAY,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,UAAU,EAAE,EAAE,CAAC,CAAA;QAE/G,IAAI,CAAC,QAAQ;YAAE,OAAO,IAAI,CAAA;QAE1B,MAAM,OAAO,GAAG,MAAM,IAAA,qBAAa,EAAC,yBAAS,CAAC;aAC3C,kBAAkB,CAAC,OAAO,CAAC;aAC3B,MAAM,CAAC,iBAAiB,EAAE,OAAO,CAAC;aAClC,SAAS,CAAC,sBAAsB,EAAE,cAAc,CAAC;aACjD,SAAS,CAAC,sBAAsB,EAAE,aAAa,CAAC;aAChD,KAAK,CAAC,0BAA0B,EAAE,EAAE,QAAQ,EAAE,CAAC;aAC/C,QAAQ,CAAC,gCAAgC,EAAE,EAAE,UAAU,EAAE,CAAC;aAC1D,SAAS,EAA8E,CAAA;QAE1F,OAAO;YACL,UAAU;YACV,IAAI,EAAE,QAAQ,CAAC,IAAI,IAAI,IAAI;YAC3B,YAAY,EAAE,MAAM,CAAC,OAAO,EAAE,KAAK,IAAI,CAAC,CAAC;YACzC,YAAY,EAAE,OAAO,EAAE,YAAY,IAAI,IAAI;YAC3C,WAAW,EAAE,OAAO,EAAE,WAAW,IAAI,IAAI;SAC1C,CAAA;IACH,CAAC;IAED,iDAAiD;IAG3C,AAAN,KAAK,CAAC,kBAAkB,CAAoB,UAAkB,EAAS,OAAwB;QAC7F,MAAM,qBAAU,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,CAAC,CAAA;QAC5D,OAAO,IAAI,CAAA;IACb,CAAC;IAED,+EAA+E;IAC/E;;;;;;OAMG;IAEG,AAAN,KAAK,CAAC,oBAAoB,CAAiB,OAAe,EAAS,OAAwB;QACzF,OAAO,qBAAU,CAAC,kBAAkB,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,CAAA;IACxE,CAAC;IAMK,AAAN,KAAK,CAAC,gBAAgB,CAAQ,OAAwB;QACpD,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAA;QACxC,MAAM,IAAI,GAAG,MAAM,qBAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAC5C;;;WAGG;QACH,MAAM,KAAK,GAAG,IAAI,GAAG,CACnB,qBAAU,CAAC,iBAAiB,CAC1B,QAAQ,EACR,IAAI,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CACnC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAC9B,CAAA;QACD;;;;;;;;;WASG;QACH,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE;YACzB,MAAM,CAAC,GAAG,qBAAU,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,UAAU,CAAC,CAAA;YACpD,OAAO;gBACL,GAAG,CAAC;gBACJ,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,IAAI;gBACrC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,SAAS,EAAE,CAAC,CAAC,cAAc,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI;aAC3F,CAAA;QACH,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,8CAA8C;IAExC,AAAN,KAAK,CAAC,kBAAkB,CAAoB,UAAkB,EAAS,OAAwB;QAC7F,OAAO,qBAAU,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,CAAC,CAAA;IAC/D,CAAC;IAED;;;;OAIG;IAEG,AAAN,KAAK,CAAC,aAAa,CAAQ,OAAwB;QACjD,MAAM,IAAI,GAAG,MAAM,qBAAU,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QAC3D,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAA;IACzE,CAAC;IAED;;;OAGG;IAEH,iBAAiB;QACf,OAAO,IAAA,iCAAa,GAAE,CAAA;IACxB,CAAC;CACF,CAAA;AAnQY,sDAAqB;AAiB1B;IAJL,IAAA,wBAAS,EAAC,uGAAuG,CAAC;IAClH,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE;QAC5B,WAAW,EAAE,oJAAoJ;KAClK,CAAC;IAEC,mBAAA,IAAA,kBAAG,EAAC,YAAY,CAAC,CAAA;IACjB,mBAAA,IAAA,kBAAG,GAAE,CAAA;IACL,mBAAA,IAAA,kBAAG,EAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;IAC/B,mBAAA,IAAA,kBAAG,EAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,oBAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;IAGtD,mBAAA,IAAA,kBAAG,EAAC,eAAe,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;;;;8DAY1C;AAIK;IAFL,IAAA,wBAAS,EAAC,uGAAuG,CAAC;IAClH,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,4FAA4F,EAAE,CAAC;IACpH,mBAAA,IAAA,kBAAG,EAAC,YAAY,CAAC,CAAA;IAAsB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;6DAInE;AAoBK;IALL,IAAA,wBAAS,EAAC,uGAAuG,CAAC;IAClH,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,oBAAY,EAAE;QACjC,WAAW,EACT,ohBAAohB;KACvhB,CAAC;IAEC,mBAAA,IAAA,kBAAG,EAAC,YAAY,CAAC,CAAA;IACjB,mBAAA,IAAA,kBAAG,EAAC,eAAe,CAAC,CAAA;IACpB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;iEAgBP;AAmCK;IARL,IAAA,wBAAS,EAAC,uGAAuG,CAAC;IAClH,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,oBAAY,EAAE;QACjC,WAAW,EACT,6GAA6G;YAC7G,wGAAwG;YACxG,mGAAmG;YACnG,+EAA+E;KAClF,CAAC;IAEC,mBAAA,IAAA,kBAAG,EAAC,YAAY,CAAC,CAAA;IACjB,mBAAA,IAAA,kBAAG,EAAC,MAAM,CAAC,CAAA;IACX,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;+DAgBP;AAeK;IALL,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,oBAAY,EAAE;QAC9B,QAAQ,EAAE,IAAI;QACd,WAAW,EACT,qMAAqM;KACxM,CAAC;IACyB,mBAAA,IAAA,kBAAG,EAAC,YAAY,CAAC,CAAA;IAAsB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;gEAsBtE;AAKK;IAFL,IAAA,wBAAS,EAAC,uGAAuG,CAAC;IAClH,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,yFAAyF,EAAE,CAAC;IAC/G,mBAAA,IAAA,kBAAG,EAAC,YAAY,CAAC,CAAA;IAAsB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;+DAGrE;AAWK;IADL,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,WAAW,EAAE,yEAAyE,EAAE,CAAC;IAC3F,mBAAA,IAAA,kBAAG,EAAC,SAAS,CAAC,CAAA;IAAmB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;iEAEjE;AAMK;IAJL,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,CAAC,oBAAY,CAAC,EAAE;QAChC,WAAW,EACT,8vBAA8vB;KACjwB,CAAC;IACsB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;6DA+B5B;AAIK;IADL,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,oBAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,2FAA2F,EAAE,CAAC;IACnI,mBAAA,IAAA,kBAAG,EAAC,YAAY,CAAC,CAAA;IAAsB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;+DAErE;AAQK;IADL,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,WAAW,EAAE,iGAAiG,EAAE,CAAC;IAC1H,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;0DAGzB;AAOD;IADC,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,oBAAY,EAAE,EAAE,WAAW,EAAE,yHAAyH,EAAE,CAAC;;;;8DAG1K;gCAlQU,qBAAqB;IAFjC,IAAA,uBAAQ,GAAE;IACV,IAAA,4BAAa,EAAC,qBAAa,CAAC;GAChB,qBAAqB,CAmQjC","sourcesContent":["import { Arg, Ctx, Mutation, Query, Resolver, Directive, UseMiddleware } from 'type-graphql'\n\nimport { ScalarObject, SpeakRefusals, getRepository } from '@things-factory/shell'\n\nimport { TwinEngine } from '../../engine/index.js'\n/* 정책 읽기는 그 모듈에서 직접 가져온다 — 다른 소비처(`reference-resolver`)와 같은 길이다. */\nimport { readRestartPolicy } from '../../engine/restart-policy.js'\nimport { TwinInstance } from '../twin-instance/twin-instance.js'\nimport { TwinEvent } from '../twin-event/twin-event.js'\n\nimport type { TwinModelDef } from '@operato/ops-contract'\n\n/* 도메인 어휘 카탈로그(시스템별 노드 타입) — 커널이 SSOT. 앱/UI 는 이걸 소싱하고 재선언하지 않는다(방언 금지).\n * 조합은 domain-catalog.ts 가 소유 — 리졸버와 서버측 소비자(board-ai 도구 등)가 같은 형태를 본다. */\nimport { domainCatalog } from './domain-catalog.js'\n\n/*\n * 트윈 인스턴스 생명주기 채널 — 프로비저닝(상향, ADR-0015).\n * State/Command/Scenario 는 이미 running 인 인스턴스를 다루지만, 이 채널은 인스턴스 자체를 만들고/설정하고/없앤다.\n * 생명주기: provision(save, 미기동) → start → stop → delete. 모두 domain 스코프(context.state.domain.id).\n * model(TwinModelDef+layout)=구조, scenario=자극 은 분리 — 여기선 구조만 세운다. 자극은 **원본이 선언하고**\n * (`TwinReference.connectionConfig.scenario`) 기동이 싣거나, 원본의 구동 능력으로 태운다(ADR-0029 §4·§8).\n */\n@Resolver()\n@UseMiddleware(SpeakRefusals)\nexport class TwinLifecycleMutation {\n /*\n * `saveTwinInstance` 는 **없앴다**(2026-08-08, ADR-0028).\n *\n * 트윈의 구조를 임의의 model 로 덮어쓰는 문이었고, 유일한 소비자는 손저작 편집기였다.\n * 구조는 원본에서 온다 — 템플릿(`createFromTemplate`)이나 커넥터(`ingestMaster`)가 그 길이다.\n * 문을 열어 두면 화면만 지워도 다음 소비자가 다시 생긴다.\n *\n * 시뮬 트윈을 세우는 것은 여전히 가능하다. 다만 그 입구는 **원본을 만드는 쪽**이지\n * 트윈을 고치는 쪽이 아니다(ADR-0029 — 시뮬레이터는 원본의 한 종류).\n */\n\n /** 기동 — model 를 주면 provision+start, 없으면 레지스트리에 저장된 구조로 start. */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => Boolean, {\n description: 'Start a twin runtime instance and begin the worker tick. If model is given it provisions first; otherwise it starts from the saved registry model.'\n })\n async startTwinInstance(\n @Arg('instanceId') instanceId: string,\n @Ctx() context: ResolverContext,\n @Arg('kind', { nullable: true }) kind?: string,\n @Arg('model', type => ScalarObject, { nullable: true }) model?: any,\n /** 새 시뮬레이션은 재기동 때 저널을 이어야 한다. 엔진에는 이미 필수지만 이 API가 누락해\n * zero-to-twin 경로가 기동 단계에서 막혔다. */\n @Arg('restartPolicy', { nullable: true }) restartPolicy?: 'resync' | 'resume' | 'reset'\n ): Promise<boolean> {\n const domainId = context.state.domain.id\n // 시각 기준은 **공간**이 갖는다(테넌트 아님) — 교대의 HH:MM 을 어느 기준으로 읽나.\n if (model) {\n /* model 를 함께 주는 것은 프로비저닝이다 — 그러면 종류도 말해야 한다.\n 예전에는 `kind ?? 'wms'` 로 메웠는데, 종류를 빠뜨린 야드/생산 트윈이 **창고로 알리지 않고 기동**했다. */\n if (!kind) throw new Error('kind is required when model is given — it decides which domain kernel runs this twin')\n TwinEngine.start(instanceId, domainId, kind, await TwinEngine.withSpaceTimeBase(model as TwinModelDef, domainId), restartPolicy)\n }\n else await TwinEngine.startFromRegistry(domainId, instanceId)\n return true\n }\n\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => Boolean, { description: 'Stop a running twin runtime instance by its id (only if it belongs to the current domain).' })\n async stopTwinInstance(@Arg('instanceId') instanceId: string, @Ctx() context: ResolverContext): Promise<boolean> {\n if (!TwinEngine.owns(context.state.domain.id, instanceId)) return false // 테넌트 격리 — 남의 도메인 트윈 정지 차단\n await TwinEngine.stop(context.state.domain.id, instanceId)\n return true\n }\n\n /**\n * 이 트윈이 **자기 과거를 어떻게 대할 것인가**를 다시 선언한다 (2026-08-20).\n *\n * ── 왜 이 문이 필요한가 ────────────────────────────────────────────────────\n * `restartPolicy` 는 사람이 하는 선언인데, 만들 때 한 번 정하면 **고칠 길이 없었다.** 그래서 「씨앗\n * 재현용」으로 세운 트윈이 나중에 사람들이 이력을 보는 트윈이 되어도 선언은 `reset` 으로 남았다.\n * 선언과 쓰임이 어긋난 채로 두면, 선언대로 구현하는 순간(저널 초기화) 이력을 잃는다.\n *\n * ── 지금 도는 것에는 손대지 않는다 ─────────────────────────────────────────\n * 이 값은 **다음 기동**의 규칙이다. 돌고 있는 런타임의 구동 방식을 바꾸려면 정지·기동이 필요하고,\n * 그 판단은 사람의 것이다(도는 트윈을 알리지 않고 갈아 끼우면 그 사이의 사실이 어디로 갔는지 알 수 없다).\n * 그래서 결과에 「지금 도는 것과 다르다」는 사실을 함께 낸다.\n */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => ScalarObject, {\n description:\n \"Re-declare how a twin treats its own past on the next boot: 'resync' (re-read from the source), 'resume' (carry its own journal forward) or 'reset' (reproduce from the seed, journal cleared). The value is read strictly — an unknown or legacy word is refused with the accepted words named, because a silent default would decide whether a twin's history survives. This changes the declaration only: a running runtime keeps the mode it was started with, and the result says so (appliesAtNextStart) rather than swapping it underneath.\"\n })\n async setTwinRestartPolicy(\n @Arg('instanceId') instanceId: string,\n @Arg('restartPolicy') restartPolicy: string,\n @Ctx() context: ResolverContext\n ): Promise<any> {\n const domainId = context.state.domain.id\n /* 값은 **엄격히** 읽는다 — 모르는 말을 기본값으로 메우면 그 트윈의 이력이 남을지가 알리지 않고 결정된다. */\n const policy = readRestartPolicy(restartPolicy, `twin \"${instanceId}\"`)\n const repo = getRepository(TwinInstance)\n const row = await repo.findOne({ where: { domain: { id: domainId }, instanceId } })\n if (!row) throw new Error(`unknown twin \"${instanceId}\" in this domain — nothing was changed`)\n\n const before = row.restartPolicy\n if (before === policy) return { instanceId, restartPolicy: policy, changed: false, appliesAtNextStart: false }\n\n await repo.update({ id: row.id }, { restartPolicy: policy })\n /* 도는 런타임의 구동 방식은 그대로다 — 그 사실을 숨기지 않는다. */\n const running = !!TwinEngine.metrics(domainId, instanceId)\n return { instanceId, restartPolicy: policy, previous: before, changed: true, appliesAtNextStart: running }\n }\n\n /**\n * 트윈의 **이름을 다시 붙인다** (2026-08-26).\n *\n * ── 왜 이 문이 필요한가 ────────────────────────────────────────────────────\n * 이름은 트윈을 처음 세울 때 한 번 정해지고(`provision`), 다시 읽기는 그것을 건드리지 않는다. 그\n * 자체는 안전하다 — 사람이 붙인 이름을 연결이 덮어쓰면 안 된다. 그런데 그래서 **첫날의 이름에\n * 갇힌다.**\n *\n * 실제로 그런 일이 났다: 태양광 발전소를 붙일 때 커넥터가 아는 이름이 연결된 시스템의 코드\n * (`ES202307202202`)뿐이었다. 뒤에 사람이 부르는 이름(`호현에너지`)이 어디 있는지 알아내 커넥터를\n * 고쳤는데, 트윈 이름은 그대로였다. **연결된 시스템을 따라가는 트윈에서는 이름이 나중에 좋아진다** —\n * 커넥터가 나아질 때마다 이 일이 난다.\n *\n * 그때 남은 유일한 길이 트윈을 지우고 다시 세우는 것이었다. 그러면 지난 기록이 사라진다 — **이름\n * 하나 때문에 사실을 버리는 선택**이 된다. 그래서 이름만 바꾸는 문을 낸다.\n *\n * ── 연결이 따라오게 하지 않는다 ────────────────────────────────────────────\n * 다시 읽기가 이름을 따라가게 하면 사람이 붙인 이름을 덮어쓴다. 「사람이 손대지 않았으면 따라간다」가\n * 옳아 보이지만, 그러려면 「손댔다」는 표시를 새로 만들어야 하고 그것은 이름 하나 때문에 지기에 무거운\n * 빚이다. 사람이 바꾸는 길만 낸다.\n *\n * ── 빈 이름은 받지 않는다 ──────────────────────────────────────────────────\n * 이름을 지우는 것과 바꾸는 것은 다른 일이다. 빈 값을 받으면 그 트윈은 목록에서 식별자만 남는데,\n * 그것을 「이름을 바꿨다」로 보고하면 거짓이다. 지우고 싶으면 그 뜻의 문이 따로 있어야 한다.\n */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => ScalarObject, {\n description:\n 'Rename a twin instance. The name is set when the twin is first provisioned and a re-read never touches it, ' +\n 'so a twin stays with the name its connector knew on day one — often a code from the connected system. ' +\n 'This changes the name only; nothing about the twin data, its journal or its runtime is affected. ' +\n 'An empty name is refused: clearing a name and changing it are different acts.'\n })\n async renameTwinInstance(\n @Arg('instanceId') instanceId: string,\n @Arg('name') name: string,\n @Ctx() context: ResolverContext\n ): Promise<any> {\n const domainId = context.state.domain.id\n const next = String(name ?? '').trim()\n if (!next) throw new Error(`a twin name cannot be empty — \"${instanceId}\" was left as it was`)\n\n const repo = getRepository(TwinInstance)\n const row = await repo.findOne({ where: { domain: { id: domainId }, instanceId } })\n if (!row) throw new Error(`unknown twin \"${instanceId}\" in this domain — nothing was changed`)\n\n const before = row.name ?? ''\n if (before === next) return { instanceId, name: next, changed: false }\n\n await repo.update({ id: row.id }, { name: next, updater: context.state.user })\n /* 도는 런타임은 이름을 쓰지 않는다(식별자로 돈다) — 그래서 정지·기동이 필요하지 않다. */\n return { instanceId, name: next, previous: before || null, changed: true }\n }\n\n /**\n * 폐기 전에 사라질 것을 읽는다.\n *\n * 삭제는 레지스트리 행 하나만 없애는 일이 아니다. `TwinEngine.remove` 는 같은 트윈의 저널도\n * purge 한다. 그래서 확인 창이 추측한 숫자를 쓰지 않고, **삭제와 같은 domain·instance 범위**를\n * 직접 센다. 저널이 비어 있는 트윈도 `0` 과 null 기간을 돌려준다 — 없는 기록을 기간으로\n * 꾸며내면 \"아무것도 안 지워진다\"와 \"언제부터인지 모른다\"가 섞인다.\n */\n @Query(returns => ScalarObject, {\n nullable: true,\n description:\n 'Describe what deleting one twin would purge before it is deleted: its registry identity and the count plus first/last event time of its journal. Null means this twin is not in the current domain.'\n })\n async twinDeletionPreview(@Arg('instanceId') instanceId: string, @Ctx() context: ResolverContext): Promise<any | null> {\n const domainId = context.state.domain.id\n const instance = await getRepository(TwinInstance).findOne({ where: { domain: { id: domainId }, instanceId } })\n\n if (!instance) return null\n\n const journal = await getRepository(TwinEvent)\n .createQueryBuilder('event')\n .select('COUNT(event.id)', 'count')\n .addSelect('MIN(event.eventTime)', 'firstEventAt')\n .addSelect('MAX(event.eventTime)', 'lastEventAt')\n .where('event.domain = :domainId', { domainId })\n .andWhere('event.instanceId = :instanceId', { instanceId })\n .getRawOne<{ count?: string | number; firstEventAt?: unknown; lastEventAt?: unknown }>()\n\n return {\n instanceId,\n name: instance.name ?? null,\n journalCount: Number(journal?.count ?? 0),\n firstEventAt: journal?.firstEventAt ?? null,\n lastEventAt: journal?.lastEventAt ?? null\n }\n }\n\n /** 삭제 — 정지 + 레지스트리 삭제 + 저널 purge(domain 스코프). */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => Boolean, { description: 'Delete a twin instance: stop it, remove the registry entry and purge its event journal.' })\n async deleteTwinInstance(@Arg('instanceId') instanceId: string, @Ctx() context: ResolverContext): Promise<boolean> {\n await TwinEngine.remove(context.state.domain.id, instanceId)\n return true\n }\n\n /** 관리 목록 — 등록된 인스턴스 전체(kind/status/running/revision/노드·무버 수). 데이터 없으면 빈 배열. */\n /**\n * **이 공간의 트윈 id 들** — 한 현실을 여러 렌즈가 비추므로 공간을 물으면 그 렌즈 전부를 답한다.\n *\n * 화면이 전체 목록을 받아 걸러 쓰던 규칙을 서버로 모은다. 예측 화면은 같은 확장을 한 파일에서\n * **두 번** 복제하고 있었고, 규칙이 흩어지면 한쪽만 고쳐진다. 떠 있지 않은 것도 포함한다 —\n * 공간에 무엇이 있는지는 기동 여부와 다른 사실이다.\n */\n @Query(returns => [String], { description: 'Ids of the twins that live in a space (all its lenses), running or not.' })\n async twinSpaceInstanceIds(@Arg('spaceId') spaceId: string, @Ctx() context: ResolverContext): Promise<string[]> {\n return TwinEngine.instanceIdsOfSpace(context.state.domain.id, spaceId)\n }\n\n @Query(returns => [ScalarObject], {\n description:\n 'List provisioned twin instances in the current domain with management metadata. Each row also carries `sync`: a one-line answer to \"is this twin keeping in step with the floor?\" — a verdict (syncing | mapping-blocked | no-traffic | not-connected | not-applicable | unknown), the accepted share of the last closed 10-minute window, and up to 36 closed windows of offered/rejected counts for a sparkline. Reasons and rejected-record samples are deliberately absent here; ask twinIngestHealth for one twin when you need them, because carrying raw records for every row would make this list heavy for no gain. acceptedRatio is null while the first window is still filling — a partially filled window under-counts, and drawing it as zero would read as \"nothing passed\".'\n })\n async twinInstanceList(@Ctx() context: ResolverContext): Promise<any[]> {\n const domainId = context.state.domain.id\n const list = await TwinEngine.list(domainId)\n /*\n * 동기화 요약을 **같은 응답에 얹는다.** 따로 질의하면 트윈 수만큼 왕복이 생기고(N+1), 그 사이 화면은\n * 판정 없는 목록을 먼저 그린다 — 사람이 그 순간을 「정상」으로 읽는다.\n */\n const brief = new Map(\n TwinEngine.ingestHealthBrief(\n domainId,\n list.map((x: any) => x.instanceId)\n ).map(b => [b.instanceId, b])\n )\n /*\n * **쓰기 사실도 같은 응답에 얹는다** (2026-08-20).\n *\n * 「이 트윈이 사실을 적고 있나」는 운영자가 트윈 화면에서 묻는 질문인데, 그 답은 벤치(슈퍼유저)의\n * 부하 계기판에만 있었다. 유입(`sync`)과 나란히 두는 이유는 둘이 한 사슬이기 때문이다 — 받은 것이\n * 있어도 적히지 않으면 저널이 비고, 그러면 성과·시간여행이 함께 빈다.\n *\n * 계기는 메모리라 왕복이 없다. **도는 트윈만** 답이 있고, 멈춘 트윈은 `null` 이다(0 이 아니다 —\n * 「쓰지 않았다」와 「돌지 않는다」는 다른 사실이다).\n */\n return list.map((x: any) => {\n const m = TwinEngine.metrics(domainId, x.instanceId)\n return {\n ...x,\n sync: brief.get(x.instanceId) ?? null,\n write: m ? { rate: m.journalRate, journaled: m.journaledTotal, backlog: m.backlog } : null\n }\n })\n }\n\n /** 단건 상세 — 프로비저닝 에디터용 model(구조+layout) 포함. */\n @Query(returns => ScalarObject, { nullable: true, description: 'Get one provisioned twin instance with its TwinModelDef (structure + layout) for editing.' })\n async twinInstanceDetail(@Arg('instanceId') instanceId: string, @Ctx() context: ResolverContext): Promise<any | null> {\n return TwinEngine.detail(context.state.domain.id, instanceId)\n }\n\n /**\n * 라이브(메모리) 인스턴스 id 목록 — 콘솔 셀렉터·포트폴리오용. **현재 도메인만**(테넌트 격리).\n * 과거엔 Object.keys(TwinEngine.instances) 로 전역 반환 → 다른 도메인 트윈이 종합현황/셀렉터에 새던 버그.\n * 도메인 스코프 목록(twinInstanceList 와 동일 소스)에서 running 만 추린다.\n */\n @Query(returns => [String], { description: 'List ids of running (in-memory) twin runtime instances in the current domain (tenant-isolated).' })\n async twinInstances(@Ctx() context: ResolverContext): Promise<string[]> {\n const list = await TwinEngine.list(context.state.domain.id)\n return list.filter((x: any) => x.running).map((x: any) => x.instanceId)\n }\n\n /**\n * 도메인 어휘 카탈로그 — 커널(레퍼런스) SSOT 를 그대로 노출. { wms:{label,locationTypes[]}, yms:…, mes:… }.\n * 프로비저닝 UI 는 이걸 소싱해 노드 타입/시스템을 채운다(앱 내 하드코딩 금지).\n */\n @Query(returns => ScalarObject, { description: 'Domain vocabulary catalog (per-system location location types) sourced from the twin kernel — SSOT for provisioning UI.' })\n twinDomainCatalog(): any {\n return domainCatalog()\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/twin-metrics/index.ts"],"names":[],"mappings":";;;AAAA,mEAA0D;AAE7C,QAAA,SAAS,GAAG,CAAC,wCAAgB,CAAC,CAAA","sourcesContent":["import { TwinMetricsQuery } from './twin-metrics-query.js'\n\nexport const resolvers = [TwinMetricsQuery]\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"twin-metrics-query.js","sourceRoot":"","sources":["../../../server/service/twin-metrics/twin-metrics-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Directive, Query, Resolver } from 'type-graphql'\n\nimport { In } from 'typeorm'\n\nimport { Domain, getRepository, ScalarObject } from '@things-factory/shell'\nimport { PrivilegeObject, checkPermission } from '@things-factory/auth-base'\n\nimport { TwinEngine } from '../../engine/index.js'\nimport { newCpuSampler, sampleCpu } from '../../engine/load-meter.js'\nimport { modelGapOf } from '../../engine/model-gap.js'\n\n/*\n * 트윈 처리량 계측(모니터, ④-1) — 라이브 동기 인터페이스 부하를 읽는 일급 지표.\n * 유입률(ingestRate)·브로드캐스팅률(broadcastRate)·저널률(journalRate)·백로그(backlog)·누적. 폴링으로 라이브 관측.\n * (부하가 화면에 안 보인다던 문제의 정공법 — \"얼마나 들어오고 얼마나 브로드캐스팅/기록되나\"를 수치로.)\n */\n/* CPU 는 누적값이라 **표본 사이의 차이**로만 점유율이 나온다. 그래서 호출 사이에 상태가 남아야 한다. */\nconst CPU_SAMPLER = newCpuSampler()\n\n/**\n * 거부된 **레코드 원문**을 볼 수 있는 자격.\n *\n * 트윈을 고칠 수 있는 사람과 같은 자격으로 둔다 — 매핑을 고치려면 원천이 실제로 보낸 모양을 봐야 하므로\n * 권한이 그 일과 맞는다. 도메인 소유자·슈퍼유저는 통과한다(프레임워크 규약과 같다).\n *\n * 판정·추이·사유는 이 자격과 무관하게 낸다. 「맞춰지고 있나」는 감출 일이 아니다.\n */\nconst RAW_RECORD_PRIVILEGE: PrivilegeObject = { category: 'twin', privilege: 'mutation', owner: true, super: true }\n\n@Resolver()\nexport class TwinMetricsQuery {\n @Query(returns => ScalarObject, { nullable: true, description: 'Live throughput metrics of one twin instance (ingest/broadcast/journal rate, backlog). Null if not a live instance or not in the current domain.' })\n twinMetrics(@Arg('instanceId') instanceId: string, @Ctx() context: ResolverContext): any {\n if (!TwinEngine.owns(context.state.domain.id, instanceId)) return null // 테넌트 격리\n return TwinEngine.metrics(context.state.domain.id, instanceId)\n }\n\n @Query(returns => [ScalarObject], {\n description:\n 'Throughput metrics of every running twin instance in the current domain — simulations as well as mirrors (monitor dashboard). Simulations report ingestRate 0 because they receive nothing from a source: they emit their own facts, and that zero is a fact rather than a gap. Until 2026-08-20 simulations carried no meter at all and were silently absent from this list, which made a host full of running simulations look idle.'\n })\n async twinMetricsAll(@Ctx() context: ResolverContext): Promise<any[]> {\n return TwinEngine.allMetrics(context.state.domain.id) // 도메인 스코프\n }\n\n /*\n * ── 동기화 건강 — 「이 트윈이 현장과 맞춰지고 있나」 ──────────────────────────\n *\n * `twinMetrics` 는 **통과한** 이벤트의 초당 수를 답한다. 여기서 답하는 것은 **제시된 것 중 얼마가\n * 통과했나**이고, 통과하지 못한 것의 이유와 실제 모양까지 낸다.\n *\n * 왜 따로 있나: 거부된 레코드는 **저널에 없다**(거부의 뜻이 그것이다). 저널을 계산해 나오는 것은 통과한\n * 쪽뿐이라, 거부의 추이는 세는 자리를 따로 두어야 나온다.\n *\n * 왜 도메인으로 자르나: 이것은 **테넌트의 데이터**다. 거부 표본에는 그 고객의 레코드 원문이 들어\n * 있으므로 남의 것을 보여 줄 수 없다(부하 계기판이 프로세스 전체를 내는 것과 반대다 — 그쪽은\n * 이벤트 루프가 프로세스 공유라서 그렇게 했다).\n *\n * ── 수치와 원문은 민감도가 다르다 ───────────────────────────────────────────\n * 판정·추이·사유는 「맞춰지고 있나」를 알려면 누구나 봐야 한다. 반면 **거부된 레코드 원문**은 그 고객의\n * 업무 데이터다(품목·수량·거래처가 그대로 들어 있다). 같은 도메인이라는 것만으로 원문까지 열면 필요\n * 없는 사람에게도 열린다.\n *\n * 그래서 원문만 한 겹 뒤에 둔다 — 트윈을 **고칠 수 있는 사람**에게만. 매핑을 고치려면 원천이 실제로\n * 무엇을 보냈는지 봐야 하므로 권한이 그 일과 맞는다. 감출 때는 **감췄다고 말한다**(`samplesWithheld`);\n * 빈 배열로 두면 「거부된 것이 없다」로 읽힌다.\n */\n @Query(returns => ScalarObject, {\n /*\n * ── `null` 이 정답인 자리다 (2026-08-29 실측) ───────────────────────────────\n * 이 조회는 세 경우에 `null` 을 낸다: 다른 도메인의 트윈 · 등록되지 않은 트윈 · 아직 돌지 않는\n * 트윈. 설명문에도 그렇게 적혀 있었는데 **스키마가 그것을 금지하고 있었다.**\n *\n * 그래서 화면이 아직 돌지 않는 트윈을 열면 조회 전체가 이 오류로 죽었다.\n *\n * Cannot return null for non-nullable field Query.twinIngestHealth.\n *\n * 「없다」를 낼 수 있어야 화면이 「아직 잰 것이 없다」를 말한다. 빈 객체로 바꾸면 그것이\n * 「재 봤는데 아무것도 없다」로 읽힌다 — 두 사실이 다르다.\n */\n nullable: true,\n description:\n 'Answers one question: is this twin keeping in step with the floor? Returns a verdict — syncing | mapping-blocked | no-traffic | not-connected | not-applicable | unknown — together with the threshold it was judged by, so a screen can say why. `recent` is the last **closed** 10-minute window (null while the first window is still filling: a partially filled window under-counts, so it is not called \"recent\"), and `trend` carries up to 36 closed windows (6 hours) oldest-first with offered / accepted / rejected per window. Accepted and rejected are reported together because counting only rejections makes \"nothing is arriving\" and \"everything is fine\" both look like zero. `reasons` groups rejections by the reason the kernel gave, most frequent first, each with the last time that reason occurred — a reason whose time has stopped is one you already fixed. `samples` holds one rejected record per reason (the raw record, truncated with recordTruncated when long) because a reason alone does not tell you what the source actually sent. Counts that could not be shown are reported rather than dropped: sampleDropped, trendDropped, and reasonsCapped when distinct reasons hit their limit and the rest were folded into \"(other reasons)\". A twin that has never received anything reports null figures rather than zeros, since never-measured and measured-as-zero are different facts. This ledger lives in memory: it covers roughly the last 6 hours and resets when the process restarts. The verdict, trend and reasons are returned to anyone in the owning domain, but the raw records in `samples` are business data of that tenant, so they require the same privilege as changing the twin (twin/mutation, with domain owner and superuser granted); when the caller lacks it, `samples` comes back empty and `samplesWithheld` says how many were held back — an empty list alone would read as \"nothing was rejected\". `modelGap` answers a different question from the verdict: the verdict is about the feed (is anything arriving, does it pass), while modelGap is about the model (did we declare what the source knows). The ingest gate deliberately does not reject a record that references an id the master never declared — a source may legitimately report a new place before the structure sync catches up, and rejecting it would lose that measurement. The kernel absorbs it and marks it `origin: \"observed\"` instead, and modelGap counts those per axis (locations / equipment / persons) with a capped id list plus how many more were left out. It also carries the kernel\\'s `unhandled` tally — event kinds the twin could not apply at all — and, for energy twins, how many state reports arrived from equipment the model does not know. `notCovered` names axes this cannot answer yet: meters carry no origin mark in the kernel, so a mistyped meterId silently becomes a new meter whose kW is accumulated into the site demand. Null while the twin is not running: there is nothing to compare against, and never-compared is not the same as none. Null if the instance is not in the current domain.'\n })\n async twinIngestHealth(@Arg('instanceId') instanceId: string, @Ctx() context: ResolverContext): Promise<any> {\n const domainId = context.state.domain.id\n /*\n * 테넌트 격리 — 다만 `owns` 는 **도는 트윈**만 참이다. 멈춘 트윈의 장부도 볼 수 있어야 하므로\n * (「고치려고 멈춰 뒀는데 마지막에 뭐가 거부됐나」), 등록 여부로도 판정한다. 도는 트윈이면 DB 를\n * 묻지 않는다(대부분의 조회가 그쪽이다).\n */\n if (!TwinEngine.owns(domainId, instanceId) && !(await TwinEngine.ownsRegistered(domainId, instanceId))) return null\n\n const view = TwinEngine.ingestHealthOf(domainId, instanceId)\n\n /*\n * ── 「원본이 말한 적 없는 것」을 함께 낸다 (2026-08-20) ──────────────────────\n * 유입 문은 참조 오류를 거부하지 않는다(실측: 모르는 로케이션·bizStep·계량기가 모두 통과). 거부하지\n * 않는 것이 옳다 — 원천이 모델보다 먼저 새 자리를 말하는 것은 정상이고, 거부하면 그 선행 계측을\n * 잃는다. 대신 커널이 **흡수하고 표시한다**(`origin: 'observed'`).\n *\n * 그 표시를 읽는 곳이 모델 인스펙션 화면 하나였다. 그런데 「이 트윈이 현장과 맞춰지고 있나」를\n * 보는 사람이 바로 그것을 알아야 한다 — 원천이 우리가 모르는 로케이션만 보내면 트윈은 도는데\n * 스키매틱은 비어 있고, 그게 우리가 「빈 공장」이라 불러 온 상태다.\n *\n * 판정에는 섞지 않는다. 판정은 **피드**를 말하고 이것은 **모델**을 말한다(`model-gap.ts`).\n */\n const modelGap = modelGapOf(TwinEngine.snapshot(domainId, instanceId))\n\n /* 원문을 볼 수 있나 — 못 보면 **몇 건을 감췄는지** 함께 낸다(빈 배열은 「없었다」로 읽힌다). */\n const maySeeRecords = await checkPermission(\n RAW_RECORD_PRIVILEGE,\n context.state.user,\n context.state.domain\n )\n if (maySeeRecords) return { ...view, modelGap }\n\n const { samples, ...rest } = view\n return { ...rest, modelGap, samples: [], samplesWithheld: samples.length }\n }\n\n /*\n * ── 이벤트 루프 부하 — **운영 계기판이다. 테넌트 기능이 아니다.** ─────────────\n *\n * `twinMetrics` 는 \"얼마나 들어오고 나가나\"(초당 이벤트)를 답한다. 여기서 묻는 것은 \"그 일을 하느라\n * 이벤트 루프를 얼마나 붙잡고 있나\" 다. 시뮬 틱은 트윈마다 setInterval 로 **메인 루프에서 동기로**\n * 돌기 때문에, 한 트윈이 느려지면 HTTP·구독이 함께 느려진다. 실제로 그 상태를 겪었는데 볼 계기판이\n * 없어서 \"포트는 열렸는데 응답이 없다\" 로만 보였다.\n *\n * ── 왜 도메인으로 자르지 않나 ───────────────────────────────────────────────\n * 처음에는 호출자의 도메인만 냈다. 테넌트 격리로는 맞아 보이지만 **이벤트 루프는 프로세스 하나가\n * 공유한다.** 실측으로 드러났다: 한 도메인 트윈 22개가 CPU 를 110% 쓰는 동안, 다른 도메인 화면은\n * `실행 중 0 · 루프 점유 —` 였다. \"내 도메인은 한가한데 왜 느리지\" 를 설명할 수 없었다.\n *\n * 그렇다고 테넌트에게 남의 부하를 조금 보여 주는 것도 답이 아니다 — 쓸 수 없는 정보이고 봐서도 안 된다.\n * 층이 잘못이었다: 이것은 **프로세스를 운영하는 사람의 계기판**이다. 그래서 도메인으로 자르지 않고\n * (전 테넌트 합산) **슈퍼유저에게만** 연다.\n */\n @Directive('@privilege(superUserGranted: true)')\n @Query(returns => ScalarObject, {\n description:\n 'Event-loop load of every twin in this process, for system operators (superuser only). Deliberately NOT scoped to the caller\\'s domain: the event loop is shared by the whole process, so a tenant-scoped view reports an idle domain while the machine is saturated by someone else. Reports how many instances are running (by mode), how many forecast forks have been created, and per instance how long each kind of work takes (kernel tick, state snapshot, broadcast payload, publish, journal write, live ingest, Monte-Carlo forecast, fork trajectory). Each instance reports both `recent` (the last closed sampling window — what it is doing now) and `total` (cumulative since boot), with `busyRatio` telling how much of the window that twin held the loop. `fleetBusyRatio` above 1 means the twins together cannot finish inside one tick interval, which is when HTTP and subscriptions start to stall. Work that has never been measured is reported as null rather than zero, because zero would read as \"instant\".'\n })\n async twinLoad(@Ctx() context: ResolverContext): Promise<any> {\n const fleet = TwinEngine.fleet() // 프로세스 전체 — 도메인으로 자르지 않는다(위 주석)\n\n /* 도메인 id 는 UUID 라 사람이 못 읽는다. 읽을 수 있는 이름을 채운다 — **모르면 id 를 그대로 둔다**\n (지어내지 않는다). 라이브 인스턴스에는 이미 붙어 있고, 시뮬은 여기서 한 번 조회한다. */\n const ids = [...new Set((fleet.byDomain ?? []).map((d: any) => d.domainId).filter((v: any) => v && v !== '(unknown)'))]\n if (ids.length) {\n const found = await getRepository(Domain).find({ where: { id: In(ids as string[]) } })\n const nameOf = new Map(found.map(d => [d.id, d.subdomain || d.name]))\n for (const d of fleet.byDomain) if (nameOf.get(d.domainId)) d.label = nameOf.get(d.domainId) as string\n for (const r of fleet.slowest ?? []) if (r.domainId && nameOf.get(r.domainId)) r.domainLabel = nameOf.get(r.domainId)\n for (const r of fleet.details ?? []) if (r.domainId && nameOf.get(r.domainId)) r.domainLabel = nameOf.get(r.domainId)\n }\n\n /*\n * 프로세스가 실제로 쓴 CPU — **계기판의 다른 숫자와 뜻이 다르다.**\n *\n * 위의 값들은 트윈의 계측된 작업만 잰 것이라, 그 밖의 일(GraphQL·TypeORM·GC·webpack·HTTP)은\n * 분모에도 분자에도 없다. \"루프 점유 <1%\" 인데 CPU 는 110% 였던 상태를 실제로 겪었다. 두 숫자를\n * 나란히 두어야 **트윈이 범인인지** 가릴 수 있다.\n *\n * 표본 간 차이라 첫 호출은 `null` 이다 — 0 이 아니다.\n */\n const usage = process.cpuUsage()\n const cpu = sampleCpu(CPU_SAMPLER, usage.user + usage.system, Date.now())\n\n return { ...fleet, processCpuRatio: cpu?.ratio ?? null, processCpuSpanMs: cpu?.spanMs ?? null }\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"axis-journal-evidence.js","sourceRoot":"","sources":["../../../server/service/twin-model/axis-journal-evidence.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,8CAqBC;AAxDD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qEAA8D;AAE9D;;;;GAIG;AACH,MAAM,WAAW,GAA0D;IACzE,KAAK,EAAE,EAAE,KAAK,EAAE,CAAC,aAAa,CAAC,EAAE;IACjC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,cAAc,CAAC,EAAE;IACnC,KAAK,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE;IAC3B,aAAa,EAAE,EAAE,KAAK,EAAE,CAAC,sBAAsB,CAAC,EAAE;CACnD,CAAA;AAED,qDAAqD;AAC9C,KAAK,UAAU,iBAAiB,CACrC,IAAS,EACT,KAA8B,EAC9B,IAAY;IAEZ,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAA;IAC9B,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IAEtB,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC3B,IAAI,MAAM,IAAA,gCAAa,EAAC,IAAI,EAAE,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;gBAAE,OAAO,IAAI,CAAA;QACxE,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IACD;;;;OAIG;IACH,MAAM,EAAE,IAAI,EAAE,GAAG,wDAAa,SAAS,GAAC,CAAA;IACxC,OAAO,IAAA,gCAAa,EAAC,IAAI,EAAE,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAA;AAC9E,CAAC","sourcesContent":["/*\n * **저널에 그 축의 사실이 있나** — 「아직 없다」와 「왔는데 상태에 없다」를 가른다 (2026-08-22).\n *\n * ── 왜 필요했나 (실측) ──────────────────────────────────────────────────────\n * 화면이 「작업 — 아직 하나도 없습니다」라고 말하는데, 그 트윈의 저널에는 `task.status` 5,786건이\n * 있었고 **마지막 상태가 `in-progress`** 였다. 사용자가 「작업 지시 835건인데 작업이 0인 이유」를\n * 물어 반나절을 조사해 원인을 찾았다: 관측 모드 커널이 관측기의 작업을 스냅샷으로 내보내지 않는다\n * (커널 자리이고 그 레인에 넘겼다).\n *\n * 화면 몫은 다른 것이다. **카드가 단정하면 안 된다.** 「아직 하나도 없습니다」는 「선언은 있고 사실이\n * 아직 일어나지 않았다」는 뜻인데, 실제로는 「사실이 일어났는데 상태에 오지 못했다」였다 — 그 차이가\n * 사용자를 반나절 조사로 보낸다.\n *\n * 그래서 상태가 빈 축에 대해 **저널을 한 건만 물어본다.** 있으면 화면이 그렇게 말한다.\n *\n * ── 어휘의 집 ───────────────────────────────────────────────────────────────\n * 어느 사건이 어느 축을 채우는지는 **커널이 선언한 낱말**에서 온다(`OP_EVENT`·`ENERGY_EVENT`, 그리고\n * EPCIS 접두사). 여기서 이름을 새로 만들지 않는다 — 커널이 낱말을 늘리면 이 표에 한 줄이 늘고,\n * 표에 없는 축은 **아무 말도 하지 않는다**(모르는 것을 지어내지 않는다).\n */\nimport { anyJournalRow } from '../twin-event/journal-count.js'\n\n/**\n * 상태 축 → 그 축을 채우는 사건 낱말. 표에 없으면 판단하지 않는다.\n *\n * 물품은 EPCIS 로 온다(사건 이름이 여러 개다) — 그래서 접두사로 본다.\n */\nconst AXIS_EVENTS: Record<string, { types?: string[]; prefix?: string }> = {\n tasks: { types: ['task.status'] },\n orders: { types: ['order.status'] },\n items: { prefix: 'epcis.' },\n demandWindows: { types: ['energy.demand.window'] }\n}\n\n/** 이 축의 사실이 저널에 한 건이라도 있나. 판단할 수 없으면 `null`(모른다). */\nexport async function axisSeenInJournal(\n repo: any,\n where: Record<string, unknown>,\n axis: string\n): Promise<boolean | null> {\n const spec = AXIS_EVENTS[axis]\n if (!spec) return null\n\n if (spec.types) {\n for (const t of spec.types) {\n if (await anyJournalRow(repo, { ...where, eventType: t })) return true\n }\n return false\n }\n /*\n * 접두사로 찾는 축(물품) — 한 건만 있으면 된다. `Like` 를 쓰는 대신 **낱말 목록**을 쓰지 않는 이유는\n * EPCIS 사건 이름이 커널에서 자라기 때문이다(객체·집약·변환·거래…). 이름을 여기 베끼면 커널이\n * 하나 늘릴 때 이 자리가 알리지 않고 낡는다.\n */\n const { Like } = await import('typeorm')\n return anyJournalRow(repo, { ...where, eventType: Like(`${spec.prefix}%`) })\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"choose-recipe.js","sourceRoot":"","sources":["../../../server/service/twin-model/choose-recipe.ts"],"names":[],"mappings":";;AAkBA,oCAYC;AA9BD;;;;;;GAMG;AACH;;;;;;;;;;GAUG;AACH,SAAgB,YAAY,CAC1B,OAAc,EACd,UAAiB,EACjB,aAAiC;IAEjC,IAAI,aAAa,EAAE,CAAC;QAClB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,aAAa,CAAC,CAAA;QAC/D,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAA;IACnF,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAA;IACtF,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,CAAA;IACxD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;AAC1B,CAAC","sourcesContent":["/*\n * **어느 레시피로 만들었나** — 판정만. 순수하다.\n *\n * ── 왜 따로 있나 ───────────────────────────────────────────────────────────\n * 계보 질의는 TypeORM 과 shell 을 끌어온다. 규칙만 재려는 시험이 그것까지 들고 오면 무겁고, 이\n * 저장소에서 그 이유로 시험이 안 서는 일을 겪었다. 판단과 배선을 나눈다 — 여기는 값만 보고 답한다.\n */\n/**\n * 짐작하지 않는 규칙.\n *\n * order 오더가 말했다. 버전이 둘이든 열이든 그것을 쓴다\n * only-candidate 오더는 말 안 했지만 후보가 하나다 — 고를 것이 없으면 짐작이 아니다\n * ambiguous 후보가 둘 이상인데 말해 주는 것이 없다 → **답하지 않는다**\n * said-but-undeclared 오더는 말했는데 그 레시피가 선언에 없다 — 모르는 것이 아니라 어긋난 것이다\n *\n * 마지막 둘을 가르는 이유: 사람이 할 일이 다르다. 어긋남은 선언이나 원본을 고칠 일이고,\n * 모호함은 그 개체가 어느 것으로 만들어졌는지 기록이 없는 일이다.\n */\nexport function chooseRecipe(\n recipes: any[],\n candidates: any[],\n saidRecipeKey: string | undefined\n): { recipe?: any; basis: 'order' | 'only-candidate' | 'ambiguous' | 'said-but-undeclared' | 'none' } {\n if (saidRecipeKey) {\n const said = recipes.find((r: any) => r?.key === saidRecipeKey)\n return said ? { recipe: said, basis: 'order' } : { basis: 'said-but-undeclared' }\n }\n if (candidates.length === 1) return { recipe: candidates[0], basis: 'only-candidate' }\n if (candidates.length > 1) return { basis: 'ambiguous' }\n return { basis: 'none' }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"epcis-coverage.js","sourceRoot":"","sources":["../../../server/service/twin-model/epcis-coverage.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * GS1 EPCIS 2.0 적합성 기준 — **물류·제조 트윈이 표준의 어디를 채우고 있나.**\n *\n * ── 왜 따로 있나 ────────────────────────────────────────────────────────────\n * 적합성 패널이 ISA-95 하나만 말하고 있었다. 그런데 창고·야드·공장 트윈에서 「무슨 일이 있었나」와\n * 「이 번호가 무엇을 가리키나」를 정하는 것은 EPCIS 다. ISA-95 표만 보이면 그 트윈이 기대는 기준의\n * 절반이 화면에 없다.\n *\n * ── 수준은 근거를 보고 적었다 ───────────────────────────────────────────────\n * 「있다/없다」를 기억으로 적지 않는다. 각 줄의 판단 근거:\n * · 타입 선언 — 커널 `epcis.ts`(이벤트·필드의 모양)\n * · 실제 사용 — 커널 도메인(`mes-kernel`·`face2-adapter`·`observed-reducer`)과 호스트 인제스트\n * · 화면 노출 — 저널·계보·엔티티 360\n * 모양만 있고 아무도 채우지 않는 필드는 **구조만 있음**으로 적는다(그게 사실이다).\n *\n * ISA-95 표와 같은 규율이다(`isa95-coverage.ts` 머리말): 사람이 판단해 적고, 틀리면 여기를 고친다.\n */\nimport { reconcileAxes, type Isa95Concept, type CoverageLevel } from './isa95-coverage.js'\n\n/* Part: EPCIS 2.0 의 사건 종류 — 무엇이 일어났는가. */\nconst EVENTS: Isa95Concept[] = [\n {\n std: 'ObjectEvent',\n part: 'events',\n label: 'twin.epcis.ObjectEvent',\n axis: 'items',\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.epcis.note.ObjectEvent'\n },\n {\n std: 'AggregationEvent',\n part: 'events',\n label: 'twin.epcis.AggregationEvent',\n axis: null,\n shownOn: ['items'],\n structure: 'full',\n behavior: 'full',\n surface: 'partial',\n note: 'twin.epcis.note.AggregationEvent'\n },\n {\n std: 'TransactionEvent',\n part: 'events',\n label: 'twin.epcis.TransactionEvent',\n axis: 'orders',\n structure: 'full',\n behavior: 'full',\n surface: 'partial',\n note: 'twin.epcis.note.TransactionEvent'\n },\n {\n std: 'TransformationEvent',\n part: 'events',\n label: 'twin.epcis.TransformationEvent',\n axis: null,\n shownOn: ['items', 'operations'],\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.epcis.note.TransformationEvent'\n },\n /* EPCIS 2.0 이 더한 사건 — 우리는 타입조차 두지 않았다. 없는 것은 없다고 적는다. */\n {\n std: 'AssociationEvent',\n part: 'events',\n label: 'twin.epcis.AssociationEvent',\n axis: null,\n structure: 'none',\n behavior: 'none',\n surface: 'none',\n note: 'twin.epcis.note.AssociationEvent'\n }\n]\n\n/* Part: 사건이 답하는 다섯 가지 — 무엇을·어디서·언제·왜, 그리고 어떤 거래로. */\nconst FIELDS: Isa95Concept[] = [\n {\n /*\n * **무엇을 한 것인가** — 표준 `action`: `ADD`(생겼다) · `OBSERVE`(보았다) · `DELETE`(없어졌다).\n *\n * 이 줄이 표에 없었다. 표준 필드이고 우리가 계약·유입·검증에서 다 쓰는데도 세지 않았습니다.\n *\n * ── 2026-08-25 그 뜻이 커졌다 ─────────────────────────────────────────────\n * `DELETE` 를 낱개 번호가 있는 물품에만 적용하고 있었다. 그래서 **낱개 번호가 없는 현장에서는\n * 재고를 지울 방법이 하나도 없었다** — 승화푸드는 모든 재고가 수량으로 오는 현장이고, 한 번 본\n * 재고가 영원히 남았다(실제 1,704건인데 트윈이 10,038건).\n *\n * 이제 수량으로 온 재고도 없어질 수 있다. 자리를 함께 말하면 그 자리의 것만, 말하지 않으면\n * 그 로트를 모든 자리에서 지운다 — 짐작이 아니라 진술의 범위를 읽는다.\n *\n * 화면은 아직 없다. 「이것이 없어졌다」를 사람에게 보이는 자리를 만들지 않았다.\n */\n std: 'action',\n part: 'fields',\n label: 'twin.epcis.action',\n axis: null,\n shownOn: ['items'],\n structure: 'full',\n behavior: 'full',\n surface: 'none'\n },\n {\n std: 'epcList / quantityList',\n part: 'fields',\n label: 'twin.epcis.identity',\n axis: 'items',\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.epcis.note.identity'\n },\n /*\n * ── 2026-08-24 주장 근거가 달라졌다 ────────────────────────────────────────\n * 이 줄은 오래 `full` 이었지만 근거가 **우리 선언**이었다. 이제 **정본 원문 대조**다:\n * CBV Standard Release 2.0(Ratified Jun 2022) §7.2.3 의 처분 38개를 전수 확인했다.\n *\n * 그 대조가 실제로 셋을 바꿨다.\n * · `expired` — 있는 낱말인데 우리가 쓰지 않고 있었다(기한 경과를 처분으로 말할 수 있게 됐다)\n * · `conformant` / `non_conformant` — 검사 판정을 처분으로 남기는 낱말. 함께 `inspecting` bizStep\n * · `non_sellable_expired` 는 표준이 **폐기**하고 `expired` 로 대체한 것을 확인\n *\n * ★ 그리고 **검증 방법에 함정이 있다**: `ref.gs1.org/cbv/…` 로 URN 을 조회하면 **지어낸 값에도\n * 똑같은 응답**이 온다. 그것으로 확인했다고 여기면 없는 낱말을 발행한다(한 번 그렇게 했다).\n * 확인은 정본 문서로만 한다.\n */\n {\n std: 'bizStep / disposition (CBV)',\n part: 'fields',\n label: 'twin.epcis.cbv',\n axis: null,\n shownOn: ['items', 'tasks'],\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.epcis.note.cbv'\n },\n {\n std: 'readPoint / bizLocation',\n part: 'fields',\n label: 'twin.epcis.where',\n axis: 'locations',\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.epcis.note.where'\n },\n {\n std: 'bizTransactionList',\n part: 'fields',\n label: 'twin.epcis.bizTransaction',\n axis: 'orders',\n structure: 'full',\n behavior: 'full',\n surface: 'partial',\n note: 'twin.epcis.note.bizTransaction'\n },\n {\n std: 'ilmd',\n part: 'fields',\n label: 'twin.epcis.ilmd',\n axis: null,\n shownOn: ['items'],\n structure: 'full',\n behavior: 'full',\n surface: 'partial',\n note: 'twin.epcis.note.ilmd'\n },\n {\n /*\n * `shownOn` 은 「어디에 속하나」다 — 정정 선언은 물품의 사실이다. 화면에 그려지는지는 `surface` 가\n * 따로 답한다(지금은 `none` 이고 `note` 가 그 사실을 말한다). 둘은 다른 물음이다(§`shownOn`).\n */\n shownOn: ['items'],\n std: 'errorDeclaration',\n part: 'fields',\n label: 'twin.epcis.errorDeclaration',\n axis: null,\n structure: 'full',\n behavior: 'partial',\n surface: 'none',\n note: 'twin.epcis.note.errorDeclaration'\n },\n /* 모양만 있고 아무도 채우지 않는 것들 — 구조만 있음. */\n {\n /* 어디에 속하나 — 소유·보관의 이전은 그 물품의 사실이다. 화면에 그려지는지는 `surface` 가 따로 답한다(§`shownOn`). */\n shownOn: ['items'],\n std: 'sourceList / destinationList',\n part: 'fields',\n label: 'twin.epcis.sourceDestination',\n axis: null,\n structure: 'partial',\n behavior: 'none',\n surface: 'none',\n note: 'twin.epcis.note.sourceDestination'\n },\n {\n /* 어디에 속하나 — 지속 처분은 그 물품에 붙는다. 화면에 그려지는지는 `surface` 가 따로 답한다(§`shownOn`). */\n shownOn: ['items'],\n std: 'persistentDisposition',\n part: 'fields',\n label: 'twin.epcis.persistentDisposition',\n axis: null,\n structure: 'partial',\n behavior: 'none',\n surface: 'none',\n note: 'twin.epcis.note.persistentDisposition'\n },\n /*\n * ── 2026-08-24 계측이 어디로 들어오는지 확정됐다 ──────────────────────────\n * 「계측은 EPCIS 센서 필드로 오지 않는다」는 사실이 이제 **자리를 갖는다**: 자리의 상시 관측은\n * ISA-95 `OperationsEvent` 로 들어오고(커널 `location.measured` 채널), 에너지는 그 위의 누적기가\n * 받는다. ISA-95 표의 `OperationsEvent` 줄이 그 판정을 든다.\n *\n * 왜 EPCIS 가 아닌가: `SensorElement` 는 **개체에 붙는** 관측이고, 냉장실의 온도는 그 안의 물건\n * 수백 개와 관계되며 그 수백 개는 시간에 따라 바뀐다. 개체마다 붙이면 같은 사실이 수백 벌이 되고,\n * 물건이 떠나면 그 방의 온도 이력이 함께 사라진다. 주인은 **자리**다.\n *\n * 그래서 이 줄은 `behavior: 'none'` 으로 남는다 — 결손이 아니라 **다른 층으로 들어온다는 사실**이다.\n * 그 구별을 표가 말하지 않으면 「센서를 못 받는 트윈」으로 읽힌다.\n */\n {\n /* 어디에 속하나 — 표준에서 센서 관측은 **개체에 붙는다** — 우리가 그것을 자리로 옮긴 것이 다른 이야기다. 화면에 그려지는지는 `surface` 가 따로 답한다(§`shownOn`). */\n shownOn: ['items'],\n std: 'sensorElementList',\n part: 'fields',\n label: 'twin.epcis.sensorElement',\n axis: null,\n structure: 'partial',\n behavior: 'none',\n surface: 'none',\n note: 'twin.epcis.note.sensorElement'\n }\n]\n\nexport const EPCIS_CONCEPTS: Isa95Concept[] = [...EVENTS, ...FIELDS]\n\nexport function epcisCoverage(axes: string[]): {\n model: string\n concepts: (Isa95Concept & { axisMissing?: boolean })[]\n totals: { concepts: number; structure: number; behavior: number; surface: number }\n} {\n const concepts = EPCIS_CONCEPTS.map(c => reconcileAxes(c, new Set(axes)))\n const count = (k: 'structure' | 'behavior' | 'surface') => concepts.filter(c => c[k] === 'full').length\n return {\n model: 'GS1 EPCIS 2.0',\n concepts,\n totals: { concepts: concepts.length, structure: count('structure'), behavior: count('behavior'), surface: count('surface') }\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"external-resolver.js","sourceRoot":"","sources":["../../../server/service/twin-model/external-resolver.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;;AAuEH,4DAUC;AAGD,4DAEC;AAGD,wDAEC;AAQD,0CAeC;AASD,4CAmBC;AAhFD,MAAM,SAAS,GAAG,IAAI,GAAG,EAA4B,CAAA;AAErD;;;;;;GAMG;AACH,SAAgB,wBAAwB,CAAC,CAAmB,EAAE,IAA4B;IACxF,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,EAAE,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IAC7C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,IAAI,EAAE,CAAC,2DAA2D,CAAC,CAAA;QACnE,OAAM;IACR,CAAC;IACD,IAAI,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1B,IAAI,EAAE,CAAC,wBAAwB,MAAM,8CAA8C,CAAC,CAAA;IACtF,CAAC;IACD,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC,CAAA;AACzC,CAAC;AAED,8CAA8C;AAC9C,SAAgB,wBAAwB;IACtC,OAAO,CAAC,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;AACrC,CAAC;AAED,wBAAwB;AACxB,SAAgB,sBAAsB;IACpC,SAAS,CAAC,KAAK,EAAE,CAAA;AACnB,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,eAAe,CACnC,MAAc,EACd,QAAgB,EAChB,GAAsB,EACtB,OAA+B,EAC/B,IAA4B;IAE5B,MAAM,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAC/B,IAAI,CAAC,CAAC,EAAE,OAAO;QAAE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,CAAA;IACrD,IAAI,CAAC;QACH,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,CAAA;IAC1D,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,IAAI,EAAE,CAAC,wBAAwB,MAAM,gBAAgB,MAAM,CAAC,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC,EAAE,CAAC,CAAA;QAC/E,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAA;IACjC,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,gBAAgB,CACpC,QAAgB,EAChB,UAAkB,EAClB,IAAY,EACZ,MAAc,EACd,IAA4B;IAE5B,MAAM,GAAG,GAAuB,EAAE,CAAA;IAClC,KAAK,MAAM,CAAC,IAAI,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC;QACnC,IAAI,CAAC,CAAC,CAAC,QAAQ;YAAE,SAAQ;QACzB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,CAAA;YAChE,yDAAyD;YACzD,KAAK,MAAM,CAAC,IAAI,GAAG,IAAI,EAAE;gBAAE,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC;oBAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QAC9D,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,IAAI,EAAE,CAAC,wBAAwB,CAAC,CAAC,MAAM,0BAA0B,MAAM,CAAC,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC,EAAE,CAAC,CAAA;QAC7F,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC","sourcesContent":["/*\n * **board 밖을 가리키는 관계를 푸는 자리** — 선언은 있고 코드가 0곳이던 이음새.\n *\n * ── 무엇이 있었나 (2026-09-05 실측) ────────────────────────────────────────\n * 계약이 `target: { kind: 'external', entity: … }` 를 선언하고 주석에 「해소는 호스트가 한다」고\n * 적어 두었다. 그런데 **`headless-twin/server` 어디에도 `kind === 'external'` 을 다루는 코드가\n * 없었다**(0곳).\n *\n * `space.area` 가 도는 것처럼 보인 이유는 이음새가 있어서가 아니라 **호스트에서 그것이 평범한 FK\n * 라서**다. TypeORM 메타데이터가 우연히 답해 준 것이지 선언이 길을 낸 것이 아니다.\n *\n * ── 왜 지금 채우나 ─────────────────────────────────────────────────────────\n * 아침에는 「아무도 안 막혔으니 급하지 않다」로 적었다. **그 판단이 규모를 작게 잡은 것이었다.**\n *\n * 360(한 대상에 걸린 것을 한자리에)의 소비처는 무한히 늘어난다. 그러면 각 제품의 자기 저장소가\n * 관계의 원천이 된다 — MES 의 실적·상태 구간·정비 계획이 그렇고, 다음 제품도 그럴 것이다.\n *\n * 그때 **선언은 「밖을 가리킨다」고 말하는데 푸는 길이 없으면 제품마다 자기 길을 낸다.** 그것이\n * 정확히 「무한히 갈린다」다. 이 파일이 그 갈림을 막는다.\n *\n * ── 커널은 어느 표인지 모른다 ──────────────────────────────────────────────\n * 계약은 「이 축이 밖을 가리킨다」까지만 안다. **「어느 표의 어느 칸」은 제품이 안다.** 그래서 제품이\n * 부팅에서 해소기를 꽂는다 — 장부 기록기(`installIngestWindowWriter`)와 같은 규율이다.\n *\n * 커널이 표 이름을 알게 되면 한 제품의 저장 구조에 매이고, 다음 제품이 다른 표를 쓰면 커널을 또\n * 고쳐야 한다.\n *\n * ── 없는 것을 숨기지 않는다 ────────────────────────────────────────────────\n * 해소기가 없으면 **그 사실을 낸다**(`unresolved`). 링크를 지우면 화면은 「그런 관계가 없다」로\n * 읽고, 실제로는 「아무도 답할 수 없다」다. 그 둘은 사람이 할 일이 다르다.\n */\n\n/** 밖에 사는 대상 하나 — 화면이 이름을 보이고 갈 곳을 정할 수 있게. */\nexport interface ExternalRef {\n id: string\n /** 사람이 읽는 이름. 없으면 화면이 id 를 보인다(지어내지 않는다). */\n name?: string\n /**\n * 그 제품에서 이것을 열 수 있는 자리 — 없으면 화면이 링크를 만들지 않는다.\n *\n * 막다른 링크를 만들지 않는 것이 이 칸의 요점이다. 눌러서 아무 일도 안 일어나면 사람은 그것을\n * 결함으로 읽는다.\n */\n href?: string\n /** 그 id 가 실제로 있나 — **없으면 없다고 말한다.** 답에서 빼지 않는다. */\n missing?: boolean\n}\n\n/**\n * 밖에서 이 대상을 가리키는 것들 — 들어오는 관계 한 묶음.\n *\n * `tense` 를 **제품이 선언한다**(계약의 `RelationTense`). 실적·상태 구간은 지난 일이고 정비 계획·\n * 지시는 앞의 일이다 — 「걸린 것이 열」이 두 뜻이 되는 자리이므로 묶음이 스스로 말해야 한다.\n */\nexport interface ExternalIncoming {\n /** 관계의 이름(i18n 키) — 계약의 `via` 와 같은 규칙. 제품은 자기 접두를 쓴다. */\n via: string\n /** 어디서 오나 — 사람이 읽는 축 이름이 아니라 그 제품의 축 키다. */\n from: string\n /** 언제의 일인가. 안 주면 「선언이 말하지 않았다」다 — `standing` 으로 바꾸지 않는다. */\n tense?: 'past' | 'future' | 'standing'\n ids: string[]\n /** 전체 수 — `ids` 가 잘렸어도 몇인지 말한다. */\n total: number\n}\n\n/**\n * 한 external 대상을 푸는 제품 쪽 구현.\n *\n * 둘 다 선택이다. 이름만 대 줄 수 있는 제품과 들어오는 관계만 낼 수 있는 제품이 다 있다 — 하나만\n * 꽂아도 그만큼은 답해진다.\n */\n/** What the relation being resolved belongs to. Required — a resolver that answers without it answers for the wrong twin. */\nexport interface ExternalResolveContext {\n instanceId: string\n}\n\nexport interface ExternalResolver {\n /** 계약의 `target.entity` 와 **같은 낱말**. 다르면 아무 관계에도 안 붙는다. */\n entity: string\n /**\n * 이 id 들이 무엇인가 — **어느 트윈의 관계인지와 함께** 묻는다.\n *\n * 인스턴스를 넘기는 이유(2026-09-18): 밖의 id 는 도메인에서 유일하지 않다. 구역 id(`areaId`)는\n * 공간 안에서만 유일하므로, 도메인만으로 찾으면 다른 공간의 같은 id 가 답이 되어 **남의 공간 이름**이\n * 이 자리의 부모로 보인다. 부르는 쪽은 인스턴스를 알고 있다.\n */\n resolve?(domainId: string, ids: readonly string[], context: ExternalResolveContext): Promise<ExternalRef[]>\n /** 이 대상을 가리키는 것들. */\n incoming?(domainId: string, instanceId: string, axis: string, itemId: string): Promise<ExternalIncoming[]>\n}\n\nconst resolvers = new Map<string, ExternalResolver>()\n\n/**\n * 해소기를 꽂는다 — **부팅에서 한 번.**\n *\n * 같은 `entity` 를 두 번 꽂으면 뒤엣것이 이긴다. 그것이 개발 중 재적재의 정상 동작이고, 운영에서\n * 일어나면 두 제품이 같은 대상을 자기 것이라 말하는 것이므로 **경고를 남긴다** — 조용히 덮으면\n * 어느 쪽이 답하고 있는지 아무도 모른다.\n */\nexport function registerExternalResolver(r: ExternalResolver, warn?: (msg: string) => void): void {\n const entity = String(r?.entity ?? '').trim()\n if (!entity) {\n warn?.('[external-resolver] entity 없는 해소기는 꽂지 않는다 — 아무 관계에도 안 붙는다')\n return\n }\n if (resolvers.has(entity)) {\n warn?.(`[external-resolver] '${entity}' 해소기를 덮어쓴다 — 두 제품이 같은 대상을 답하려 하면 그중 하나만 답한다`)\n }\n resolvers.set(entity, { ...r, entity })\n}\n\n/** 지금 꽂혀 있는 것들 — 「무엇을 풀 수 있나」를 밖에서 잴 수 있게. */\nexport function externalResolverEntities(): string[] {\n return [...resolvers.keys()].sort()\n}\n\n/** 시험이 재기동을 넣을 수 있게. */\nexport function clearExternalResolvers(): void {\n resolvers.clear()\n}\n\n/**\n * 그 id 들을 푼다 — **못 풀면 못 푼다고 말한다.**\n *\n * 해소기가 터져도 360 전체를 죽이지 않는다. 한 관계가 답을 못 하는 것과 화면이 안 뜨는 것은 다른\n * 사고이고, 뒤엣것이 훨씬 나쁘다.\n */\nexport async function resolveExternal(\n entity: string,\n domainId: string,\n ids: readonly string[],\n context: ExternalResolveContext,\n warn?: (msg: string) => void\n): Promise<{ refs?: ExternalRef[]; unresolved?: 'no-resolver' | 'failed' }> {\n const r = resolvers.get(entity)\n if (!r?.resolve) return { unresolved: 'no-resolver' }\n try {\n return { refs: await r.resolve(domainId, ids, context) }\n } catch (e: any) {\n warn?.(`[external-resolver] '${entity}' 해소가 실패했다 — ${String(e?.message ?? e)}`)\n return { unresolved: 'failed' }\n }\n}\n\n/**\n * 밖에서 이 대상을 가리키는 것들을 모은다.\n *\n * 꽂힌 것이 없으면 **빈 배열이지 오류가 아니다** — 밖에서 가리키는 것이 없는 대상이 정상이다.\n * 다만 「해소기가 없어서 모른다」와 「없다」는 다르므로, 그 구별은 `externalResolverEntities()` 로\n * 밖에서 잰다.\n */\nexport async function externalIncoming(\n domainId: string,\n instanceId: string,\n axis: string,\n itemId: string,\n warn?: (msg: string) => void\n): Promise<ExternalIncoming[]> {\n const out: ExternalIncoming[] = []\n for (const r of resolvers.values()) {\n if (!r.incoming) continue\n try {\n const got = await r.incoming(domainId, instanceId, axis, itemId)\n /* 빈 묶음은 싣지 않는다 — 여기서는 「그 제품이 이 축을 안 본다」는 뜻이고 할 말이 없다. */\n for (const g of got ?? []) if (g && g.total > 0) out.push(g)\n } catch (e: any) {\n warn?.(`[external-resolver] '${r.entity}' 의 들어오는 관계 조회가 실패했다 — ${String(e?.message ?? e)}`)\n }\n }\n return out\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"iec61850-coverage.js","sourceRoot":"","sources":["../../../server/service/twin-model/iec61850-coverage.ts"],"names":[],"mappings":";;;AAiGA,4CAaC;AA9GD;;;;;;;;;;;;;;;GAeG;AACH,2DAA0F;AAE1F;;;GAGG;AACH,MAAM,aAAa,GAAmB;IACpC;;;OAGG;IACH;QACE,GAAG,EAAE,MAAM;QACX,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,WAAW;QACjB,SAAS,EAAE,MAAM;QACjB,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,MAAM;QACf,IAAI,EAAE,yBAAyB;KAChC;IACD;;;OAGG;IACH;QACE,GAAG,EAAE,MAAM;QACX,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,eAAe;QACrB,SAAS,EAAE,MAAM;QACjB,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,MAAM;QACf,IAAI,EAAE,yBAAyB;KAChC;IACD;;;;;;;;OAQG;IACH;QACE,GAAG,EAAE,MAAM;QACX,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,IAAI;QACV,OAAO,EAAE,CAAC,WAAW,CAAC;QACtB,SAAS,EAAE,MAAM;QACjB,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,MAAM;QACf,IAAI,EAAE,yBAAyB;KAChC;IACD;QACE,GAAG,EAAE,MAAM;QACX,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,IAAI;QACV,OAAO,EAAE,CAAC,WAAW,CAAC;QACtB,SAAS,EAAE,MAAM;QACjB,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,MAAM;QACf,IAAI,EAAE,yBAAyB;KAChC;IACD;QACE,GAAG,EAAE,MAAM;QACX,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,IAAI;QACV,OAAO,EAAE,CAAC,WAAW,CAAC;QACtB,SAAS,EAAE,MAAM;QACjB,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,MAAM;QACf,IAAI,EAAE,yBAAyB;KAChC;CACF,CAAA;AAEY,QAAA,iBAAiB,GAAmB,CAAC,GAAG,aAAa,CAAC,CAAA,CAAC,wCAAwC;AAE5G,SAAgB,gBAAgB,CAAC,IAAc;IAK7C,MAAM,QAAQ,GAAG,yBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAA,iCAAa,EAAC,CAAC,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAC5E,MAAM,KAAK,GAAG,CAAC,CAAuC,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,MAAM,CAAA;IACvG,OAAO;QACL,mDAAmD;QACnD,KAAK,EAAE,wBAAwB;QAC/B,QAAQ;QACR,MAAM,EAAE,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,EAAE;KAC7H,CAAA;AACH,CAAC","sourcesContent":["/*\n * IEC 61850 적합성 기준 — **에너지 트윈이 표준의 어디를 채우고 있나.**\n *\n * ── 왜 따로 있나 ────────────────────────────────────────────────────────────\n * 인스펙션의 적합성 패널은 트윈 종류와 무관하게 ISA-95 하나만 말하고 있었다. 에너지 트윈에서 그것은\n * 거짓은 아니지만(설비·계층·속성은 에너지에서도 ISA-95 자원이다) **부족하다** — 계량기·차단기·태양광·\n * 축전지의 어휘를 주는 것은 IEC 61850 이고, 무엇이 비었는지도 그 목록으로만 셀 수 있다.\n *\n * ── 주장 수준을 못 박는다 ───────────────────────────────────────────────────\n * 여기서 재는 것은 **어휘와 모델**이다. 프로토콜(MMS·GOOSE·샘플드값)을 구현했다는 뜻이 아니며,\n * 그 주장은 적합성 시험이 뒷받침해야 한다. 표 이름을 「IEC 61850 (vocabulary)」로 쓰는 이유다.\n *\n * ── 값은 관측이 아니라 선언이다 ─────────────────────────────────────────────\n * ISA-95 표와 같은 규율이다(`isa95-coverage.ts` 머리말): 사람이 판단해 적고, 틀리면 여기를 고친다.\n * 자동으로 알 수 있는 것 하나만 자동으로 한다 — 축 이름이 커널 축 목록에 없으면 구조가 없는 것이다.\n */\nimport { reconcileAxes, type Isa95Concept, type CoverageLevel } from './isa95-coverage.js'\n\n/*\n * Part 7-4 — 논리 노드 클래스. 이 프로파일이 쓰는 것만 적는다(표준 전체를 옮기지 않는다):\n * 계량·계측·개폐·발전·저장.\n */\nconst LOGICAL_NODES: Isa95Concept[] = [ // vocabulary-guard: allow — IEC 61850 의 「논리 노드(logical node)」는 표준 용어다(우리가 은퇴시킨 node 어휘가 아니다)\n /*\n * 계량(적산) — 커넥터가 누적 전력량을 실어 오고 상태가 지점별로 들고 있다. 화면은 계량 컴포넌트가\n * 지점의 값을 보인다.\n */\n {\n std: 'MMTR',\n part: '7-4',\n label: 'twin.iec61850.MMTR',\n axis: 'equipment',\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.iec61850.note.MMTR'\n },\n /*\n * 계측(유효전력·역률) — 표본이 수요 구간에 합쳐지고, 구간·피크·계약 대비가 그 위에 선다.\n * 이 프로파일에서 가장 두껍게 채운 자리다.\n */\n {\n std: 'MMXU',\n part: '7-4',\n label: 'twin.iec61850.MMXU',\n axis: 'demandWindows',\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.iec61850.note.MMXU'\n },\n /*\n * ── 셋이 함께 올라갔다 (2026-08-15) ──────────────────────────────────────\n * 개폐·발전·저장은 오래 「구조만 있음」이었다. 능력이 상태 필드를 선언해 두었는데 **값을 받는 문이\n * 없었기 때문**이다. 이제 셋 다 실제로 돈다: 설비 상태가 계약에 서고(`EquipmentState`), 커널이\n * `energy.equipment` 로 받아 그 설비에 적고, 씬 컴포넌트가 그린다.\n *\n * 세 층을 함께 올리는 이유: 값이 없는데 화면만 만들면 영원히 「—」인 카드가 되고, 화면이 없는데\n * 기능만 올리면 아무도 그 값을 보지 못한다. 「값이 먼저, 컴포넌트는 그 다음」의 결과다.\n */\n {\n std: 'XCBR',\n part: '7-4',\n label: 'twin.iec61850.XCBR',\n axis: null,\n shownOn: ['equipment'],\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.iec61850.note.XCBR'\n },\n {\n std: 'DPVA',\n part: '7-4',\n label: 'twin.iec61850.DPVA',\n axis: null,\n shownOn: ['equipment'],\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.iec61850.note.DPVA'\n },\n {\n std: 'ZBAT',\n part: '7-4',\n label: 'twin.iec61850.ZBAT',\n axis: null,\n shownOn: ['equipment'],\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.iec61850.note.ZBAT'\n }\n]\n\nexport const IEC61850_CONCEPTS: Isa95Concept[] = [...LOGICAL_NODES] // vocabulary-guard: allow — 표준 용어(위 주석)\n\nexport function iec61850Coverage(axes: string[]): {\n model: string\n concepts: (Isa95Concept & { axisMissing?: boolean })[]\n totals: { concepts: number; structure: number; behavior: number; surface: number }\n} {\n const concepts = IEC61850_CONCEPTS.map(c => reconcileAxes(c, new Set(axes)))\n const count = (k: 'structure' | 'behavior' | 'surface') => concepts.filter(c => c[k] === 'full').length\n return {\n /* 이름에 (vocabulary) 를 달아 둔다 — 프로토콜 적합성으로 읽히지 않게. */\n model: 'IEC 61850 (vocabulary)',\n concepts,\n totals: { concepts: concepts.length, structure: count('structure'), behavior: count('behavior'), surface: count('surface') }\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/twin-model/index.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;GAQG;AACH,uDAAiD;AAAxC,gHAAA,YAAY,OAAA;AACrB,yDAAmD;AAA1C,kHAAA,aAAa,OAAA;AACtB,yDAAmD;AAA1C,kHAAA,aAAa,OAAA;AAEtB,yDAAiD;AACjD,2DAAmD;AACnD,2DAAmD;AAEnD,+DAAsD;AACtD,yEAA+D;AAC/D,yEAA+D;AAC/D,qEAA4D;AAC5D,mEAA0D;AAE7C,QAAA,QAAQ,GAAG,CAAC,+BAAY,EAAE,iCAAa,EAAE,iCAAa,CAAC,CAAA;AACvD,QAAA,SAAS,GAAG,CAAC,oCAAc,EAAE,6CAAkB,EAAE,6CAAkB,EAAE,0CAAiB,EAAE,wCAAgB,CAAC,CAAA","sourcesContent":["/*\n * 트윈 구조 모델 — `TwinInstance.model`(JSON 덩어리) 에서 풀어낸 **표준 엔티티**(ADR-0032).\n *\n * 여기 있는 것은 전부 **원본에서 온 캐시**다. 정본은 원본 시스템이고(ADR-0028), 이 행들은\n * 인제스트가 만들며 언제든 전부 지우고 다시 만들 수 있어야 한다.\n *\n * 오늘은 **실제로 채워지는 세 축**만 있다 — 자리·설비·공정. 사람·자산·등급·속성·생산 정의는\n * 지금 0건이라 만들지 않는다(\"설계와 착수는 다르다\", ADR-0032-H). 모델은 그때 옳게 세운다.\n */\nexport { TwinLocation } from './twin-location.js'\nexport { TwinEquipment } from './twin-equipment.js'\nexport { TwinOperation } from './twin-operation.js'\n\nimport { TwinLocation } from './twin-location.js'\nimport { TwinEquipment } from './twin-equipment.js'\nimport { TwinOperation } from './twin-operation.js'\n\nimport { TwinModelQuery } from './twin-model-query.js'\nimport { TwinModelItemQuery } from './twin-model-item-query.js'\nimport { TwinModelTreeQuery } from './twin-model-tree-query.js'\nimport { TwinModelMutation } from './twin-model-mutation.js'\nimport { TwinLineageQuery } from './twin-lineage-query.js'\n\nexport const entities = [TwinLocation, TwinEquipment, TwinOperation]\nexport const resolvers = [TwinModelQuery, TwinModelItemQuery, TwinModelTreeQuery, TwinModelMutation, TwinLineageQuery]\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"isa95-coverage.js","sourceRoot":"","sources":["../../../server/service/twin-model/isa95-coverage.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * ISA-95 적합성 기준 — **표준이 요구하는 것의 목록.**\n *\n * ── 왜 이 파일이 필요한가 ──────────────────────────────────────────────────────\n * 지금까지 화면은 \"우리가 가진 축\" 만 보였다. 그러면 채운 것은 세어지지만 **빠진 것은 셀 수 없다** —\n * 없는 것은 화면에 없으므로 아무도 그것을 묻지 않는다. 표준 준수는 \"우리 축에 표준 이름을 붙였다\"\n * 가 아니라 **표준의 목록을 기준으로 빠진 것이 없다**는 말이어야 한다. 기준이 코드로 있어야 그\n * 문장을 검사할 수 있다.\n *\n * ── 세 층에서 본다 ────────────────────────────────────────────────────────────\n * 표준 준수는 한 층의 일이 아니다. 개념마다 셋을 따로 본다:\n *\n * · `structure` — **데이터구조**가 있나(커널 축·호스트 표에 이 개념의 자리가 있나)\n * · `behavior` — **기능**이 그것을 쓰나(커널이 계획·집행·집계에서 실제로 다루나)\n * · `surface` — **UI 노출**이 있나(사용자가 그것을 볼 수 있나)\n *\n * 셋을 합치면 \"구조는 있는데 아무도 안 쓰는 컬럼\" 과 \"커널은 쓰는데 화면에 없는 사실\" 이 같은\n * 값으로 보인다. 실제로 이 프로젝트에서 둘 다 나왔다(인력 명세는 구조·기능이 있었지만 화면에\n * 없었고, `properties` 는 구조만 있다).\n *\n * ── 값은 관측이 아니라 선언이다 ────────────────────────────────────────────────\n * 여기 적힌 것은 **사람이 판단해 적은 사실**이다(자동 판정이 아니다). 그래서 틀릴 수 있고, 틀리면\n * 여기를 고쳐야 한다 — 화면은 이 선언을 그대로 보이고 지어내지 않는다. 자동으로 알 수 있는 것\n * 하나는 자동으로 한다: `axis` 가 커널 축 목록에 없으면 그것은 **구조가 없는 것**이다(오타 방지).\n *\n * ── 이 표가 커널에 없는 이유 ──────────────────────────────────────────────────\n * 커널은 **런타임 모델의 SSOT** 다(무엇이 있는가). 이 표는 **참조 모델의 목록**이다(무엇이 있어야\n * 하는가). 둘은 다른 종류의 사실이고, 후자는 커널을 배포하지 않고도 늘어야 한다.\n */\n\n/** 한 층의 상태 — 있음 · 부분 · 없음. \"부분\" 을 두는 이유: 절반 있는 것을 있다고 하면 기준이 죽는다. */\nexport type CoverageLevel = 'full' | 'partial' | 'none'\n\nexport interface Isa95Concept {\n /** 표준이 부르는 이름 — 이 표의 키다(우리 어휘가 아니라 표준 어휘). */\n std: string\n /** 표준의 어느 부분인가 — Part 2(자원 모델) · Part 4(일정·실적) 등. */\n part: string\n /** 사람이 읽는 이름(i18n 키). 화면이 번역한다. */\n label: string\n /** 우리 커널 축 — 있으면 데이터구조가 있는 것이다. 없으면 `null`. */\n axis: string | null\n /** 세 층의 상태. `structure` 는 `axis` 로도 검증된다. */\n structure: CoverageLevel\n behavior: CoverageLevel\n surface: CoverageLevel\n /** 왜 그 수준인지 — **모르는 것을 감추지 않기 위해** 한 줄로 적는다(i18n 키). */\n note?: string\n /**\n * 자기 축이 없을 때 **어디서 보이나** — 그 사실을 들고 있는 축들.\n *\n * 「어디에」칸이 두 가지를 합쳐고 있었다: *아직 축이 없다(부채)* 와 *축일 수 없다(설계)*. 가용 능력은\n * 개체가 아니라 자원의 지금 상태이므로 축이 될 수 없는데, 세 층이 다 완전한 줄이 \"개념 축이 없습니다\"\n * 라고만 말하면 **표가 스스로 모순된 말을 한다**(다 됐다면서 없다고 한다).\n *\n * 그래서 축일 수 없는 것은 갈 곳을 가리킨다. 여기가 비어 있고 축도 없으면 그것은 **진짜 빈칸**이다.\n *\n * ── ★ 뜻을 고쳐 적는다 (2026-08-24, 사용자 지적) ──────────────────────────\n * 한때 이 칸을 「**어디서 보이나**」로 읽고, 화면이 그것을 「해당 축 항목에 표시됩니다」라는 문장으로\n * 냈다. 두 가지가 잘못이었다.\n *\n * ① 그 문장은 **한국어가 아니었다**(사용자 지적). 그리고 칩이 이미 갈 곳을 보이므로 **군더더기**다.\n * ② 뜻이 틀렸다. 이 칸은 「**어디에 속하나**」다 — 그 개념이 자기 목록을 갖지 않고 **어느 축의 안에\n * 들어 있나**. 화면에 그려지는지는 `surface` 가 따로 답한다. 둘은 다른 물음이다.\n *\n * 그래서 **`surface` 와 짝을 맞추지 않는다.** 자기 축이 없으면 언제나 갈 곳을 적는다 — 아직 그려지지\n * 않는 개념도 **어디에 속하는지는 사실**이고, 그것을 비우면 「어디에도 없는 것」으로 읽힌다.\n *\n * 여기가 비어 있고 축도 없으면 그것은 **진짜 빈칸**이다(위 규칙).\n */\n shownOn?: string[]\n}\n\n/*\n * 가용 능력(2026-08-12, 커널 0.6.8) — **판정과 이유가 상태에 실려 온다**(`capability {available, reason}`).\n *\n * 셋 다 완전인 근거: 상태가 판정을 들고 오고(시뮬·미러가 같은 함수로 파생한다), 배정이 같은 판정을\n * 쓰고, 두 화면이 같은 낱말로 그 이유를 보인다(자원 탭 배지 · 인스펙션 관측 열).\n *\n * 왜 축이 없는가 — 가용 능력은 개체가 아니라 **자원의 지금 상태**다. 개념 카드로 세우면 사람·설비와\n * 나란히 놓여 \"능력이 몇 개인가\" 를 세게 되는데, 그건 물을 일이 아니다.\n */\n\n/*\n * 생산 능력(2026-08-13, 커널 0.6.12) — **구간의 양을 표준 종류로 구분한다.**\n *\n * `structure` 완전: 계약이 보고서를 낸다(`operationsCapabilityOf`) — 구간(StartTime·EndTime) · 표준\n * 열거값(Committed·Available·Unattainable) · 공정별 분해 · 제약하는 축. 낱말은 B2MML 대조 후 표준 것을\n * 그대로 쓴다(우리 낱말을 만들지 않았다).\n * `behavior` 완전: 약정은 오더에서, 달성불가는 자원 판정(`capabilityOf`)에서 온다 — 그리고 그 판정은\n * 배정이 쓰는 것과 같은 한 벌이다. 표준의 `Unattainable` 정의가 그 판정과 겹친다.\n * `surface` 완전: 두 자리에서 보인다 — 성과 화면이 현장 전체의 \"앞으로 더 받을 수 있는 몫\" 을, 공정\n * 상세가 그 공정 하나의 분해(상한·약정·달성불가·가용)와 제약하는 축을 보인다. 두 화면은 **같은 낱말**을\n * 쓴다(같은 i18n 키) — 같은 사실을 다르게 부르면 어느 쪽이 맞는지 아무도 모른다.\n *\n * 남은 것 — 표준은 **게시된(약속된) 능력**도 담는다(계산이 아니라 선언). 값을 실을 소스가 아직\n * 침묵하므로 빈 축을 먼저 만들지 않는다(상태 쪽 `workCalendar` 를 지운 이유와 같다).\n *\n * 과거 분해(Used·Unused)는 이 보고서에 없다 — 그것은 실적이고 이미 두 곳에 있다(설비 OEE 의 가동/유휴,\n * 성과 화면의 구간 가동률). 표준이 그 둘을 다른 종류로 부르므로 **합치지 않는다.**\n */\n\n/*\n * Part 2 — 자원 모델. 네 자원 각각이 **등급/정의 ↔ 개체** 이중구조를 갖는 것이 이 부분의 핵심이고,\n * 그 이중성이 이 트윈의 뼈대이기도 하다.\n */\nconst PART2: Isa95Concept[] = [\n { std: 'PersonnelClass', part: '2', label: 'twin.isa95.PersonnelClass', axis: 'personnelClasses', structure: 'full', behavior: 'full', surface: 'full' },\n { std: 'Person', part: '2', label: 'twin.isa95.Person', axis: 'persons', structure: 'full', behavior: 'full', surface: 'full' },\n { std: 'PersonnelCapability', part: '2', label: 'twin.isa95.PersonnelCapability', axis: null, shownOn: ['persons'], structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.capability' },\n { std: 'EquipmentClass', part: '2', label: 'twin.isa95.EquipmentClass', axis: 'equipmentClasses', structure: 'full', behavior: 'full', surface: 'full' },\n { std: 'Equipment', part: '2', label: 'twin.isa95.Equipment', axis: 'equipment', structure: 'full', behavior: 'full', surface: 'full' },\n { std: 'EquipmentCapability', part: '2', label: 'twin.isa95.EquipmentCapability', axis: null, shownOn: ['equipment'], structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.capability' },\n { std: 'PhysicalAssetClass', part: '2', label: 'twin.isa95.PhysicalAssetClass', axis: 'assetClasses', structure: 'full', behavior: 'full', surface: 'full' },\n { std: 'PhysicalAsset', part: '2', label: 'twin.isa95.PhysicalAsset', axis: 'assets', structure: 'full', behavior: 'full', surface: 'full' },\n { std: 'MaterialClass', part: '2', label: 'twin.isa95.MaterialClass', axis: 'materialClasses', structure: 'full', behavior: 'partial', surface: 'full' },\n { std: 'MaterialDefinition', part: '2', label: 'twin.isa95.MaterialDefinition', axis: 'materialDefinitions', structure: 'full', behavior: 'full', surface: 'full' },\n /*\n * 로트·서브로트는 개체 축이 아니라 **관측**으로 존재한다(EPCIS 개체) — 구조는 저널이 갖는다.\n *\n * ── 2026-08-24 로트가 시험 결과를 들게 됐다 (커널 0.7.57) ─────────────────\n * `ItemState.testResults` 로 「이 로트를 쓸 수 있나」의 **근거**가 로트에 실린다. 그전에는 판정\n * (`disposition`)만 있었고 「왜 그렇게 판정했나」를 되짚을 수 없었다.\n *\n * **구조를 `full` 로 올리지 않는다.** `MaterialLotType` 원문(B2MML-Material.xsd)이 드는 것 중\n * 아직 없는 것이 있다: `MaterialLotProperty`(시험이 아닌 로트 속성 — 원산지·포장 형태) ·\n * `TestSpecificationID`(로트별 기준 참조 — 품목 정의가 대신 답하므로 두지 않았다) · `Status` ·\n * `HierarchyScope` · `Version`. 채운 것만 세는 것이 이 표의 규율이다.\n */\n /*\n * ── 2026-08-25 — 목록이 쌓이기만 하던 것을 고쳤다 ──────────────────────────\n * 연결된 시스템은 매 주기 현재 재고를 전부 보내는데, 트윈은 그것을 낱낱의 관측으로 받아 **더하기만\n * 했다.** 「그리고 이것 말고는 없다」를 받는 곳이 없어서 목록에서 빠진 줄이 영원히 남았다.\n * 실측: 실제 1,704건인데 트윈이 10,038건.\n *\n * 그동안 이 줄은 「기능 있음」이었다. **현재 재고를 맞게 유지하지 못하는 상태로 그렇게 적을 수\n * 없다.** 오늘 「이 목록이 전부다」를 받는 길을 냈고(커널 0.7.63), 수량으로 온 재고도 없어질 수\n * 있게 했다. 이제 사실이다.\n */\n { std: 'MaterialLot', part: '2', label: 'twin.isa95.MaterialLot', axis: 'items', structure: 'partial', behavior: 'full', surface: 'partial', note: 'twin.isa95.note.lot' },\n /*\n * ── 2026-08-24 판정 정정: `none` 이 아니었다 ──────────────────────────────\n * 이 줄은 셋 다 `none` 이었는데 **표가 코드보다 뒤처져 있었다.** 커널은 서브로트를 든다:\n *\n * `ItemState.subLotId` 표준 `MaterialSubLot.ID` (계약이 그렇게 적고 있다)\n * `subLotIdOf(class, location)` 비직렬 로트가 자리마다 나뉠 때 그 부분의 이름을 만든다\n * `itemKeyOf(item)` **개체의 정체성이 이 값이다** — `subLotId ?? epc`\n *\n * 기능이 `full` 인 근거: 이 축이 없으면 같은 로트를 rack-1 에 100개·rack-2 에 60개 관측했을 때\n * **뒤에 온 관측이 앞을 덮어 100개가 오류 없이 사라진다**(합계 160 → 60). 실제로 그 결함을 이 축으로\n * 고쳤다. 즉 장식이 아니라 정체성을 지탱한다.\n *\n * 구조가 `partial` 인 이유: 서브로트는 로트와 같은 모양이고(위 참조) 그 로트가 `partial` 이다.\n * 화면이 `partial` 인 이유: 값이 물품의 **이름으로** 나온다(`twin-item` 의 line) — 「이 로트가 자리마다\n * 나뉘어 있다」를 말하는 자리는 아직 없다.\n *\n * 이 부류(코드에 있는데 표가 `none`)는 자동으로 잡히지 않는다 — 표의 가드는 반대 방향만 본다\n * (축이 사라지면 구조를 내린다). 그래서 축을 늘릴 때 이 표를 함께 보는 것이 규율이다.\n */\n { std: 'MaterialSublot', part: '2', label: 'twin.isa95.MaterialSublot', axis: null, shownOn: ['items'], structure: 'partial', behavior: 'full', surface: 'partial' },\n { std: 'ProcessSegment', part: '2', label: 'twin.isa95.ProcessSegment', axis: 'operations', structure: 'full', behavior: 'full', surface: 'full' },\n /*\n * 속성은 자원마다 붙는 확장이라 **자기 축이 아니다** — 그래서 `axis` 는 없지만 구조는 완전하다:\n * 계약(`ResourceProperty`: 값·데이터형·단위·하위·등급속성)이 표준을 그대로 담고, 인제스트·상태·\n * 화면이 그것을 통과시킨다.\n *\n * 기능은 **부분**이다: 호스트의 주행 추정기가 `speed` 를 읽어 이동 시간을 만드는 것이 유일한 소비처다\n * (다른 속성은 아직 아무도 읽지 않는다). 그 사실을 `full` 로 올리면 표가 거짓이 된다.\n */\n { std: 'Property', part: '2', label: 'twin.isa95.Property', axis: null, shownOn: ['equipment', 'persons', 'assets'], structure: 'full', behavior: 'partial', surface: 'full', note: 'twin.isa95.note.property' },\n /*\n * **자리의 상시 관측** — 2026-08-24 신설(커널 0.7.56). 표에 이 개념이 아예 없었다.\n *\n * 왜 이 축이 필요한가: 트윈의 판정 대상은 물건의 상태이고, 물건의 상태는 **조건 없이 정해지지\n * 않는다.** 「이 로트가 냉장실에 있었다」까지만 아는 트윈은 그 로트가 괜찮았는지 말할 수 없다. 그리고\n * **이 조인은 트윈만 할 수 있다** — 계측 시스템은 물건의 자리 이력을 모르고, 물류 시스템은 조건\n * 이력을 모른다.\n *\n * 표준 앵커: `OperationsEventType`(B2MML-OperationsEvent.xsd, ISA-95.00.02-2018) +\n * `OperationsRecordTemplateType`. 우리가 든 것 — `EffectiveTimestamp` · `EffectiveEndDate` ·\n * `RecordTimestamp` · `HierarchyScope`(자리) · `Source` · 값은 `ValueType` 으로 **좁혔다**\n * (단위 없는 물리량은 판정의 재료가 못 된다).\n *\n * 붙는 자리가 `HierarchyScope.EquipmentID` 인 것이 중요하다 — ISA-95 에서 장소 계층이 곧 설비\n * 계층이고 냉장실은 `StorageZone` 수준의 설비다. `OperationalLocationType` 에는 이 축이 없다.\n *\n * 구조가 `partial`: 봉투 전체(`ID`·`Description`·사건 분류·`OperationsRecord` 구조)가 아니라 관측\n * 하나를 담는 데 필요한 칸만 든다. 2026-08-25 에 지난 기록의 봉투가 사람이 적은 문장을 함께\n * 나르게 됐지만(`CanonicalEnvelope.description`), 그것은 **기록에 남는 말**이고 이 타입의\n * `Description` 칸이 아니다 — 상태에 담기지 않으므로 이 줄의 등급을 올리지 않는다.\n * 기능이 `full`: 사건 채널(`location.measured`)이 상태를 만들고, 선언된 기준으로 판정해\n * `observation-out-of-limit` 신호를 세운다.\n * 화면이 `none`: 클라이언트에 이 축을 그리는 곳이 없다(실측 0곳).\n */\n /*\n * ── 2026-08-25 — 이 줄이 실제보다 높게 적혀 있었다 ────────────────────────\n * 「기능 있음」으로 적어 두었다. 커널이 관측을 반영하고 기준으로 판단하는 것은 사실이었으므로\n * 그렇게 적었는데, **연결된 시스템이 그 값을 보낼 길이 없었다** — 유입 경로가 `locationId` 를\n * 보지 않아 「어느 사실인지 모른다」로 거부했다.\n *\n * **아무도 먹일 수 없는 기능은 동작하는 기능이 아니다.** 오늘 그 경로를 냈고(커널 0.7.62),\n * 이제 「기능 있음」이 사실이다. 그때까지는 「일부」였어야 했다.\n *\n * 같은 부류를 오늘 세 번 만났다 — 표가 **할 수 있는 일을 못 한다고** 적은 것이 여섯 줄이었고,\n * 이것은 반대로 **못 하는 일을 할 수 있다고** 적은 것이다. 뒤쪽이 더 위험하다.\n */\n { std: 'OperationsEvent', part: '2', label: 'twin.isa95.OperationsEvent', axis: null, shownOn: ['locations'], structure: 'partial', behavior: 'full', surface: 'none' }\n]\n\n/*\n * Part 4 — 일정과 실적.\n *\n * **2026-08-11 이전에는 이 부분이 거의 비어 있었다.** 오더·작업은 런타임과 저널에 실재하는데 조회\n * 모델의 축이 아니었고, 그래서 \"이 트윈이 표준의 어디를 채우나\" 라는 질문에 절반만 답했다.\n *\n * 커널이 계약을 넓혀(`source: 'state'`) 두 축이 추가되었다 — 이제 요구(`orders`)와 응답(`tasks`)을 개념으로\n * 가리켜 걸어 들어갈 수 있다. 남은 것은 **과거로 다시 계산하는 길**(저널 커서 페이징)과 능력·검사 명세다.\n */\nconst PART4: Isa95Concept[] = [\n { std: 'OperationsDefinition', part: '4', label: 'twin.isa95.OperationsDefinition', axis: 'productionSpec', structure: 'full', behavior: 'full', surface: 'partial', note: 'twin.isa95.note.opsdef' },\n /*\n * 2026-08-11 — **축이 추가되었다**(커널 0.6.3 · `source: 'state'`). 요구는 `orders`, 응답은 `tasks` 다.\n *\n * `structure` 를 `full` 로 올린다: 담을 자리가 계약에 있다(상태 계약 + 축 선언). **테이블 투영이\n * 없는 것은 결손이 아니다** — 이 축의 정본은 저널이고, 투영하지 않는 것이 결정이다(ADR-0032-D).\n *\n * `surface` 도 `full` 이다(같은 날 두 번째 단계): 목록·상세·관계에 더해 **과거로 다시 계산하는 길**이 화면에\n * 있다 — 시각 커서를 옮기면 그 시점의 사실이 나오고, 무엇을 근거로 답했는지도 화면이 말한다\n * (라이브=권위 · 저널=파생 · 계산한 리비전). 실측: 이른 시각 0건 · 하루 뒤 504건.\n */\n { std: 'OperationsSchedule', part: '4', label: 'twin.isa95.OperationsSchedule', axis: 'orders', structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.schedule' },\n { std: 'OperationsPerformance', part: '4', label: 'twin.isa95.OperationsPerformance', axis: 'tasks', structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.performance' },\n { std: 'OperationsCapability', part: '4', label: 'twin.isa95.OperationsCapability', axis: null, shownOn: ['operations'], structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.opscapability' },\n /*\n * 2026-08-12 — **축이 추가되었다**(커널 0.6.5). 자원 아홉이 가리키던 참조에 대상이 생겼고, 없는 명세를\n * 가리키면 선 채움이 끊어진 참조로 센다.\n *\n * 기능이 **`partial` 로 살아났다**(커널 0.6.6): 결과(`TestResult`)가 계약에 들어오면서 커널이 배정에서\n * 그것을 본다 — 만료·불합격이면 그 자격은 성립하지 않는다(실측: 만료된 용접사는 용접 작업 5건 중 한\n * 번도 배정되지 않았다).\n *\n * ── 2026-08-24 위 문장 하나가 낡았다 ─────────────────────────────────────\n * 「**사람만 판정한다**」고 적혀 있었다. 지금은 아니다 — `capabilityOfResource` 가 설비·사람·자산\n * **셋 모두**에 `requiredTests` 를 걸고, 그 답이 배정의 가용성 필터로 들어간다. 그리고 판정 대상이\n * 자원 밖으로 넓어졌다: **자리의 상시 관측**(`OperationsEvent` 줄)과 **로트의 시험 결과**\n * (`TestResult` 줄)도 같은 기준으로 판정한다.\n *\n * 남은 규율은 하나다: **결과가 없으면 판정하지 않는다**(선언한 것만 제약이 된다 — 결손이 아니라 규율).\n *\n * 그리고 기준의 **숫자 한계는 우리가 더한 것**이다. 표준 `TestSpecificationCriteria.Expression` 은\n * 자유 문장(`TextType`)이고, B2MML 일곱 파일에 `Minimum`·`Maximum`·`Tolerance` 가 하나도 없다\n * (원문 대조). 그것 없이는 판정할 수 없으므로 `limit: {minimum, maximum, uom}` 을 더했고, 발명한\n * 자리임을 계약에 적었다. 커널은 `Expression` 을 **읽지 않는다** — 문법이 정의되지 않았으므로\n * 파싱하면 그 순간 방언이다.\n */\n { std: 'TestSpecification', part: '4', label: 'twin.isa95.TestSpecification', axis: 'testSpecifications', structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.test' },\n /*\n * **시험 결과** — 2026-08-24 신설(커널 0.7.57). 기준만 있고 결과의 자리가 표에 없었다.\n *\n * 표준 `TestResultType`(B2MML-OperationsTest.xsd)에서 우리가 든 것:\n * `EvaluationDate`(at) · `Expiration`(expiresAt) · `TestableObjectID` · `PropertyMeasurement`\n * · `EvaluatedCriterionResult` → **`result: 'pass'|'fail'` 로 좁혔다**(표준은 열거하지 않는다)\n * 아직 없는 것: `ID` · `Description` · `HierarchyScope` · `OperationsTestRequirementID` ·\n * `TestResultChild`. 그래서 구조는 `partial` 이다.\n *\n * 기능이 `partial` 인 이유를 정확히 적는다 — **자원에서는 결정을 바꾸고 로트에서는 아직 바꾸지\n * 않는다.** 만료·불합격 자원에는 작업이 배정되지 않지만, 불합격 로트가 할당에서 빠지거나 주의 신호를\n * 세우지는 않는다(들고·판정하고·되짚을 수는 있다). 그 자리를 `full` 로 적으면 표가 거짓이 된다.\n *\n * 화면은 `none` 이다 — 클라이언트에 이 축을 그리는 곳이 없다(실측: `operato-twin/client` 에 0곳).\n */\n { std: 'TestResult', part: '4', label: 'twin.isa95.TestResult', axis: null, shownOn: ['items'], structure: 'partial', behavior: 'partial', surface: 'none' },\n { std: 'WorkMaster', part: '4', label: 'twin.isa95.WorkMaster', axis: 'recipes', structure: 'full', behavior: 'full', surface: 'full' },\n { std: 'WorkDirective', part: '4', label: 'twin.isa95.WorkDirective', axis: null, structure: 'none', behavior: 'none', surface: 'none' }\n]\n\n/** 표준이 요구하는 것 전부 — 이 배열이 곧 기준이다. */\nexport const ISA95_CONCEPTS: Isa95Concept[] = [...PART2, ...PART4]\n\n/**\n * 적합성 보고 — **채운 것과 빠진 것을 함께.**\n *\n * `axis` 를 커널 축 목록과 맞춰 본다: 축이 있다고 적혀 있는데 커널에 없으면 그 선언이 낡은 것이므로\n * **구조 없음으로 낮추고 그 사실을 말한다**(알리지 않고 통과시키면 이 표가 거짓이 된다).\n */\n/**\n * 선언한 축을 커널의 축 목록과 맞춘다 — **어긋난 것을 알리지 않고 지우지 않는다.**\n *\n * 두 자리가 어긋날 수 있다.\n * · `axis` — 그 개념 자신의 문. 커널에 없으면 구조를 「없음」으로 낮추고 그 사실을 말한다.\n * · `shownOn` — 그 사실을 들고 있는 다른 축. 예전에는 없는 축을 **말없이 걸러 냈다.** 그러면 그 줄은\n * 「개념 축이 없습니다」로 보이고, 사람은 그것을 **원래 그런 것**으로 읽는다. 실제로 그 일이 있었다:\n * EPCIS 표의 일곱 줄이 커널에 없는 축(`items`)에 기대고 있었는데, 경고가 뜬 것은 두 줄뿐이었다.\n *\n * 둘 중 하나라도 어긋나면 `axisMissing` 이다 — 표가 스스로 「최신이 아니다」라고 말하게 한다.\n */\nexport function reconcileAxes(c: Isa95Concept, known: Set<string>): Isa95Concept & { axisMissing?: boolean } {\n const ownMissing = !!c.axis && !known.has(c.axis)\n const shownOn = c.shownOn?.filter(a => known.has(a))\n const lostShown = !!c.shownOn && (shownOn?.length ?? 0) !== c.shownOn.length\n const fixed = lostShown ? { ...c, shownOn } : c\n if (!ownMissing && !lostShown) return fixed\n /* 구조를 낮추는 것은 **자기 문이 없어진 경우**다 — 남의 축에서 보이던 것은 구조가 아니라 표시의 문제다. */\n return ownMissing ? { ...fixed, structure: 'none' as CoverageLevel, axisMissing: true } : { ...fixed, axisMissing: true }\n}\n\nexport function isa95Coverage(axes: string[]): {\n model: string\n concepts: (Isa95Concept & { axisMissing?: boolean })[]\n totals: { concepts: number; structure: number; behavior: number; surface: number }\n} {\n const concepts = ISA95_CONCEPTS.map(c => reconcileAxes(c, new Set(axes)))\n /* 채운 수는 `full` 만 센다 — 부분을 채운 것으로 세면 기준이 스스로를 속인다. */\n const count = (k: 'structure' | 'behavior' | 'surface') => concepts.filter(c => c[k] === 'full').length\n return {\n model: 'ISA-95',\n concepts,\n totals: { concepts: concepts.length, structure: count('structure'), behavior: count('behavior'), surface: count('surface') }\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"item-ref.js","sourceRoot":"","sources":["../../../server/service/twin-model/item-ref.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;;AAaH,8BAWC;AAaD,oDAaC;AAhDD;;;;;;;;;;GAUG;AACH,SAAgB,SAAS,CAAC,GAAY;IACpC,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IACxC,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAA;IACpB,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAA;IAEnB,sEAAsE;IACtE,MAAM,KAAK,GAAG,wBAAwB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAC9C,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC,CAAC,CAAC,CAAA;IAE1B,iDAAiD;IACjD,OAAO,CAAC,CAAA;AACV,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,oBAAoB,CAAI,GAAkB,EAAE,QAAwB;IAClF,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAA;IAC1B,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC/B,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAA;IACrC,IAAI,GAAkB,CAAA;IACtB,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,QAAQ,EAAE,CAAC;QACnC,IAAI,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YAAE,SAAQ;QACvD,KAAK,EAAE,CAAA;QACP,IAAI,KAAK,GAAG,CAAC;YAAE,OAAO,SAAS,CAAA;QAC/B,GAAG,GAAG,KAAK,CAAA;IACb,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC","sourcesContent":["/*\n * **개체에서 품목으로** — 정체성 모양이 하나가 아니다 (2026-08-23).\n *\n * ── 무엇이 알리지 않고 비어 있었나 ───────────────────────────────────────────────\n * 화면이 「이 개체는 선언된 자재가 아닙니다(라인 내부의 중간 산물일 수 있습니다)」라고 말했는데, 그\n * 품목은 원본의 선언에 이름까지 있었다(「냉동그린빈스」). 대응 표(`productionSpec.binding`)도 멀쩡했다.\n *\n * 원인은 **개체에서 품목을 꺼내는 규칙**이었다. 그 규칙이 GS1 SGTIN URN 하나만 알았다.\n *\n * urn:epc:id:sgtin:0614141.077101.42 → 077101 (알던 모양)\n * https://…/class/product-M0000438@M2R0001 → null (모르던 모양)\n *\n * 웹 URI 로 정체성을 말하는 원본에서는 언제나 `null` 이 나왔고, 그래서 **개체를 자재에 이을 수 없었다.**\n * 그 결과가 화면 두 곳에서 들어온 것이 없는 빈칸이었다(계보 적합성 · 오더가 무엇을 만드나). 「없으면 붙이지\n * 않는다」는 규율을 지키기 때문에 아무 오류도 나지 않았다.\n *\n * ── 무엇을 돌려주나 ─────────────────────────────────────────────────────────\n * **그 트윈의 대응 표가 쓰는 것과 같은 모양**이다. 두 세계가 서로 다른 것을 값으로 쓴다.\n *\n * SGTIN 세계 binding 값 = 품목 참조 숫자(`077101`) → 숫자를 돌려준다\n * URI 세계 binding 값 = 클래스 URI(`…/product-M…`) → 클래스 URI 를 돌려준다\n *\n * 그래서 두 세계를 한 함수가 답한다 — 부르는 자리가 원본의 정체성 방식을 알지 않아도 된다.\n *\n * ── 무엇을 지어내지 않나 ────────────────────────────────────────────────────\n * **어느 부호가 개체를 가리키는지 모른다.** 그것은 원본마다 다르고 커널 계약이 정한 적이 없다 — 한\n * 원본(chef)이 쓰는 `@` 를 규칙으로 굳히면 이 화면이 그 레거시 위에 서게 된다. 자르는 판단은 그 트윈의\n * **선언**이 하고(§`resolveByDeclaration`), 후보가 둘 이상이면 답하지 않는다. 짐작으로 이으면 남의\n * 레시피와 비교하게 되고, 그것이 「설계와 다르다」는 거짓 진단이 된다.\n */\n\n/**\n * 개체 식별자 → **품목 참조**. 대응 표의 값과 비교할 수 있는 모양으로 돌려준다.\n *\n * SGTIN 은 표준이 자리를 정해 두었으므로 그 자리에서 품목 참조를 꺼낸다. 그 밖의 모양은 **손대지\n * 않는다** — 어느 부호가 개체를 가리키는지는 원본마다 다르고, 커널 계약이 그 구분자를 정한 적이 없다.\n * 앞서 이 자리에 「`@` 뒤는 개체다」를 넣었는데, 그것은 한 원본(chef)의 모양을 규칙으로 굳힌 것이었다.\n *\n * 그래서 자르는 일은 여기서 하지 않고, **그 트윈의 선언과 비교해** 판정한다(§`resolveByDeclaration`).\n * 선언이 답을 주지 못하면 `null` 이다 — 짐작으로 이으면 남의 레시피와 비교하게 되고, 그것이 「설계와\n * 다르다」는 거짓 진단이 된다.\n */\nexport function itemRefOf(epc: unknown): string | null {\n if (typeof epc !== 'string') return null\n const s = epc.trim()\n if (!s) return null\n\n /* GS1 SGTIN — 회사 접두사와 일련번호 사이의 품목 참조. `idpat` 의 `*` 도 같은 자리에서 걸린다. */\n const sgtin = /sgtin:[^.]+\\.([^.]+)\\./.exec(s)\n if (sgtin) return sgtin[1]\n\n /* 그 밖의 정체성(웹 URI 등)은 그대로 낸다 — 자르는 판단은 선언이 한다. */\n return s\n}\n\n/**\n * 이 식별자가 가리키는 **선언 항목**을 찾는다 — 원본의 정체성 관행을 알지 않고, 구분자도 모른다.\n *\n * 세 가지를 차례로 본다. 어느 단계든 **후보가 둘 이상이면 답하지 않는다**(모르는 것이 틀린 답보다 낫다).\n *\n * ① 그대로 같다\n * ② 관측된 식별자가 선언을 **확장**한다 — 개체는 자기 클래스로 시작한다(`…/product-M1` + 개체 표시)\n * ③ 화면이 **짧게 줄인** 식별자를 건넸다 — 선언이 그것으로 끝난다(`net/class/product-M1`)\n *\n * ②③은 부호를 모른다. 그래서 원본이 `@`·`#`·`/` 무엇을 쓰든, 또는 아무것도 쓰지 않든 같은 규칙이 선다.\n */\nexport function resolveByDeclaration<T>(ref: string | null, declared: Map<string, T>): T | undefined {\n if (!ref) return undefined\n const exact = declared.get(ref)\n if (exact !== undefined) return exact\n let hit: T | undefined\n let found = 0\n for (const [id, value] of declared) {\n if (!(ref.startsWith(id) || id.endsWith(ref))) continue\n found++\n if (found > 1) return undefined\n hit = value\n }\n return hit\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"name-index.js","sourceRoot":"","sources":["../../../server/service/twin-model/name-index.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 항목의 **이름**을 붙이는 자리 — 화면이 id 만 보이지 않게. (2026-08-22 공용으로 옮김)\n *\n * 이 둘은 항목 질의 안에 사적으로 있었다. 그런데 **전개(BOM 나무) 화면도 같은 이름이 필요하다** —\n * 사용자가 「각 노드의 식별자가 반복되어서 이해가 어렵다. 식별자와 이름이 보이면」이라고 했다.\n * 두 벌을 만들면 한쪽만 고쳐지므로 한 곳에 둔다(재발명 금지).\n */\n/* 커널 런타임 로드 — 어느 축이 있는지는 카탈로그가 선언한다(이 파일은 도메인 모양을 알지 않는다). */\nconst { TWIN_AXES, axisSource } = require('@operato/ops-contract')\n\n/** `productionSpec.definition.recipes` 같은 경로를 문서에서 따라간다. */\nexport const at = (o: any, path: string) => path.split('.').reduce((a: any, k: string) => (a == null ? undefined : a[k]), o)\n\n/**\n * 문서 항목의 **식별자** — 축마다 이름이 다르다(자리는 `id`, 공정은 `key`, 레시피도 `key`).\n *\n * 도메인 모양을 코드가 아는 것이 아니라 **식별자 관례**를 아는 것이다. 후보를 순서대로 보고 첫\n * 값을 쓴다. 하나도 없으면 그 항목은 가리킬 수 없으므로 `null` — 목록에서 빼지 않고 **id 없음**으로\n * 낸다(오류 없이 빠지면 건수가 안 맞는다).\n */\nexport const idOf = (o: any, info?: { idField?: string | string[] }): string | null => {\n if (typeof o === 'string') return o\n /*\n * **무엇이 이 항목을 가리키는지는 선언이 말한다**(§`TwinAxisInfo.idField`).\n *\n * 예전에는 `id`·`key`·`gtin` 을 짐작했다. 그래서 `id` 가 없는 축(수요 구간 — 정체성이 구간 시작\n * 시각이다)의 항목이 전부 「식별자 없음 — 참조할 수 없는 항목」으로 보였다. 값은 실재하는데\n * 가리킬 수 없다고 말한 것이다.\n */\n /*\n * 선언이 **차례**를 말할 수 있다(2026-08-22) — 정체성이 한 칸으로 정해지지 않는 축이 있다. 물품이\n * 그렇다: 직렬 물품은 `epc` 가 유일하지만 비직렬 로트가 자리마다 나뉘면 `subLotId` 가 개체를\n * 구별한다(같은 로트의 두 부분은 같은 `epc` 를 갖는다). 앞에서 값이 있는 첫 칸이 이긴다.\n */\n for (const f of info?.idField ? (Array.isArray(info.idField) ? info.idField : [info.idField]) : []) {\n const v = o?.[f]\n if (v !== undefined && v !== null) return String(v)\n }\n return o?.id ?? o?.key ?? o?.gtin ?? null\n}\n\n\n/**\n * **id → 이름**, 모든 축을 한 표로.\n *\n * ── 왜 이 표가 필요한가 ──────────────────────────────────────────────────────\n * 행은 다른 것을 **id 로** 가리킨다(사람의 `personnelClassIds`, 설비의 `homeLocationId`, 공정의\n * `locationType`). 그 id 의 이름은 **가리켜진 축의 행**에 있으므로, 화면이 그것을 알려면 축을\n * 하나씩 더 읽어야 한다 — 그래서 지금까지 표면마다 그냥 id 를 찍었다(`cutter-op`, `cut-station`).\n * 표면을 하나씩 고치는 것은 끝이 없다: 새 화면은 처음부터 다시 id 를 찍는다.\n *\n * 그래서 **서버가 붙인다.** 서버는 이미 모델을 손에 들고 있고, 어느 축이 있는지는 커널 카탈로그가\n * 선언한다(`TWIN_AXES`) — 도메인이 늘어도 이 함수는 그대로다.\n *\n * 값은 그 항목이 **자기 이름이라고 말한 것**이다(`name`·`label`·`description`). 그중 어떤 것은\n * i18n 키이므로(등급의 `description`) 번역은 화면의 일로 남긴다. 이름이 없으면 표에 넣지 않는다 —\n * 없는 것을 지어내지 않고, 그 경우 화면은 id 를 그대로 보인다.\n */\nexport function nameIndex(model: any): Record<string, string> {\n const out: Record<string, string> = {}\n for (const info of TWIN_AXES as any[]) {\n /* 관측 축(상태·저널)은 문서에 없다 — 경로가 없는 축을 읽으면 모델 루트를 훑는다. */\n if (axisSource(info.axis) !== 'document') continue\n const raw = at(model ?? {}, info.path)\n const list = Array.isArray(raw) ? raw : raw ? [raw] : []\n for (const o of list) {\n const id = idOf(o)\n if (!id || typeof o !== 'object') continue\n const label = o.name ?? o.label ?? o.description\n /* 이름이 id 와 같으면 표에 넣지 않는다 — 같은 값을 두 번 보이는 셈이다. */\n if (typeof label === 'string' && label && label !== id && !out[id]) out[id] = label\n }\n }\n return out\n}\n\n\n/**\n * 이 응답이 **실제로 가리킨 것만** 골라 낸다.\n *\n * 모델 전체 표를 그대로 실으면 자리가 10만인 현장에서 목록 한 페이지에 10만 줄짜리 이름표가\n * 따라온다. 나온 값에 걸리는 것만 싣는다 — 규모에서 성립하는 유일한 방식이다.\n */\nexport function namesFor(rows: any[], index: Record<string, string>): Record<string, string> {\n const out: Record<string, string> = {}\n const take = (v: any, depth: number): void => {\n if (v == null || depth > 4) return\n if (typeof v === 'string') { if (index[v]) out[v] = index[v]; return }\n if (Array.isArray(v)) { for (const x of v) take(x, depth + 1); return }\n if (typeof v === 'object') for (const x of Object.values(v)) take(x, depth + 1)\n }\n for (const r of rows) { take(r?.fields ?? r, 0); if (typeof r?.id === 'string' && index[r.id]) out[r.id] = index[r.id] }\n return out\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"project-structure.js","sourceRoot":"","sources":["../../../server/service/twin-model/project-structure.ts"],"names":[],"mappings":"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","sourcesContent":["import { getRepository } from '@things-factory/shell'\n\nimport { TwinInstance } from '../twin-instance/twin-instance.js'\nimport { TwinArea } from '../twin-space/twin-area.js'\nimport { TwinLocation } from './twin-location.js'\nimport { TwinEquipment } from './twin-equipment.js'\nimport { TwinOperation } from './twin-operation.js'\nimport { readBoardEquipment } from '@operato/ops-contract'\n\n/*\n * 구조 투영 — `model`(커널 입력 문서) → **표준 엔티티 행**(ADR-0032).\n *\n * ── 왜 전량 교체인가 ────────────────────────────────────────────────────────\n * 이 행들은 **원본에서 온 캐시**다(ADR-0032-D). 원본이 진실이므로 diff 로 아끼는 것보다\n * **매번 그대로 다시 그리는 것**이 맞다 — diff 는 \"우리 쪽에 남겨야 할 것이 있다\" 는 전제를\n * 깔고, 그 전제가 소유 침범의 시작이다.\n *\n * 규모가 커지면 서명(`TwinStructure.signature`)이 같을 때 건너뛰는 장치가 이미 있으므로\n * 그것을 앞에 둔다. 여기서 diff 를 발명하지 않는다.\n *\n * ── 순서가 중요하다 ────────────────────────────────────────────────────────\n * 설비가 자리를 FK 로 가리키므로 **자리를 먼저** 넣는다. 지우는 것은 반대 순서다.\n * (자리 삭제 시 설비의 `homeLocation` 은 `SET NULL` 이지만, 전량 교체에서는 둘 다 지운다.)\n */\nexport interface ProjectionReport {\n locations: number\n equipment: number\n operations: number\n /** 자리를 못 찾아 기준 자리를 비운 설비 — **알리지 않고 넘기지 않는다.** */\n unresolvedHome: string[]\n /** 구역을 못 찾아 비운 자리. 구역은 model 밖(호스트 개념)이라 어긋날 수 있다. */\n unresolvedArea: string[]\n}\n\n/**\n * 이 트윈의 구조 행을 **원본 그대로 다시 그린다.**\n *\n * `sourceRef` 는 어느 원본에서 왔는지다(`ReferenceMaster.source` = instanceId). 없는 행은\n * 만들 수 없다 — 캐시라는 사실을 구조로 지킨다.\n */\nexport async function projectStructure(domainId: string, instanceId: string, model: any, sourceRef: string): Promise<ProjectionReport> {\n const instance = await getRepository(TwinInstance).findOne({ where: { domain: { id: domainId }, instanceId } })\n if (!instance) throw new Error(`cannot project structure: twin instance \"${instanceId}\" is not registered`)\n\n /*\n * **같은 id 가 둘이면 그 사실을 말한다** — 제약 위반 문구를 사용자에게 보이지 않는다.\n *\n * 실측(2026-08-18): 현장 값을 그 현장의 트윈 전부에 얹다가 한 트윈에서 투영이 터졌다. 올라온 것은\n * `SQLITE_CONSTRAINT: UNIQUE constraint failed: twin_equipment...` 뿐이라 **무엇이 잘못됐는지 아무도\n * 알 수 없었다.** 원인은 그 모델에 설비 `load-1` 이 두 번 들어 있던 것이다(id 는 트윈 안에서 유일해야\n * 한다). 알리지 않고 하나만 남기지 않는다 — 그것은 모델링 오류를 감추는 일이고, 어느 쪽이 맞는지 우리는\n * 모른다.\n */\n const dupOf = (list: any[] | undefined, what: string): string | undefined => {\n const seen = new Set<string>()\n for (const e of list ?? []) {\n const id = String(e?.id ?? '')\n if (!id) continue\n if (seen.has(id)) return `twin \"${instanceId}\" has two ${what} with id \"${id}\" — ids must be unique within a twin, so the model cannot be projected`\n seen.add(id)\n }\n return undefined\n }\n const dupMsg = dupOf(model?.locations, 'locations') ?? dupOf(readBoardEquipment(model), 'equipment')\n if (dupMsg) throw new Error(dupMsg)\n\n const locRepo = getRepository(TwinLocation)\n const eqRepo = getRepository(TwinEquipment)\n const opRepo = getRepository(TwinOperation)\n const scope = { domain: { id: domainId } as any, instance: { id: instance.id } as any }\n const ingestedAt = new Date()\n\n /*\n * 전량 교체 — 이 행들은 **원본에서 온 캐시**이므로 매번 그대로 다시 그린다.\n * diff 로 아끼는 것은 \"우리 쪽에 남겨야 할 것이 있다\" 는 전제를 깔고, 그 전제가 소유 침범의 시작이다.\n *\n * **관측으로 자란 자원은 여기 쓰지 않는다.** 커널은 마스터에 없던 설비도 이벤트로 알게 되지만\n * (`observed-reducer` 의 `origin: 'observed'`), 그것을 이 표에 밀어 넣으면 **쓰기 경로가 둘**이 되고\n * 틱 루프에서 DB 로 쓰는 길이 생긴다(ADR-0032-D 위반). 그 차이는 **조회 시점에 비교해 보고**한다 —\n * 숨겨야 할 불일치가 아니라 \"원본이 모르는 설비가 실행 중이다\" 는 사실이다.\n */\n await eqRepo.delete(scope)\n await opRepo.delete(scope)\n await locRepo.delete(scope)\n\n /* 구역은 model 밖에 산다(호스트의 TwinArea). 이름으로 찾아 FK 를 잇는다. */\n const areas = await getRepository(TwinArea).find({ where: { domain: { id: domainId } as any } })\n const areaOf = new Map(areas.map(a => [a.areaId, a]))\n const unresolvedArea: string[] = []\n\n /*\n * ── 전기 상류를 「구역」으로 읽지 않는다 (2026-08-18) ────────────────────────\n *\n * 자리의 부모가 **다른 자리**일 수 있다(분기의 상류는 수전이다). 그 부모를 구역 표에서 찾으면 당연히\n * 없고, 예전에는 그것을 매번 「구역이 이 트윈에 없다」로 경고했다 — 에너지 트윈을 인제스트할 때마다\n * 분기 수만큼 거짓 경보가 났다. 경보가 소음이 되면 **사람이 경보 자체를 읽지 않게 된다.**\n *\n * 자리를 가리키는 부모는 계층(자리 ⊃ 자리)이고, 구역은 model 밖의 다른 표다. 그래서 부모가 이 모델의\n * 자리이면 구역 해석을 **시도하지 않는다**(없는 것을 못 찾았다고 말하지 않는다).\n */\n const locationIds = new Set((model?.locations ?? []).filter((l: any) => l?.id).map((l: any) => String(l.id)))\n\n const locations: TwinLocation[] = (model?.locations ?? [])\n .filter((l: any) => l?.id)\n .map((l: any) => {\n const parentIsLocation = l.parentId ? locationIds.has(String(l.parentId)) : false\n const area = l.parentId && !parentIsLocation ? areaOf.get(l.parentId) : undefined\n if (l.parentId && !parentIsLocation && !area) unresolvedArea.push(`${l.id}→${l.parentId}`)\n return locRepo.create({\n ...scope,\n locationId: l.id,\n /* **이름을 옮긴다.** 버리고 있었다 — 그래서 화면에 자리 id 만 남았다(§name 주석 참조). */\n name: l.name ?? null,\n description: l.description ?? null,\n type: l.type,\n capacity: l.capacity ?? null,\n parallelism: l.parallelism ?? null,\n area: area ? ({ id: area.id } as any) : null,\n gs1Id: l.gs1Id ?? null,\n /* 자리의 속성도 옮긴다 — 옮기지 않으면 계약전력·요금이 항목 패널에서 사라진다(들어온 것이 없는 누락). */\n properties: l.properties ?? null,\n sourceRef,\n ingestedAt\n } as any)\n })\n const savedLocations = locations.length ? await locRepo.save(locations, { chunk: 500 }) : []\n const locOf = new Map(savedLocations.map(l => [l.locationId, l]))\n\n const unresolvedHome: string[] = []\n const equipment: TwinEquipment[] = (model?.equipment ?? [])\n .filter((m: any) => m?.id)\n .map((m: any) => {\n /*\n * ── 설비는 자리에도 구역에도 설 수 있다 (2026-09-06) ─────────────────────\n * 자리 목록에서 먼저 찾고, 없으면 구역에서 찾는다. `locations.parentId` 가 이미 같은 규율을\n * 쓴다(§`parentIsLocation`).\n *\n * 실물이 있다 — `EQ-CAM-SHOWER-01`(에어샤워)이 `LINE-CAM`(구역)에 딸려 있고, 라인 전체에\n * 붙는 설비는 자리 하나를 고를 수 없다. 그동안 자리에서만 찾아서 **끊어진 참조로 보고**했다.\n *\n * 마스터가 `homeLocationKind: 'area'` 로 말해 주지만 여기서는 조회로 판정한다 — 옛 트윈의\n * model 에는 그 칸이 없고, 있는 것을 못 찾았다고 말하는 것이 더 나쁘다.\n */\n const home = m.homeLocation ? locOf.get(m.homeLocation) : undefined\n const homeArea = m.homeLocation && !home ? areaOf.get(m.homeLocation) : undefined\n /* 끊어진 기준 자리는 **이름을 대고** 보고한다 — 자리에도 구역에도 없을 때만이다. */\n if (m.homeLocation && !home && !homeArea) unresolvedHome.push(`${m.id}→${m.homeLocation}`)\n return eqRepo.create({\n ...scope,\n equipmentId: m.id,\n name: m.name ?? null,\n description: m.description ?? null,\n kind: m.kind,\n /* 속성도 옮긴다 — 행에 자리가 없어 흘리고 있었다(소비처가 실제로 읽는 사실이다: 주행 속도). */\n properties: m.properties ?? null,\n /* 시험 참조도 함께 — 흘리면 명세 쪽에서 \"누가 나를 가리키나\" 에 이 설비가 빠진다. */\n testSpecificationIds: m.testSpecificationIds ?? null,\n /* 결과까지 — 참조만 있으면 \"무엇으로 검증한다\" 까지고, 자격 성립은 결과가 말한다. */\n testResults: m.testResults ?? null,\n homeLocation: home ? ({ id: home.id } as any) : null,\n /* 둘 다 차는 일은 없다 — 자리에서 찾았으면 구역은 안 본다. */\n homeArea: homeArea ? ({ id: homeArea.id } as any) : null,\n mtbfMs: m.mtbfMs ?? null,\n mttrMs: m.mttrMs ?? null,\n /* 선언된 정체성 — 예전에는 선언에 없는 `gs1Id` 를 읽어 이 칸이 늘 비어 있었다. */\n identity: (m as any).identity ?? null,\n sourceRef,\n ingestedAt\n } as any)\n })\n if (equipment.length) await eqRepo.save(equipment, { chunk: 500 })\n\n const operations: TwinOperation[] = (model?.operations ?? [])\n .filter((o: any) => o?.key)\n .map((o: any, i: number) =>\n opRepo.create({\n ...scope,\n operationKey: o.key,\n /*\n * 공정은 마스터가 `label` 로 말한다 — 행에서는 **규약의 이름 하나만** 둔다.\n *\n * 한동안 `label` 컬럼도 함께 남겨 뒀더니 조회 화면에 \"이름\" 열이 **두 개** 떴다(같은 값이\n * 두 번). 어휘를 하나로 한다고 하면서 둘을 남기면 반드시 이렇게 새어 나온다.\n */\n name: o.name ?? o.label ?? null,\n description: o.description ?? null,\n intent: o.intent ?? null,\n locationType: o.locationType ?? null,\n resourceType: o.resourceType ?? null,\n /* 소요시간은 ISO 8601 문자열이 정본 — 분으로 줄이면 초가 오류 없이 사라진다. */\n duration: o.duration ?? null,\n variability: o.variability ?? null,\n parameters: o.parameters ?? null,\n /* **네 자원 명세를 함께 옮긴다.** 셋이 빠져 있어서 조회 화면이 \"원본이 말한 값\" 이라 적고도\n 넷 중 하나만 보였다 — 사람이 필요한 공정을 보고 사람이 필요 없다고 결론 내게 된다. */\n personnelSpecification: o.personnelSpecification ?? null,\n physicalAssetSpecification: o.physicalAssetSpecification ?? null,\n materialSpecification: o.materialSpecification ?? null,\n seq: i,\n sourceRef,\n ingestedAt\n } as any)\n )\n if (operations.length) await opRepo.save(operations, { chunk: 500 })\n\n return {\n locations: savedLocations.length,\n equipment: equipment.length,\n operations: operations.length,\n unresolvedHome,\n unresolvedArea\n }\n}\n"]}
|