@things-factory/headless-twin 10.1.278 → 10.1.280
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":"live-feed-registry.js","sourceRoot":"","sources":["../../server/engine/live-feed-registry.ts"],"names":[],"mappings":";;AAuBA,sDAEC;AAQD,4CASC;AAYD,0CAIC;AA1DD,qCAAmC;AAoBnC,MAAM,MAAM,GAAoB,EAAE,CAAA;AAElC,gCAAgC;AAChC,SAAgB,qBAAqB,CAAC,KAAoB;IACxD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AACpB,CAAC;AAED;;;;;GAKG;AACH,SAAgB,gBAAgB,CAAC,UAAkB;IACjD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,IAAI,KAAK,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC;gBAAE,OAAO,IAAI,CAAA;QAC/C,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,IAAA,iBAAQ,EAAC,gDAAgD,EAAE,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC,CAAA;QAC7E,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAYD,SAAgB,eAAe,CAAC,IAAuE;IACrG,IAAI,IAAI,CAAC,aAAa,KAAK,QAAQ;QAAE,OAAO,gBAAgB,CAAA;IAC5D,IAAI,CAAC,IAAI,CAAC,OAAO;QAAE,OAAO,gBAAgB,CAAA;IAC1C,OAAO,gBAAgB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAA;AACpE,CAAC","sourcesContent":["import { twinWarn } from './log.js'\n/*\n * 「이 트윈에 계측이 들어오고 있나」 — **그 사실을 한 자리에서 묻는다.**\n *\n * ── 무엇이 보이지 않았나 (2026-08-14) ───────────────────────────────────────\n * 미러 트윈은 커널이 돌기만 하면 「도는 중」이라 보고했다. 그런데 계측을 나르는 피드가 떨어져 있으면\n * 그 트윈은 **아무것도 받지 않으면서 도는 중**이다 — 화면에서 두 상태가 똑같이 보였고, 사람은\n * 「값이 안 변하네」로 뒤늦게 알게 됐다(재기동 뒤 실제로 그랬다).\n *\n * ── 왜 장부가 하나여야 하나 ────────────────────────────────────────────────\n * 피드를 여는 곳이 둘이다: 커넥터 기반(`reference-live`)과 프리셋 기반(앱의 `provision-from-preset`).\n * 한쪽만 보고 「끊김」이라 말하면 **멀쩡히 흐르는 트윈을 끊겼다고 부르게 된다** — 거짓 경보는 참 경보를\n * 죽인다. 그래서 각 계층이 자기 장부를 여기 **등록**하고, 묻는 쪽은 이 한 자리에만 묻는다.\n *\n * 호스트는 어떤 어댑터·프리셋이 있는지 몰라도 된다(계층을 거꾸로 잇지 않는다).\n */\n\n/** 지금 피드가 붙어 있는 인스턴스 id 들 — 그 계층의 장부가 답한다. */\nexport type LiveFeedProbe = () => string[]\n\nconst probes: LiveFeedProbe[] = []\n\n/** 피드 장부를 등록한다(모듈 로드 시 한 번). */\nexport function registerLiveFeedProbe(probe: LiveFeedProbe): void {\n probes.push(probe)\n}\n\n/**\n * 피드가 붙어 있나 — 어느 장부든 그렇다고 하면 그렇다.\n *\n * 장부가 터지는 것을 이 판정 전체의 실패로 만들지 않는다: 한 계층이 답하지 못해도 다른 계층의 사실은\n * 그대로 참이다. 다만 **알리지 않고 넘기지 않는다** — 답하지 못한 장부는 로그에 남긴다.\n */\nexport function liveFeedAttached(instanceId: string): boolean {\n for (const probe of probes) {\n try {\n if (probe().includes(instanceId)) return true\n } catch (e: any) {\n twinWarn('[twin-live] a feed registry could not answer —', e?.message ?? e)\n }\n }\n return false\n}\n\n/**\n * 이 트윈의 **피드 사실** — 화면이 그대로 말할 수 있는 세 값.\n *\n * `not-applicable` 을 `detached` 와 섞지 않는다(이 시스템이 내내 지켜 온 「없음 ≠ 해당 없음」):\n * · 시뮬 트윈에는 나를 피드가 **없어야 한다** — 그건 결함이 아니라 그 트윈의 성질이다.\n * · 멈춘 트윈에게 피드를 묻는 것은 뜻이 없다 — 사람이 멈춰 둔 것이다.\n * · 남는 것 하나가 우리가 보이려던 사실이다: **도는 미러인데 아무것도 오지 않는다.**\n */\nexport type LiveFeedState = 'attached' | 'detached' | 'not-applicable'\n\nexport function liveFeedStateOf(args: { restartPolicy?: string; running?: boolean; instanceId: string }): LiveFeedState {\n if (args.restartPolicy !== 'resync') return 'not-applicable'\n if (!args.running) return 'not-applicable'\n return liveFeedAttached(args.instanceId) ? 'attached' : 'detached'\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"load-meter.js","sourceRoot":"","sources":["../../server/engine/load-meter.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 부하 계기판 — **어떤 작업이 이벤트 루프를 얼마나 쓰는지** 작업별로 잰다. 순수(import 없음).\n *\n * ── 왜 필요한가 (2026-08-07) ───────────────────────────────────────────────\n * 시뮬 틱은 트윈마다 `setInterval(1000ms)` 로 **메인 이벤트 루프에서 동기로** 수행된다. 부팅 시 16개가\n * 자동 프로비저닝되므로 초당 16번이 같은 루프에서 차례로 실행된다. 한 주기가 예산을 넘기면 타이머가\n * 밀려 쌓이고, 같은 루프에 있는 HTTP·GraphQL·웹소켓이 **함께 멈춘다.**\n *\n * 실제로 그런 상태를 만났다: 포트는 LISTEN 이고 webpack 은 `compiled successfully` 인데 요청이 하나도\n * 처리되지 않고 CPU 만 99% 였다. **터지면 알지만 느려지면 몰랐다** — `tickGuarded` 는 예외만 잡고\n * 시간은 재지 않았고, `TwinMetrics` 에는 라이브 지표(유입·브로드캐스팅·저널)뿐이라 시뮬 틱은 아무도 안 봤다.\n *\n * ── 총량만으로는 부족하다 ─────────────────────────────────────────────────\n * \"틱이 800ms 걸렸다\" 는 어디를 고칠지 알려 주지 않는다. 한 주기는 여러 작업으로 이뤄진다 —\n * 커널 틱 · 상태 투영(snapshot) · 브로드캐스팅 payload 만들기 · 발행 · 저널 기록 · 유입 흡수. 그래서\n * **작업마다 따로** 잰다. 어느 작업이 총 시간의 몇 %인지 보이면 손댈 곳이 정해진다.\n *\n * 재고 말하는 것이 먼저다. 워커 스레드로 옮기는 것은 그다음이다 — 무엇이 느린지 모른 채 옮기면\n * 느린 채로 옮겨진다.\n */\n\n/** 작업 이름 — 새 작업을 재기 시작하면 여기 늘린다(문자열을 자유롭게 쓰지 않는다: 오타가 새 칸이 된다). */\n/*\n * ── 부팅 구간을 재는 두 이름 (2026-08-22) ───────────────────────────────────\n * 실측으로 부팅이 620~717초였고 트윈 하나를 세우는 데 15~93초였다. 그런데 **어느 작업이 그 시간을\n * 쓰는지 아무 계기도 답하지 못했다** — 재는 이름이 도는 중의 작업(틱·브로드캐스팅·저널)뿐이었다.\n * 짐작으로 원인을 적으면 그것이 사실로 읽히므로, 그 자리에 이름을 둔다.\n *\n * · `warmStart` — 저장된 상태를 커널에 심는다(물품·오더·작업·주목 시각).\n * · `stimulus` — 선언된 자극을 싣고 시작한다(실측: 큰 트윈에서 이 뒤가 29~72초였다).\n */\nexport const LOAD_PHASES = ['tick', 'snapshot', 'deltas', 'publish', 'journal', 'ingest', 'forecast', 'forkTick', 'warmStart', 'stimulus'] as const\nexport type LoadPhase = (typeof LOAD_PHASES)[number]\n\nexport interface PhaseStats {\n /** 잰 횟수. */\n count: number\n /** 마지막 소요(ms). */\n lastMs: number\n /** 가장 오래 걸린 것(ms) — 평균은 순간 정체를 가린다. */\n maxMs: number\n /** 소요 합(ms). 나눗셈은 읽는 쪽에서 한다. */\n totalMs: number\n}\n\n/** 창(window) 하나의 누적 — \"지금 얼마나 쓰고 있나\" 를 답한다. */\ninterface Window {\n /** 창이 열린 시각. **아직 안 열렸으면 `-1`** — 0 으로 두면 시각 0 과 구별이 안 된다(검사가 잡았다). */\n startMs: number\n phases: Partial<Record<LoadPhase, PhaseStats>>\n}\n\nexport interface LoadMeter {\n /** **누적** — 프로세스가 뜬 뒤 전부. 총량과 최악값을 본다. */\n phases: Partial<Record<LoadPhase, PhaseStats>>\n /** 지금 쌓고 있는 창. */\n window: Window\n /** **마지막으로 닫힌 창** — 이것이 \"실시간\" 이다. 없으면 아직 한 창도 안 지났다. */\n recent: (Window & { endMs: number }) | null\n /** 이 트윈에서 만들어진 fork 수(누적) — 예측 한 번이 수십 개를 만든다. */\n forksCreated: number\n /** 한 주기(틱)가 예산을 넘긴 횟수. */\n overBudget: number\n /**\n * 마지막 경고 시각(ms). **한 번도 안 했으면 `-Infinity`** — 0 으로 두면 \"0 시점에 경고했다\" 가 되어\n * 첫 느린 틱이 알리지 않고 지나간다(검사가 그것을 잡았다).\n */\n lastWarnMs: number\n}\n\nexport function newLoadMeter(): LoadMeter {\n return { phases: {}, window: { startMs: -1, phases: {} }, recent: null, forksCreated: 0, overBudget: 0, lastWarnMs: -Infinity }\n}\n\n/**\n * 예산 — 틱 간격의 절반. 넘으면 루프의 절반을 이 트윈 하나가 쓰고 있다는 뜻이다.\n *\n * 절반인 이유: 트윈이 여럿이라 각자 간격을 다 쓰면 이미 포화다. 절반에서 알아야 손 쓸 틈이 있다.\n */\nexport const TICK_BUDGET_RATIO = 0.5\n\n/** 경고를 다시 낼 때까지의 최소 간격(ms) — 느린 트윈은 매 틱 느리므로, 그대로 두면 로그가 원인을 덮는다. */\nexport const WARN_COOLDOWN_MS = 30_000\n\n/**\n * 한 작업의 소요를 기록한다. **누적만 하고 판단하지 않는다.**\n *\n * 음수·NaN 은 기록하지 않는다(시계가 뒤로 간 경우). 없는 값을 0 으로 세면 평균이 거짓이 된다.\n */\nexport function recordPhase(meter: LoadMeter, phase: LoadPhase, tookMs: number, nowMs = Date.now()): void {\n if (!Number.isFinite(tookMs) || tookMs < 0) return\n\n rollWindow(meter, nowMs)\n for (const bucket of [meter.phases, meter.window.phases]) {\n const s = bucket[phase] ?? (bucket[phase] = { count: 0, lastMs: 0, maxMs: 0, totalMs: 0 })\n s.count += 1\n s.lastMs = tookMs\n s.totalMs += tookMs\n if (tookMs > s.maxMs) s.maxMs = tookMs\n }\n}\n\n/** fork 하나가 만들어졌다 — 예측 한 번이 수십 개를 만든다. 수를 알아야 부하의 출처를 짚는다. */\nexport function recordFork(meter: LoadMeter, count = 1): void {\n meter.forksCreated += count\n}\n\n/** 실시간 창의 길이(ms) — 이보다 길게 보려면 누적을 본다. */\nexport const WINDOW_MS = 10_000\n\n/** 창이 다 찼으면 닫고 새로 연다. **닫힌 창만 \"최근\" 으로 읽는다** — 채우는 중인 창은 과소 집계다. */\nexport function rollWindow(meter: LoadMeter, nowMs: number, windowMs = WINDOW_MS): void {\n if (meter.window.startMs < 0) {\n meter.window.startMs = nowMs\n return\n }\n if (nowMs - meter.window.startMs < windowMs) return\n\n meter.recent = { ...meter.window, endMs: nowMs }\n meter.window = { startMs: nowMs, phases: {} }\n}\n\n/**\n * 이 주기가 예산을 넘었나 — 넘었으면 세고, **말할 때인지**까지 답한다.\n */\nexport function judgeCycle(\n meter: LoadMeter,\n tookMs: number,\n tickMs: number,\n nowMs: number,\n budgetRatio = TICK_BUDGET_RATIO,\n cooldownMs = WARN_COOLDOWN_MS\n): { over: boolean; warn: boolean; budgetMs: number } {\n const budgetMs = tickMs * budgetRatio\n const over = Number.isFinite(tookMs) && tookMs > budgetMs\n if (!over) return { over: false, warn: false, budgetMs }\n\n meter.overBudget += 1\n const warn = nowMs - meter.lastWarnMs >= cooldownMs\n if (warn) meter.lastWarnMs = nowMs\n\n return { over: true, warn, budgetMs }\n}\n\nconst round2 = (v: number) => Math.round(v * 100) / 100\n/**\n * 비율은 자릿수를 더 준다 — **작은 값이 0 이 되면 \"안 쓴다\" 로 읽힌다.**\n *\n * 24ms/11초 = 0.0022 인데 소수 둘째 자리로 반올림하면 0 이 되어, 재서 나온 값과 없는 값이 같아진다.\n * 오늘 겪은 착각(0 과 없음을 구별 못 함)과 같은 종류라 여기서 막는다.\n */\nconst round4 = (v: number) => Math.round(v * 10000) / 10000\n\nexport interface PhaseRow {\n phase: LoadPhase\n count: number\n lastMs: number\n maxMs: number\n avgMs: number\n totalMs: number\n /** 잰 작업 전체 시간 중 이 작업의 몫(0~1). 어디를 고칠지 이 값이 정한다. */\n share: number\n}\n\nexport interface LoadView {\n phases: PhaseRow[]\n measuredTotalMs: number\n /** 이 구간의 길이(ms). 누적은 프로세스 기동 이후라 알 수 없으므로 `null`. */\n spanMs: number | null\n /** 구간 중 몇 %를 이 트윈이 썼나(0~1+). 구간을 모르면 `null` — **0 이 아니다.** */\n busyRatio: number | null\n}\n\nfunction toRows(phases: Partial<Record<LoadPhase, PhaseStats>>): { rows: PhaseRow[]; totalMs: number } {\n const entries = (Object.entries(phases) as [LoadPhase, PhaseStats][]).filter(([, s]) => s.count > 0)\n const totalMs = entries.reduce((sum, [, s]) => sum + s.totalMs, 0)\n const rows = entries\n .map(([phase, s]) => ({\n phase,\n count: s.count,\n lastMs: round2(s.lastMs),\n maxMs: round2(s.maxMs),\n avgMs: round2(s.totalMs / s.count),\n totalMs: round2(s.totalMs),\n share: totalMs > 0 ? round4(s.totalMs / totalMs) : 0\n }))\n .sort((a, b) => b.totalMs - a.totalMs)\n\n return { rows, totalMs }\n}\n\nfunction toView(phases: Partial<Record<LoadPhase, PhaseStats>>, spanMs: number | null): LoadView | null {\n const { rows, totalMs } = toRows(phases)\n if (!rows.length) return null\n\n return {\n phases: rows,\n measuredTotalMs: round2(totalMs),\n spanMs,\n busyRatio: spanMs && spanMs > 0 ? round4(totalMs / spanMs) : null\n }\n}\n\n/**\n * 읽기용 요약 — **실시간과 누적을 함께** 낸다.\n *\n * · `recent` — 마지막으로 **닫힌** 창. 지금 이 트윈이 루프를 얼마나 쓰고 있나. 채우는 중인 창은\n * 과소 집계라 쓰지 않는다.\n * · `total` — 기동 이후 전부. 총량과 최악값.\n *\n * 잰 적이 없으면 **`null` 이다.** 0 으로 채우면 \"0ms 다\"(= 아주 빠르다)로 읽혀, 재지 않은 것과 정반대의\n * 뜻이 된다. 몫(`share`)도 잰 작업들 사이의 비율일 뿐 — **재지 않은 작업은 분모에 없다.**\n */\nexport function loadSummary(meter: LoadMeter | undefined, tickMs: number): {\n recent: LoadView | null\n total: LoadView | null\n forksCreated: number\n overBudget: number\n budgetMs: number\n /** 마지막 틱이 간격을 얼마나 썼나(0~1+). 1 이면 간격을 통째로 쓴다. */\n loadRatio: number | null\n} | null {\n if (!meter) return null\n\n const total = toView(meter.phases, null)\n const recent = meter.recent ? toView(meter.recent.phases, meter.recent.endMs - meter.recent.startMs) : null\n if (!total && !recent && !meter.forksCreated) return null\n\n const tick = meter.phases.tick\n return {\n recent,\n total,\n forksCreated: meter.forksCreated,\n overBudget: meter.overBudget,\n budgetMs: tickMs * TICK_BUDGET_RATIO,\n loadRatio: tick && tick.count > 0 ? round4(tick.lastMs / tickMs) : null\n }\n}\n\n/**\n * 경고 한 줄 — **무엇이 느린지까지 말한다.** \"틱이 800ms\" 만으로는 어디를 볼지 알 수 없다.\n *\n * 가장 무거운 작업 셋을 몫과 함께 적는다.\n */\nexport function slowTickMessage(instanceId: string, tookMs: number, budgetMs: number, meter: LoadMeter): string {\n const { rows } = toRows(meter.recent?.phases ?? meter.phases)\n const top = rows\n .slice(0, 3)\n .map(p => `${p.phase} ${p.avgMs}ms(${Math.round(p.share * 100)}%)`)\n .join(' · ')\n\n return (\n `[twin-engine] \"${instanceId}\" 틱 ${round2(tookMs)}ms — 예산 ${round2(budgetMs)}ms 초과. ` +\n (top ? `무거운 작업: ${top}. ` : '') +\n `시뮬 틱이 메인 이벤트 루프에서 수행됩니다 — 이 상태가 이어지면 HTTP·구독이 함께 느려집니다.`\n )\n}\n\n/**\n * 전체 한눈에 — **누가 루프를 점유하고 있나.**\n *\n * 인스턴스 목록을 받아 대수·모드별 수·fork 총수를 세고, **최근 창의 부하가 큰 순서**로 줄을 세운다.\n * 누적이 아니라 최근으로 세우는 이유: 지금 아픈 것을 먼저 봐야 한다(누적 1위는 오래 떠 있던 것일 뿐일\n * 수 있다).\n */\nexport function fleetLoad(\n rows: { instanceId: string; mode: string; domainId?: string; domainLabel?: string; meter?: LoadMeter }[],\n tickMs: number\n): {\n instances: number\n byMode: Record<string, number>\n /**\n * **테넌트별 몫** — 운영자가 먼저 묻는 것은 \"어느 테넌트가 루프를 먹나\" 다.\n *\n * 실측으로 필요해졌다: 한 도메인 트윈 22개가 CPU 를 110% 쓰는 동안 다른 도메인 화면은 0 이었다.\n * 무거운 순으로 낸다. 몫은 **잰 트윈들 사이의 비율**이다(안 잰 트윈은 분모에 없다).\n */\n byDomain: { domainId: string; label: string; instances: number; recentBusyMs: number; share: number }[]\n forksCreated: number\n /** 최근 창에서 트윈들이 쓴 시간의 합(ms) — 창 길이를 넘으면 루프가 이미 밀리고 있다. */\n recentBusyMs: number\n /** 그 창의 길이(ms). 창이 하나도 안 닫혔으면 `null`. */\n recentSpanMs: number | null\n /** 전체가 루프를 얼마나 쓰나(0~1+). 1 을 넘으면 **한 틱 안에 다 못 끝낸다.** */\n fleetBusyRatio: number | null\n slowest: { instanceId: string; mode: string; domainId?: string; domainLabel?: string; recentBusyMs: number; topPhase: LoadPhase | null; overBudget: number }[]\n} {\n const byMode: Record<string, number> = {}\n const domains = new Map<string, { label: string; instances: number; recentBusyMs: number }>()\n let forksCreated = 0\n let recentBusyMs = 0\n let recentSpanMs: number | null = null\n\n const slowest = rows.map(r => {\n byMode[r.mode] = (byMode[r.mode] ?? 0) + 1\n forksCreated += r.meter?.forksCreated ?? 0\n\n const recent = r.meter?.recent\n const { rows: phaseRows, totalMs } = toRows(recent?.phases ?? {})\n /*\n * **잎에서 한 번만 반올림한다.** 도메인 합계와 전체 합계는 이 값을 더해서 만든다.\n *\n * 전에는 트윈·도메인·전체를 각자 원값에서 반올림했다. 그래서 화면의 숫자가 서로 안 맞았다:\n * system 도메인 0.08ms 인데 그 도메인의 트윈 둘은 0.08 + 0.01 = 0.09ms (2026-08-07 지적).\n * 보이는 부분들의 합이 보이는 합계와 달라지면 계기판을 믿을 수 없다.\n */\n const busyMs = round2(totalMs)\n if (recent) {\n recentBusyMs += busyMs\n const span = recent.endMs - recent.startMs\n /* 창 길이는 트윈마다 조금씩 다를 수 있다 — 가장 긴 것을 쓴다(짧은 것을 쓰면 비율이 부풀려진다). */\n if (span > 0 && (recentSpanMs === null || span > recentSpanMs)) recentSpanMs = span\n }\n\n /* 테넌트별 합산 — 라벨을 모르면 id 를 그대로 쓴다(지어내지 않는다). */\n const key = r.domainId ?? '(unknown)'\n const dom = domains.get(key) ?? { label: r.domainLabel || key, instances: 0, recentBusyMs: 0 }\n dom.instances += 1\n dom.recentBusyMs += busyMs\n domains.set(key, dom)\n\n return {\n instanceId: r.instanceId,\n mode: r.mode,\n domainId: r.domainId,\n domainLabel: r.domainLabel,\n recentBusyMs: busyMs,\n topPhase: phaseRows[0]?.phase ?? null,\n overBudget: r.meter?.overBudget ?? 0\n }\n })\n\n slowest.sort((a, b) => b.recentBusyMs - a.recentBusyMs)\n\n const byDomain = [...domains.entries()]\n .map(([domainId, d]) => ({\n domainId,\n label: d.label,\n instances: d.instances,\n recentBusyMs: round2(d.recentBusyMs),\n share: recentBusyMs > 0 ? round4(d.recentBusyMs / recentBusyMs) : 0\n }))\n .sort((a, b) => b.recentBusyMs - a.recentBusyMs)\n\n return {\n instances: rows.length,\n byMode,\n byDomain,\n forksCreated,\n recentBusyMs: round2(recentBusyMs),\n recentSpanMs,\n fleetBusyRatio: recentSpanMs && recentSpanMs > 0 ? round4(recentBusyMs / recentSpanMs) : null,\n slowest\n }\n}\n\n/**\n * 프로세스가 실제로 쓴 CPU — **계기판의 다른 숫자들과 뜻이 다르다.**\n *\n * 이 파일의 다른 값들은 트윈의 **계측된 8개 작업**만 잰 것이다. 그런데 프로세스는 그 밖에도 일한다:\n * GraphQL·TypeORM·GC·webpack·HTTP. 그래서 \"루프 점유 <1%\" 인데 CPU 는 110% 일 수 있고, 실제로 그런\n * 상태를 겪었다. 두 숫자를 나란히 두면 **트윈이 범인인지 아닌지**를 바로 가릴 수 있다.\n *\n * 코어 여러 개를 쓰면 1 을 넘을 수 있다(운영체제의 `%CPU` 와 같은 뜻).\n */\nexport type CpuSampler = { lastMicros: number; lastMs: number }\n\n/** 첫 표본은 비교 대상이 없다 — `-1` 로 두어 \"아직 모른다\" 와 0 을 구별한다. */\nexport function newCpuSampler(): CpuSampler {\n return { lastMicros: -1, lastMs: -1 }\n}\n\n/**\n * 두 표본 사이의 CPU 점유율. 처음 부르면 `null`(0 이 아니다 — 잰 구간이 없다).\n *\n * @param totalMicros `process.cpuUsage()` 의 user + system (마이크로초 누적)\n */\nexport function sampleCpu(s: CpuSampler, totalMicros: number, nowMs: number): { ratio: number; spanMs: number } | null {\n const prevMicros = s.lastMicros\n const prevMs = s.lastMs\n s.lastMicros = totalMicros\n s.lastMs = nowMs\n\n if (prevMicros < 0 || prevMs < 0) return null\n\n const spanMs = nowMs - prevMs\n if (spanMs <= 0) return null\n\n const usedMs = (totalMicros - prevMicros) / 1000\n if (usedMs < 0) return null\n\n return { ratio: round4(usedMs / spanMs), spanMs: Math.round(spanMs) }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"local-declarations.js","sourceRoot":"","sources":["../../server/engine/local-declarations.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 로컬 선언 — **구조는 원천이, 수는 우리가.**\n *\n * ── 낱말부터: `reference` ↔ `local` (2026-08-18 확정) ───────────────────────\n * 이 파일이 다루는 축은 **「이 값을 누가 말했나」** 하나다. 답은 둘뿐이다.\n *\n * · `reference` — 외부 원천 시스템이 준 값(SCADA·WMS·MES 의 마스터). 우리가 고르지 않았다.\n * · `local` — **이 트윈 쪽에서 정한 값.** 원천이 모르는 수를 운영 주체가 넣은 것이다.\n *\n * 「사람이 넣었나」가 아니라 **「권위가 어디 있나」**가 기준이다 — 앞으로 AI 가 사용자 대리로 채워도\n * 여전히 `local` 이다. 표식은 커널 계약의 `PropertySource` 로 선언돼 있고, **표식이 없으면 모델을 그린\n * 쪽**(참조에서 그렸으면 원천, 템플릿으로 만들었으면 그 템플릿)이다.\n *\n * 장소 낱말을 쓰지 않는 이유(옛 이름 `siteDeclarations` 를 버린 이유):\n * · `site` — 이 코드에서는 **외부 시스템이 부르는 사업장**이다(SAP 는 `P3`, Oracle 은 `W01` 로 같은\n * 현장을 부른다). 여기 담기는 것은 그 시스템이 **모르는** 값이므로 이름이 내용과 정반대다.\n * · `facility` — 화면에 나가는 영어(현장)이고, 코드에 남은 것은 남의 API 필드뿐이다(Oracle lgfapi).\n * · `space` — 「어디」에 답하는 낱말이다(트윈이 서는 그릇). 이 축은 「누가」에 답한다. 게다가 공간\n * 수준 선언(한 현장의 여러 트윈이 함께 읽는 요금)은 앞으로 정말 필요할 수 있어 비워 둔다.\n *\n * ── 왜 필요한가 ─────────────────────────────────────────────────────────────\n * 커넥터로 만든 트윈은 구조를 원 시스템에서 읽어 온다(계통도·계량 지점·설비). 그런데 요금 단가·계약\n * 정보·축전지 충전율은 원 시스템이 모른다 — SCADA 게이트웨이는 우리 전기 계약을 알지 못한다. 그래서 그\n * 값들을 넣을 문이 없었고, 계량이 흐르는 트윈에서도 금액이 영원히 나오지 않았다(실제로 그 벽에 부딪혔다).\n *\n * ── 왜 모델에 바로 쓰지 않고 겹으로 두나 ────────────────────────────────────\n * 모델은 **원천의 사본**이다. 다시 읽기(resync)는 그 사본을 새로 그리므로, 모델에 직접 쓴 값은 다음\n * 다시 읽기에서 **오류 없이 사라진다** — 요금이 사라지면 금액도 사라지고, 사용자는 이유를 알 수 없다.\n * 그래서 로컬 선언은 따로 보관하고, 모델을 그릴 때마다 그 위에 얹는다. 덤으로 「누가 말한 값인가」에\n * 답할 수 있다 — 이 시스템이 근거를 값과 함께 다니게 하는 규율과 같다(`basis`·`derived` 와 한 결).\n *\n * ── 지어낸 속성을 받지 않는다 ───────────────────────────────────────────────\n * 커널이 읽는 속성만 받는다. 모르는 id 를 받아 두면 사용자는 값을 넣었다고 믿고 화면은 아무것도 바뀌지\n * 않는다 — 그 침묵이 가장 비싼 오류다. 단위·범위도 커널의 명세(`EMS_PROPERTY_SPEC`)로 검사한다:\n * 「SOC 80%」를 `0.8` 로 넣으면 배터리가 0.8% 로 서고, 그 사실을 아무도 모른 채 피크가 깎이지 않는다.\n *\n * **속성 어휘는 사용자가 정의하지 않는다** — 커널이 정한 속성에 값을 넣는 문이다(그래서 이름도\n * 「사용자 정의 속성」이 아니다).\n */\nimport { ATTENTION_PROPERTY, OPERATION_PROPERTY } from '@operato/twin-kernel'\nimport { EMS_PROPERTY_SPEC, OP_PARAM } from '@operato/ops-contract'\n\nimport { EQUIPMENT_PROPERTY, SPEED_TO_MPS } from './travel-estimator.ts'\nimport { declarationTargetsOf } from './property-effects.ts'\n\n/** 로컬 선언이 겨눌 수 있는 대상 — 자원 둘과 **공정 종류**(자원이 아니라 명세다). */\nexport type DeclarationTarget = 'location' | 'equipment' | 'operation'\n\n/** 한 대상(자리·설비·공정)에 얹을 로컬 선언. */\nexport interface LocalDeclaration {\n target: DeclarationTarget\n /** 대상 id — 모델에 있는 그 이름. */\n id: string\n properties: {\n id: string\n value: string | number\n /**\n * 단위(UN/CEFACT 코드) — **어떤 값은 단위 없이는 뜻이 없다.**\n *\n * 속도가 그렇다: 단위가 없으면 이동 시간 추정기가 그 설비를 **거절한다**(그러면 공정 시간이 알리지 않고\n * 사라진다). 명세가 단위를 요구하면(`uomChoices`) 받고, 그 목록에 없는 코드는 거절한다 — 읽는 쪽이\n * 모르는 단위를 받아 두면 화면은 넣었다고 말하고 계산은 그 값을 버린다.\n */\n uom?: string\n /**\n * 이 선언이 **덮은 원천 값** — 철회하면 이것으로 되돌린다.\n *\n * ── 왜 기억해야 하나 (2026-08-18) ────────────────────────────────────────\n * 선언은 모델에 얹혀 **저장된 모델에 구워진다.** 그래서 겹에서 줄만 지워도 원래 값이 돌아오지\n * 않는다 — 실제로 되돌릴 방법이 없어서, 잘못 넣은 값을 「원래 값을 다시 선언하는」 우회로만\n * 고칠 수 있었다(사용자가 원래 값을 알고 있어야 한다는 뜻이다).\n *\n * · 문자열 — 원천이 그 자리에 갖고 있던 값이다.\n * · `null` — 원천에는 그 속성이 **없었다**(철회하면 속성을 지운다).\n * · 없음 — **우리가 모른다**(이 필드가 생기기 전에 선언된 것). 지어내지 않고 그렇게 말한다.\n *\n * 사용자가 넣는 값이 아니라 얹을 때 우리가 채우는 값이다(매 원천 재읽기마다 새로 채워져 낡지 않는다).\n */\n replaced?: string | null\n }[]\n}\n\nexport interface RejectedDeclaration {\n target: string\n id: string\n propertyId: string\n /**\n * 왜 받지 않았나 — 언어중립 코드(화면이 옮긴다).\n * · `unknown-property` — 커널이 읽지 않는 속성 id 다\n * · `out-of-range` — 명세가 정한 범위 밖이다(단위를 잘못 읽은 값이 대개 여기 걸린다)\n * · `not-a-number` — 수를 요구하는 속성에 수가 아닌 값이 왔다\n * · `empty-value` — 값이 비어 있다(지우려면 그 뜻을 따로 말해야 한다)\n * · `no-property-vocabulary` — 이 종류의 트윈에는 선언 어휘가 아직 없다\n * · `no-target-id` — 어느 대상에 얹을지 말하지 않았다(선언의 모양이 틀렸다)\n * · `no-property-id` — 어느 속성인지 말하지 않았다(선언의 모양이 틀렸다)\n *\n * 뒤의 둘이 있는 이유: 예전에는 대상 id·속성 id 가 비면 **아무 말 없이 건너뛰었다.** 그래서 필드 이름을\n * 틀린 호출이 「받아준 선언이 없다」는 답만 받고 `rejected` 는 비어 있었다 — 무엇이 틀렸는지 알 길이\n * 없는 침묵이다(실제로 그 침묵에 걸렸다).\n */\n reason:\n | 'unknown-property'\n | 'out-of-range'\n | 'not-a-number'\n | 'empty-value'\n | 'no-property-vocabulary'\n | 'no-target-id'\n | 'no-property-id'\n /** 단위를 요구하는 속성인데 오지 않았다(그 값은 읽는 쪽이 버린다). */\n | 'uom-required'\n /** 읽는 쪽이 모르는 단위 코드다(명세가 받는 목록에 없다). */\n | 'unknown-uom'\n /**\n * 이 속성은 그 종류의 대상에 얹히지 않는다 — 읽는 코드가 다른 축을 본다.\n *\n * 예전에는 지게차에 계약전력을 넣을 수 있었다(문은 받고 화면은 「넣었습니다」라고 말하는데 읽는\n * 쪽은 자리만 본다). 어느 축의 속성인지는 효과 등록부가 이미 알고 있으므로 그것으로 거절한다.\n */\n | 'wrong-target'\n /**\n * 그 이름의 공정을 이 트윈에서 확인할 수 없다 — 커널이 돌린 적도 없고 명세 행에도 없다.\n *\n * 공정 선언은 자리·설비와 달리 **모델에 행이 없어도 성립한다**(창고·야드에는 행이 아예 없다).\n * 그래서 오타를 잡아 줄 것이 이름 대조뿐이다 — 그마저 없으면 값은 알리지 않고 아무 일도 하지 않는다.\n */\n | 'unknown-operation'\n /** 무엇을 기대했나 — 사람이 고칠 수 있게(명세의 문장). */\n expected?: string\n}\n\n/** 이 종류의 트윈에서 커널이 읽는 선언 명세. 없으면 그 종류에는 선언 어휘가 없다. */\nfunction specOf(kind: string | undefined, propertyId: string): DeclarablePropertySpec | undefined {\n /* 종류 어휘와 공용 어휘를 함께 본다 — 속도는 어느 트윈에서나 읽히므로 EMS 밖에서도 받아야 한다. */\n return declarablePropertySpecs(kind).find(s => s.id === propertyId)\n}\n\n/** 이 종류의 트윈에 선언 어휘가 있나 — 없으면 검사할 근거가 없으므로 받지 않는다. */\nexport function hasPropertyVocabulary(kind: string | undefined): boolean {\n return kind === 'ems'\n}\n\n/** 이 종류에 선언할 수 있는 것이 하나라도 있나 — 공용 어휘가 있으므로 대개 참이다. */\nexport function hasAnyDeclarable(kind: string | undefined): boolean {\n return declarablePropertySpecs(kind).length > 0\n}\n\n/**\n * 이 종류의 트윈에 **선언할 수 있는 속성 목록** — 화면이 도메인의 모양을 알지 않게.\n *\n * 화면이 「에너지 트윈이면 계약전력·요금 단가…」를 알고 있으면, 도메인이 늘 때마다 화면을 고쳐야 하고\n * 단위·범위가 두 곳에 적힌다(ADR-0031: 카탈로그가 선언하고 화면은 그린다). 그래서 커널의 명세를 그대로\n * 낸다 — 화면은 받은 것을 렌더할 뿐이다.\n *\n * 어휘가 없는 종류는 **빈 목록**이다(「아직 없다」와 「아무 값이나 받는다」는 다르다).\n */\nexport interface DeclarablePropertySpec {\n id: string\n /** 사람이 읽는 단위(명세가 정한 것) — 값에 붙여 보인다. */\n uom?: string\n dataType: string\n range?: [number, number]\n note: string\n /**\n * 값과 **함께 단위를 골라야 하는** 속성 — 그 목록(UN/CEFACT 코드).\n *\n * 어떤 값은 단위 없이는 뜻이 없다: 속도는 단위가 없으면 추정기가 **거절한다**(「speed has no unit of\n * measure」). 그래서 명세가 「단위를 받아야 한다」와 「무엇을 받나」를 함께 말한다 — 화면이 목록을\n * 지어내면 화면이 받아 준 단위를 읽는 쪽이 모르는 일이 생긴다.\n */\n uomChoices?: string[]\n /**\n * 어느 종류의 대상에 얹히나(효과 등록부의 축) — 없으면 **우리가 제한을 모른다**(받아 준다).\n *\n * 화면은 이것으로 속성 목록을 대상에 맞게 좁힌다: 공정 시간을 설비에 넣으려는 선택지를 아예 보이지\n * 않게 하는 것이, 넣게 하고 거절하는 것보다 낫다.\n */\n targets?: string[]\n}\n\n/**\n * **어느 종류에나 있는 선언** — 「읽는 코드가 있는 것」만.\n *\n * 효과 등록부(`PROPERTY_EFFECTS`)가 그 목록의 근거다: 값이 계산을 바꾸는 속성만 여기 온다. 지금은\n * 설비의 `speed` 하나다(이동 시간 추정이 읽는다) — 자재의 `perBaseUnit` 은 자원 축이 아니라 자재\n * 정의에 얹히므로 이 화면의 대상이 아니다.\n *\n * 지어내지 않는다: 새 효과를 코드로 붙이는 사람이 등록부에 한 줄을 더하면 여기에도 따라 나온다.\n */\n/**\n * 시간 단위(UN/CEFACT) → ms. **목록은 여기 하나** — 화면·AI 는 명세의 `uomChoices` 로 받아 간다.\n *\n * 셋만 두는 이유: 현장이 공정 시간을 말할 때 쓰는 단위가 초·분·시간이다. 일 단위는 공정이 아니라 체류에\n * 가깝고, 그때는 그 값을 넣는 사람이 시간으로 환산해 말하는 편이 오해가 없다.\n */\nconst DURATION_UOM_MS: Record<string, number> = { SEC: 1_000, MIN: 60_000, HUR: 3_600_000 }\n\nconst COMMON_SPECS: DeclarablePropertySpec[] = [\n /*\n * 주의 판정 기준 — **경보가 소음이 되지 않게 현장이 정한다.**\n *\n * 혼잡 90%·불량 15%는 우리가 정한 수였다. 통로를 95%로 채워 쓰는 창고는 그 기준에서 영구히 「병목」\n * 이고, 초기 라인은 계속 「불량 과다」로 운다 — 그때 진짜 경보도 함께 묻힌다. 선언이 없으면 기본값을\n * 쓰되 주의가 `basis: 'default'` 로 그 사실을 밝힌다(알리지 않고 기본값을 진실로 두지 않는다).\n */\n {\n id: ATTENTION_PROPERTY.congestionRatio,\n uom: '%',\n dataType: 'xs:double',\n range: [1, 100],\n note: 'congestion threshold as a percentage 1-100 (95 means 95% of capacity) — above it this location is called a bottleneck. default 90.'\n },\n {\n id: ATTENTION_PROPERTY.scrapRate,\n uom: '%',\n dataType: 'xs:double',\n range: [1, 100],\n note: 'scrap threshold as a percentage 1-100 (25 means 25%) — above it this equipment is called high-scrap, and twice it is severe. default 15.'\n },\n {\n id: ATTENTION_PROPERTY.scrapMinSamples,\n dataType: 'xs:double',\n range: [1, 100000],\n note: 'how many pieces must be counted before the scrap rate is judged at all — a ratio over few pieces is not evidence. default 10.'\n },\n {\n id: EQUIPMENT_PROPERTY.speed,\n dataType: 'xs:double',\n range: [0.01, 50],\n note: 'travel speed of this equipment — the estimator turns distance into duration with it. a unit is required.',\n uomChoices: Object.keys(SPEED_TO_MPS)\n },\n /*\n * 공정 소요시간 — **측정이 없을 때 상수를 대신한다.**\n *\n * 대상은 자리·설비가 아니라 **공정 종류**(`putaway`·`pick`·`spot`…)이고, 그 종류에 명세 행이 없어도\n * 성립한다(실측: 창고·야드 트윈에는 행이 0개다). 단위를 반드시 받는다 — 「4」만으로는 4초인지 4분인지\n * 알 수 없고, 짐작하면 예측이 60배 틀린다.\n */\n {\n id: OPERATION_PROPERTY.duration,\n dataType: 'xs:double',\n range: [0.01, 100000],\n note: 'how long this operation takes at this site — the simulation and the forecast use it instead of the built-in constant. a unit is required. history still wins where it exists.',\n uomChoices: Object.keys(DURATION_UOM_MS)\n },\n /*\n * 양품률 — **퍼센트로 받는다**(80 이 80%다).\n *\n * 커널이 읽는 값은 0..1 이지만(ISA-95 `Parameter` 값 그대로) 사람에게 0.8 을 넣으라고 하면 SOC 에서\n * 겪은 함정을 되풀게 된다(0.8 을 0.8% 로 쓴 일이 있다). 그래서 화면·AI 는 퍼센트로 말하고, 비율로\n * 바꾸는 일은 얹는 자리 한 곳에서 한다.\n */\n {\n id: OPERATION_PROPERTY.yield,\n uom: '%',\n dataType: 'xs:double',\n range: [1, 100],\n note: 'good-output rate of this operation as a percentage 1-100 (95 means 95%) — the kernel reads it as a ratio, and this value replaces the built-in default that also drives the high-scrap attention.'\n },\n /* 셋업·체인지오버 — 가동 앞에 붙는 시간. 소요시간과 같은 단위 목록을 쓴다(같은 종류의 값이다). */\n {\n id: OPERATION_PROPERTY.setupDuration,\n dataType: 'xs:double',\n range: [0.01, 100000],\n note: 'changeover time before this operation runs — added in front of the cycle. a unit is required.',\n uomChoices: Object.keys(DURATION_UOM_MS)\n }\n]\n\n/**\n * 값 + 단위 → **ISO 8601 기간** — 커널이 읽는 표기로 바꾼다(커널은 초·분을 모른다, 표준 표기만 안다).\n *\n * 읽을 수 없으면 `undefined` — 부르는 쪽이 그 사실을 거절 사유로 올린다(0 을 만들지 않는다: 소요 0 인\n * 작업은 영원히 끝나지 않는다).\n */\n/**\n * 공정 모수 표에 한 칸 담는다 — **커널이 읽는 표기 그대로**(수율 0..1, 셋업 ISO 8601).\n *\n * 표를 통째로 새로 만들어 얹는다: 같은 모델 객체를 여러 겹이 지나므로(현장 → 트윈) 그때마다 새 객체여야\n * 앞 겹의 것을 알리지 않고 덮어쓰지 않는다.\n */\nfunction putParam(next: any, kind: string, paramId: string, value: string): void {\n const table = { ...(next.localParams ?? {}) }\n table[kind] = { ...(table[kind] ?? {}), [paramId]: value }\n next.localParams = table\n}\n\nexport function isoDurationOf(value: string | number, uom: string): string | undefined {\n const n = Number(value)\n const per = DURATION_UOM_MS[String(uom ?? '').trim()]\n if (!Number.isFinite(n) || n <= 0 || !per) return undefined\n return `PT${(n * per) / 1000}S`\n}\n\n/**\n * 이 종류의 트윈에 **선언할 수 있는 속성 목록** — 화면이 도메인의 모양을 알지 않게.\n *\n * 종류와 무관한 것(효과 등록부에서 온다) + 그 종류의 어휘(지금은 EMS 뿐). 어휘가 없는 종류도 **빈손이\n * 아니다**: 속도는 어느 트윈에서나 읽히므로 창고·야드·생산 트윈에서도 넣을 수 있어야 한다.\n */\nexport function declarablePropertySpecs(kind: string | undefined): DeclarablePropertySpec[] {\n const domain = hasPropertyVocabulary(kind)\n ? Object.entries(EMS_PROPERTY_SPEC as Record<string, any>).map(([id, spec]) => ({\n id,\n ...(spec.uom ? { uom: spec.uom } : {}),\n dataType: spec.dataType,\n ...(spec.range ? { range: spec.range as [number, number] } : {}),\n note: spec.note\n }))\n : []\n /*\n * **어느 대상에 얹히나는 효과 등록부가 답한다** — 명세에 손으로 또 적지 않는다(두 곳에 적으면 한쪽만\n * 바뀐다). 등록부에 없는 속성은 제한을 **모르는** 것이므로 그대로 둔다(속성은 열려 있다).\n */\n const all: DeclarablePropertySpec[] = [...COMMON_SPECS, ...domain]\n return all.map(s => {\n const targets = s.targets ?? declarationTargetsOf(s.id)\n return targets ? { ...s, targets } : s\n })\n}\n\n/**\n * 로컬 선언을 검사한다 — **받은 것과 받지 않은 것을 함께** 돌려준다.\n *\n * 알리지 않고 걸러내지 않는 이유: 사용자는 값을 넣었다고 믿는다. 무엇이 왜 빠졌는지 말해야 고칠 수 있다.\n */\nexport function validateLocalDeclarations(\n kind: string | undefined,\n declarations: readonly LocalDeclaration[] | undefined | null,\n opts?: {\n /**\n * 이 트윈에서 확인된 공정 이름들 — 있으면 그 밖의 이름을 거절한다(오타를 잡는 유일한 수단이다).\n *\n * **모르면 넘기지 않고 모른다고 둔다**: 아직 돌지 않은 트윈은 어떤 공정을 돌릴지 알 수 없으므로\n * 목록을 주지 않고(=undefined) 받아 준다. 부르는 쪽이 「확인하지 못했다」는 사실을 답에 싣는다.\n */\n knownOperations?: readonly string[]\n }\n): { accepted: LocalDeclaration[]; rejected: RejectedDeclaration[] } {\n const accepted: LocalDeclaration[] = []\n const rejected: RejectedDeclaration[] = []\n for (const d of declarations ?? []) {\n const target: DeclarationTarget = d?.target === 'equipment' ? 'equipment' : d?.target === 'operation' ? 'operation' : 'location'\n const id = String(d?.id ?? '').trim()\n if (!id) {\n /* 어느 대상인지 모르면 얹을 자리가 없다 — 그 사실을 말한다(알리지 않고 버리지 않는다). */\n rejected.push({ target, id: '', propertyId: '', reason: 'no-target-id' })\n continue\n }\n const props: { id: string; value: string }[] = []\n for (const p of d?.properties ?? []) {\n const propertyId = String(p?.id ?? '').trim()\n if (!propertyId) {\n rejected.push({ target, id, propertyId: '', reason: 'no-property-id' })\n continue\n }\n const raw = p?.value\n if (raw === undefined || raw === null || String(raw).trim() === '') {\n rejected.push({ target, id, propertyId, reason: 'empty-value' })\n continue\n }\n if (!hasAnyDeclarable(kind)) {\n rejected.push({ target, id, propertyId, reason: 'no-property-vocabulary' })\n continue\n }\n const spec = specOf(kind, propertyId)\n if (!spec) {\n rejected.push({ target, id, propertyId, reason: 'unknown-property' })\n continue\n }\n /*\n * **읽는 쪽이 보는 축에만 얹는다** — 지게차에 계약전력을 넣을 수 있던 구멍을 여기서 막는다.\n * 명세가 제한을 말하지 않으면(등록부에 없는 속성) 받아 준다: 속성은 열려 있고, 효과만 닫혀 있다.\n */\n if (spec.targets?.length && !spec.targets.includes(target)) {\n rejected.push({ target, id, propertyId, reason: 'wrong-target', expected: `one of ${spec.targets.join(', ')}` })\n continue\n }\n /* 공정 이름은 대조로만 걸러진다 — 모델에 행이 없어도 성립하는 선언이라 다른 안전망이 없다. */\n if (target === 'operation' && opts?.knownOperations && !opts.knownOperations.includes(id)) {\n rejected.push({ target, id, propertyId, reason: 'unknown-operation', expected: `one of ${[...opts.knownOperations].join(', ')}` })\n continue\n }\n if (spec.dataType === 'xs:double') {\n const n = Number(raw)\n if (!Number.isFinite(n)) {\n rejected.push({ target, id, propertyId, reason: 'not-a-number', expected: spec.note })\n continue\n }\n if (spec.range && (n < spec.range[0] || n > spec.range[1])) {\n rejected.push({ target, id, propertyId, reason: 'out-of-range', expected: spec.note })\n continue\n }\n }\n /*\n * 단위를 요구하는 속성은 **단위까지 검사한다** — 값만 받아 두면 읽는 쪽이 그 설비를 건너뛴다.\n * 목록은 명세가 든다(`uomChoices`): 화면이 목록을 지어내면 화면이 받아 준 단위를 계산이 모른다.\n */\n const uom = String((p as any)?.uom ?? '').trim()\n if (spec.uomChoices?.length) {\n if (!uom) {\n rejected.push({ target, id, propertyId, reason: 'uom-required', expected: `one of ${spec.uomChoices.join(', ')}` })\n continue\n }\n if (!spec.uomChoices.includes(uom)) {\n rejected.push({ target, id, propertyId, reason: 'unknown-uom', expected: `one of ${spec.uomChoices.join(', ')}` })\n continue\n }\n }\n props.push({ id: propertyId, value: String(raw), ...(uom ? { uom } : {}) })\n }\n if (props.length) accepted.push({ target, id, properties: props })\n }\n return { accepted, rejected }\n}\n\n/**\n * 겹을 합친다 — 같은 대상·같은 속성이면 **새 값이 이긴다**(현장이 값을 고치는 일은 흔하다).\n *\n * 대상이나 속성을 지우는 일은 여기서 하지 않는다: 지움은 「값을 모르는 상태로 되돌린다」는 뜻이고,\n * 그것은 값을 넣는 것과 다른 결정이므로 다른 문으로 말해야 한다.\n */\nexport function mergeLocalDeclarations(\n current: readonly LocalDeclaration[] | undefined | null,\n incoming: readonly LocalDeclaration[]\n): LocalDeclaration[] {\n const key = (t: string, id: string) => `${t}:${id}`\n const out = new Map<string, LocalDeclaration>()\n for (const d of current ?? []) out.set(key(d.target, d.id), { ...d, properties: [...d.properties] })\n for (const d of incoming) {\n const k = key(d.target, d.id)\n const cur = out.get(k)\n if (!cur) {\n out.set(k, { ...d, properties: [...d.properties] })\n continue\n }\n for (const p of d.properties) {\n const at = cur.properties.findIndex(x => x.id === p.id)\n if (at >= 0) cur.properties[at] = { ...p }\n else cur.properties.push({ ...p })\n }\n }\n return [...out.values()]\n}\n\n/**\n * 모델 위에 로컬 선언을 얹는다 — **모델을 바꾸지 않고 새 모델을 돌려준다.**\n *\n * 원천이 같은 속성을 이미 말했으면 **로컬 값이 이긴다**: 계약전력을 게이트웨이가 말하더라도, 우리가\n * 고쳐 넣었다는 것은 「원천의 값이 낡았다」는 뜻이다. 다만 그 사실이 보여야 하므로 `source: 'local'` 를\n * 값에 붙인다 — 화면·감사가 「누가 말한 값인가」를 되짚을 수 있다.\n *\n * 모델에 없는 대상에 얹으려 하면 **알리지 않고 버리지 않고** 그 사실을 돌려준다(구조가 바뀌어 그 설비가\n * 사라졌을 수 있고, 그때 사용자의 선언은 갈 곳이 없다).\n */\nexport function applyLocalDeclarations<\n T extends { locations?: any[]; equipment?: any[]; localDurations?: Record<string, string>; localParams?: Record<string, Record<string, string>> }\n>(\n model: T,\n declarations: readonly LocalDeclaration[] | undefined | null\n): {\n model: T\n missingTargets: { target: string; id: string }[]\n /**\n * 읽을 수 있는 값으로 바꿀 수 없었던 선언 — **알리지 않고 버리지 않기 위한 자리.**\n *\n * 지금은 공정 시간에서만 난다(단위가 없거나 수가 아니면 ISO 8601 기간을 만들 수 없다). 문에서 이미\n * 검사하므로 정상 경로에서는 비어 있고, 옛 겹이나 손으로 넣은 데이터에서 드러난다.\n */\n unreadable: { target: string; id: string; propertyId: string }[]\n layer: LocalDeclaration[]\n} {\n const decls = declarations ?? []\n if (!decls.length) return { model, missingTargets: [], unreadable: [], layer: [] }\n\n const missingTargets: { target: string; id: string }[] = []\n const unreadable: { target: string; id: string; propertyId: string }[] = []\n /* 얹으면서 **무엇을 덮었는지** 함께 적어 돌려준다 — 철회는 그 기억이 있어야 성립한다. */\n const layer: LocalDeclaration[] = decls.map(d => ({ ...d, properties: d.properties.map(p => ({ ...p })) }))\n\n const put = (list: any[] | undefined, d: LocalDeclaration, memo: LocalDeclaration): boolean => {\n const item = (list ?? []).find(x => String(x?.id ?? '') === d.id)\n if (!item) return false\n const props: any[] = Array.isArray(item.properties) ? [...item.properties] : []\n for (let i = 0; i < d.properties.length; i++) {\n const p = d.properties[i]\n const at = props.findIndex(x => String(x?.id ?? '') === p.id)\n const noted = memo.properties[i]\n /*\n * 기억은 **표식이 없는 값일 때만** 갱신한다 — ADR-0034 의 규칙 그대로: 표식이 없으면 모델을 그린\n * 쪽(대개 원천)이 말한 값이고, 표식이 있으면 **누군가 찍은 것**이므로 맨 원천 값이 아니다.\n *\n * 이 함수는 원천에서 새로 그린 모델에도, 이미 선언이 구워진 저장 모델에도 얹힌다. 뒤쪽에서\n * 무턱대고 적으면 **우리 값을 원천 값으로 기억**하게 되고(600 을 원천이 말한 것으로 적는다),\n * 철회해도 같은 값으로 되돌아간다.\n *\n * `!== 'local'` 로 보지 않는 이유: 옛 표식(`'site'`)이 구워진 모델이 남아 있고, 그것을 「표식\n * 아님」으로 읽으면 바로 그 오인이 일어난다. 호환 코드를 두는 것이 아니라 **규칙을 그대로 적는\n * 것**이다 — 표식의 종류를 따지지 않는다.\n */\n if (at < 0) noted.replaced = null\n else if (props[at]?.source === undefined) noted.replaced = String(props[at]?.value ?? '')\n /*\n * **단위도 함께 얹는다** (2026-08-18 실측으로 고침).\n *\n * 겹에는 단위를 담고도 모델에 얹을 때 떨어뜨리고 있었다 — 그러면 속도를 넣어도 이동 시간 추정기가\n * 그 설비를 **버린다**(「speed has no unit of measure」). 화면은 「넣었습니다」라고 말하고 계산은\n * 그 값을 못 쓰는, 가장 들어온 것이 없는 종류의 결함이다.\n */\n const next = { id: p.id, value: String(p.value), ...((p as any).uom ? { uom: String((p as any).uom) } : {}), source: 'local' as const }\n if (at >= 0) props[at] = { ...props[at], ...next }\n else props.push(next)\n }\n item.properties = props\n return true\n }\n\n /**\n * 공정 시간은 **자원 속성이 아니다** — 종류별 표(`localDurations`)에 담는다.\n *\n * 왜 자리·설비처럼 속성으로 담지 않나: 커널의 공정 명세 행(`OperationDef`)은 `label`·`intent` 를\n * 요구하는데(ISA-95 `OperationsSegment`) 창고·야드 트윈에는 그 행이 **아예 없다**(실측: 35개 중\n * WMS·YMS 전부 0개). 행을 지어 만들면 지어낸 `intent` 가 능력 계산까지 오염시킨다 — 「몇 분 걸리나」를\n * 말하려고 「무슨 작업인가」를 발명하지 않는다.\n *\n * 이 표는 **전부 우리가 정한 값**이다(원천이 그리는 자리가 아니다). 그래서 기억(`replaced`)은 언제나\n * `null` 이고, 철회는 「표에서 뺀다」로 끝난다 — 그러면 원천 명세나 커널 상수가 다시 답한다.\n */\n const putOperation = (next: any, d: LocalDeclaration, memo: LocalDeclaration): void => {\n for (let i = 0; i < d.properties.length; i++) {\n const p = d.properties[i]\n const uom = String((p as any).uom ?? '')\n /*\n * 값을 **커널이 읽는 표기로** 바꿔 담는다 — 시간은 ISO 8601, 수율은 비율(0..1).\n * 바꿀 수 없으면 담지 않고 그 사실을 낸다(알리지 않고 0 이나 빈 값으로 만들지 않는다).\n */\n if (p.id === OPERATION_PROPERTY.duration) {\n const iso = isoDurationOf(p.value, uom)\n if (!iso) { unreadable.push({ target: 'operation', id: d.id, propertyId: p.id }); continue }\n next.localDurations = { ...(next.localDurations ?? {}), [d.id]: iso }\n memo.properties[i].replaced = null\n continue\n }\n if (p.id === OPERATION_PROPERTY.setupDuration) {\n const iso = isoDurationOf(p.value, uom)\n if (!iso) { unreadable.push({ target: 'operation', id: d.id, propertyId: p.id }); continue }\n putParam(next, d.id, OP_PARAM.setupDuration, iso)\n memo.properties[i].replaced = null\n continue\n }\n if (p.id === OPERATION_PROPERTY.yield) {\n const pct = Number(p.value)\n if (!Number.isFinite(pct) || pct <= 0 || pct > 100) { unreadable.push({ target: 'operation', id: d.id, propertyId: p.id }); continue }\n /* 퍼센트 → 비율. 이 환산이 있는 자리는 여기 하나다(두 곳에서 나누면 100 배가 두 번 걸린다). */\n putParam(next, d.id, OP_PARAM.yield, String(pct / 100))\n memo.properties[i].replaced = null\n continue\n }\n unreadable.push({ target: 'operation', id: d.id, propertyId: p.id })\n }\n }\n\n /* 얕은 복사로는 안 된다 — 자리·설비 항목을 손대므로 그 배열과 항목까지 새로 만든다. */\n const next: any = {\n ...model,\n ...(model.locations ? { locations: model.locations.map(n => ({ ...n, ...(n?.properties ? { properties: [...n.properties] } : {}) })) } : {}),\n ...(model.equipment ? { equipment: model.equipment.map(e => ({ ...e, ...(e?.properties ? { properties: [...e.properties] } : {}) })) } : {}),\n ...(model.localDurations ? { localDurations: { ...model.localDurations } } : {}),\n ...(model.localParams ? { localParams: Object.fromEntries(Object.entries(model.localParams).map(([k, v]) => [k, { ...v }])) } : {})\n }\n for (let i = 0; i < decls.length; i++) {\n const d = decls[i]\n if (d.target === 'operation') {\n /* 빈 표를 남기지 않는다 — 「선언이 있다」와 「읽을 수 있는 선언이 하나도 없다」는 다른 사실이다\n (`putOperation` 이 담을 것이 있을 때만 표를 만든다). */\n putOperation(next, d, layer[i])\n continue\n }\n const ok = put(d.target === 'equipment' ? next.equipment : next.locations, d, layer[i])\n if (!ok) missingTargets.push({ target: d.target, id: d.id })\n }\n return { model: next as T, missingTargets, unreadable, layer }\n}\n\n/** 철회 요청 하나 — 어느 대상의 어느 속성을 원천으로 되돌리나. */\nexport interface DeclarationRevocation {\n target: DeclarationTarget\n id: string\n propertyId: string\n}\n\n/** 철회된 항목 — 되돌릴 값을 함께 든다(모르면 `replaced` 가 없다). */\nexport interface RevokedDeclaration extends DeclarationRevocation {\n replaced?: string | null\n}\n\n/**\n * 겹에서 선언을 **뺀다** — 값을 넣는 것과 다른 결정이므로 다른 문이다.\n *\n * 없는 것을 뺐다고 말하지 않는다: 겹에 없던 대상·속성은 `notFound` 로 돌려준다(알리지 않고 성공하면\n * 사용자는 지웠다고 믿고 값은 그대로 남는다 — 가장 비싼 종류의 침묵이다).\n *\n * 속성이 하나도 남지 않은 대상은 겹에서 사라진다(빈 껍데기를 남기지 않는다).\n */\nexport function revokeLocalDeclarations(\n current: readonly LocalDeclaration[] | undefined | null,\n revocations: readonly DeclarationRevocation[] | undefined | null\n): { layer: LocalDeclaration[]; revoked: RevokedDeclaration[]; notFound: DeclarationRevocation[] } {\n const layer: LocalDeclaration[] = (current ?? []).map(d => ({ ...d, properties: d.properties.map(p => ({ ...p })) }))\n const revoked: RevokedDeclaration[] = []\n const notFound: DeclarationRevocation[] = []\n\n for (const r of revocations ?? []) {\n const target: DeclarationTarget = r?.target === 'equipment' ? 'equipment' : r?.target === 'operation' ? 'operation' : 'location'\n const id = String(r?.id ?? '').trim()\n const propertyId = String(r?.propertyId ?? '').trim()\n if (!id || !propertyId) {\n notFound.push({ target, id, propertyId })\n continue\n }\n const d = layer.find(x => x.target === target && x.id === id)\n const at = d ? d.properties.findIndex(p => p.id === propertyId) : -1\n if (!d || at < 0) {\n notFound.push({ target, id, propertyId })\n continue\n }\n const [gone] = d.properties.splice(at, 1)\n revoked.push({ target, id, propertyId, ...('replaced' in gone ? { replaced: gone.replaced } : {}) })\n }\n\n return { layer: layer.filter(d => d.properties.length), revoked, notFound }\n}\n\n/**\n * 철회한 속성을 **모델에서 원천 값으로 되돌린다** — 저장된 모델에 값이 구워져 있기 때문이다.\n *\n * · 기억한 값이 있으면 그 값으로 되돌리고 `source: 'local'` 표식을 뗀다(다시 원천이 말한 값이다).\n * · `null` 이면 원천에 없던 속성이므로 **지운다**.\n * · 기억이 없으면(옛 선언) **손대지 않고** `unknownSource` 로 말한다 — 원래 값을 지어내는 것은\n * 잘못 넣은 값을 그대로 두는 것보다 나쁘다(다음 원천 재읽기가 그 자리를 바로잡는다).\n */\nexport function restoreRevoked<\n T extends { locations?: any[]; equipment?: any[]; localDurations?: Record<string, string>; localParams?: Record<string, Record<string, string>> }\n>(\n model: T,\n revoked: readonly RevokedDeclaration[]\n): { model: T; restored: RevokedDeclaration[]; unknownSource: DeclarationRevocation[]; missingTargets: { target: string; id: string }[] } {\n if (!revoked.length) return { model, restored: [], unknownSource: [], missingTargets: [] }\n\n const next: any = {\n ...model,\n ...(model.locations ? { locations: model.locations.map(n => ({ ...n, ...(n?.properties ? { properties: [...n.properties] } : {}) })) } : {}),\n ...(model.equipment ? { equipment: model.equipment.map(e => ({ ...e, ...(e?.properties ? { properties: [...e.properties] } : {}) })) } : {}),\n ...(model.localDurations ? { localDurations: { ...model.localDurations } } : {}),\n ...(model.localParams ? { localParams: Object.fromEntries(Object.entries(model.localParams).map(([k, v]) => [k, { ...v }])) } : {})\n }\n const restored: RevokedDeclaration[] = []\n const unknownSource: DeclarationRevocation[] = []\n const missingTargets: { target: string; id: string }[] = []\n\n for (const r of revoked) {\n /*\n * 공정 시간은 표에서 **뺀다** — 되돌릴 원천 값이 없는 자리다(전부 우리가 정한 값이었다).\n * 빼면 원천 명세(있으면)나 커널 상수가 다시 답한다: 그것이 「선언 이전」의 상태다.\n */\n if (r.target === 'operation') {\n /* 시간은 시간 표에서, 모수(수율·셋업)는 모수 표에서 뺀다 — 담긴 자리에서 빼야 실제로 빠진다. */\n const paramId =\n r.propertyId === OPERATION_PROPERTY.yield\n ? OP_PARAM.yield\n : r.propertyId === OPERATION_PROPERTY.setupDuration\n ? OP_PARAM.setupDuration\n : undefined\n if (paramId) {\n const params: Record<string, Record<string, string>> = next.localParams ?? {}\n if (!params[r.id] || !(paramId in params[r.id])) {\n missingTargets.push({ target: r.target, id: r.id })\n continue\n }\n delete params[r.id][paramId]\n /* 빈 껍데기를 남기지 않는다 — 「선언이 있다」와 「빈 표가 있다」가 구별되지 않는다. */\n if (!Object.keys(params[r.id]).length) delete params[r.id]\n if (!Object.keys(params).length) delete next.localParams\n else next.localParams = params\n restored.push(r)\n continue\n }\n const table: Record<string, string> = next.localDurations ?? {}\n if (!(r.id in table)) {\n missingTargets.push({ target: r.target, id: r.id })\n continue\n }\n delete table[r.id]\n if (!Object.keys(table).length) delete next.localDurations\n else next.localDurations = table\n restored.push(r)\n continue\n }\n const list: any[] = (r.target === 'equipment' ? next.equipment : next.locations) ?? []\n const item = list.find(x => String(x?.id ?? '') === r.id)\n if (!item) {\n missingTargets.push({ target: r.target, id: r.id })\n continue\n }\n if (!('replaced' in r)) {\n unknownSource.push({ target: r.target, id: r.id, propertyId: r.propertyId })\n continue\n }\n const props: any[] = Array.isArray(item.properties) ? [...item.properties] : []\n const at = props.findIndex(x => String(x?.id ?? '') === r.propertyId)\n if (r.replaced === null) {\n if (at >= 0) props.splice(at, 1)\n } else if (at >= 0) {\n const { source: _dropped, ...rest } = props[at] ?? {}\n props[at] = { ...rest, value: String(r.replaced) }\n } else {\n /* 겹은 있었는데 모델에는 없다 — 원천 값을 되살려 넣는다(우리가 지웠을 수 있다). */\n props.push({ id: r.propertyId, value: String(r.replaced) })\n }\n item.properties = props\n restored.push(r)\n }\n\n return { model: next as T, restored, unknownSource, missingTargets }\n}\n\n/** 선언이 어느 범위의 것인가 — 좁은 것이 이긴다(`space` 먼저, `instance` 나중). */\nexport type DeclarationScope = 'space' | 'instance'\n\n/**\n * 두 겹을 **순서대로** 얹는다 — 현장 먼저, 트윈이 나중.\n *\n * ── 왜 순서가 규율인가 (2026-08-18) ─────────────────────────────────────────\n * 요금 단가는 현장의 계약이므로 그 현장의 트윈 전부가 같은 값을 읽어야 한다. 그런데 한 트윈만 다른\n * 값으로 실험하는 일이 있다(인상된 단가로 what-if). 그때 현장 값을 고치게 하면 **나머지 트윈이 함께\n * 흔들린다.** 그래서 더 좁은 선언이 이긴다 — 넓은 값은 그대로 두고 좁은 자리에서만 달라진다.\n *\n * 기억(`replaced`)은 각 겹이 **자기가 덮은 값**을 적는다: 트윈 겹의 기억은 「현장 값을 덮었다」가 되고,\n * 철회하면 현장 값으로 돌아간다(원천까지 가지 않는다). 그것이 사람이 기대하는 되돌림이다.\n */\nexport function applyDeclarationLayers<\n T extends { locations?: any[]; equipment?: any[]; localDurations?: Record<string, string>; localParams?: Record<string, Record<string, string>> }\n>(\n model: T,\n spaceLayer: readonly LocalDeclaration[] | undefined | null,\n instanceLayer: readonly LocalDeclaration[] | undefined | null\n): {\n model: T\n /** 현장 겹을 얹은 결과 — 저장해야 기억이 낡지 않는다. */\n spaceLayerApplied: LocalDeclaration[]\n instanceLayerApplied: LocalDeclaration[]\n /** 갈 곳이 없던 대상 — 어느 범위에서 났는지 함께 낸다(현장 겹은 그 트윈에 없을 수 있다). */\n missingTargets: { scope: DeclarationScope; target: string; id: string }[]\n /** 읽을 수 있는 값으로 바꿀 수 없던 선언 — 범위와 함께(알리지 않고 버리지 않는다). */\n unreadable: { scope: DeclarationScope; target: string; id: string; propertyId: string }[]\n} {\n const first = applyLocalDeclarations(model, spaceLayer)\n const second = applyLocalDeclarations(first.model, instanceLayer)\n return {\n model: second.model,\n spaceLayerApplied: first.layer,\n instanceLayerApplied: second.layer,\n unreadable: [\n ...first.unreadable.map(u => ({ scope: 'space' as const, ...u })),\n ...second.unreadable.map(u => ({ scope: 'instance' as const, ...u }))\n ],\n /*\n * **현장 겹의 「갈 곳 없음」은 오류가 아니다.**\n *\n * 한 현장에는 여러 종류의 트윈이 산다(창고·야드·에너지). 요금을 현장에 선언하면 그 값은 수전 자리를\n * 가진 에너지 트윈에만 얹히고, 창고 트윈에는 얹을 자리가 없다 — 그것이 정상이다. 그래서 범위를 함께\n * 실어 보내고, 부르는 쪽이 현장 겹의 것은 경고로 올리지 않는다.\n */\n missingTargets: [\n ...first.missingTargets.map(t => ({ scope: 'space' as const, ...t })),\n ...second.missingTargets.map(t => ({ scope: 'instance' as const, ...t }))\n ]\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"log.js","sourceRoot":"","sources":["../../server/engine/log.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 트윈 로그의 문 — **모든 줄에 시각을 찍는다.** 순수(프레임워크 의존 없음).\n *\n * ── 왜 필요한가 (2026-08-22) ────────────────────────────────────────────────\n * 「서버가 왜 느린가」를 조사하면서 루프가 55초 막힌 것을 계기로 알아냈는데(`loop-lag.ts`),\n * **그 시각에 무슨 일이 있었는지 로그에서 찾을 수 없었다.** 트윈의 로그가 `console.log` 로 나가\n * 시각이 없었기 때문이다. 프레임워크 로그(`2026-08-22T05:54:12+09:00 info: …`)와 섞여 있어서, 어느\n * 줄이 언제 찍혔는지 앞뒤 프레임워크 줄로 짐작해야 했다.\n *\n * ── 왜 프레임워크 로거를 그대로 쓰지 않나 ──────────────────────────────────\n * 재발명을 피하려고 `@things-factory/env` 의 `logger` 를 쓰려 했는데, 실측으로 막혔다: 앱 **밖**에서는\n * 그 로거에 전송지가 하나도 없다(설정이 앱에서 주입된다). 그래서 시험·도구에서 부르면\n *\n * [winston] Attempt to write logs with no transports … {\"message\":\"…\",\"level\":\"info\"}\n *\n * 가 나오고 **메시지 자체가 사라진다.** 트윈 엔진은 시험과 CLI 도구에서도 불려 다니므로, 그 조건에서\n * 로그가 없어지는 것은 바꿔선 안 되는 거동이다(로그가 사라지는 것이 지금 문제의 원인이었다).\n *\n * 그래서 **싱크는 그대로 두고**(console) 빠진 사실 하나만 채운다 — 시각. 형식은 프레임워크와 같은\n * 모양으로 맞춰 한 파일에서 섞여 읽히게 하고, **밀리초까지** 남긴다(계기의 값이 epoch ms 라\n * 초 단위로는 같은 초 안의 순서를 가릴 수 없다).\n */\n\n/** 두 자리·세 자리 0 채우기 — 형식이 흔들리면 정렬해 읽을 수 없다. */\nconst p2 = (n: number) => String(n).padStart(2, '0')\nconst p3 = (n: number) => String(n).padStart(3, '0')\n\n/**\n * 지역 시각 + 오프셋 — `2026-08-22T05:54:12.345+09:00`.\n *\n * UTC 로 찍지 않는다: 이 로그를 읽는 사람은 현장의 시각으로 이야기하고, 프레임워크의 다른 줄도\n * 지역 시각이다. 두 줄이 다른 기준이면 나란히 놓고 읽을 수 없다.\n */\nexport function stamp(at: Date = new Date()): string {\n const off = -at.getTimezoneOffset()\n const sign = off >= 0 ? '+' : '-'\n const abs = Math.abs(off)\n return (\n `${at.getFullYear()}-${p2(at.getMonth() + 1)}-${p2(at.getDate())}` +\n `T${p2(at.getHours())}:${p2(at.getMinutes())}:${p2(at.getSeconds())}.${p3(at.getMilliseconds())}` +\n `${sign}${p2(Math.floor(abs / 60))}:${p2(abs % 60)}`\n )\n}\n\n/**\n * 인자 여럿을 한 줄로 — **어떤 인자도 버리지 않는다.**\n *\n * `console.error(msg, err)` 처럼 쓰던 자리가 많다. 프레임워크 로거에 그대로 넘기면 두 번째 인자는\n * meta 로 들어가 우리 형식(`message` 만 찍는다)에서 **오류 없이 사라진다.** 그래서 여기서 문자열로 편다.\n * 오류는 스택까지 — 스택이 없으면 「어디서 났나」를 잃는다.\n */\nexport function line(args: unknown[]): string {\n return args\n .map(a => {\n if (typeof a === 'string') return a\n if (a instanceof Error) return a.stack ?? a.message\n if (a === undefined) return 'undefined'\n if (a === null) return 'null'\n try {\n return typeof a === 'object' ? JSON.stringify(a) : String(a)\n } catch {\n /* 순환 참조 등 — 값을 못 적어도 **무엇이 있었다는 사실**은 남긴다. */\n return String(a)\n }\n })\n .join(' ')\n}\n\n/* 수준 표기는 프레임워크와 같은 낱말을 쓴다(`info`·`warn`·`error`) — 같은 파일에서 걸러 읽는다. */\nexport const twinLog = (...args: unknown[]): void => console.log(`${stamp()} info: ${line(args)}`)\nexport const twinWarn = (...args: unknown[]): void => console.warn(`${stamp()} warn: ${line(args)}`)\nexport const twinError = (...args: unknown[]): void => console.error(`${stamp()} error: ${line(args)}`)\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"loop-lag.js","sourceRoot":"","sources":["../../server/engine/loop-lag.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 이벤트 루프 지체 — **화면이 느린 이유를 지어내지 않기 위해.** 순수(import 없음).\n *\n * ── 왜 필요한가 (2026-08-21) ────────────────────────────────────────────────\n * 「UI 반응성이 느리다」를 진단하려 했는데, 있는 계기판으로는 답이 나오지 않았다. `twinLoad` 는 **트윈의\n * 계측된 작업**만 재고, `processCpuRatio` 는 프로세스 전체를 재지만 **언제 얼마나 멈췄나**를 말하지\n * 못한다. 밖에서 HTTP 지연을 재 보니 같은 요청이 44ms 와 5.8초로 갈라졌는데, 그 5.8초가 트윈 때문인지\n * webpack 재빌드 때문인지 남의 재기동 때문인지 **가릴 근거가 없었다.**\n *\n * 근거 없이 원인을 고르면 엉뚱한 곳을 고친다. 그래서 루프가 실제로 얼마나 막혔는지를 잰다.\n *\n * ── 어떻게 재나 ─────────────────────────────────────────────────────────────\n * 일정 간격으로 깨어나는 타이머를 두고, **예정 시각과 실제 시각의 차이**를 본다. 루프가 막혀 있었으면\n * 그만큼 늦게 깨어난다. 재는 비용은 그 차이 계산 하나뿐이다(창마다 값 다섯).\n *\n * 이 값은 **원인을 말하지 않는다** — 「200ms 이상 막힌 일이 이 창에서 세 번, 가장 긴 것은 4.6초」까지가\n * 사실이다. 원인은 그 시각의 트윈 부하(`twinLoad`)와 프로세스 CPU 와 나란히 놓고 사람이 가린다.\n * 재지 않은 것을 0 으로 적지 않는다 — 한 번도 깨어나지 않았으면 `null` 이다.\n */\n\n/** 표본 간격(ms) — 촘촘할 필요가 없다. 긴 정체를 놓치지 않는 정도면 된다. */\nexport const LAG_SAMPLE_MS = 500\n/** 이 값을 넘는 지체만 「정체」로 센다(ms). 사람이 화면에서 느끼기 시작하는 크기. */\nexport const LAG_STALL_MS = 200\n/** 창 길이(ms) — `twinLoad` 의 창과 같은 크기로 두어 나란히 읽는다. */\nexport const LAG_WINDOW_MS = 10_000\n\nexport interface LoopLagWindow {\n /** 이 창에서 가장 길게 막힌 시간(ms). */\n maxMs: number\n /** 정체(문턱 초과) 횟수. */\n stalls: number\n /** 표본 수 — 분모다. */\n samples: number\n /** 지체 합(ms) — 평균은 읽는 쪽에서 낸다. */\n totalMs: number\n /** 창이 열린 시각. */\n startMs: number\n}\n\nexport interface LoopLagView {\n /** 마지막으로 닫힌 창 — 「지금 어떤가」. 아직 한 창도 안 지났으면 null. */\n recent: (LoopLagWindow & { endMs: number }) | null\n /** 기동 이후 누적. */\n total: LoopLagWindow\n /** 기동 이후 가장 길게 막힌 시각(ms, epoch) — 언제였는지가 원인을 가리는 데 쓰인다. */\n worstAtMs: number | null\n /** 표본 간격 — 읽는 쪽이 이 값보다 짧은 정체는 못 본다는 것을 알아야 한다. */\n sampleMs: number\n /** 정체로 세는 문턱. */\n stallMs: number\n}\n\nconst emptyWindow = (startMs: number): LoopLagWindow => ({ maxMs: 0, stalls: 0, samples: 0, totalMs: 0, startMs })\n\nexport class LoopLagSampler {\n private window: LoopLagWindow\n private closed: (LoopLagWindow & { endMs: number }) | null = null\n private total: LoopLagWindow\n private worstAtMs: number | null = null\n private timer: any\n private expectedMs: number\n\n private sampleMs: number\n private stallMs: number\n private windowMs: number\n private now: () => number\n\n /* 생성자 파라미터 프로퍼티(`private x = …`)는 쓰지 않는다 — 시험이 소스를 그대로 실행하는데\n (타입 제거 실행 방식) 그 문법은 지원되지 않아 파일 전체가 열리지 않는다. */\n constructor(sampleMs = LAG_SAMPLE_MS, stallMs = LAG_STALL_MS, windowMs = LAG_WINDOW_MS, now: () => number = Date.now) {\n this.sampleMs = sampleMs\n this.stallMs = stallMs\n this.windowMs = windowMs\n this.now = now\n const t = this.now()\n this.window = emptyWindow(t)\n this.total = emptyWindow(t)\n this.expectedMs = t + sampleMs\n }\n\n /** 표본을 하나 넣는다 — 타이머가 부르고, 시험은 직접 부른다(시계를 밀어 넣어 잰다). */\n sample(atMs = this.now()): number {\n const lag = Math.max(0, atMs - this.expectedMs)\n this.expectedMs = atMs + this.sampleMs\n for (const w of [this.window, this.total]) {\n w.samples += 1\n w.totalMs += lag\n if (lag > this.stallMs) w.stalls += 1\n if (lag > w.maxMs) w.maxMs = lag\n }\n if (lag >= this.total.maxMs && lag > this.stallMs) this.worstAtMs = atMs\n if (atMs - this.window.startMs >= this.windowMs) {\n this.closed = { ...this.window, endMs: atMs }\n this.window = emptyWindow(atMs)\n }\n return lag\n }\n\n /** 계기판이 읽는 값. **한 창도 닫히지 않았으면 `recent` 는 null** — 채우는 중인 창은 과소 집계다. */\n view(): LoopLagView {\n return { recent: this.closed, total: this.total, worstAtMs: this.worstAtMs, sampleMs: this.sampleMs, stallMs: this.stallMs }\n }\n\n /** 표본 타이머 기동(1회) — 프로세스 종료를 막지 않는다. */\n start(): void {\n if (this.timer) return\n this.timer = setInterval(() => this.sample(), this.sampleMs)\n if (typeof this.timer?.unref === 'function') this.timer.unref()\n }\n\n stop(): void {\n if (!this.timer) return\n clearInterval(this.timer)\n this.timer = undefined\n }\n}\n\n/** 프로세스 하나의 루프를 재는 것이므로 표본기도 하나다. */\nexport const loopLag = new LoopLagSampler()\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"measured-estimator.js","sourceRoot":"","sources":["../../server/engine/measured-estimator.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;;;AA4BH,wDAyCC;AAhDD,mEAAmE;AACtD,QAAA,mBAAmB,GAAG,CAAC,CAAA;AAEpC;;;GAGG;AACH,SAAgB,sBAAsB,CACpC,MAAkH,EAClH,OAAgC,EAAE;IAElC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,2BAAmB,CAAA;IACzD,MAAM,OAAO,GAA2B,EAAE,CAAA;IAC1C,MAAM,OAAO,GAAqD,EAAE,CAAA;IACpE,MAAM,SAAS,GAAqC,EAAE,CAAA;IACtD,MAAM,OAAO,GAAsC,EAAE,CAAA;IACrD,KAAK,MAAM,CAAC,IAAI,MAAM,IAAI,EAAE,EAAE,CAAC;QAC7B,IAAI,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,GAAG,KAAK,SAAS;YAAE,SAAQ;QAC5C,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAA;QACpB,MAAM,KAAK,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,CAAA;QAC3B,MAAM,GAAG,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,CAAA;QACzB,IAAI,KAAK,GAAG,UAAU,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC;YAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;YAAC,SAAQ;QAAC,CAAC;QACxF,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAC5B,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,CAAA;QACrB,6DAA6D;QAC7D,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,CAAA;QACnB,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,CAAA;QACnB,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC;YAC1E,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;YAC5B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;YAC5B,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;YACjC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,YAAY,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAAA;QACzG,CAAC;aAAM,CAAC;YACN,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;QACrC,CAAC;IACH,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM;QAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,CAAA;IAClF,OAAO;QACL,OAAO;QACP,OAAO;QACP,OAAO;QACP,UAAU;QACV,SAAS,EAAE;YACT,QAAQ,CAAC,GAAG;gBACV,OAAO,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;YAC5B,CAAC;SACF;KACF,CAAA;AACH,CAAC","sourcesContent":["/*\n * 실측 소요 추정기 — **이력에서 배운 값이 상수·선언값을 이긴다.**\n *\n * 배경(operato-twin design/plans/simulation-spec.md §5-4): 소요시간을 명세로 받을 수 있게 됐지만\n * 명세는 선언이다 — 그 현장에서 실제로 얼마 걸리는지는 저널이 안다. `gap-analytics` 는 지표 수준의\n * 편향만 배웠고(예측값 보정), **공정별 소요를 추정기로 되먹이는 경로는 없었다.**\n *\n * 여기서 하는 일: KPI 폴드가 작업 종류별로 낸 **작업시간(착수→완료) p50** 을 그 종류의 소요로 쓴다.\n * 리드타임(생성→완료)이 아니라 작업시간을 쓰는 이유: 리드타임에는 **대기가 섞여 있고**, 대기는\n * 시뮬레이션이 자원 경합으로 스스로 만들어야 하는 값이다. 대기를 소요에 넣으면 이중 계산이 된다.\n *\n * **평균만 배우면 대기와 병목을 과소평가한다.** 소요가 늘 정확히 같으면 줄이 생기지 않는다 — 현장의\n * 줄은 흔들림에서 나온다. 그래서 p50 만이 아니라 **관측된 퍼짐(p10..p90)** 을 함께 넘기고, 표본은\n * 커널이 자기 난수로 뽑는다(fork 결정성). 양쪽 꼬리를 다 관측에서 가져오는 이유: p90 만 있으면\n * 아래쪽을 위쪽에서 거울처럼 베껴야 하는데 그건 발명이다.\n *\n * 정직 규율:\n * - 표본이 적으면 쓰지 않는다(`minSamples`). 한두 건의 p50 은 그 현장의 값이 아니다.\n * - 'unknown' 축은 쓰지 않는다 — 종류를 모르는 기록을 특정 종류의 근거로 삼을 수 없다.\n * - 퍼짐이 성립하지 않으면(p10 ≥ p90, 또는 0) **퍼짐 없이 평균만** 넘긴다. 없는 흔들림을 만들지 않는다.\n * - 무엇을 배웠고 무엇을 표본 부족으로 버렸는지 함께 낸다(알리지 않고 일부만 반영하지 않는다).\n */\n\n/** 커널 `DurationEstimate` 와 같은 모양(커널 타입에 의존하지 않기 위해 구조로 맞춘다). */\nexport interface MeasuredEstimate {\n meanMs: number\n spread?: { distribution: 'triangular'; minMs: number; maxMs: number; modeMs: number }\n}\n\nexport interface MeasuredEstimatorResult {\n estimator?: {\n estimate(ctx: { kind: string; fromNode: string; toNode: string; resourceKind?: string }): MeasuredEstimate | undefined\n }\n /** 작업 종류 → 실측 소요(ms). 추정기가 답하는 종류들. */\n learned: Record<string, number>\n /** 퍼짐까지 배운 종류 — 평균만 배운 것과 구별한다(변동 없는 소요는 대기를 과소평가한다). */\n spreads: Record<string, { minMs: number; maxMs: number }>\n /** 표본이 모자라 쓰지 않은 종류와 그 건수 — 배우지 못한 것을 밝힌다. */\n skipped: { kind: string; count: number }[]\n minSamples: number\n}\n\n/** 기본 표본 하한 — 이보다 적으면 그 종류는 배우지 않는다(작은 표본의 p50 을 현장 값으로 승격 금지). */\nexport const DEFAULT_MIN_SAMPLES = 5\n\n/**\n * 작업 종류별 KPI 그룹에서 추정기를 만든다(순수 — DB·커널 모름).\n * `groups` 는 `computeTwinKpi({ groupBy: 'taskKind' })` 의 `groups.items` 를 그대로 넘긴다.\n */\nexport function buildMeasuredEstimator(\n groups: { key: string; workTime?: { count: number; p10Ms?: number; p50Ms: number; p90Ms?: number } }[] | undefined,\n opts: { minSamples?: number } = {}\n): MeasuredEstimatorResult {\n const minSamples = opts.minSamples ?? DEFAULT_MIN_SAMPLES\n const learned: Record<string, number> = {}\n const spreads: Record<string, { minMs: number; maxMs: number }> = {}\n const estimates: Record<string, MeasuredEstimate> = {}\n const skipped: { kind: string; count: number }[] = []\n for (const g of groups ?? []) {\n if (!g?.key || g.key === 'unknown') continue\n const w = g.workTime\n const count = w?.count ?? 0\n const p50 = w?.p50Ms ?? 0\n if (count < minSamples || !(p50 > 0)) { skipped.push({ kind: g.key, count }); continue }\n const mean = Math.round(p50)\n learned[g.key] = mean\n /* 퍼짐은 **관측된 두 꼬리**로만 만든다. 한쪽이라도 없거나 구간이 성립하지 않으면 평균만 넘긴다. */\n const lo = w?.p10Ms\n const hi = w?.p90Ms\n if (typeof lo === 'number' && typeof hi === 'number' && lo > 0 && hi > lo) {\n const minMs = Math.round(lo)\n const maxMs = Math.round(hi)\n spreads[g.key] = { minMs, maxMs }\n estimates[g.key] = { meanMs: mean, spread: { distribution: 'triangular', minMs, maxMs, modeMs: mean } }\n } else {\n estimates[g.key] = { meanMs: mean }\n }\n }\n if (!Object.keys(learned).length) return { learned, spreads, skipped, minSamples }\n return {\n learned,\n spreads,\n skipped,\n minSamples,\n estimator: {\n estimate(ctx) {\n return estimates[ctx.kind]\n }\n }\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"measured-yield.js","sourceRoot":"","sources":["../../server/engine/measured-yield.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;;;AA6BH,kDAsCC;AAnDD;;;;;;GAMG;AACU,QAAA,yBAAyB,GAAG,EAAE,CAAA;AAE3C;;;GAGG;AACH,SAAgB,mBAAmB,CACjC,MAAgF,EAChF,OAAgC,EAAE;IAElC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,iCAAyB,CAAA;IAC/D,MAAM,OAAO,GAA2B,EAAE,CAAA;IAC1C,MAAM,OAAO,GAAoD,EAAE,CAAA;IACnE,MAAM,OAAO,GAAuC,EAAE,CAAA;IAEtD,KAAK,MAAM,CAAC,IAAI,MAAM,IAAI,EAAE,EAAE,CAAC;QAC7B,IAAI,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,GAAG,KAAK,SAAS;YAAE,SAAQ;QAC5C,MAAM,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,IAAI,IAAI,CAAC,CAAA;QACjC,MAAM,KAAK,GAAG,CAAC,CAAC,OAAO,EAAE,KAAK,IAAI,CAAC,CAAA;QACnC,MAAM,MAAM,GAAG,IAAI,GAAG,KAAK,CAAA;QAC3B,gEAAgE;QAChE,IAAI,MAAM,KAAK,CAAC;YAAE,SAAQ;QAC1B,IAAI,MAAM,GAAG,UAAU,EAAE,CAAC;YACxB,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,CAAA;YACrC,SAAQ;QACV,CAAC;QACD,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,MAAM,CAAA;QAC9B,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;IAClC,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAClC,IAAI,CAAC,KAAK,CAAC,MAAM;QAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,CAAA;IAEnE,OAAO;QACL;;;WAGG;QACH,SAAS,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;QACpD,OAAO;QACP,OAAO;QACP,OAAO;QACP,UAAU;KACX,CAAA;AACH,CAAC","sourcesContent":["/*\n * 실측 양품률 추정기 — **이력에서 배운 수율이 선언값·상수를 이긴다.**\n *\n * ── 왜 필요했나 (2026-08-19) ────────────────────────────────────────────────\n * 소요시간에는 이력 층이 있었다(`measured-estimator`): 저널이 「그 현장에서 실제로 얼마 걸렸나」를 안다.\n * 수율에는 그 층이 없었다 — 양품률의 기본값은 커널 소스의 상수(0.8)였고, **그 상수가 불량 과다 주목\n * 신호까지 만들었다.** 초기 라인이든 성숙한 라인이든 같은 수로 판정된다는 뜻이다.\n *\n * 그런데 이력을 만들 재료도 없었다: 양품/불량은 설비 누적 카운터(종류를 모른다)와 EPCIS disposition\n * (종류를 모른다)에만 남았다. 그래서 커널이 **판정을 일어난 자리(작업 완료)에 적게** 했고\n * (`TaskStatusDelta.outcome`), 폴드가 축으로 들고(`TaskFacets.outcome`), KPI 가 종류별로 센다\n * (`KpiGroup.quality`). 이 파일은 그 수를 비율로 바꿔 커널 시임에 넣는다.\n *\n * ── 정직 규율 (소요 추정기와 같은 것) ───────────────────────────────────────\n * - **표본이 적으면 쓰지 않는다**(`minSamples`). 세 건 중 한 건 불량은 「불량률 33%」가 아니다.\n * - `'unknown'` 축은 쓰지 않는다 — 종류를 모르는 기록을 특정 종류의 근거로 삼을 수 없다.\n * - **판정이 없는 작업은 분모에 넣지 않는다.** KPI 가 이미 그렇게 세지만(`quality` 는 판정된 것만),\n * 여기서도 `good + scrap` 만 쓴다 — `tasks` 를 분모로 쓰면 이동·체류가 섞여 양품률이 낮게 나온다.\n * - **0 도 사실이다.** 전부 불량이면 0 을 배운다(그 현장이 그랬다). 「값이 없다」와 구별해 다룬다 —\n * 커널이 `0` 을 받으면 전량 불량으로 굴러가고, 그것이 관측과 같은 결론이다.\n * - 무엇을 배웠고 무엇을 표본 부족으로 버렸는지 함께 낸다(알리지 않고 일부만 반영하지 않는다).\n */\n\n/** 커널 `YieldEstimator` 와 같은 모양(커널 타입에 의존하지 않기 위해 구조로 맞춘다). */\nexport interface YieldEstimatorResult {\n estimator?: {\n estimate(ctx: { kind: string }): number | undefined\n }\n /** 작업 종류 → 실측 양품률(0..1). 추정기가 답하는 종류들. */\n learned: Record<string, number>\n /** 종류별 표본 — 배운 값의 근거 크기(화면·로그가 「몇 건에서 배웠나」를 말할 수 있게). */\n samples: Record<string, { good: number; scrap: number }>\n /** 표본이 모자라 쓰지 않은 종류와 판정 건수 — 배우지 못한 것을 밝힌다. */\n skipped: { kind: string; judged: number }[]\n minSamples: number\n}\n\n/**\n * 기본 표본 하한 — 판정 20건 미만이면 그 종류는 배우지 않는다.\n *\n * 소요(5건)보다 높게 잡는 이유: 비율은 **드문 사건**을 재는 값이다. 불량률 5% 인 공정에서 20건을 봐도\n * 불량은 한 건이고, 그 한 건이 있고 없음으로 비율이 0% 와 5% 사이를 오간다. 낮게 잡으면 추정기가\n * 이력이라는 이름으로 잡음을 실어 보낸다.\n */\nexport const DEFAULT_YIELD_MIN_SAMPLES = 20\n\n/**\n * 작업 종류별 KPI 그룹에서 양품률 추정기를 만든다(순수 — DB·커널 모름).\n * `groups` 는 `computeTwinKpi({ groupBy: 'taskKind' })` 의 `groups.items` 를 그대로 넘긴다.\n */\nexport function buildYieldEstimator(\n groups: { key: string; quality?: { good: number; scrap: number } }[] | undefined,\n opts: { minSamples?: number } = {}\n): YieldEstimatorResult {\n const minSamples = opts.minSamples ?? DEFAULT_YIELD_MIN_SAMPLES\n const learned: Record<string, number> = {}\n const samples: Record<string, { good: number; scrap: number }> = {}\n const skipped: { kind: string; judged: number }[] = []\n\n for (const g of groups ?? []) {\n if (!g?.key || g.key === 'unknown') continue\n const good = g.quality?.good ?? 0\n const scrap = g.quality?.scrap ?? 0\n const judged = good + scrap\n /* 판정이 아예 없는 종류는 **건너뛴 것이 아니다** — 품질을 가리지 않는 공정(이동·체류)이 정상이다. */\n if (judged === 0) continue\n if (judged < minSamples) {\n skipped.push({ kind: g.key, judged })\n continue\n }\n learned[g.key] = good / judged\n samples[g.key] = { good, scrap }\n }\n\n const kinds = Object.keys(learned)\n if (!kinds.length) return { learned, samples, skipped, minSamples }\n\n return {\n /*\n * 시임은 **무상태**다(커널이 fork 할 때 참조를 공유한다 — 소요 추정기와 같은 규약).\n * 배우지 않은 종류에는 `undefined` 를 답한다: 그러면 커널이 선언값 → 상수로 내려간다.\n */\n estimator: { estimate: ({ kind }) => learned[kind] },\n learned,\n samples,\n skipped,\n minSamples\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"model-basis.js","sourceRoot":"","sources":["../../server/engine/model-basis.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 모델 근거 — **배운 모델에만 보정을 적용하기 위한 비교.**\n *\n * 예측 출력 보정(P3.2 `twinLearnCalibration`)은 \"우리 모델이 늘 이만큼 적게 본다\" 를 배워 결과를\n * 밀어 준다. 그 학습은 **그때의 모델**을 전제로 한다. 그런데 소요시간 추정기(실측·거리계산)와 공정\n * 명세가 들어오면서 모델 자체가 바뀔 수 있게 됐다 — 모델이 이미 그 편향을 잃었는데 보정이 또 밀어\n * 주면 **이중 보정**이 되어 정확도가 나빠진다. 학습 시점의 accAfter 도 더는 유효하지 않다.\n *\n * 그래서 보정 기록에 **그때 모델의 근거**를 함께 남기고, 적용 전에 지금 근거와 비교한다. 달라졌으면\n * 적용하지 않고 **그 사실을 응답에 싣는다**(오류 없이 사라지면 사용자는 학습이 왜 안 듣는지 모른다).\n *\n * 비교는 **상대 허용오차**로 한다(양자화 지문이 아니라). 실측 p50 은 새 기록이 쌓일 때마다 1~2초씩\n * 흔들리는데, 값을 버킷으로 합쳐 비교하면 버킷 경계에 걸린 값이 1초 드리프트로도 달라져 버린다 —\n * \"작은 드리프트로 학습을 무효화하지 않는다\" 는 목적을 경계에서 못 지킨다. 비율로 비교하면 어디서든\n * 같은 규칙이 성립한다: 10% 이내는 같은 모델, 30초→47초(57%)는 다른 모델.\n *\n * 재료는 호스트가 아는 것들이다(커널 API 에 의존하지 않아 발행 상태와 무관하게 동작한다):\n * ① 실측으로 배운 작업 종류별 소요 ② 자원 종류별 속도 ③ 선언된 공정 소요.\n */\n\n/**\n * 허용오차의 **기본값** — 선언이 아무 말도 없을 때만 쓴다.\n *\n * 10% 는 근거에서 나온 수가 아니라 **판단값**이다(그 사실을 적어 두는 것이 이 주석의 목적이다). 그리고\n * 단일 잣대로 쓰면 틀린다 — 우리 트레일러 템플릿이 그것을 보여 준다: 용접은 변동폭을 `PT55M~PT1H35M`\n * (선언 소요 `PT1H10M` 기준 −21%~+36%)로 **스스로 선언한다.** 10% 관문은 그 공정의 **정상 변동조차**\n * \"다른 모델\" 로 판정해 보정을 끊는다. 반대로 1% 변동도 치명적인 공정에는 10% 가 너무 느슨하다.\n *\n * 그래서 허용오차는 **그 공정이 선언한 변동폭에서 나온다**(§toleranceOf). 선언이 있으면 선언이 이기고,\n * 없을 때만 이 값으로 떨어진다 — 도메인 설정을 새로 만들지 않는다(현장이 이미 말한 것을 읽는다).\n */\nexport const BASIS_TOLERANCE = 0.1\n\nexport interface ModelBasis {\n /** 작업 종류 → 실측 소요(ms). `buildMeasuredEstimator().learned`. */\n measured?: Record<string, number>\n /** 자원 종류 → 속도(m/s). `buildTravelEstimator().speedsByKind`. */\n speeds?: Record<string, number>\n /**\n * 선언된 공정 — key + ISO 8601 원문(문자열은 그대로 비교한다: 선언이 바뀌면 다른 모델).\n *\n * **변동폭도 지문에 든다.** 변동폭이 허용오차를 정하므로(§toleranceOf), 그것이 바뀌었는데 \"같은 모델\"\n * 이라고 하면 판정의 잣대가 알리지 않고 바뀐 채로 학습이 이어진다.\n */\n declared?: DeclaredOperation[]\n}\n\n/** 선언된 공정 하나 — 소요와 변동폭(모델의 `operations` 원형에서 필요한 만큼만). */\nexport interface DeclaredOperation {\n key: string\n duration?: string\n variability?: { distribution?: string; min?: string; max?: string; mode?: string }\n}\n\n/** ISO 8601 기간 → 밀리초. 읽을 수 없으면 `undefined`(짐작해 고치지 않는다). */\nfunction isoMs(iso?: string): number | undefined {\n if (!iso) return undefined\n const m = /^P(?:(\\d+)D)?(?:T(?:(\\d+)H)?(?:(\\d+)M)?(?:([\\d.]+)S)?)?$/.exec(iso)\n if (!m || !m.slice(1).some(Boolean)) return undefined\n return ((Number(m[1] ?? 0) * 24 + Number(m[2] ?? 0)) * 60 + Number(m[3] ?? 0)) * 60_000 + Number(m[4] ?? 0) * 1000\n}\n\n/**\n * 작업 종류별 **허용오차** — 그 공정이 선언한 변동폭에서 나온다.\n *\n * 실측 p50 은 정상 변동 안에서 흔들린다. 그 흔들림을 \"다른 모델\" 로 읽으면 보정이 자꾸 끊기고, 사용자는\n * 학습이 왜 안 듣는지 모른다. 얼마나 흔들리는 것이 정상인지는 **현장이 이미 선언했다**(`variability`).\n *\n * 반폭을 쓴다(`(max − min) / 2 / 소요`) — 중앙에서 어느 쪽으로든 그만큼은 정상이라는 뜻이다.\n * 선언이 없거나 읽을 수 없으면 기본값으로 떨어진다(짐작해 만들지 않는다).\n */\nexport function toleranceOf(declared?: DeclaredOperation[]): Record<string, number> {\n const out: Record<string, number> = {}\n for (const o of declared ?? []) {\n if (!o?.key) continue\n const base = isoMs(o.duration)\n const min = isoMs(o.variability?.min)\n const max = isoMs(o.variability?.max)\n if (!base || min === undefined || max === undefined || !(max > min)) continue\n const half = (max - min) / 2 / base\n if (Number.isFinite(half) && half > 0) out[o.key] = half\n }\n return out\n}\n\n/** 뜻 있는 수만 남긴다 — 0·음수·NaN 은 모델이 아니다. */\nfunction clean(map?: Record<string, number>): Record<string, number> {\n const out: Record<string, number> = {}\n for (const [k, v] of Object.entries(map ?? {})) if (Number.isFinite(v) && v > 0) out[k] = v\n return out\n}\n\nfunction declaredMap(list?: DeclaredOperation[]): Record<string, string> {\n const out: Record<string, string> = {}\n for (const o of list ?? []) {\n if (!o?.key) continue\n const v = o.variability\n /* 변동폭까지 한 문장으로 — 순서를 고정해 같은 선언이 같은 문자열이 되게 한다. */\n const spread = v ? `~${v.distribution ?? '?'}:${v.min ?? ''}..${v.max ?? ''}@${v.mode ?? ''}` : ''\n if (o.duration || spread) out[o.key] = `${o.duration ?? ''}${spread}`\n }\n return out\n}\n\n/** 두 수치 지도가 실질적으로 다른가 — 키 집합이 다르거나, 값이 허용오차를 넘어 다르면 다르다. */\nfunction numericChanged(a: Record<string, number>, b: Record<string, number>, tolOf: (key: string) => number): boolean {\n const keys = new Set([...Object.keys(a), ...Object.keys(b)])\n for (const k of keys) {\n const x = a[k]\n const y = b[k]\n if (x === undefined || y === undefined) return true // 새로 배운 종류·사라진 종류 = 다른 모델\n const base = Math.max(Math.abs(x), Math.abs(y))\n if (base > 0 && Math.abs(x - y) / base > tolOf(k)) return true\n }\n return false\n}\n\n/**\n * 모델이 달라졌는가 — 보정을 적용하기 전의 관문.\n * 한쪽 근거를 모르면 **판정하지 않는다**(false) — 모르는 것을 근거로 학습을 끊지 않는다.\n */\nexport function modelBasisChanged(before?: ModelBasis, after?: ModelBasis, tol: number = BASIS_TOLERANCE): boolean {\n if (!before || !after) return false\n /*\n * 허용오차는 **선언이 정한다** — 배운 시점과 지금의 선언이 다를 수 있으므로 둘 중 **넓은 쪽**을 쓴다.\n * 좁은 쪽을 쓰면 선언을 넓힌 직후에 정상 변동이 \"다른 모델\" 로 잡힌다(그 변경 자체는 아래 선언 비교가\n * 이미 잡으므로, 여기서 또 잡을 필요가 없다).\n */\n const tolBefore = toleranceOf(before.declared)\n const tolAfter = toleranceOf(after.declared)\n const tolOf = (key: string): number => Math.max(tolBefore[key] ?? tol, tolAfter[key] ?? tol)\n if (numericChanged(clean(before.measured), clean(after.measured), tolOf)) return true\n /* 속도는 자원 종류별이고 선언된 변동폭이 없다 — 기본값이 그대로 쓰인다. */\n if (numericChanged(clean(before.speeds), clean(after.speeds), () => tol)) return true\n const da = declaredMap(before.declared)\n const db = declaredMap(after.declared)\n const keys = new Set([...Object.keys(da), ...Object.keys(db)])\n for (const k of keys) if (da[k] !== db[k]) return true\n return false\n}\n\n/**\n * 사람이 읽는 근거 요약 — **로그·진단용**(비교의 근거가 아니다).\n * 지문으로 같음을 판정하지 않는 이유는 위 주석 참조. 두 근거가 왜 달라졌는지 눈으로 보려면 이것을 찍는다.\n */\nexport function describeModelBasis(basis?: ModelBasis): string {\n if (!basis) return 'unknown'\n const seg: string[] = []\n const m = Object.entries(clean(basis.measured))\n .map(([k, v]) => `${k}=${Math.round(v / 1000)}s`)\n .sort()\n if (m.length) seg.push(`measured(${m.join(',')})`)\n const s = Object.entries(clean(basis.speeds))\n .map(([k, v]) => `${k}=${Math.round(v * 10) / 10}m/s`)\n .sort()\n if (s.length) seg.push(`speed(${s.join(',')})`)\n const d = Object.entries(declaredMap(basis.declared))\n .map(([k, v]) => `${k}=${v}`)\n .sort()\n if (d.length) seg.push(`declared(${d.join(',')})`)\n return seg.length ? seg.join(' ') : 'kernel defaults only'\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"model-gap.js","sourceRoot":"","sources":["../../server/engine/model-gap.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;;;AA6GH,gCAgCC;AAnHD;;;;;GAKG;AACU,QAAA,YAAY,GAAG,EAAE,CAAA;AAoD9B,uDAAuD;AAC1C,QAAA,kBAAkB,GAAG,QAAQ,CAAA;AAE1C,SAAS,MAAM,CAAC,IAAoD;IAClE,MAAM,GAAG,GAAa,EAAE,CAAA;IACxB,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,IAAI,OAAO,GAAG,CAAC,CAAA;IACf,KAAK,MAAM,CAAC,IAAI,IAAI,IAAI,EAAE,EAAE,CAAC;QAC3B,IAAI,CAAC,EAAE,MAAM,KAAK,UAAU,EAAE,CAAC;YAC7B,KAAK,EAAE,CAAA;YACP,IAAI,GAAG,CAAC,MAAM,GAAG,oBAAY;gBAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QAC7D,CAAC;aAAM,IAAI,CAAC,EAAE,MAAM,KAAK,QAAQ,EAAE,CAAC;YAClC,kDAAkD;YAClD,OAAO,EAAE,CAAA;QACX,CAAC;IACH,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAA;AACvE,CAAC;AAED;;;;;GAKG;AACH,SAAgB,UAAU,CAAC,QAA2C;IACpE,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAA;IAE1B,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;IAC5C,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;IAC5C,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;IACxC,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAE9C,OAAO;QACL,SAAS;QACT,SAAS;QACT,OAAO;QACP,MAAM;QACN,KAAK,EAAE,SAAS,CAAC,KAAK,GAAG,SAAS,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK;QACvE,SAAS,EAAE,CAAC,QAAQ,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC9C,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,8CAA8C;YAC9C,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,QAAkB,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI;SAC1F,CAAC,CAAC;QACH;;;WAGG;QACH,uBAAuB,EACrB,OAAO,QAAQ,CAAC,MAAM,EAAE,gBAAgB,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI;QACvH;;;WAGG;QACH,UAAU,EAAE,EAAE;KACf,CAAA;AACH,CAAC","sourcesContent":["/*\n * **원본이 말한 적 없는 것** — 미해결 참조를 세는 한 자리. 순수(import 없음).\n *\n * ── 무엇을 답하나 ───────────────────────────────────────────────────────────\n * 원천이 우리 모델에 없는 id 를 보내면 어떻게 되나. 유입 문(`ingestCanonicalRecords`)은 **거부하지\n * 않는다** — 실측으로 확인했다(2026-08-20):\n *\n * 모르는 로케이션 「오타-99」 → 통과\n * 모르는 bizStep 「PUTAWAY_X17」 → 통과\n * 모르는 계량기 「MMXU-오타-99」 → 통과\n *\n * 거부하지 않는 것이 옳다. 원천이 모델보다 먼저 새 로케이션을 말하는 것은 정상이고(구조 갱신은 늦게 온다),\n * 거부하면 그 선행 계측을 잃는다. 그래서 커널은 **흡수하고 표시한다** — 마스터에 없던 로케이션은\n * `origin: 'observed'` 로 승격되어 스냅샷에 실린다(`ObservedReducer.touchLocation`).\n *\n * ── 왜 이 파일이 있나 ───────────────────────────────────────────────────────\n * 그 표시를 읽는 곳이 **모델 인스펙션 화면 하나**였다. 그런데 이 사실을 필요로 하는 사람은 「이 트윈이\n * 현장과 맞춰지고 있나」를 보는 사람이다 — 원천이 우리가 모르는 로케이션만 보내고 있으면 트윈은 도는데\n * 스키매틱은 비어 있고, 그것이 우리가 「빈 공장」이라 불러 온 상태다.\n *\n * 도출을 두 곳에 적으면 두 화면이 다른 수를 말하게 된다. 그래서 여기 한 벌만 둔다.\n *\n * ── 판정을 바꾸지 않는다 ────────────────────────────────────────────────────\n * 동기화 판정(`syncVerdictOf`)은 **피드**를 말한다: 오고 있나, 통과하나. 이 갭은 **모델**을 말한다:\n * 원천이 아는 것을 우리가 선언했나. 둘을 한 판정에 섞으면 「유입은 정상인데 모델이 뒤처졌다」와\n * 「유입이 막혔다」가 같은 색으로 보이고, 처방이 완전히 다르다.\n */\n\n/** 커널 스냅샷에서 우리가 읽는 것만. 나머지 필드는 알 필요가 없다. */\nexport interface ModelGapSource {\n locations?: { id?: string; origin?: string }[]\n equipment?: { id?: string; origin?: string }[]\n persons?: { id?: string; origin?: string }[]\n /** 커널이 담을 줄 몰라 세어 둔 사건 — 종류별 개수와 처음·마지막 시각. */\n unhandled?: { eventType: string; count: number; firstAtMs?: number; lastAtMs?: number }[]\n /**\n * EMS 상태 — **`energy` 안에 있다.**\n *\n * `unknownEquipment` 를 루트에서 읽고 있었다(2026-08-20에 잡음). 커널은 그것을 `EnergyState` 안에\n * 담으므로, 루트에서 읽으면 **영원히 `null`** 이다 — 오류도 없이 「에너지 트윈이 아니다」로 읽힌다.\n * 나만 읽는 필드를 잘못된 경로에서 읽으면 이렇게 오류 없이 사라진다.\n *\n * 계량 지점의 `origin` 은 **없을 수 있다**(옛 스냅샷에서 이어받은 지점). 그때는 「선언되었다」가\n * 아니라 **모른다**다.\n */\n energy?: {\n points?: { id?: string; origin?: string }[]\n /** 모델이 모르는 설비가 에너지 상태를 보내 온 횟수. 0 이면 커널이 이 칸을 만들지 않는다. */\n unknownEquipment?: number\n }\n}\n\n/**\n * 화면에 내는 id 수 상한.\n *\n * 오타 하나를 찾는 데 스무 개면 충분하고, 원천이 수천 개를 보내는 경우(모델을 아예 안 맞춘 경우)에\n * 목록으로 화면을 채울 이유가 없다. **넘친 수는 함께 낸다** — 알리지 않고 자르지 않는다.\n */\nexport const GAP_ID_LIMIT = 20\n\nexport interface ModelGapAxis {\n /** 관측으로만 알게 된 id 수. */\n count: number\n /** 그중 화면에 싣는 것(최대 `GAP_ID_LIMIT`). */\n ids: string[]\n /** 상한에서 밀려난 수. 0 이면 전부 실었다. */\n more: number\n /**\n * 출처를 **모르는** 수 — 표시가 없는 항목.\n *\n * 「선언되었다」로 세지 않는다. 계량기는 이 표시가 늦게 생겼으므로 옛 스냅샷에서 이어받은 지점에는\n * 칸이 없다(`undefined`). 그것을 마스터로 세면 오타를 놓치고, 관측으로 세면 정상을 문제로 만든다.\n * 둘 다 틀리므로 **모른다고 센다.**\n */\n unknown: number\n}\n\nexport interface ModelGapView {\n locations: ModelGapAxis\n equipment: ModelGapAxis\n persons: ModelGapAxis\n /**\n * EMS 계량 지점.\n *\n * ── 이 축이 가장 무겁다 ────────────────────────────────────────────────────\n * 모르는 `meterId` 는 알리지 않고 새 계량기가 되고 그 kW 가 그 현장의 **수요 구간·피크·요금 판정**에\n * 더해진다. 오타 하나면 진짜 계량기는 멈추고 유령이 누적된다 — 숫자는 그럴듯하고 틀린다. 자리 오타는\n * 스키매틱이 비는 것으로 드러나지만, 이건 그럴듯한 숫자로 나온다.\n *\n * 커널이 `origin` 을 **표본마다 다시 맞춘다** — 선언의 정본은 모델이고 모델은 채택으로 늘어나므로,\n * 한 번 찍고 두면 나중에 선언된 계량기가 영원히 `observed` 로 남는다. 그래서 이 값은 「지금 모델에\n * 있는가」의 답이고, 「한 번 오타였다」의 답이 아니다.\n */\n meters: ModelGapAxis\n /** 세 축의 합 — 화면이 「있나 없나」를 한 번에 묻기 위한 값. */\n total: number\n /** 커널이 담을 줄 모른 사건들(종류별). 참조 문제와 다른 사실이므로 따로 낸다. */\n unhandled: { eventType: string; count: number; lastAt: string | null }[]\n /** EMS: 모델이 모르는 설비의 에너지 보고 횟수. 없으면 `null`(0 과 구별한다). */\n unknownEquipmentReports: number | null\n /**\n * 아직 덮지 못하는 축 — **모르는 것을 모른다고 말한다.**\n *\n * 계량기는 커널 0.7.39 부터 덮인다(그 전에는 `origin` 표시가 없어 오타를 찾아 줄 수 없었다).\n * 비어 있는 것이 정상이고, 새 축이 표시 없이 들어오면 여기에 이름을 넣는다 — 화면이 「다 찾아\n * 준다」고 말하지 않게.\n */\n notCovered: string[]\n}\n\n/** 계량기 축이 아직 덮이지 않는 이유 — 화면이 그대로 말할 수 있게 이름을 여기 둔다. */\nexport const NOT_COVERED_METERS = 'meters'\n\nfunction axisOf(rows: { id?: string; origin?: string }[] | undefined): ModelGapAxis {\n const ids: string[] = []\n let count = 0\n let unknown = 0\n for (const r of rows ?? []) {\n if (r?.origin === 'observed') {\n count++\n if (ids.length < GAP_ID_LIMIT) ids.push(String(r.id ?? ''))\n } else if (r?.origin !== 'master') {\n /* 표시가 없다 — 마스터로도 관측으로도 세지 않는다. 모르는 것은 모른다고 센다. */\n unknown++\n }\n }\n return { count, ids, more: Math.max(0, count - ids.length), unknown }\n}\n\n/**\n * 스냅샷 → 모델 갭.\n *\n * 스냅샷이 없으면 `null` 이다 — **0 이 아니다.** 트윈이 돌지 않으면 비교할 상대가 없고, 「없다」와\n * 「세어 보지 못했다」는 다른 사실이다(모델 인스펙션이 같은 규율을 쓴다).\n */\nexport function modelGapOf(snapshot: ModelGapSource | null | undefined): ModelGapView | null {\n if (!snapshot) return null\n\n const locations = axisOf(snapshot.locations)\n const equipment = axisOf(snapshot.equipment)\n const persons = axisOf(snapshot.persons)\n const meters = axisOf(snapshot.energy?.points)\n\n return {\n locations,\n equipment,\n persons,\n meters,\n total: locations.count + equipment.count + persons.count + meters.count,\n unhandled: (snapshot.unhandled ?? []).map(u => ({\n eventType: u.eventType,\n count: u.count,\n /* 마지막 시각이 멈춘 종류는 이미 고쳐진 것이다. 없으면 지어내지 않는다. */\n lastAt: Number.isFinite(u.lastAtMs) ? new Date(u.lastAtMs as number).toISOString() : null\n })),\n /*\n * 커널은 0 이면 이 칸을 만들지 않는다. 그래서 「없음」은 두 뜻이다 — 에너지 트윈이 아니거나, 세었고\n * 0 이었다. 둘을 가르려면 `energy` 자체가 있는지를 본다: 있으면 세었고 0 이다.\n */\n unknownEquipmentReports:\n typeof snapshot.energy?.unknownEquipment === 'number' ? snapshot.energy.unknownEquipment : snapshot.energy ? 0 : null,\n /*\n * 이제 계량기도 덮는다(커널 0.7.39 가 `MeterPointState.origin` 을 낸다). 덮지 못하는 축이 생기면\n * 다시 여기에 이름을 넣는다 — 화면이 「다 찾아 준다」고 말하지 않게.\n */\n notCovered: []\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"model-refusal.js","sourceRoot":"","sources":["../../server/engine/model-refusal.ts"],"names":[],"mappings":";;AAgBA,wCAKC;AAGD,sCAMC;AA9BD,iDAA+C;AAE/C;;;;;;;;;;;;;GAaG;AACH,SAAgB,cAAc,CAAC,KAAc,EAAE,UAAkB;IAC/D,IAAI,KAAK,YAAY,eAAO;QAAE,OAAO,KAAK,CAAA;IAC1C,MAAM,CAAC,GAAG,KAAuE,CAAA;IACjF,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,IAAI,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAC1G,OAAO,IAAI,eAAO,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,GAAI,CAAC,CAAC,MAA8B,EAAE,UAAU,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;AAC/G,CAAC;AAED,2FAA2F;AAC3F,SAAgB,aAAa,CAAI,UAAkB,EAAE,IAAa;IAChE,IAAI,CAAC;QACH,OAAO,IAAI,EAAE,CAAA;IACf,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,cAAc,CAAC,KAAK,EAAE,UAAU,CAAC,CAAA;IACzC,CAAC;AACH,CAAC","sourcesContent":["import { Refusal } from '@things-factory/shell'\n\n/*\n * **A twin model the kernel refuses is said by name** (ADR-0054).\n *\n * ── What went wrong (2026-10-10) ─────────────────────────────────────────────\n * The MES kernel refuses a model without a production declaration. The engine let that error through\n * as it was, so `twinReplay(miratek)` reached the screen as INTERNAL_SERVER_ERROR with the kernel's\n * English sentence in the shell's notice. The model was what was wrong, not the server.\n *\n * ── The rule ─────────────────────────────────────────────────────────────────\n * The kernel names its refusals: an error that carries a string `code` and an object `params`. The\n * kernel cannot reach the shell, so the host turns that into a shell `Refusal` here, adding which twin\n * it was. The sentence is made where the reader's language is known (`SpeakRefusals`, or the import\n * result's `refusal()`), under `error.<code>`. Anything without a name passes through untouched.\n */\nexport function asModelRefusal(error: unknown, instanceId: string): unknown {\n if (error instanceof Refusal) return error\n const e = error as { code?: unknown; params?: unknown; message?: unknown } | null\n if (!e || typeof e.code !== 'string' || !e.code || !e.params || typeof e.params !== 'object') return error\n return new Refusal(e.code, { ...(e.params as Record<string, any>), instanceId }, String(e.message ?? e.code))\n}\n\n/** Run a step that loads or replays a model; a kernel refusal comes out as a `Refusal`. */\nexport function refusingModel<T>(instanceId: string, step: () => T): T {\n try {\n return step()\n } catch (error) {\n throw asModelRefusal(error, instanceId)\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"model-vocabulary.js","sourceRoot":"","sources":["../../server/engine/model-vocabulary.ts"],"names":[],"mappings":";;;AAyCA,wCA6BC;AAtED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wDAA8E;AAE9E,wDAAwD;AACxD,MAAM,YAAY,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,eAAe,CAAU,CAAA,CAAC,yEAAyE;AAE5I,yDAAyD;AACzD,MAAM,UAAU,GAAG,CAAC,kBAAkB,EAAE,kBAAkB,EAAE,cAAc,EAAE,iBAAiB,CAAU,CAAA;AAEvG;;;;;GAKG;AACH,SAAgB,cAAc,CAAU,KAAQ;IAC9C,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IAE7E,MAAM,IAAI,GAA4B,EAAE,CAAA;IACxC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,EAAE,CAAC;QACtE,IAAK,YAAkC,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,SAAQ;QAC7D,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IACb,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAA;QACtB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;YAAE,SAAQ;QAClC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAC9B,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,WAAW;YACpD,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YACrE,CAAC,CAAC,CAAC,CACN,CAAA;IACH,CAAC;IAED,uDAAuD;IACvD,OAAO,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,IAAA,iCAAkB,EAAC,KAAY,CAAC,EAAE,SAAS,EAAE,IAAA,iCAAkB,EAAC,KAAY,CAAC,EAAO,CAAA;AACnH,CAAC;AAED;;;;;GAKG;AACU,QAAA,sBAAsB,GAAG;IACpC,IAAI,EAAE,CAAC,KAAU,EAAE,EAAE,CAAC,cAAc,CAAC,KAAK,CAAC;IAC3C,EAAE,EAAE,CAAC,KAAU,EAAE,EAAE,CAAC,KAAK;CAC1B,CAAA","sourcesContent":["/*\n * 저장된 트윈 모델의 **어휘 세대를 흡수한다** — 읽는 순간 한 어휘로. 순수.\n *\n * ── 계기 (2026-08-06, 사용자 신고) ───────────────────────────────────────────\n * 트윈 모델러의 엔티티 목록이 **통째로 비어 있었다.** 오류도 없었다. 원인은 이 인스턴스의 저장된 모델가\n * 옛 어휘였고 화면과 호스트는 새 어휘(`locations`/`equipment`)를 읽었기 때문이다. 그 옛 이름들은\n * 아래 `RETIRED_KEYS` 에 있다 — 흡수할 세대의 이름을 적지 않고는 이 파일을 쓸 수 없다.\n *\n * 커널에는 이미 세대를 흡수하는 리더가 있다(`readBoardLocations`·`readBoardEquipment` — 세 세대\n * `movers`→`equipmentList`→`equipment`, 실측 주석에 \"저장된 모델 23개 중 13개가 `movers`\"). 그런데 **그\n * 리더를 거치는 곳이 목록뿐**이었다. 상세·구조 서명·주행 추정기·설비 동기는 컬렉션을 직접 읽어\n * **오류 없이 빈 공장**으로 읽었다:\n *\n * · 모델러·팔레트·보드 자동생성 → 엔티티 0(사용자가 본 증상)\n * · 구조 서명 → `N[]M[]` — **서로 다른 두 공장이 구조적으로 같아 보인다**(변경 감지가 죽는다)\n * · 주행 추정기 → 설비 0이라 거리 기반 소요시간을 못 만든다\n * · 설비 동기 → 아는 설비가 0이라 중복 append + 한 모델에 두 세대가 섞인다\n *\n * ── 왜 자리마다 고치지 않나 ─────────────────────────────────────────────────\n * 저장된 모델를 읽는 자리가 **27곳**이다. 자리마다 리더를 쓰게 하면 다음에 추가되는 자리가 또 빠진다 —\n * 그리고 빠진 것은 오류가 아니라 **빈 공장**이라 들어온 것이 없다. 그래서 **DB에서 읽는 순간 한 번** 정규화한다\n * (엔티티 컬럼 변환기). 그 뒤로는 코드가 세대를 알 필요가 없다.\n *\n * ── 쓰기는 건드리지 않는다 ──────────────────────────────────────────────────\n * 저장은 주는 대로 한다(새 쓰기는 이미 새 어휘다). 읽기가 정규화되므로, 저장된 옛 모델는 **다음에\n * 저장될 때 자연히 새 어휘로 옮겨간다** — 마이그레이션을 따로 실행하지 않는다.\n */\nimport { readBoardLocations, readBoardEquipment } from '@operato/ops-contract'\n\n/** 흡수하고 **내보내지 않는** 옛 키 — 남겨 두면 소비처가 두 어휘를 다시 보게 된다. */\nconst RETIRED_KEYS = ['nodes', 'movers', 'equipmentList'] as const // vocabulary-guard: allow — 옛 어휘를 흡수하는 것이 이 코드의 일이다(세대 이름을 적어야 흡수할 수 있다)\n\n/** 등급 축들 — 넷이 같은 모양이라 같은 규칙을 쓴다(어느 자원이든 표시명은 `name`). */\nconst CLASS_KEYS = ['personnelClasses', 'equipmentClasses', 'assetClasses', 'materialClasses'] as const\n\n/**\n * 보드를 정본 어휘로 — `locations`·`equipment` 만 남는다. 새 어휘 모델에는 **아무 일도 하지 않는다**\n * (같은 값을 다시 담을 뿐이라 멱등).\n *\n * 모델이 아닌 것(없음·배열·원시값)은 **그대로 통과**시킨다 — 모양을 짐작해 고치지 않는다.\n */\nexport function canonicalModel<T = any>(model: T): T {\n if (!model || typeof model !== 'object' || Array.isArray(model)) return model\n\n const rest: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(model as Record<string, unknown>)) {\n if ((RETIRED_KEYS as readonly string[]).includes(k)) continue\n rest[k] = v\n }\n\n /*\n * **등급의 표시명은 `name` 이다** — 옛 모델는 `description` 에 담았다.\n *\n * 표준 마스터가 등급 표시명을 `description` 으로 주기 때문에 그대로 저장돼 있었고, 화면은 그것을\n * \"설명\" 열에 앉혔다 — 사용자에게는 이름인데 머리글은 설명이라, 같은 것을 축마다 다른 이름으로\n * 부르게 된다. 인제스트 문에서 이미 `name` 으로 옮기고 있지만, **이미 저장된 모델**가 그대로 남아\n * 새 트윈만 고쳐진다. 정규화의 집은 여기 하나다(위 머리말의 이유 그대로).\n */\n for (const key of CLASS_KEYS) {\n const list = rest[key]\n if (!Array.isArray(list)) continue\n rest[key] = list.map((c: any) =>\n c && typeof c === 'object' && !c.name && c.description\n ? (({ description, ...keep }) => ({ ...keep, name: description }))(c)\n : c\n )\n }\n\n /* 리더가 세대를 고른다 — 여기서 `??` 사슬을 다시 적으면 세대 규칙이 두 곳이 된다. */\n return { ...rest, locations: readBoardLocations(model as any), equipment: readBoardEquipment(model as any) } as T\n}\n\n/**\n * 엔티티 컬럼 변환기 — **읽을 때만** 정규화한다.\n *\n * `to`(쓰기)는 손대지 않는다: 저장은 호출부가 준 그대로가 진실이고, 읽기가 정규화되므로 다음 저장에서\n * 자연히 새 어휘가 된다.\n */\nexport const modelColumnTransformer = {\n from: (value: any) => canonicalModel(value),\n to: (value: any) => value\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"oee-accumulator.js","sourceRoot":"","sources":["../../server/engine/oee-accumulator.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * OEE 이벤트 누적기 (순수·근사) — live-모드 OEE 입력 생산의 한 경로(face2-inbound-live §1.1, option ②).\n *\n * 문제: OEE 공식(computeOee)은 sim/live 공유되나, 입력 카운터(runMs/downMs/…)는 미러 State·이벤트에 없다\n * (emitEquipment 는 status/location/motion 만 방출). → live 는 카운터를 별도 생산해야 한다.\n * 이 누적기: equipment.status 전이 스트림으로 무버별 시간-in-상태를 누적해 카운터를 근사 생산.\n *\n * 정확: downMs(고장 시간 = down 구간 합) · quality(good/scrap = quality.output 델타). 근사·한계:\n * - busyMs 를 runMs 로(setup 분리 불가 — 이벤트에 setup 없음) → performance 낙관·availability 는 setup 손실 미반영.\n * - quality.output 이 없으면(예: WMS/YMS) quality=1(품질 개념 없음).\n * 시간축 = Date.parse(eventTime)(ms) — 구간 차분은 sim/실시간 무관하게 정확(고정 오프셋 상쇄).\n */\n/* OEE 공식은 계약에 있다 — 소비자가 셋이라(시뮬·미러·MES) 커널에 두면 밖에서 다시 쓰게 된다. */\nimport { computeOee, nonWorkingMsIn } from '@operato/ops-contract'\nimport type { OeeCounters, WorkCalendarEntry } from '@operato/ops-contract'\nimport type { CanonicalEnvelope, OeeMetrics } from '@operato/ops-contract'\n\ninterface Acc {\n status?: string\n lastMs: number\n startMs: number\n busyMs: number\n downMs: number\n goodCount: number\n nonconformingCount: number\n}\n\n/** 누적기의 완전한 재개점 — 저장했다 되살릴 수 있어야 꼬리만 계산할 수 있다. */\nexport interface OeeCheckpoint {\n maxMs: number\n acc: ({ moverId: string } & Acc)[]\n}\n\nexport class OeeAccumulator {\n private acc = new Map<string, Acc>()\n private maxMs = 0 // 최신 이벤트 시각 — nowMs 기본값(현재 계측 시점)\n\n /*\n * ── 현장이 언제 도나 — **계획 조업 시간의 근거** (2026-09-07) ────────────────\n * 이것이 없던 동안 계획 조업 시간이 「재기 시작한 뒤 흐른 시간 전부」였다. 주말도 야간도 휴일도\n * 계획 조업으로 세었다.\n *\n * [산술] 8시간 × 주5일이면 168시간 중 40시간이 조업이다 — PBT 가 4.2배 크고 가동률이 그만큼 작다.\n * 그리고 그 수가 1로 잘려 있어서(오늘 걷었다) 그럴싸하게 보였다. 두 오류가 서로를 가렸다.\n *\n * plant 레인이 같은 자리를 먼저 겪고 실측을 냈다 — 3교대를 물려받으면 `availability 0.4583`,\n * 달력이 아예 없으면 **수를 안 낸다**. 트윈은 창 전체를 계획으로 쳐서 수를 냈다.\n *\n * ⚠ **선언이 없으면 채우지 않는다.** `nonWorkingMsIn` 이 그때 `undefined` 를 내고, 그 값을 0 으로\n * 바꾸지 않는다 — 0 은 「달력을 봤고 쉬는 시간이 없었다」는 주장이다. 지금은 그러면 PBT 가 창 전체가\n * 되어 예전과 같은 수가 나온다. **그것을 「안 낸다」로 바꿀지는 아직 결정되지 않았다** — 거동 변화가\n * 크고 화면이 비는 자리라 사용자 판단을 기다린다(성숙도에 미달로 세우는 짝과 함께 가야 한다).\n */\n private siteCalendar?: readonly WorkCalendarEntry[]\n private calendarByResource = new Map<string, readonly WorkCalendarEntry[]>()\n private utcOffsetMinutes?: number\n\n /**\n * 모델에서 조업 달력을 읽어 얹는다 — 모델을 들고 있는 쪽(`TwinEngine`)이 부른다.\n *\n * ── 우선순위가 여기 있는 이유 ───────────────────────────────────────────────\n * ```\n * 설비의 달력 이 설비의 가동 계획 — 선언했으면 이것이 이긴다 (24시간 도는 로)\n * 현장의 달력 이 현장의 조업일\n * 없음 제약 없음 — 계획 조업 시간이 창 전체가 된다\n * ```\n * 계획 조업 시간을 내는 자리가 여기라 그 규칙도 여기 둔다. 부르는 쪽마다 고르게 하면 라이브와\n * 히스토리가 다른 답을 낸다.\n *\n * ⚠ **설비의 달력은 사람의 근무조가 아니다.** 씨앗이 사람의 근무조를 설비 달력에 복사한 일이 있었고,\n * 그때 아무도 근무하지 않는 시간이 근무 시간으로 기록됐다. 사람 쪽은 `persons[].workCalendar` 다.\n *\n * 세우는 자리에서 한 번만 주지 않고 따로 부르는 이유는 모델이 다시 실릴 수 있기 때문이다\n * (재동기화 · 이관). 그때 달력만 갈아 끼우면 누적된 계측을 버리지 않는다.\n */\n setWorkCalendar(model?: { workCalendar?: readonly WorkCalendarEntry[]; equipment?: readonly any[]; utcOffsetMinutes?: number }): void {\n this.siteCalendar = model?.workCalendar?.length ? model.workCalendar : undefined\n this.utcOffsetMinutes = model?.utcOffsetMinutes\n this.calendarByResource.clear()\n for (const e of model?.equipment ?? []) {\n const cal = e?.workCalendar\n if (e?.id && Array.isArray(cal) && cal.length) this.calendarByResource.set(String(e.id), cal)\n }\n }\n\n /**\n * 이 설비의 계획 가동 달력 — 자기 것이 있으면 그것, 없으면 현장의 것.\n *\n * 인자 이름이 `moverId` 인 것은 **봉투의 옛 세대 필드 이름**이라 그렇다(§`apply`). 새로 만드는\n * 이름에는 그 낱말을 쓰지 않는다 — 어휘 가드가 그것을 잡았고, 맞게 잡았다.\n */\n private calendarFor(moverId: string): readonly WorkCalendarEntry[] | undefined {\n return this.calendarByResource.get(moverId) ?? this.siteCalendar\n }\n\n private ensure(moverId: string, t: number): Acc {\n let a = this.acc.get(moverId)\n if (!a) {\n a = { lastMs: t, startMs: t, busyMs: 0, downMs: 0, goodCount: 0, nonconformingCount: 0 }\n this.acc.set(moverId, a)\n }\n return a\n }\n\n /**\n * **재개점** — 이 누적기의 상태 전부(이어 계산의 씨앗).\n *\n * ── 왜 필요한가 (2026-08-18) ────────────────────────────────────────────────\n * 가동 이력은 원 시스템이 누적을 주지 않아 **우리가 이벤트를 적분해** 만든다. 그래서 저널을 꼬리만\n * 계산하면 이 누적기는 꼬리분만 갖게 되고, 화면의 가용률·수율이 알리지 않고 작아진다(오류는 나지 않는다).\n * 재개점을 함께 저장·복원해야 「이어 계산한 값」이 「0부터 계산한 값」과 같아진다.\n */\n serialize(): OeeCheckpoint {\n return {\n maxMs: this.maxMs,\n acc: [...this.acc.entries()].map(([moverId, a]) => ({ moverId, ...a }))\n }\n }\n\n /** 재개점에서 되세운다 — 모르는 것은 지어내지 않는다(없으면 빈 누적기다). */\n restore(cp: OeeCheckpoint | undefined | null): void {\n this.maxMs = cp?.maxMs ?? 0\n this.acc = new Map((cp?.acc ?? []).map(({ moverId, ...a }) => [moverId, { ...a }]))\n }\n\n /** equipment.status(시간-in-상태) + quality.output(양품/불량) 소비. 다른 eventType 무시. */\n apply(env: CanonicalEnvelope): void {\n const t = Date.parse(env.eventTime)\n if (t > this.maxMs) this.maxMs = t\n if (env.eventType === 'quality.output') {\n const q = env.data as { moverId?: string; goodCount?: number; nonconformingCount?: number }\n if (!q?.moverId) return\n const a = this.ensure(q.moverId, t)\n a.goodCount = q.goodCount ?? a.goodCount // 누적값 최신화(good/scrap 은 무버 누적)\n a.nonconformingCount = q.nonconformingCount ?? a.nonconformingCount\n return\n }\n if (env.eventType !== 'equipment.status') return\n const d = env.data as { moverId?: string; status?: string }\n if (!d?.moverId) return\n const a = this.ensure(d.moverId, t)\n const dt = Math.max(0, t - a.lastMs)\n if (a.status === 'busy') a.busyMs += dt\n else if (a.status === 'down') a.downMs += dt\n a.status = d.status\n a.lastMs = t\n }\n\n /** 무버 OEE 카운터 — 마지막 상태 구간을 nowMs(기본=최신 이벤트 시각)까지 flush. busy→run 근사, setup=0. */\n counters(moverId: string, nowMs: number = this.maxMs): OeeCounters {\n const a = this.acc.get(moverId)\n /*\n * **이 설비의 이벤트를 하나도 못 봤다** — 계측 창이 없다는 뜻이고, 그것을 `0` 으로 메우면\n * \"1970년부터 재고 있었다\" 가 된다. 시각이 밀리초 epoch 이므로 그 값이 곧 지금 시각이 되어,\n * 화면에 `idleMs 1767226603000`(약 56,000년) 유휴가 섰다. 창을 **말하지 않는다** — 공식이\n * 없음을 없음으로 다룬다(§computeOee).\n */\n if (!a) return { runMs: 0, setupMs: 0, downMs: 0, goodCount: 0, nonconformingCount: 0 }\n const dt = Math.max(0, nowMs - a.lastMs)\n let busyMs = a.busyMs\n let downMs = a.downMs\n if (a.status === 'busy') busyMs += dt\n else if (a.status === 'down') downMs += dt\n /*\n * 계획 정지 — 계약이 이 칸에 그 뜻을 적어 뒀다(점심 · 예방보전 · 교대). 휴일과 주말이 같은\n * 갈래다. `metricsSinceMs` 를 당겨서 같은 효과를 내지 않는다: 그 칸은 「언제부터 쟀나」라는\n * 사실이고, 파생값을 얹으면 실제보다 늦게 재기 시작했다고 말하는 것이 된다.\n */\n const holdMs = nonWorkingMsIn(this.calendarFor(moverId), a.startMs, nowMs, this.utcOffsetMinutes)\n return {\n runMs: busyMs,\n setupMs: 0,\n downMs,\n goodCount: a.goodCount,\n nonconformingCount: a.nonconformingCount,\n metricsSinceMs: a.startMs,\n ...(holdMs !== undefined ? { holdMs } : {})\n }\n }\n\n /** 공유 공식으로 근사 OEE 산출 — 정확한 OEE 는 실 텔레메트리(option ①). nowMs 기본=최신 이벤트 시각. */\n oee(moverId: string, nowMs: number = this.maxMs): OeeMetrics {\n return computeOee(this.counters(moverId, nowMs), nowMs)\n }\n}\n\n/**\n * live State 를 OEE 로 보강 — projector 미러 State(관측)의 무버에 누적기 OEE(계산)를 채운다.\n * live-모드 publishEntityData 가 buildEntityDeltas 전에 적용 → equipment data(tag) payload 에 oee 포함\n * (sim 은 커널이 채움 — 이로써 live 도 동일 payload 형상). 순수(입력 불변, 얕은 복사).\n */\nexport function withLiveOee<S extends { equipment?: any[] }>(state: S, acc: OeeAccumulator, nowMs?: number): S {\n if (!state?.equipment) return state\n return { ...state, equipment: state.equipment.map(m => ({ ...m, oee: acc.oee(m.id, nowMs) })) }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"operation-basis.js","sourceRoot":"","sources":["../../server/engine/operation-basis.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 이 트윈이 **어떤 공정을 돌리고, 그 시간이 어디서 오나.**\n *\n * ── 왜 이 목록이 필요한가 (2026-08-18) ─────────────────────────────────────\n * 「측정이 없을 때의 표준 시간」을 현장이 선언하려면 먼저 **무엇에 선언할지** 보여야 한다. 그런데 공정은\n * 자리·설비와 달리 모델에 행이 없을 수 있다 — 실측: 운영 중인 트윈 35개 중 창고·야드는 공정 명세 행이\n * 0개였고(MES만 4개), 그 트윈들의 시간은 전부 커널 상수에서 왔다.\n *\n * 그래서 목록을 **지어내지 않고 네 곳에서 모은다**:\n * ① 원천 명세 행(`model.operations`) — 원 시스템이 말한 공정.\n * ② 커널의 자기보고(`specCoverage()`) — **실제로 돌린** 공정과 그 시간의 출처.\n * ③ 이력에서 배운 종류(`measuredOperationKinds`) — 재기동에도 남는 근거.\n * ④ 우리가 이미 선언한 것(`model.localDurations`·`model.localParams`) — 선언해 둔 공정이 목록에서\n * 사라지지 않게(사라지면 철회할 자리가 없다).\n *\n * ③이 필요한 이유는 실측으로 드러났다: ②는 **작업이 새로 생길 때** 채워지므로, 이력이 풍부한 창고\n * 트윈도 재기동 직후에는 목록이 비었다 — 값을 채울 곳을 보여야 하는 화면이 정작 그때 아무 말도 못 한다.\n *\n * 그래도 셋 다 비는 트윈은 있다(한 번도 돌지 않고 명세 행도 없는 새 트윈). 그때 「공정이 없다」고 말하지\n * 않는다(모르는 것이다) — 부르는 쪽이 그 사실을 그대로 화면에 옮긴다.\n */\nimport { readSpecCoverage } from './spec-coverage.ts'\n\n/** 공정 한 종류 — 이름과 「지금 무엇으로 도나」. */\nexport interface OperationBasisRow {\n /** 작업 종류(`FlowTask.kind` = `OperationDef.key`) — 선언이 겨누는 id 다. */\n kind: string\n /** 사람이 읽는 이름 — 원천 명세 행이 있을 때만(없으면 종류 이름이 곧 이름이다). */\n label?: string\n /**\n * 지금 이 종류의 시간이 어디서 오나 — 커널의 자기보고, 또는 **이력에서 배운 사실**. 없으면 모른다.\n * · `measured` — 이 현장의 이력에서 배운 값(가장 강한 근거). **선언보다 이긴다.**\n * · `declared` — 선언된 값(현장이 정한 것 또는 원천 명세 행).\n * · `default` — 커널 소스의 상수. 이 목록의 존재 이유다.\n */\n basis?: 'measured' | 'declared' | 'default'\n /** 원천 명세 행이 말한 시간(ISO 8601) — 있으면. 우리 선언이 이것을 이긴다. */\n referenceDuration?: string\n /** 우리가 선언해 둔 시간(ISO 8601) — 있으면. */\n localDuration?: string\n /**\n * 우리가 선언해 둔 공정 모수 — **커널이 읽는 표기 그대로**(수율 0..1, 셋업 ISO 8601).\n *\n * 사람 단위(퍼센트·분)로 되돌리지 않는다: 화면은 선언 겹에서 사용자가 넣은 값을 그대로 들고 있고,\n * 여기 있는 것은 **실제로 커널에 실린 값**이다 — 둘을 견주면 환산이 잘못된 것도 드러난다.\n */\n localParams?: Record<string, string>\n /** 원천 명세 행이 말한 모수 — 있으면(우리 선언이 이것을 이긴다). */\n referenceParams?: Record<string, string>\n /**\n * 모수를 **무엇으로 읽었나** — 커널의 자기보고(`id → measured|declared`) (2026-08-19).\n *\n * 수율이 이력에서 온 값이면 선언값은 예비로만 남는다(소요시간과 같은 규율) — 화면이 그 사실을 말해야\n * 사람이 헛일을 하지 않는다. 커널이 아직 그 공정을 돌리지 않았으면 이 칸이 빈다(모른다).\n */\n paramBasis?: Record<string, 'measured' | 'declared'>\n}\n\n/**\n * 공정 목록을 모은다 — **넷의 합집합**, 순서는 원천 행 → 돌린 것 → 이력에서 배운 것 → 선언한 것.\n *\n * `kernel` 이 없으면(정지 트윈) ②가 빠지고, `measuredKinds` 를 주지 않으면 ③이 빠진다: 그 사실은\n * `basis` 가 비는 것으로 드러난다(빈칸이 곧 「모른다」다).\n */\nexport function operationBasis(model: any, kernel?: any, measuredKinds?: readonly string[]): OperationBasisRow[] {\n const rows = new Map<string, OperationBasisRow>()\n const at = (kind: string): OperationBasisRow => {\n const cur = rows.get(kind)\n if (cur) return cur\n const row: OperationBasisRow = { kind }\n rows.set(kind, row)\n return row\n }\n\n for (const o of (model?.operations ?? []) as any[]) {\n if (!o?.key) continue\n const row = at(String(o.key))\n if (o.label) row.label = String(o.label)\n if (o.duration) row.referenceDuration = String(o.duration)\n /* 원천이 말한 모수도 함께 든다 — 우리 선언과 견줄 수 있어야 「무엇을 고쳤나」가 보인다. */\n for (const p of (o.parameters ?? []) as any[]) {\n if (!p?.id || p.value === undefined) continue\n row.referenceParams = { ...(row.referenceParams ?? {}), [String(p.id)]: String(p.value) }\n }\n }\n\n const coverage = kernel ? readSpecCoverage(kernel) : undefined\n for (const o of coverage?.operations ?? []) {\n if (!o?.kind) continue\n const row = at(String(o.kind))\n row.basis = o.duration\n if (o.parameterBasis && Object.keys(o.parameterBasis).length) row.paramBasis = { ...o.parameterBasis }\n }\n\n /*\n * 이력에서 배운 종류 — 커널이 아직 이 종류를 돌리지 않았어도 **추정기가 답할 수 있다**. 그래서 근거를\n * `measured` 로 채운다: 짐작이 아니라 설치된 추정기가 그 종류에 실측값을 줄 것이라는 사실이다.\n * 커널이 이미 보고한 종류는 **덮지 않는다** — 실제로 돌린 것이 더 강한 사실이다.\n */\n for (const kind of measuredKinds ?? []) {\n if (!kind) continue\n const row = at(String(kind))\n if (!row.basis) row.basis = 'measured'\n }\n\n const declared = (model?.localDurations ?? {}) as Record<string, string>\n for (const [kind, iso] of Object.entries(declared)) {\n if (!kind) continue\n at(kind).localDuration = String(iso)\n }\n\n const params = (model?.localParams ?? {}) as Record<string, Record<string, string>>\n for (const [kind, entries] of Object.entries(params)) {\n if (!kind) continue\n const row = at(kind)\n for (const [id, value] of Object.entries(entries ?? {})) row.localParams = { ...(row.localParams ?? {}), [id]: String(value) }\n }\n\n return [...rows.values()]\n}\n\n/**\n * 선언을 받아도 되는 공정 이름들 — **하나도 확인할 수 없으면 `undefined`**(모른다).\n *\n * 빈 배열로 답하지 않는 이유: 빈 배열은 「어떤 이름도 안 된다」로 읽히고, 그러면 아직 돌지 않은 트윈에는\n * 아무것도 선언할 수 없다. 모르는 것과 없는 것은 다르다 — 모를 때는 받아 주고 그 사실을 말한다.\n */\nexport function knownOperationKinds(model: any, kernel?: any, measuredKinds?: readonly string[]): string[] | undefined {\n const kinds = operationBasis(model, kernel, measuredKinds).map(r => r.kind)\n return kinds.length ? kinds : undefined\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"producer-guard.js","sourceRoot":"","sources":["../../server/engine/producer-guard.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;;;AAWH,gCAQC;AAQD,4EAeC;AAxCY,QAAA,cAAc,GAAG,kCAAkC,CAAA;AAQhE,wGAAwG;AACxG,SAAgB,UAAU,CAAC,MAAe;IACxC,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAA;IAC3D,MAAM,CAAC,GAAG,MAAoE,CAAA;IAC9E,IAAI,CAAC,CAAC,IAAI,KAAK,WAAW,IAAI,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM;QAAE,OAAO,aAAa,CAAC,CAAC,MAAM,EAAE,CAAA;IACtG,IAAI,CAAC,CAAC,IAAI,KAAK,UAAU,IAAI,OAAO,CAAC,CAAC,UAAU,KAAK,QAAQ,IAAI,CAAC,CAAC,UAAU;QAAE,OAAO,YAAY,CAAC,CAAC,UAAU,EAAE,CAAA;IAChH,0GAA0G;IAC1G,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI;QAAE,OAAO,GAAG,CAAC,CAAC,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,UAAU,IAAI,EAAE,CAAC,EAAE,CAAA;IACtG,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,SAAS,MAAM,CAAC,MAAe;IAC7B,MAAM,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAA+C,CAAA;IACtE,OAAO,MAAM,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,UAAU,IAAI,EAAE,CAAC,IAAI,YAAY,CAAA;AAC/D,CAAC;AAED,4FAA4F;AAC5F,SAAgB,gCAAgC,CAC9C,QAAgB,EAChB,WAAoB,EACpB,cAAuB;IAEvB,MAAM,GAAG,GAAG,UAAU,CAAC,WAAW,CAAC,CAAA;IACnC,MAAM,KAAK,GAAG,UAAU,CAAC,cAAc,CAAC,CAAA;IACxC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAA;IAC/B,IAAI,GAAG,IAAI,KAAK,IAAI,GAAG,KAAK,KAAK;QAAE,OAAO,IAAI,CAAA;IAC9C,MAAM,QAAQ,GAAG,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,YAAY,CAAA;IACzD,OAAO;QACL,IAAI,EAAE,sBAAc;QACpB,MAAM,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE;QAC9B,OAAO,EAAE,SAAS,QAAQ,iBAAiB,QAAQ,0DAA0D;KAC9G,CAAA;AACH,CAAC","sourcesContent":["/*\n * **A twin made by one producer is not rebuilt by another.** Pure.\n *\n * ── What went wrong (2026-09-24) ─────────────────────────────────────────────\n * An import's instance id is the adapter's site id (`master.source`). Importing the warehouse connection\n * `wms-miratek` with site id `miratek` — the same as the plant connection's — landed on the running plant\n * mirror `miratek`, and `ingestMaster` gave it the warehouse structure as a new revision: 16 places,\n * 23 equipment, 17 people and 14 assets gone. The row already said who made it (`origin.source` was\n * `mes-miratek`); nothing asked.\n *\n * ── The rule (chief architect ruling, 2026-09-24) ────────────────────────────\n * Compare producers by identity, not by the whole origin: a reference is kind + source, a template is\n * kind + templateId. The same import carries more or fewer fields at different call sites (siteId,\n * spaceId), and a template re-run has new params — those are the same producer re-reading its own twin.\n *\n * both declared, same producer allowed (resync, re-import, re-run)\n * both declared, different refused, naming the producer that owns it\n * prior undeclared, incoming not refused — nobody can say who made it; delete it first to replace it\n * prior declared, incoming not refused — same reason, the other way\n * neither declared allowed — the demo seed re-ingesting its own twin at boot. Closes when\n * the seed declares a template origin.\n */\n\nexport const INSTANCE_TAKEN = 'instance-taken-by-another-source'\n\nexport interface ProducerRefusal {\n code: typeof INSTANCE_TAKEN\n params: { instance: string; producer: string }\n message: string\n}\n\n/** Who produced a twin, as one comparable string — or undefined when the origin says nothing usable. */\nexport function producerOf(origin: unknown): string | undefined {\n if (!origin || typeof origin !== 'object') return undefined\n const o = origin as { kind?: unknown; source?: unknown; templateId?: unknown }\n if (o.kind === 'reference' && typeof o.source === 'string' && o.source) return `reference:${o.source}`\n if (o.kind === 'template' && typeof o.templateId === 'string' && o.templateId) return `template:${o.templateId}`\n /* A kind this guard does not know is still somebody — keep it distinct rather than reading it as none. */\n if (typeof o.kind === 'string' && o.kind) return `${o.kind}:${String(o.source ?? o.templateId ?? '')}`\n return undefined\n}\n\nfunction nameOf(origin: unknown): string {\n const o = (origin ?? {}) as { source?: unknown; templateId?: unknown }\n return String(o.source ?? o.templateId ?? '') || 'undeclared'\n}\n\n/** null when `incoming` may rebuild the twin `prior` was made by; otherwise the refusal. */\nexport function refuseOverwriteByAnotherProducer(\n instance: string,\n priorOrigin: unknown,\n incomingOrigin: unknown\n): ProducerRefusal | null {\n const had = producerOf(priorOrigin)\n const comes = producerOf(incomingOrigin)\n if (!had && !comes) return null\n if (had && comes && had === comes) return null\n const producer = had ? nameOf(priorOrigin) : 'undeclared'\n return {\n code: INSTANCE_TAKEN,\n params: { instance, producer },\n message: `twin \"${instance}\" was made by ${producer} — delete it first, or import this site under another id`\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"property-effects.js","sourceRoot":"","sources":["../../server/engine/property-effects.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 자원 속성의 **효과 등록부** — 이 속성이 무엇을 바꾸나.\n *\n * ── 왜 있나 ─────────────────────────────────────────────────────────────────\n * 속성은 **열려 있다**: 템플릿·마스터가 아무 id 로나 사실을 실어 보낼 수 있고, 커널은 그것을 그대로\n * 나르고 화면은 단위와 함께 보인다(표준이 속성 어휘를 정하지 않으므로 그것이 옳다).\n *\n * 그런데 **효과는 닫혀 있다**: 값이 계산·배정·판정을 바꾸려면 그 규칙을 코드가 알아야 한다. 지금\n * 효과가 있는 것은 둘뿐이다. 문제는 그 사실이 **어디에도 드러나지 않는다**는 것이었다 — 사용자는\n * 속성을 넣으면 무언가 달라질 것으로 기대하는데, 실제로는 표에 한 줄이 늘 뿐이다.\n *\n * 그래서 이 등록부는 **지어내지 않고 드러낸다**: 읽는 코드가 있는 것만 적고, 없는 것은 \"표시만 된다\"\n * 고 화면이 말한다. 새 효과를 코드로 붙이는 사람은 **여기 한 줄을 더한다** — 그러지 않으면 사용자는\n * 그 효과가 있는지 알 수 없다.\n *\n * ⚠️ 이 파일은 등록부이고 판정이 아니다. 값을 실제로 읽는 곳은 각자다(아래 `readBy` 가 그 자리를\n * 가리킨다) — 여기에 계산을 옮겨 오면 규칙이 두 벌이 된다.\n */\n\n/** 효과가 있는 속성 하나. */\nexport interface PropertyEffect {\n /** 속성 id — 선언에 적히는 그 이름(`ResourceProperty.id`). */\n id: string\n /** 어느 축의 속성인가 — 그 축의 항목에만 이 효과를 말한다. */\n axes: string[]\n /** 무엇을 바꾸나 — 화면이 읽는 i18n 키(언어중립: 서버는 사람 말을 만들지 않는다). */\n effect: string\n /** 실제로 읽는 코드 — 지어낸 효과를 등록하지 않기 위한 표식이다(없는 자리를 가리키면 리뷰가 잡는다). */\n readBy: string\n}\n\nexport const PROPERTY_EFFECTS: PropertyEffect[] = [\n {\n id: 'speed',\n axes: ['equipment'],\n effect: 'twin.propeffect.speed',\n readBy: 'headless-twin/server/engine/travel-estimator.ts (buildTravelEstimator)'\n },\n {\n id: 'perBaseUnit',\n axes: ['materialDefinitions'],\n effect: 'twin.propeffect.perBaseUnit',\n readBy: '@operato/twin-kernel conversionFactorOf'\n },\n /*\n * ── 에너지 속성 (2026-08-18) ──────────────────────────────────────────────\n *\n * 이 값들은 **오래전부터 읽히고 있었다** — 커널이 부하·피크·배터리를 그것으로 계산하고, 호스트가\n * 금액을 그것으로 만든다. 그런데 등록부에 없어서 화면은 그 값들에 대해 「표시만 됩니다」라고 말했다.\n * 들어온 것이 없는 거짓말이다: 사용자는 계약전력을 넣고도 「이 값은 아무것도 바꾸지 않는다」는 문장을 읽었다.\n *\n * 효과를 새로 만든 것이 아니라 **있는 효과를 드러낸다.** `readBy` 는 실제로 읽는 자리를 가리킨다.\n */\n {\n id: 'contract.kW',\n axes: ['locations'],\n effect: 'twin.propeffect.contractKW',\n readBy: '@operato/twin-kernel EmsKernel.declaredContractKW (계약 대비 판정·초과 주의)'\n },\n {\n id: 'power.ratedKW',\n axes: ['equipment'],\n effect: 'twin.propeffect.ratedKW',\n readBy: '@operato/twin-kernel EmsKernel.deriveEquipmentLoad (가동 중 부하)'\n },\n {\n id: 'power.standbyKW',\n axes: ['equipment'],\n effect: 'twin.propeffect.standbyKW',\n readBy: '@operato/twin-kernel EmsKernel.deriveEquipmentLoad (대기 중 부하)'\n },\n {\n id: 'storage.capacityKWh',\n axes: ['equipment'],\n effect: 'twin.propeffect.capacityKWh',\n readBy: '@operato/twin-kernel EmsKernel (축전지 방전 가능량)'\n },\n {\n id: 'dispatch.dischargeAboveKW',\n axes: ['equipment'],\n effect: 'twin.propeffect.dischargeAboveKW',\n readBy: '@operato/twin-kernel EmsKernel (방전 시작 문턱)'\n },\n {\n id: 'dispatch.maxDischargeKW',\n axes: ['equipment'],\n effect: 'twin.propeffect.maxDischargeKW',\n readBy: '@operato/twin-kernel EmsKernel (인버터 방전 상한)'\n },\n {\n id: 'dispatch.reserveSoc',\n axes: ['equipment'],\n effect: 'twin.propeffect.reserveSoc',\n readBy: '@operato/twin-kernel EmsKernel (이 아래로는 방전하지 않는다)'\n },\n {\n id: 'storage.initialSoc',\n axes: ['equipment'],\n effect: 'twin.propeffect.initialSoc',\n readBy: '@operato/twin-kernel EmsKernel (시작 충전율 — 예측·what-if 의 씨앗)'\n },\n {\n id: 'generation.ratedKW',\n axes: ['equipment'],\n effect: 'twin.propeffect.genRatedKW',\n readBy: '@operato/twin-kernel EmsKernel.deriveGeneration (정격 발전)'\n },\n {\n id: 'generation.dailyProfile',\n axes: ['equipment'],\n effect: 'twin.propeffect.genDailyProfile',\n readBy: '@operato/twin-kernel generationFractionAt (시각별 발전 비율)'\n },\n /* 요금은 커널이 읽지 않는다 — 금액은 저널을 계산하는 호스트가 만든다. readBy 가 그 사실을 말한다. */\n {\n id: 'tariff.demandChargePerKW',\n axes: ['locations'],\n effect: 'twin.propeffect.demandChargePerKW',\n readBy: 'headless-twin/server/engine/kpi-query.ts (수요요금 계산)'\n },\n {\n id: 'tariff.energyChargePerKWh',\n axes: ['locations'],\n effect: 'twin.propeffect.energyChargePerKWh',\n readBy: 'headless-twin/server/engine/kpi-query.ts (전력량 요금 계산)'\n },\n /* 판정 기준 — 값이 경보의 유무를 바꾼다(가장 눈에 보이는 효과다). */\n {\n id: 'attention.congestionRatio',\n axes: ['locations'],\n effect: 'twin.propeffect.congestionRatio',\n readBy: '@operato/twin-kernel deriveAttentions (병목 판정 기준)'\n },\n {\n id: 'attention.scrapRate',\n axes: ['equipment'],\n effect: 'twin.propeffect.scrapRate',\n readBy: '@operato/twin-kernel deriveAttentions (불량 과다 판정 기준)'\n },\n {\n id: 'attention.scrapMinSamples',\n axes: ['equipment'],\n effect: 'twin.propeffect.scrapMinSamples',\n readBy: '@operato/twin-kernel deriveAttentions (불량률을 재기 시작할 표본 수)'\n },\n /*\n * ── 공정 소요시간 (2026-08-18) ────────────────────────────────────────────\n *\n * 실측: 운영 중인 트윈 35개 중 **창고·야드는 공정 명세 행이 0개**였다 — 그 트윈들의 시간은 전부 커널\n * 상수에서 왔고, 예측은 「같은 조건에서의 상대 비교」에 머물렀다(절대 시각 주장 불가). 이 선언이\n * 그 사다리를 한 칸 올린다: 실측이 없어도 **현장이 정한 시간**으로 굴러간다.\n *\n * 실측(이력에서 배운 값)은 여전히 이 선언을 이긴다 — 화면이 그 사실을 함께 말한다.\n */\n {\n id: 'operation.duration',\n axes: ['operations'],\n effect: 'twin.propeffect.operationDuration',\n readBy: '@operato/twin-kernel FlowEngine.durationOf ← declareDurations (engine/local-declarations.ts 가 실어 보낸다)'\n },\n /*\n * ── 공정 모수 (2026-08-19) ────────────────────────────────────────────────\n * 양품률의 기본값은 커널 소스의 상수(0.8)였고, **그 상수가 불량 과다 주목 신호까지 만들었다** — 초기\n * 라인이든 성숙한 라인이든 같은 수로 판정된다. 셋업도 상수였다. 둘 다 현장이 말할 수 있어야 한다.\n */\n {\n id: 'operation.yield',\n axes: ['operations'],\n effect: 'twin.propeffect.operationYield',\n readBy: '@operato/twin-kernel MesKernel(OP_PARAM.yield) ← declareParameters (양품/불량 판정과 산출량)'\n },\n {\n id: 'operation.setupDuration',\n axes: ['operations'],\n effect: 'twin.propeffect.operationSetup',\n readBy: '@operato/twin-kernel MesKernel(OP_PARAM.setupDuration) ← declareParameters (가동 앞에 붙는 셋업)'\n },\n {\n id: 'tariff.currency',\n axes: ['locations'],\n effect: 'twin.propeffect.currency',\n readBy: 'headless-twin/server/engine/kpi-query.ts (금액 표기 통화)'\n }\n]\n\n/**\n * 이 축의 속성 효과 — `속성 id → i18n 키`.\n *\n * 없는 속성은 **여기 없다**(빈 값으로 채우지 않는다) — 화면이 \"등록된 효과가 없다\" 를 스스로 말한다.\n */\nexport function propertyEffectsOf(axis: string): Record<string, string> {\n const out: Record<string, string> = {}\n for (const e of PROPERTY_EFFECTS) if (e.axes.includes(axis)) out[e.id] = e.effect\n return out\n}\n\n/** 등록부의 축 이름 → 선언이 겨누는 대상 이름. 대상이 아닌 축(자재 정의 등)은 여기 없다. */\nconst AXIS_TO_TARGET: Record<string, string> = { locations: 'location', equipment: 'equipment', operations: 'operation' }\n\n/**\n * 이 속성을 **어느 대상에 얹을 수 있나** — 등록부의 축이 그대로 답한다.\n *\n * ── 왜 필요한가 (2026-08-18) ────────────────────────────────────────────────\n * 지게차에 계약전력을 선언할 수 있었다. 문은 받아 주고 화면은 「넣었습니다」라고 말하는데, 그 값을 읽는\n * 코드는 자리(수전)만 본다 — 아무 일도 일어나지 않고 그 사실이 어디에도 나오지 않았다. 효과가 어느 축의\n * 것인지는 이미 등록부가 알고 있으므로, 그 지식을 검사에 쓴다(두 곳에 적지 않는다).\n *\n * 등록부에 없는 속성은 **비어 있는 목록이 아니라 `undefined`** 다: 「어디에도 못 얹는다」와 「우리가\n * 제한을 모른다」는 다르다(후자는 받아 준다 — 속성은 열려 있다는 규약을 지킨다).\n */\nexport function declarationTargetsOf(propertyId: string): string[] | undefined {\n const e = PROPERTY_EFFECTS.find(x => x.id === propertyId)\n if (!e) return undefined\n const targets = e.axes.map(a => AXIS_TO_TARGET[a]).filter((t): t is string => !!t)\n return targets.length ? targets : undefined\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"read-time.js","sourceRoot":"","sources":["../../server/engine/read-time.ts"],"names":[],"mappings":";;AAyCA,oCASC;AAYD,oCAcC;AA1ED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,yCAAyC;AACzC,MAAM,QAAQ,GAAG,0BAA0B,CAAA;AAE3C;;;;;;;;GAQG;AACH,SAAgB,YAAY,CAA0B,UAAyB,EAAE,QAA0B,EAAE,KAAc;IACzH,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAA;IACtD,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,0BAA0B,CAAC,QAAQ,CAAC,CAAA;IACvE,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,2DAA2D;QAC3D,MAAM,IAAI,KAAK,CAAC,gBAAgB,UAAU,CAAC,QAAQ,CAAC,IAAI,iCAAiC,QAAQ,GAAG,CAAC,CAAA;IACvG,CAAC;IACD,MAAM,EAAE,GAAG,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,oBAAoB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;IACnF,OAAO,EAAE,YAAY,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;AACtE,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,YAAY,CAAC,KAAc;IACzC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAA;IACtD,IAAI,KAAK,YAAY,IAAI;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAA;IAC9E,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,EAAE,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAA;QAC1B,OAAO,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAA;IAC/C,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IAE1C,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAA;IACtB,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAA;IACnB,4CAA4C;IAC5C,MAAM,EAAE,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,CAAA;IACrE,OAAO,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAA;AAC/C,CAAC","sourcesContent":["import type { ObjectLiteral, Repository } from 'typeorm'\n\n/*\n * **저장된 값을 시각으로 읽는 두 가지 물음** (2026-08-28 실측).\n *\n * ── 무엇이 어긋났나 ─────────────────────────────────────────────────────────\n * `getRawOne`·`getRawMany` 는 드라이버가 준 값을 그대로 준다. sqlite 가 주는 그 값에는 **시간대가\n * 없다**(`2026-08-27 14:37:20.049`). 그것을 `new Date()` 로 읽으면 서버의 지역 시간으로 해석해\n * 한국에서 9시간이 밀린다. 저장된 값은 UTC 이므로 그 해석이 틀렸다.\n *\n * 네 자리에서 났다. 둘은 **지우는 경계**(경계가 이르게 밀리면 정리가 0건이 되어 멈춘다), 하나는\n * 이어 읽기 지점(9시간을 건너뛰어 본 줄이 또 나왔다), 하나는 「이 목록은 언제부터 말할 수 있나」.\n *\n * ── 처음에 편법을 썼다 ──────────────────────────────────────────────────────\n * 「시간대가 없으면 UTC 로 읽는다」를 직접 적었다. 그것은 **드라이버가 어떤 문자열로 저장하는지를\n * 응용 코드가 아는 것**이고, 이 저장소의 절대 규칙(다섯 드라이버·TypeORM 관용구만)에 어긋난다.\n *\n * 표준 문이 이미 있다. `Driver.prepareHydratedValue(value, column)` — 엔티티 변환\n * (`RawSqlResultsToEntityTransformer`)이 지나는 바로 그 함수다. sqlite 구현이 공백을 `T` 로 바꾸고\n * `Z` 를 붙이고, Postgres 구현은 이미 날짜 객체인 값을 그대로 낸다. 드라이버마다 자기 구현을 갖고\n * 있으므로 이식성이 **정의상** 보장된다.\n *\n * ── 두 물음을 섞지 않는다 ───────────────────────────────────────────────────\n * `hydratedDate` **테이블의 컬럼** 값을 읽는다 — 드라이버에게 물어본다\n * `absoluteTime` **계약이 절대시각이라고 약속한** 값을 읽는다(유입 봉투 · 우리가 만든 커서)\n *\n * 앞의 것에 뒤의 규칙을 쓰면 편법이 되고, 뒤의 것에 앞의 것을 쓸 수는 없다(컬럼이 아니다).\n */\n\n/** 시간대가 적혀 있나 — `Z`·`+09:00`·`-0500`. */\nconst HAS_ZONE = /([Zz]|[+-]\\d{2}:?\\d{2})$/\n\n/**\n * 원시 결과에서 꺼낸 **컬럼 값** → 날짜. 읽을 수 없으면 `null`.\n *\n * 해석은 드라이버가 한다(`prepareHydratedValue`) — 엔티티로 받았을 때와 **같은 값**이 나온다.\n * 그래서 속도 때문에 원시로 받는 자리와 엔티티로 받는 자리가 서로 다른 답을 낼 수 없다.\n *\n * 원시로 받는 이유는 그대로 유효하다: 경계 한 줄을 찾을 때 색인만 읽으면 테이블을 열지 않는다.\n * 잃는 것은 컬럼 변환뿐이었고, 이 함수가 그것만 되돌려 준다.\n */\nexport function hydratedDate<T extends ObjectLiteral>(repository: Repository<T>, property: keyof T & string, value: unknown): Date | null {\n if (value === null || value === undefined) return null\n const column = repository.metadata.findColumnWithPropertyName(property)\n if (!column) {\n /* 컬럼을 못 찾으면 짐작하지 않는다 — 이름이 바뀌었다는 뜻이고, 그때는 값을 지어내면 안 된다. */\n throw new Error(`[read-time] \"${repository.metadata.name}\" has no column for property \"${property}\"`)\n }\n const at = repository.manager.connection.driver.prepareHydratedValue(value, column)\n return at instanceof Date && !Number.isNaN(at.getTime()) ? at : null\n}\n\n/**\n * **계약이 절대시각이라고 약속한** 값 → 날짜. 읽을 수 없으면 `null`.\n *\n * 쓰는 자리는 둘이다. 유입 봉투의 `eventTime`(정규 계약이 절대시각을 요구한다)과 우리가 만든\n * 커서(우리가 적은 것을 우리가 읽는다). 둘 다 테이블의 컬럼이 아니므로 드라이버에게 물을 수 없다.\n *\n * 시간대가 적혀 있지 않으면 UTC 로 읽는다 — 계약이 절대시각을 요구하므로 지역 시간으로 읽을 근거가\n * 없다. 「지금」으로 메우지 않는다: 옛 사건이 최근으로 보이고, 경계를 정하는 자리에서는 지우면 안\n * 되는 것을 지운다.\n */\nexport function absoluteTime(value: unknown): Date | null {\n if (value === null || value === undefined) return null\n if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value\n if (typeof value === 'number') {\n const at = new Date(value)\n return Number.isNaN(at.getTime()) ? null : at\n }\n if (typeof value !== 'string') return null\n\n const s = value.trim()\n if (!s) return null\n /* 시간대가 적혀 있으면 그 말을 믿는다 — 우리가 고쳐 붙이지 않는다. */\n const at = new Date(HAS_ZONE.test(s) ? s : `${s.replace(' ', 'T')}Z`)\n return Number.isNaN(at.getTime()) ? null : at\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"restart-policy.js","sourceRoot":"","sources":["../../server/engine/restart-policy.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;;;AAcH,8CAcC;AAGD,0CAEC;AA5BY,QAAA,gBAAgB,GAA6B,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAU,CAAA;AAEhG;;;;;;GAMG;AACH,SAAgB,iBAAiB,CAAC,GAAY,EAAE,KAAa;IAC3D,MAAM,KAAK,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IACvD,IAAK,wBAAsC,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAsB,CAAA;IAC1F,MAAM,MAAM,GAAkC,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,gBAAgB,EAAE,OAAO,EAAE,CAAA;IACpH,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CACb,GAAG,KAAK,qBAAqB,KAAK,uDAAuD,MAAM,CAAC,KAAK,CAAC,IAAI;YACxG,4IAA4I,CAC/I,CAAA;IACH,CAAC;IACD,MAAM,IAAI,KAAK,CACb,GAAG,KAAK,oBAAoB,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,YAAY,qBAAqB,wBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI;QAClH,0HAA0H,CAC7H,CAAA;AACH,CAAC;AAED,8CAA8C;AAC9C,SAAgB,eAAe,CAAC,GAAY;IAC1C,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAK,wBAAsC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;AACzF,CAAC","sourcesContent":["/*\n * **재기동 정책** — 이 트윈은 다시 세워질 때 자기 과거를 어떻게 대하나.\n *\n * ── 왜 이 이름인가 (ADR-0029 · plans/simulator-as-source.md §2) ─────────────\n * 옛 이름은 `realityMode` 였고 값은 `mirror`·`sim-world`·`sim-experiment` 였다. 그 축은 「현실이 어디서\n * 오나」를 말하는 것처럼 보였지만, 코드가 그것으로 가른 것은 **재기동 거동 하나**였다. 그리고 「현실이\n * 어디서 오나」는 ADR-0029 가 **원본의 종류**로 옮긴 물음이다(`TwinReference.adapterType`) — 트윈은 한\n * 종류이고 언제나 원본을 반영한다.\n *\n * 그래서 이 축은 사라지지 않고 **트윈에 남아 이름을 바꿨다.** 남는 이유: 「자기 저널을 잇는가」는 원본의\n * 성질이 아니라 **트윈과 자기 과거의 관계**다. 같은 시뮬레이터 원본을 보는 두 트윈이 하나는 이어가고\n * 하나는 매번 처음부터 재현할 수 있다 — 원본은 그 둘을 구별하지 못한다.\n *\n * resync — 외부 실물에서 다시 읽는다(원본이 진실이므로 우리 과거를 이어 붙이지 않는다).\n * resume — 저널을 이어 세운다(이 트윈이 낳은 타임라인이 곧 사실이다).\n * reset — 저널을 비우고 씨앗부터 다시 돌린다(재현 가능한 실험).\n *\n * ── 기본값을 두지 않는다 ────────────────────────────────────────────────────\n * 예전에는 미선언이 알리지 않고 `sim-experiment`(=저널 초기화)로 떨어졌다. 그 관용은 값이 하나 어긋나는\n * 순간 **저널을 지우는 길**이 된다 — 개명 뒤 옛 값이 남아 있는 행을 그 길로 읽으면 재기동 한 번에\n * 이력이 사라진다. 그래서 모르는 값은 **오류를 낸다.** 부르는 쪽이 선언하게 만드는 것이 이 축의 요점이다.\n */\n\n/** 재기동 정책 — 셋뿐이다. */\nexport type RestartPolicy = 'resync' | 'resume' | 'reset'\n\nexport const RESTART_POLICIES: readonly RestartPolicy[] = ['resync', 'resume', 'reset'] as const\n\n/**\n * 저장된 값을 정책으로 읽는다 — **모르면 오류를 낸다**(기본값으로 메우지 않는다).\n *\n * 옛 어휘(`mirror`·`sim-world`·`sim-experiment`)를 여기서 번역하지 않는다: 번역을 두면 옛 값이 살아남고,\n * 그 값이 다시 어딘가에 저장된다. 저장된 값은 **한 번 옮기고**(데이터 이관) 코드는 새 어휘만 안다.\n * 그래서 이 함수는 옛 값에 대해서도 오류를 내며, 무엇으로 옮겨야 하는지 문장으로 말한다.\n */\nexport function readRestartPolicy(raw: unknown, where: string): RestartPolicy {\n const value = typeof raw === 'string' ? raw.trim() : ''\n if ((RESTART_POLICIES as readonly string[]).includes(value)) return value as RestartPolicy\n const legacy: Record<string, RestartPolicy> = { mirror: 'resync', 'sim-world': 'resume', 'sim-experiment': 'reset' }\n if (legacy[value]) {\n throw new Error(\n `${where}: restart policy \"${value}\" is the old vocabulary — move the stored value to \"${legacy[value]}\" ` +\n '(ADR-0029 · plans/simulator-as-source.md §2). Nothing is translated at read time on purpose: a translation would keep the old value alive.'\n )\n }\n throw new Error(\n `${where}: restart policy ${value ? `\"${value}\"` : 'is missing'} — declare one of ${RESTART_POLICIES.join(' | ')}. ` +\n 'There is no default: an unspecified policy used to fall through to \"reset\", which erases the journal on the next reboot.'\n )\n}\n\n/** 이 값이 정책인가 — 오류를 내지 않고 묻는 자리(화면·검사)를 위해. */\nexport function isRestartPolicy(raw: unknown): raw is RestartPolicy {\n return typeof raw === 'string' && (RESTART_POLICIES as readonly string[]).includes(raw)\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-key.js","sourceRoot":"","sources":["../../server/engine/runtime-key.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;;;AAUH,gCAOC;AAGD,0CAIC;AAGD,gCAEC;AA3BD,kDAAkD;AACrC,QAAA,eAAe,GAAG,GAAG,CAAA;AAElC;;;;GAIG;AACH,SAAgB,UAAU,CAAC,QAAgB,EAAE,UAAkB;IAC7D,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,8EAA8E,CAAC,CAAA;IAC9G,IAAI,CAAC,UAAU;QAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAA;IACtE,IAAI,QAAQ,CAAC,QAAQ,CAAC,uBAAe,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CAAC,0CAA0C,uBAAe,kCAAkC,CAAC,CAAA;IAC9G,CAAC;IACD,OAAO,GAAG,QAAQ,GAAG,uBAAe,GAAG,UAAU,EAAE,CAAA;AACrD,CAAC;AAED,iDAAiD;AACjD,SAAgB,eAAe,CAAC,GAAW;IACzC,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,uBAAe,CAAC,CAAA;IACvC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,GAAG,wBAAwB,CAAC,CAAA;IACvG,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAA;AACtE,CAAC;AAED,iDAAiD;AACjD,SAAgB,UAAU,CAAC,GAAW,EAAE,QAAgB;IACtD,OAAO,CAAC,CAAC,QAAQ,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,QAAQ,GAAG,uBAAe,EAAE,CAAC,CAAA;AACtE,CAAC","sourcesContent":["/*\n * 런타임 레지스트리 키 — **트윈의 정체성은 `(도메인, 인스턴스 id)` 다.** 순수.\n *\n * ── 왜 있나 (2026-08-06 테넌트 격리 전수 확인) ───────────────────────────────\n * `TwinEngine.instances` 와 `recovered` 가 **`instanceId` 하나로** 키를 잡고 있었다. 그런데 DB 유일성은\n * `[domain, instanceId]` 이고(`TwinInstance` 인덱스) 트윈의 정체성도 그쪽이다. 즉 런타임 맵만 **전역\n * 이름 공간**이었다.\n *\n * **충돌은 이론이 아니다.** 레퍼런스 경로가 `instanceId` 로 **레퍼런스 소스 이름**을 쓴다\n * (`sap-ewm-1710`). 벤더 시스템 이름은 테넌트가 **공유하는 어휘**라, 두 테넌트가 같은 시스템에\n * 연결하면 같은 id 가 된다 — 드문 사고가 아니라 예상되는 경우다.\n *\n * 그래서 무엇이 어긋났나:\n * · `startLive` 의 조기 반환이 A 의 런타임을 B 에게 돌려주고 `ingestLive` 는 도메인을 확인하지 않아\n * **B 의 라이브 이벤트가 A 의 커널에 적용**될 수 있었다.\n * · `recovered` 가 겹치면 웜스타트 씨앗이 **남의 스냅샷**이 되어 재고가 섞인다.\n * · `start`/`stop` 이 남의 런타임을 집으므로 B 는 \"기동됨\" 을 받고도 이후 전부 거부됐다.\n *\n * 이 부류는 **이미 한 번 물었다** — `twin-lifecycle-mutation.ts` 주석이 증언한다: \"과거엔\n * `Object.keys(TwinEngine.instances)` 로 전역 반환 → 다른 도메인 트윈이 새던 버그.\" 그 자리만 고쳤다.\n * 그래서 이번에는 가드를 더 붙이지 않고 **키를 고친다** — 저수준 메서드가 `domainId` 를 받게 되는 것이\n * 요점이다. \"누구의 트윈인가\" 를 말하지 않고는 만질 수 없다.\n *\n * ── 구분자 규약 ─────────────────────────────────────────────────────────────\n * `${domainId}:${instanceId}` — 이 저장소가 이미 쓰는 모양이다(`structureRevCache`·`measuredCache`).\n * 새 모양을 만들지 않는다. 파싱은 **첫 구분자**에서 자르므로 `instanceId` 에 `:` 가 들어 있어도\n * 안전하다(레퍼런스 소스 이름에 들어갈 수 있다). 대신 `domainId` 에 구분자가 있으면 키가 모호해지므로\n * **그때는 오류를 낸다** — 알리지 않고 잘못된 키를 만들면 그게 곧 이 결함의 재발이다.\n */\n\n/** 도메인과 인스턴스 id 를 잇는 문자 — 저장소의 기존 캐시 키와 같은 모양. */\nexport const RUNTIME_KEY_SEP = ':'\n\n/**\n * 런타임 레지스트리 키.\n *\n * @throws 도메인 id 가 비었거나 구분자를 담고 있으면(키가 모호해진다). 인스턴스 id 가 비었으면.\n */\nexport function runtimeKey(domainId: string, instanceId: string): string {\n if (!domainId) throw new Error('runtimeKey: domainId is required — a twin runtime always belongs to a tenant')\n if (!instanceId) throw new Error('runtimeKey: instanceId is required')\n if (domainId.includes(RUNTIME_KEY_SEP)) {\n throw new Error(`runtimeKey: domainId must not contain \"${RUNTIME_KEY_SEP}\" (would make the key ambiguous)`)\n }\n return `${domainId}${RUNTIME_KEY_SEP}${instanceId}`\n}\n\n/** 키를 되돌린다 — 첫 구분자에서 자른다(인스턴스 id 안의 구분자는 보존). */\nexport function parseRuntimeKey(key: string): { domainId: string; instanceId: string } {\n const at = key.indexOf(RUNTIME_KEY_SEP)\n if (at <= 0 || at === key.length - 1) throw new Error(`parseRuntimeKey: \"${key}\" is not a runtime key`)\n return { domainId: key.slice(0, at), instanceId: key.slice(at + 1) }\n}\n\n/** 이 키가 이 테넌트의 것인가 — 레지스트리를 조회할 때 도메인으로 걸러낸다. */\nexport function isOfDomain(key: string, domainId: string): boolean {\n return !!domainId && key.startsWith(`${domainId}${RUNTIME_KEY_SEP}`)\n}\n"]}
|