@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":"twin-space-resolver.js","sourceRoot":"","sources":["../../../server/service/twin-space/twin-space-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import { twinError } from '../../engine/log.js'\nimport { Arg, Ctx, Mutation, Query, Resolver, Directive} from 'type-graphql'\n\nimport { ScalarObject, getRepository } from '@things-factory/shell'\n\nimport { TwinSpace } from './twin-space.js'\nimport { TwinSpaceRepresentation } from './twin-space-representation.js'\nimport { REPRESENTATION_ROLES } from './representation-role.js'\nimport { TwinSpaceArea } from './twin-space-area.js'\nimport { TwinArea } from './twin-area.js'\nimport { TwinInstance } from '../twin-instance/twin-instance.js'\nimport { TwinAuditEvent } from '../twin-audit/twin-audit-event.js'\nimport { TwinEngine } from '../../engine/index.js'\nimport { judgeMoveTwinSpace, judgeDetachTwinSpace } from './move-space.js'\nimport { checkSpaceIntegrity, sortFindings, type IntegrityPolygon } from './space-integrity.js'\n\n/*\n * 공간 구성 채널(design/ui/space-composition.md) — Site(Space) + 복수 표현(Representation) + 영역(Area).\n * model 내용은 board-service Board 에(표현 config.boardId 참조, 중복 저장 금지). 모두 domain 스코프.\n */\n@Resolver()\nexport class TwinSpaceResolver {\n /* ── Space(Site) ────────────────────────────────────────────────── */\n /**\n * 이 테넌트의 현장 목록.\n *\n * ── 세 번만 묻는다 (2026-08-13) ──────────────────────────────────────────\n * 예전에는 현장마다 표현 수를 세고 인스턴스를 조회했다 — 현장 13곳이면 **질의 27번**이고, 첫\n * 호출이 2.4초였다(헤더가 뜰 때마다 이 목록을 읽는다). 규모가 커지면 그대로 자란다.\n *\n * 이제 세 번 묻고 메모리에서 맞춘다: 현장 · 표현(공간 id 만) · 인스턴스(필요한 컬럼만).\n * 표현은 **id 만 실어 온다**(`loadRelationIds`) — 설정 blob 을 읽어 세는 것은 낭비다.\n */\n @Query(returns => [ScalarObject], { description: 'List tenant Spaces (Sites) in the current domain.' })\n async twinSpaces(@Ctx() context: ResolverContext): Promise<any[]> {\n const domainId = context.state.domain.id\n const [rows, reps, insts] = await Promise.all([\n getRepository(TwinSpace).find({ where: { domain: { id: domainId } }, order: { createdAt: 'ASC' } }),\n /* 관계를 **조인해서** 읽는다 — `loadRelationIds`+`select` 조합은 관계 자리를 비워 두어\n 표현 수가 전부 0 이 됐다(2026-08-13 실측으로 잡음). 조인 한 번이 현장마다 세는 것보다 싸다. */\n getRepository(TwinSpaceRepresentation).find({ where: { domain: { id: domainId } }, relations: { space: true } }),\n getRepository(TwinInstance).find({ where: { domain: { id: domainId } }, select: { instanceId: true, spaceId: true, purpose: true, copyOf: true } })\n ])\n\n const repCount = new Map<string, number>()\n for (const rep of reps) {\n const sid = rep.space?.id\n if (sid) repCount.set(sid, (repCount.get(sid) ?? 0) + 1)\n }\n /* 벤치 여부·원본 참조 — 인스턴스 purpose 근거(1급). 사본 현장을 헤더 선택기에서 구별·선택 가능하게. */\n const benchOf = new Map<string, { copyOf?: string }>()\n for (const i of insts) if ((i as any).purpose === 'bench' && i.spaceId && !benchOf.has(i.spaceId)) benchOf.set(i.spaceId, { copyOf: i.copyOf ?? undefined })\n\n return rows.map(r => {\n const bench = benchOf.get(r.spaceId)\n return {\n spaceId: r.spaceId,\n name: r.name,\n description: r.description,\n geo: r.geo,\n timezone: r.timezone,\n primaryRepresentationId: r.primaryRepresentationId,\n extentX: r.extentX,\n extentY: r.extentY,\n representationCount: repCount.get(r.id) ?? 0,\n bench: !!bench,\n copyOf: bench?.copyOf\n }\n })\n }\n\n /** 단건 — 표현(+영역) 트리 포함. */\n @Query(returns => ScalarObject, { nullable: true, description: 'Get one Space with its representations (and their areas).' })\n async twinSpace(@Arg('spaceId') spaceId: string, @Ctx() context: ResolverContext): Promise<any | null> {\n const domainId = context.state.domain.id\n const r = await getRepository(TwinSpace).findOne({ where: { domain: { id: domainId }, spaceId } })\n if (!r) return null\n /*\n * 묶은 이름 다음에 순서로 정렬한다. `group` 이 비어 있는 행이 앞에 오는지 뒤에 오는지는 드라이버마다\n * 다르므로 그 순서에 기대지 않는다 — 화면이 다시 묶는다(UX 레인과 그렇게 정했다).\n */\n const reps = await getRepository(TwinSpaceRepresentation).find({\n where: { domain: { id: domainId }, space: { id: r.id } },\n order: { group: 'ASC', seq: 'ASC' }\n })\n const representations: any[] = []\n for (const rep of reps) {\n const areas = await getRepository(TwinSpaceArea).find({ where: { domain: { id: domainId }, representation: { id: rep.id } }, order: { createdAt: 'ASC' } })\n representations.push({ id: rep.id, type: rep.type, name: rep.name, seq: rep.seq, group: rep.group, role: rep.role, isPrimary: rep.isPrimary, config: rep.config, areas: areas.map(a => ({ id: a.id, name: a.name, type: a.type, geometry: a.geometry, drillTo: a.drillTo, binding: a.binding, style: a.style })) })\n }\n return { spaceId: r.spaceId, name: r.name, description: r.description, geo: r.geo, timezone: r.timezone, primaryRepresentationId: r.primaryRepresentationId, extentX: r.extentX, extentY: r.extentY, representations }\n }\n\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => Boolean, { description: 'Create or update a Space (name/description/geo/timezone/primaryRepresentationId).' })\n async saveTwinSpace(\n @Arg('spaceId') spaceId: string,\n @Arg('patch', type => ScalarObject) patch: any,\n @Ctx() context: ResolverContext\n ): Promise<boolean> {\n const repo = getRepository(TwinSpace)\n const domainId = context.state.domain.id\n const existing = await repo.findOne({ where: { domain: { id: domainId }, spaceId } })\n /* 진짜 부분 병합 — patch 에 준 키만 갱신(나머지 필드 보존). id 는 관계로 재설정하지 않도록 제외. */\n const { id: _drop, ...clean } = patch ?? {}\n await repo.save(repo.create({ ...(existing ?? {}), ...clean, domain: { id: domainId } as any, spaceId }))\n return true\n }\n\n /**\n * 공간 삭제 — **이 공간의 트윈이 있으면 거절한다.** 그래서 트윈이 현장을 잃는 일은 없다.\n *\n * ── 왜 모양을 바꿨나 (2026-08-13) ────────────────────────────────────────\n * 예전에는 `Boolean` 을 돌려주고 거절은 **한국어 문장을 냈다.** 두 가지가 틀렸다:\n * ① 서버가 화면의 말을 정했다 — 일본어·스페인어 사용자에게 한국어가 새어 나간다.\n * ② 오류를 낸 예외를 화면이 받지 않아 **아무 일도 없는 것처럼** 보였다. 사용자는 삭제를 눌렀고\n * 화면은 그대로였다. 거절을 알리지 않고 하는 것은 실패보다 나쁘다.\n * 이제 `{ok, errorCode, errorParams}` 로 낸다 — 화면이 `twin.compose.del.<code>` 로 말을 만든다.\n */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => ScalarObject, {\n description:\n 'Delete a Space and its representations/areas. Refused while twins still live in it (a twin never loses its site this way). Returns {ok, errorCode, errorParams}.'\n })\n async deleteTwinSpace(@Arg('spaceId') spaceId: string, @Ctx() context: ResolverContext): Promise<any> {\n const domainId = context.state.domain.id\n // 라이프사이클 가드 — 이 공간에 바인딩된 트윈 인스턴스가 있으면 삭제 차단(dangling 방지).\n // spaceId 컬럼(space #3) 로 직접 질의 — 전 인스턴스 로드+JS필터 제거(효율·1급 질의).\n const bound = await getRepository(TwinInstance).find({ where: { domain: { id: domainId }, spaceId } })\n if (bound.length) {\n const twins = bound.map(i => i.name || i.instanceId)\n return {\n ok: false,\n errorCode: 'space-has-twins',\n errorParams: { spaceId, count: bound.length, twins: twins.slice(0, 5).join(', ') },\n error: `space \"${spaceId}\" still has ${bound.length} twin(s) — remove or move them first (${twins.join(', ')})`\n }\n }\n const space = await getRepository(TwinSpace).findOne({ where: { domain: { id: domainId }, spaceId } })\n if (space) {\n const reps = await getRepository(TwinSpaceRepresentation).find({ where: { domain: { id: domainId }, space: { id: space.id } } })\n for (const rep of reps) await getRepository(TwinSpaceArea).delete({ domain: { id: domainId } as any, representation: { id: rep.id } as any })\n await getRepository(TwinSpaceRepresentation).delete({ domain: { id: domainId } as any, space: { id: space.id } as any })\n await getRepository(TwinArea).delete({ domain: { id: domainId } as any, space: { id: space.id } as any }) // 논리 area 도 함께 정리\n }\n await getRepository(TwinSpace).delete({ domain: { id: domainId } as any, spaceId })\n return { ok: true }\n }\n\n /**\n * 이미 있는 트윈을 이 현장으로 가져온다 — **소속 갱신**(이력 이전이 아니다).\n *\n * 저널·투영 행은 `instanceId` 로 매여 있어 트윈을 따라오고, 구역·표현은 공간의 것이라 남는다\n * (근거·판정: `move-space.ts`). 그래서 이 뮤테이션이 쓰는 것은 두 곳뿐이다 —\n * 레지스트리의 `spaceId` 와 저장된 model 의 `spaceId`(커널·보드가 같은 공간을 가리켜야 한다).\n * 새 공간의 시간대로 `utcOffsetMinutes` 도 다시 파생한다(공간이 시간의 기준이다).\n *\n * `dryRun` 은 **같은 판정을 미리** 보여 준다 — 화면이 잃을 것을 먼저 말하고 사용자가 정한다.\n * 판정을 두 벌로 두면 미리보기와 실제가 어긋나므로 한 함수를 두 번 부른다.\n */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => ScalarObject, {\n description:\n 'Move an existing twin into a space (co-locate it as another lens). The journal and projected rows follow the twin; areas and representations stay with the old space. dryRun returns the same judgement without writing. Returns {ok, warnings, errorCode}.'\n })\n async moveTwinToSpace(\n @Arg('instanceId') instanceId: string,\n @Arg('spaceId') spaceId: string,\n @Ctx() context: ResolverContext,\n @Arg('dryRun', { nullable: true, description: 'Judge only — report refusal/warnings without moving.' }) dryRun?: boolean\n ): Promise<any> {\n const domainId = context.state.domain.id\n const instRepo = getRepository(TwinInstance)\n const spaceRepo = getRepository(TwinSpace)\n\n const inst = await instRepo.findOne({ where: { domain: { id: domainId }, instanceId } })\n const from = inst?.spaceId ? await spaceRepo.findOne({ where: { domain: { id: domainId }, spaceId: inst.spaceId } }) : null\n const to = await spaceRepo.findOne({ where: { domain: { id: domainId }, spaceId } })\n\n /* 판정에 필요한 사실만 모아 넘긴다 — 판정 자체는 순수 모듈이 한다. */\n const toAreaIds = to\n ? (await getRepository(TwinArea).find({ where: { domain: { id: domainId } as any, space: { id: to.id } as any } })).map(a => a.areaId)\n : []\n const fromRepresentationCount = from\n ? await getRepository(TwinSpaceRepresentation).count({ where: { domain: { id: domainId } as any, space: { id: from.id } as any } })\n : 0\n const model: any = inst?.model ?? {}\n const areaRefs: (string | undefined)[] = (model.locations ?? []).map((l: any) => l?.parentId)\n\n const judgement = judgeMoveTwinSpace({\n instanceId: inst?.instanceId,\n fromSpaceId: inst?.spaceId,\n toSpaceId: spaceId,\n toSpaceExists: !!to,\n purpose: (inst as any)?.purpose,\n running: !!inst && !!TwinEngine.runtime(domainId, instanceId),\n fromTimezone: from?.timezone ?? undefined,\n toTimezone: to?.timezone ?? undefined,\n areaRefs,\n toAreaIds,\n fromRepresentationCount\n })\n if (judgement.refusal) {\n const r = judgement.refusal\n return { ok: false, error: r.message, errorCode: r.code, errorParams: r.params, warnings: [] }\n }\n if (dryRun) return { ok: true, dryRun: true, warnings: judgement.warnings }\n\n /* 두 곳을 함께 쓴다 — 레지스트리와 model 이 다른 공간을 가리키면 보드·커널이 갈라진다. */\n const moved = { ...model, spaceId }\n const withTime = await TwinEngine.withSpaceTimeBase(moved, domainId, spaceId)\n await instRepo.save(instRepo.create({ ...(inst as any), spaceId, model: withTime }))\n\n /*\n * 감사 기록 — 소속을 바꾼 것은 사람의 결정이고, 나중에 \"왜 이 트윈이 저 현장에 있나\" 를 묻게 된다.\n * 기록이 실패해도 이전 결과는 돌려준다(감사가 본업을 막지 않는다). 다만 삼키지 않고 로그에 남긴다.\n */\n try {\n const repo = getRepository(TwinAuditEvent)\n await repo.save(\n repo.create({\n domain: context.state.domain,\n instanceId,\n commandType: 'twin.moveToSpace',\n accepted: true,\n origin: 'user',\n actorId: context.state.user?.id,\n args: { from: inst?.spaceId ?? null, to: spaceId, warnings: judgement.warnings.map(w => w.code) }\n } as any)\n )\n } catch (err) {\n twinError(`[twin-space] move audit not recorded for \"${instanceId}\"`, err)\n }\n\n return { ok: true, warnings: judgement.warnings }\n }\n\n /**\n * 트윈을 현장에서 뗀다 — **어느 현장에도 속하지 않음**으로 되돌린다.\n *\n * 더하기·옮기기만 있으면 잘못 넣은 것을 되돌릴 수 없다. 떼도 저널·로케이션·설비는 트윈에 남고\n * (`instanceId` 로 매여 있다), 구역·표현은 옛 현장에 남는다. 잃는 것은 **볼 무대**다 —\n * 판정이 그것을 말한다(`judgeDetachTwinSpace`). `dryRun` 은 같은 판정을 미리 낸다.\n */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => ScalarObject, {\n description:\n 'Detach a twin from its space (it ends up on no site). The journal and projected rows stay with the twin; areas and representations stay with the space. dryRun returns the same judgement without writing. Returns {ok, warnings, errorCode}.'\n })\n async detachTwinFromSpace(\n @Arg('instanceId') instanceId: string,\n @Ctx() context: ResolverContext,\n @Arg('dryRun', { nullable: true, description: 'Judge only — report refusal/warnings without detaching.' }) dryRun?: boolean\n ): Promise<any> {\n const domainId = context.state.domain.id\n const instRepo = getRepository(TwinInstance)\n const inst = await instRepo.findOne({ where: { domain: { id: domainId }, instanceId } })\n const from = inst?.spaceId\n ? await getRepository(TwinSpace).findOne({ where: { domain: { id: domainId }, spaceId: inst.spaceId } })\n : null\n const fromRepresentationCount = from\n ? await getRepository(TwinSpaceRepresentation).count({ where: { domain: { id: domainId } as any, space: { id: from.id } as any } })\n : 0\n\n const judgement = judgeDetachTwinSpace({\n instanceId: inst?.instanceId,\n fromSpaceId: inst?.spaceId,\n purpose: (inst as any)?.purpose,\n fromRepresentationCount,\n fromTimezone: from?.timezone ?? undefined\n })\n if (judgement.refusal) {\n const r = judgement.refusal\n return { ok: false, error: r.message, errorCode: r.code, errorParams: r.params, warnings: [] }\n }\n if (dryRun) return { ok: true, dryRun: true, warnings: judgement.warnings }\n\n /* 두 곳을 함께 비운다 — 레지스트리와 model 이 어긋나면 어느 쪽이 사실인지 알 수 없다.\n 시간 기준(utcOffsetMinutes)은 **남긴다**: 지우면 알리지 않고 UTC 로 읽힌다(판정이 그렇게 말했다). */\n const { spaceId: _dropped, ...model } = (inst?.model as any) ?? {}\n await instRepo.update({ id: (inst as any).id }, { spaceId: null as any, model })\n\n try {\n const repo = getRepository(TwinAuditEvent)\n await repo.save(\n repo.create({\n domain: context.state.domain,\n instanceId,\n commandType: 'twin.detachFromSpace',\n accepted: true,\n origin: 'user',\n actorId: context.state.user?.id,\n args: { from: inst?.spaceId ?? null, warnings: judgement.warnings.map(w => w.code) }\n } as any)\n )\n } catch (err) {\n twinError(`[twin-space] detach audit not recorded for \"${instanceId}\"`, err)\n }\n\n return { ok: true, warnings: judgement.warnings }\n }\n\n /**\n * 공간 ↔ 트윈 ↔ 가시화 정합성 점검 — **사실을 모아 순수 판정에 넘긴다.**\n *\n * 세 축은 서로를 id 로만 가리키고 DB 가 그 참조를 지켜 주지 않는다. 트윈을 옮기거나 떼거나 원본을\n * 다시 읽으면 아무 오류 없이 **가리킬 것이 없는 도형**과 **소속 없는 로케이션**이 남는다. 사람이\n * 눈으로 대조하던 일을 기계가 한다(판정·근거: `space-integrity.ts`).\n *\n * `spaceId` 를 주면 그 현장만, 없으면 테넌트 전체를 본다.\n */\n @Query(returns => [ScalarObject], {\n description:\n 'Check space ↔ twin ↔ visualization integrity: dangling site refs, registry/model mismatch, polygons binding areas or entities that are not there, primary representation, unbound board representations. Pass spaceId to check one site.'\n })\n async twinSpaceIntegrity(\n @Ctx() context: ResolverContext,\n @Arg('spaceId', { nullable: true }) spaceId?: string\n ): Promise<any[]> {\n const domainId = context.state.domain.id\n const spaceRows = await getRepository(TwinSpace).find({ where: { domain: { id: domainId } } })\n const instRows = await getRepository(TwinInstance).find({ where: { domain: { id: domainId } } })\n const repRows = await getRepository(TwinSpaceRepresentation).find({ where: { domain: { id: domainId } }, relations: ['space'] })\n const areaRows = await getRepository(TwinArea).find({ where: { domain: { id: domainId } }, relations: ['space'] })\n const polyRows = await getRepository(TwinSpaceArea).find({ where: { domain: { id: domainId } }, relations: ['representation'] })\n\n const areasBySpace: Record<string, string[]> = {}\n for (const a of areaRows) {\n const sid = (a as any).space?.spaceId\n if (!sid) continue\n ;(areasBySpace[sid] ??= []).push(a.areaId)\n }\n\n /*\n * 폴리곤 바인딩의 실제 모양은 `{ groupId?, locationId?, locationIds? }` 다(지도 저작기 기준).\n * 여러 로케이션을 묶은 도형은 **각각을 따로** 본다 — 하나만 끊겨도 그 도형은 반쯤 거짓이 된다.\n */\n const polysByRep: Record<string, IntegrityPolygon[]> = {}\n for (const p of polyRows) {\n const rid = (p as any).representation?.id\n if (!rid) continue\n const b: any = p.binding ?? {}\n const list = polysByRep[rid] ??= []\n if (b.groupId) list.push({ id: p.id, groupId: b.groupId })\n const targets: string[] = [...(b.locationId ? [b.locationId] : []), ...(Array.isArray(b.locationIds) ? b.locationIds : [])]\n for (const t of targets) list.push({ id: p.id, targetKind: 'location', targetId: t })\n }\n\n /* 보드 id 는 표현 config 가 들고 있다 — 존재 확인은 보드 서비스의 일이므로, 여기서는 표현이\n 가리키는 값이 있는지만 본다(없는 보드까지 보려면 board-service 를 물어야 한다: 아래 주석). */\n const boardIds = repRows.map(r => (r.config as any)?.boardId).filter(Boolean) as string[]\n\n const findings = checkSpaceIntegrity({\n spaces: spaceRows.map(s => ({ spaceId: s.spaceId, primaryRepresentationId: s.primaryRepresentationId })),\n instances: instRows.map(i => {\n const model: any = i.model ?? {}\n /*\n * 어휘가 맞는지는 **실행 중인 트윈만** 답할 수 있다 — 커널이 무엇을 버렸는지는 런타임의 사실이다.\n * 정지한 트윈은 `undefined`(모른다)로 둔다. 저널을 다시 계산해 알아내지는 않는다: 이 질의는\n * 테넌트 전체를 훑으므로 트윈마다 폴드를 걸면 정합성 점검이 가장 무거운 질의가 된다.\n */\n const live: any = TwinEngine.isRunning(domainId, i.instanceId)\n ? TwinEngine.snapshot(domainId, i.instanceId)\n : undefined\n return {\n instanceId: i.instanceId,\n spaceId: i.spaceId,\n modelSpaceId: model.spaceId,\n purpose: (i as any).purpose,\n locationIds: (model.locations ?? []).map((l: any) => l?.id).filter(Boolean),\n equipmentIds: (model.equipment ?? []).map((e: any) => e?.id).filter(Boolean),\n areaRefs: (model.locations ?? []).map((l: any) => l?.parentId),\n ...(Array.isArray(live?.unhandled) ? { unhandled: live.unhandled } : {})\n }\n }),\n representations: repRows.map(r => ({\n id: r.id,\n spaceId: (r as any).space?.spaceId ?? '',\n type: r.type,\n boardId: (r.config as any)?.boardId ?? null,\n polygons: polysByRep[r.id] ?? []\n })),\n areasBySpace,\n boardIds\n })\n\n const scoped = spaceId ? findings.filter(f => f.spaceId === spaceId || (!f.spaceId && !spaceId)) : findings\n return sortFindings(scoped)\n }\n\n /**\n * 기계적으로 고칠 수 있는 어긋남만 고친다 — **사람의 판단이 필요한 것은 손대지 않는다.**\n *\n * 지금 고치는 것은 하나다: **대표 표현 포인터**. 공간이 없는 표현 id 를 가리키고 있으면, 행의\n * `isPrimary` 가 켜진 표현(없으면 첫 표현)으로 다시 가리키고, 표현이 없으면 비운다. 근거가 있고\n * 답이 하나뿐인 부류라 자동으로 고쳐도 사실을 바꾸지 않는다.\n *\n * 폴리곤이 없는 구역·개체를 가리키는 것은 **고치지 않는다.** 그 도형을 지워야 하는지, 트윈을 다시\n * 데려와야 하는지, 구역을 다시 그려야 하는지는 사람이 정할 일이다 — 기계가 고르면 사용자가 그린\n * 도형이 오류 없이 사라진다.\n */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => ScalarObject, {\n description:\n 'Repair mechanically fixable space integrity findings (currently: a primary representation pointer that points nowhere). Dangling polygon bindings are left alone — that is a human decision. Returns {repaired:[{spaceId, code}], skipped}.'\n })\n async repairTwinSpaceIntegrity(\n @Ctx() context: ResolverContext,\n @Arg('spaceId', { nullable: true }) spaceId?: string\n ): Promise<any> {\n const domainId = context.state.domain.id\n const spaceRepo = getRepository(TwinSpace)\n const repRepo = getRepository(TwinSpaceRepresentation)\n const spaces = await spaceRepo.find({\n where: spaceId ? { domain: { id: domainId }, spaceId } : { domain: { id: domainId } }\n })\n const repaired: any[] = []\n for (const space of spaces) {\n const reps = await repRepo.find({ where: { domain: { id: domainId } as any, space: { id: space.id } as any }, order: { seq: 'ASC' } })\n const pointer = (space.primaryRepresentationId ?? '').trim()\n if (pointer && reps.some(r => r.id === pointer)) continue /* 이미 옳다 */\n const primary = reps.find(r => r.isPrimary) ?? reps[0]\n const next = primary?.id ?? null\n if (pointer === (next ?? '')) continue\n await spaceRepo.save(spaceRepo.create({ ...space, primaryRepresentationId: next }))\n if (primary) for (const r of reps) if (!!r.isPrimary !== (r.id === primary.id)) await repRepo.save(repRepo.create({ ...r, isPrimary: r.id === primary.id }))\n repaired.push({ spaceId: space.spaceId, code: 'primary-representation-missing', from: pointer || null, to: next })\n }\n return { ok: true, repaired, count: repaired.length }\n }\n\n /* ── Area(논리 구역, 표현 무관·공유·계층) — area 단일화 space #2 ─────── */\n @Query(returns => [ScalarObject], { description: 'List logical areas (representation-agnostic hierarchy) of a space. (area unification)' })\n async twinAreas(@Arg('spaceId') spaceId: string, @Ctx() context: ResolverContext): Promise<any[]> {\n const domainId = context.state.domain.id\n const rows = await getRepository(TwinArea).find({ where: { domain: { id: domainId }, space: { spaceId } }, order: { createdAt: 'ASC' } })\n return rows.map(a => ({ areaId: a.areaId, name: a.name, type: a.type, parentId: a.parentId, layout: a.layout }))\n }\n\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => Boolean, { description: 'Create/update a logical area (shared across representations). (area unification)' })\n async saveTwinArea(\n @Arg('spaceId') spaceId: string,\n @Arg('areaId') areaId: string,\n @Arg('patch', type => ScalarObject) patch: any,\n @Ctx() context: ResolverContext\n ): Promise<boolean> {\n const domainId = context.state.domain.id\n const space = await getRepository(TwinSpace).findOne({ where: { domain: { id: domainId }, spaceId } })\n if (!space) throw new Error(`space \"${spaceId}\" not found`)\n const repo = getRepository(TwinArea)\n const existing = await repo.findOne({ where: { domain: { id: domainId }, space: { id: space.id }, areaId } })\n await repo.save(\n repo.create({\n ...(existing ?? {}),\n domain: { id: domainId } as any,\n space: { id: space.id } as any,\n areaId,\n name: patch?.name ?? existing?.name ?? areaId,\n type: patch?.type ?? existing?.type,\n parentId: patch?.parentId !== undefined ? patch.parentId : existing?.parentId,\n layout: patch?.layout ?? existing?.layout\n })\n )\n return true\n }\n\n /*\n * 참조기반 회수(reference-counted) — area 는 참조가 남아 있으면 삭제 차단, 고아면 제거.\n * 참조: 자식 area(parentId) · 표현별 폴리곤(TwinSpaceArea binding.groupId/areaId).\n * (노드/엔티티 담김·drillTo 참조는 P2 areaId 배선 후 확장.)\n */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => Boolean, { description: 'Delete a logical area only if unreferenced (reference-counted). Throws if referenced. (area unification)' })\n async deleteTwinArea(@Arg('spaceId') spaceId: string, @Arg('areaId') areaId: string, @Ctx() context: ResolverContext): Promise<boolean> {\n const domainId = context.state.domain.id\n const space = await getRepository(TwinSpace).findOne({ where: { domain: { id: domainId }, spaceId } })\n if (!space) return false\n // 참조 1: 자식 area\n const children = await getRepository(TwinArea).count({ where: { domain: { id: domainId }, space: { id: space.id }, parentId: areaId } })\n // 참조 2: 표현별 폴리곤(이 공간의 표현들에 이 area 를 그린 것). binding.groupId/areaId 는 JSON → 로드 후 JS 필터(멀티DB).\n const reps = await getRepository(TwinSpaceRepresentation).find({ where: { domain: { id: domainId }, space: { id: space.id } } })\n let polygonRefs = 0\n for (const rep of reps) {\n const polys = await getRepository(TwinSpaceArea).find({ where: { domain: { id: domainId }, representation: { id: rep.id } } })\n polygonRefs += polys.filter(p => (p.binding as any)?.groupId === areaId || (p.binding as any)?.areaId === areaId).length\n }\n if (children > 0 || polygonRefs > 0) {\n throw new Error(`deleteArea: area \"${areaId}\" cannot be deleted due to existing references (children: ${children}, polygonRefs: ${polygonRefs}). Remove references first.`)\n }\n await getRepository(TwinArea).delete({ domain: { id: domainId } as any, space: { id: space.id } as any, areaId })\n return true\n }\n\n /* ── Representation ─────────────────────────────────────────────── */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => ScalarObject, { description: 'Create/update a representation (map|image|model). Returns {id}.' })\n async saveSpaceRepresentation(\n @Arg('spaceId') spaceId: string,\n @Arg('rep', type => ScalarObject) rep: any,\n @Ctx() context: ResolverContext\n ): Promise<any> {\n const domainId = context.state.domain.id\n const space = await getRepository(TwinSpace).findOne({ where: { domain: { id: domainId }, spaceId } })\n if (!space) return { ok: false, error: `space not found: ${spaceId}` }\n const repo = getRepository(TwinSpaceRepresentation)\n const existing = rep?.id ? await repo.findOne({ where: { id: rep.id } }) : null\n /*\n * `role` 은 닫힌 값이다 — 모르는 값을 그냥 저장하면 화면이 그것을 어느 쪽으로도 그리지 못하고,\n * 그 표현이 목록에서 오류 없이 사라진다. 비어 있는 것은 `index` 를 뜻하므로 그대로 둔다.\n */\n const role = rep.role == null || String(rep.role).trim() === '' ? undefined : String(rep.role).trim()\n if (role && !(REPRESENTATION_ROLES as readonly string[]).includes(role)) {\n return { ok: false, error: `unknown representation role \"${role}\" — one of: ${REPRESENTATION_ROLES.join(', ')}` }\n }\n /* 묶는 이름은 자유 문자열이다. 다만 공백만 있는 값은 「묶이지 않음」과 같으므로 비운다. */\n const group = rep.group == null || String(rep.group).trim() === '' ? undefined : String(rep.group).trim()\n\n const saved = await repo.save(\n repo.create({\n ...(existing ?? {}),\n domain: { id: domainId } as any,\n space: { id: space.id } as any,\n type: rep.type,\n name: rep.name,\n seq: rep.seq ?? 0,\n isPrimary: !!rep.isPrimary,\n config: rep.config,\n group: group ?? null,\n role: role ?? null\n } as Partial<TwinSpaceRepresentation>)\n )\n return { ok: true, id: saved.id }\n }\n\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => Boolean, { description: 'Delete a representation and its areas.' })\n async deleteSpaceRepresentation(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<boolean> {\n const domainId = context.state.domain.id\n await getRepository(TwinSpaceArea).delete({ domain: { id: domainId } as any, representation: { id } as any })\n await getRepository(TwinSpaceRepresentation).delete({ domain: { id: domainId } as any, id } as any)\n return true\n }\n\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => Boolean, { description: 'Set the primary representation of a space.' })\n async setPrimaryRepresentation(@Arg('spaceId') spaceId: string, @Arg('repId') repId: string, @Ctx() context: ResolverContext): Promise<boolean> {\n const domainId = context.state.domain.id\n const spaceRepo = getRepository(TwinSpace)\n const space = await spaceRepo.findOne({ where: { domain: { id: domainId }, spaceId } })\n if (!space) return false\n await spaceRepo.save(spaceRepo.create({ ...space, primaryRepresentationId: repId }))\n const repRepo = getRepository(TwinSpaceRepresentation)\n const reps = await repRepo.find({ where: { domain: { id: domainId }, space: { id: space.id } } })\n for (const rep of reps) if (!!rep.isPrimary !== (rep.id === repId)) await repRepo.save(repRepo.create({ ...rep, isPrimary: rep.id === repId }))\n return true\n }\n\n /* ── Area (map/image 폴리곤) ────────────────────────────────────── */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => ScalarObject, { description: 'Create/update a polygon area (geometry + drillTo model + binding). Returns {id}.' })\n async saveSpaceArea(\n @Arg('representationId') representationId: string,\n @Arg('area', type => ScalarObject) area: any,\n @Ctx() context: ResolverContext\n ): Promise<any> {\n const domainId = context.state.domain.id\n const repo = getRepository(TwinSpaceArea)\n const existing = area?.id ? await repo.findOne({ where: { id: area.id } }) : null\n /* drillTo/binding 은 선택적 링크 — 클라가 해제(undefined)로 보내면 JSON 에서 키가 빠져 TypeORM 이 \"변경 없음\"으로\n * 무시하므로 기존 값이 남는다. null 로 강제해 명시적으로 컬럼을 비운다(저장은 늘 전체 필드를 보냄 → 덮어쓰기 안전). */\n const saved = await repo.save(repo.create({ ...(existing ?? {}), domain: { id: domainId } as any, representation: { id: representationId } as any, name: area.name, type: area.type, geometry: area.geometry, drillTo: area.drillTo ?? null, binding: area.binding ?? null, style: area.style }))\n return { ok: true, id: saved.id }\n }\n\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => Boolean, { description: 'Delete a polygon area.' })\n async deleteSpaceArea(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<boolean> {\n await getRepository(TwinSpaceArea).delete({ domain: { id: context.state.domain.id } as any, id } as any)\n return true\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"twin-space.js","sourceRoot":"","sources":["../../../server/service/twin-space/twin-space.ts"],"names":[],"mappings":"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","sourcesContent":["import { CreateDateColumn, Entity, Index, Column, RelationId, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'\nimport { ObjectType, Field, ID } from 'type-graphql'\n\nimport { Domain, ScalarObject } from '@things-factory/shell'\n\n/*\n * TwinSpace — 테넌트 1급 공간(Site) 정의(ADR-0016). 트윈보다 먼저 세우는 몰입의 무대.\n * 공간이 보이는 방식(레이아웃·배경·영역)은 정규화됨: TwinSpaceRepresentation(map|image|model —\n * 이미지 배경은 attachment-base 참조 `config.imageUrl`) + TwinSpaceArea. 인스턴스는 model.spaceId 로 바인딩.\n * - geo = 사이트 지오레퍼런스, primaryRepresentationId = 홈/모니터 대표 표현.\n * - content(simple-json) 는 정규화 이전 레거시 단일 blob(@deprecated, P6 제거) — 신규 저작은 representation/area 로.\n * (CLAUDE.md: 모든 @ObjectType/@Field 영문 description 필수.)\n */\n@Entity()\n@Index('ix_twin_space_0', (e: TwinSpace) => [e.domain, e.spaceId], { unique: true })\n@ObjectType({ description: 'A tenant-level twin Space (Site) — the shared spatial stage set up before instances; its layout, backdrop and areas are normalized into representations (image backdrops via attachment-base) and areas.' })\nexport class TwinSpace {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier of the space record.' })\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'Owning tenant domain.' })\n domain?: Domain\n\n @RelationId((e: TwinSpace) => e.domain)\n domainId?: string\n\n @Column()\n @Field({ description: 'Space id (unique within a domain).' })\n spaceId: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Human-readable space name.' })\n name?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Space description.' })\n description?: string\n\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: 'Site georeference { lat, lon, rotation } — pins the design frame to the earth so every representation lands in the same place. `rotation` is degrees clockwise from true north (bearing convention: 0 = north, 90 = east) by which the design frame is turned.' })\n geo?: any\n\n /*\n * 설계 좌표계의 범위 — **미터**. `geo` 와 한 쌍이다.\n *\n * 구역(TwinArea.layout)은 표현 중립 설계 좌표 `{x, y, w, h}` 로 놓인다. 그 좌표가 어디까지인지를\n * 이 둘이 정하고, `geo` 가 그것을 지구에 붙인다 — 지도로 보든 도면 이미지로 보든 보드로 보든\n * 구역이 같은 자리에 있어야 하므로, 범위도 지오레퍼런스도 **표현이 아니라 공간**이 갖는다.\n *\n * 설계 좌표 (x, y) ──[extentX/extentY 로 중심 이동]──▶ 미터 오프셋 ──[geo]──▶ 위경도\n *\n * `width`/`depth` 라 부르지 않는 이유: 컴포넌트 속성의 `depth` 는 높이 축(z)이다. 같은 화면에서\n * 한 낱말이 두 축을 가리키면 읽는 사람이 매번 어느 쪽인지 물어야 한다.\n * 단위 필드는 두지 않는다 — 미터로 고정이고, 있으면 \"단위를 지원한다\" 는 거짓 신호가 된다.\n */\n @Column({ type: 'float', nullable: true })\n @Field({ nullable: true, description: 'Extent of the design frame along the east-west axis, in metres. Area layouts are placed within this frame.' })\n extentX?: number\n\n @Column({ type: 'float', nullable: true })\n @Field({ nullable: true, description: 'Extent of the design frame along the north-south axis, in metres. Area layouts are placed within this frame.' })\n extentY?: number\n\n /**\n * 이 현장의 시각 기준 — IANA 이름(`'America/Tijuana'`).\n *\n * **테넌트가 아니라 공간이 갖는다.** 한 테넌트가 Rosarito(태평양)와 한국 공장을 함께 가질 수 있고,\n * 테넌트 단위(`Domain.timezone`)로 두면 둘 중 하나는 반드시 틀린다. 교대가 \"06시\" 라고 할 때 그것이\n * 어느 06시인가는 **물리 현장의 성질**이다.\n *\n * 오프셋 숫자가 아니라 이름으로 둔다: 숫자로는 일광절약시간을 표현할 수 없고, 나중에 절대 구간\n * (표준 `StartDateTime`)으로 옮길 때도 이름이 있어야 계산할 수 있다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'IANA time zone of this physical site (e.g. America/Tijuana) — the base for reading shift times. Belongs to the space, not the tenant, because one tenant can run plants in different zones.' })\n timezone?: string\n\n /**\n * **이 현장이 언제 도나** — 표준 `WorkCalendarEntry`(복수). 휴일 · 주말 · 정기보수가 여기 든다.\n *\n * ── 왜 공간에 있나 (2026-09-07) ──────────────────────────────────────────\n * 이 칸이 없던 동안 설비와 사람만 달력을 가질 수 있었다. 그래서 **현장이 언제 쉬는지가 어디에도\n * 없었고**, 계획 조업 시간이 「재기 시작한 뒤 흐른 시간 전부」였다 — 주말도 야간도 휴일도 계획 조업으로\n * 세었다. [산술] 8시간 × 주5일이면 168시간 중 40시간이 조업이니 그 수가 4.2배 크다.\n *\n * 시간대와 같은 축이라 같은 자리에 둔다. 「이 현장의 06:00 이 언제인가」와 「이 현장이 언제 도나」는\n * 둘 다 현장이 갖는 값이고, 테넌트가 아니다 — 한 테넌트가 다른 시간대·다른 조업일의 공장을 함께 가진다.\n *\n * `withSpaceTimeBase` 가 이 값을 모델에 얹고, 거기서 OEE 누적기가 계획 정지를 낸다. 얹는 자리를\n * 시간대와 하나로 둔 이유는 **한쪽만 채워진 상태**를 만들지 않기 위해서다.\n *\n * 없으면 아무것도 가정하지 않는다 — 종일 가동으로 보는 것은 「제약이 없다」가 아니라 「선언되지\n * 않았다」이고, 그 사실은 세어져야 한다(안 그러면 사람이 수를 되살리려고 아무 달력이나 채운다).\n */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, {\n nullable: true,\n description:\n 'When this site runs — standard WorkCalendarEntry list (holidays, weekends, planned maintenance). Absent means undeclared, not unrestricted: planned busy time then counts the whole window.'\n })\n workCalendar?: any\n\n /**\n * 현장의 **우편 주소** — 사람이 읽고 서류에 적는 값.\n *\n * ── 왜 좌표와 따로 두나 (2026-09-07) ────────────────────────────────────\n * `geo` 는 도면을 지구에 붙이는 값이고 화면이 쓴다. 주소는 **사람과 서류의 값**이다 — 납품·통관·\n * 감사에 그 문자열이 그대로 들어가고, 좌표에서 되돌려 만들 수 없다(같은 좌표에 여러 주소가 있고\n * 동·호가 좌표에 없다).\n *\n * 고객이 준 것을 그대로 적는다. 우편번호·동·호를 우리가 지어내거나 좌표에서 유추하지 않는다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Postal address of this site as the customer states it. Kept apart from `geo`: a floor or unit number has no coordinate, and paperwork carries this string verbatim.' })\n address?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Id of the primary representation shown on the home/monitor.' })\n primaryRepresentationId?: string\n\n /**\n * 이 **현장**의 로컬 선언 겹 — 요금처럼 **트윈이 아니라 현장이 정하는 수**.\n *\n * ── 왜 트윈에 두면 안 되나 (2026-08-18) ────────────────────────────────────\n * 요금 단가·통화는 그 현장의 계약이므로, 같은 수전을 보는 EMS 트윈이 둘이면 **같은 값을 두 번**\n * 선언해야 했다. 두 번 적는 값은 반드시 갈라진다(하나만 고치는 날이 온다) — 그때 두 트윈이 다른\n * 금액을 말하고, 어느 쪽이 맞는지 화면으로는 알 수 없다.\n *\n * 적용 순서는 **현장 먼저, 트윈이 나중**이다. 더 좁은 선언이 이긴다 — 한 현장 안에서 특정 트윈만\n * 다른 값으로 실험하는 일이 있고(예: 인상된 단가로 what-if), 그때 현장 값을 고치게 만들면 나머지\n * 트윈이 함께 흔들린다.\n *\n * 모양은 트윈 겹과 같다(`LocalDeclaration`) — 같은 규칙(검사·기억·철회)을 두 벌로 만들지 않는다.\n */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: \"Locally-declared values that belong to this facility rather than to one twin (tariff, currency). Applied to every twin in the space before the twin's own declarations, which win when both name the same property.\" })\n localDeclarations?: any\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'Timestamp when the space was created.' })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true, description: 'Timestamp when the space was last updated.' })\n updatedAt?: Date\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/twin-state/index.ts"],"names":[],"mappings":";;;AAAA,6EAAoE;AAEvD,QAAA,SAAS,GAAG,CAAC,kDAAqB,CAAC,CAAA","sourcesContent":["import { TwinStateSubscription } from './twin-state-subscription.js'\n\nexport const resolvers = [TwinStateSubscription]\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"twin-state-subscription.js","sourceRoot":"","sources":["../../../server/service/twin-state/twin-state-subscription.ts"],"names":[],"mappings":";;;;AAAA,+CAA2C;AAC3C,+CAAwF;AAExF,iDAA0F;AAE1F,oDAAkD;AAClD,8EAA+H;AAE/H,+EAA+E;AAExE,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;CAY5B,CAAA;AAZY,4CAAgB;AAE3B;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,mDAAmD,EAAE,CAAC;;oDAC1D;AAGlB;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,yCAAyC,EAAE,CAAC;;8CACtD;AAGZ;IADC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,kBAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,2BAA2B,EAAE,CAAC;;kDAChE;AAGjB;IADC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,oBAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uFAAuF,EAAE,CAAC;;iDACzI;2BAXF,gBAAgB;IAD5B,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,mEAAmE,EAAE,CAAC;GACpF,gBAAgB,CAY5B;AAED;;;;;;;;;;;GAWG;AAEI,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IAqBhC,SAAS,CAAS,OAAyB,EAAqB,UAAkB;QAChF,OAAO,OAAO,CAAC,SAA6B,CAAA;IAC9C,CAAC;CACF,CAAA;AAxBY,sDAAqB;AAqBhC;IApBC,IAAA,2BAAY,EAAC,OAAO,CAAC,EAAE,CAAC,gBAAgB,EAAE;QACzC,SAAS,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE;YACrC,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI,CAAA;YAC3B,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAI,OAAe,EAAE,KAAK,IAAI,EAAE,CAAA;YACtD,MAAM,IAAA,oCAA4B,EAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;YAE1F,uGAAuG;YACvG,IAAI,CAAC,qBAAU,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,6CAA6C,UAAU,EAAE,CAAC,CAAA;YAEvH,qHAAqH;YACrH,OAAO,CAAC,QAAQ,CAAC,GAAG,EAAE;gBACpB,MAAM,IAAI,GAAG,qBAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,CAAC,CAAA;gBACvD,IAAI,IAAI,EAAE,CAAC;oBACT,cAAM,CAAC,OAAO,CAAC,wCAAgB,EAAE,IAAA,wCAAgB,EAAC,MAAM,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;gBACzI,CAAC;YACH,CAAC,CAAC,CAAA;YAEF,OAAO,IAAA,mBAAI,EAAC,cAAM,CAAC,SAAS,CAAC,wCAAgB,CAAC,EAAE,IAAA,qBAAM,EAAC,IAAA,uCAAe,EAAC,MAAM,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC,CAAA;QACjG,CAAC;KACF,CAAC;IACS,mBAAA,IAAA,mBAAI,GAAE,CAAA;IAA6B,mBAAA,IAAA,kBAAG,EAAC,YAAY,CAAC,CAAA;;;4CAAsB,gBAAgB;sDAEpG;gCAvBU,qBAAqB;IADjC,IAAA,uBAAQ,GAAE;GACE,qBAAqB,CAwBjC","sourcesContent":["import { filter, pipe } from 'graphql-yoga'\nimport { Arg, Field, Int, ObjectType, Resolver, Root, Subscription } from 'type-graphql'\n\nimport { assertDomainSubscribeAllowed, pubsub, ScalarObject } from '@things-factory/shell'\n\nimport { TwinEngine } from '../../engine/index.js'\nimport { TWIN_STATE_TOPIC, twinStateFilter, twinStatePayload, type TwinStatePayload } from '../../engine/twin-state-channel.js'\n\n/* State 채널 메시지 — snapshot | delta | clock 멀티플렉싱(커널 SubscriptionMessage 대응). */\n@ObjectType({ description: 'Twin State channel message multiplexing snapshot | delta | clock.' })\nexport class TwinStateMessage {\n @Field({ description: 'Twin runtime instance id this message belongs to.' })\n instanceId: string\n\n @Field({ description: 'Message kind: snapshot | delta | clock.' })\n kind: string\n\n @Field(type => Int, { nullable: true, description: 'Monotonic state revision.' })\n revision?: number\n\n @Field(type => ScalarObject, { nullable: true, description: 'Payload — snapshot: StateSnapshot / delta: CanonicalEnvelope / clock: { simClockMs }.' })\n payload?: any\n}\n\n/*\n * 트윈 State 구독 — state-register/data-resolver 패턴 미러.\n * 구독 즉시 현재 snapshot 발행(process.nextTick) 후 delta/clock 스트림.\n *\n * The tenant is asked while streaming, not only at subscribe time (2026-09-24). Ownership used to be\n * checked once and the stream filtered on instanceId alone; the same id in two tenants is the normal\n * case (`runtime-key.ts`), so other tenants' deltas came through, and the snapshot sent to a new\n * subscriber reached every tenant subscribed to that id. Messages now carry their domain and the filter\n * compares it (`engine/twin-state-channel.ts`). A twin the caller does not own is refused rather than\n * given an empty stream — an empty stream cannot tell \"nothing changed\" from \"you may not see this\"\n * (shell's `data` throws too).\n */\n@Resolver()\nexport class TwinStateSubscription {\n @Subscription(returns => TwinStateMessage, {\n subscribe: async ({ args, context }) => {\n const { instanceId } = args\n const { domain, user } = (context as any)?.state ?? {}\n await assertDomainSubscribeAllowed(domain, user, (d, u) => process.superUserGranted(d, u))\n\n /* One refusal for both cases, so the answer does not reveal whether another tenant has such a twin. */\n if (!TwinEngine.owns(domain.id, instanceId)) throw new Error(`twin instance not running in this tenant: ${instanceId}`)\n\n /* Current state first (a late subscriber still sees snapshot → delta). It reaches this tenant's subscribers only. */\n process.nextTick(() => {\n const snap = TwinEngine.snapshot(domain.id, instanceId)\n if (snap) {\n pubsub.publish(TWIN_STATE_TOPIC, twinStatePayload(domain.id, { instanceId, kind: 'snapshot', revision: snap.revision, payload: snap }))\n }\n })\n\n return pipe(pubsub.subscribe(TWIN_STATE_TOPIC), filter(twinStateFilter(domain.id, instanceId)))\n }\n })\n twinState(@Root() payload: TwinStatePayload, @Arg('instanceId') instanceId: string): TwinStateMessage {\n return payload.twinState as TwinStateMessage\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/twin-structure/index.ts"],"names":[],"mappings":";;;AAAA,2DAAmD;AAEtC,QAAA,QAAQ,GAAG,CAAC,iCAAa,CAAC,CAAA","sourcesContent":["import { TwinStructure } from './twin-structure.js'\n\nexport const entities = [TwinStructure]\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"twin-structure.js","sourceRoot":"","sources":["../../../server/service/twin-structure/twin-structure.ts"],"names":[],"mappings":";;;;AAAA,qCAAgH;AAChH,+CAAyD;AAEzD,iDAA4D;AAC5D,yDAAgD;AAChD,0EAAyE;AAEzE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAOI,IAAM,aAAa,GAAnB,MAAM,aAAa;CAyDzB,CAAA;AAzDY,sCAAa;AAGf;IAFR,IAAA,gCAAsB,EAAC,MAAM,CAAC;IAC9B,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,iBAAE,EAAE,EAAE,WAAW,EAAE,qDAAqD,EAAE,CAAC;;yCACvE;AAInB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,cAAM,CAAC;IACzB,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,cAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uBAAuB,EAAE,CAAC;sCACvE,cAAM;6CAAA;AAGf;IADC,IAAA,oBAAU,EAAC,CAAC,CAAgB,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;;+CAC1B;AAIjB;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,kDAAkD,EAAE,CAAC;;iDACzD;AAUlB;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACvB,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,kBAAG,EAAE,EAAE,WAAW,EAAE,uKAAuK,EAAE,CAAC;;0CAClM;AAUX;IAFC,IAAA,gBAAM,EAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACtB,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,mNAAmN,EAAE,CAAC;;gDAC3N;AAMjB;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,aAAa,EAAE,WAAW,EAAE,4CAAsB,EAAE,CAAC;IACpE,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,oBAAY,EAAE,EAAE,WAAW,EAAE,gMAAgM,EAAE,CAAC;;4CACrO;AAKV;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,8EAA8E,EAAE,CAAC;;8CACvG;AAIhB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,wCAAwC,EAAE,CAAC;sCACrF,gBAAI;8CAAA;AAGd;IADC,IAAA,oBAAU,EAAC,CAAC,CAAgB,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;;gDAC1B;AAIlB;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,iCAAiC,EAAE,CAAC;sCAC9D,IAAI;gDAAA;wBAxDL,aAAa;IANzB,IAAA,gBAAM,GAAE;IACR,IAAA,eAAK,EAAC,qBAAqB,EAAE,CAAC,CAAgB,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACrG,IAAA,yBAAU,EAAC;QACV,WAAW,EACT,0QAA0Q;KAC7Q,CAAC;GACW,aAAa,CAyDzB","sourcesContent":["import { CreateDateColumn, Entity, Index, Column, RelationId, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'\nimport { ObjectType, Field, ID, Int } from 'type-graphql'\n\nimport { Domain, ScalarObject } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\nimport { modelColumnTransformer } from '../../engine/model-vocabulary.js'\n\n/*\n * TwinStructure — **한 트윈이 거쳐 온 공장 구조들.** 리비전 하나가 그 시절의 공장이다.\n *\n * ── 왜 필요한가: 역사를 잃거나 거짓말을 하거나 ──────────────────────────────\n * 공장은 바뀐다. 도장 부스를 넷 더 놓고, 라인 하나를 닫는다. 그런데 지금까지는 구조 서명이 바뀌면\n * **그 트윈의 저널을 통째로 지우는 것**이 유일한 길이었다. 안 지우면 옛 이벤트를 새 공장에 대고\n * 계산하게 되어 이력이 거짓말을 하기 때문이다 — 부스가 둘이던 시절의 사실을 여섯 개짜리 공장에 계산하면\n * 그때 없던 부스에서 일이 있었던 것처럼 보인다.\n *\n * 그래서 선택지가 둘뿐이었다: **역사를 잃거나, 거짓말을 하거나.**\n *\n * 셋째 길이 이 표다. 이벤트가 `structureRev` 로 자기 구조를 달고 다니고, 재생은 구조가 바뀌는\n * 지점에서 전환한 뒤 이어서 계산한다(`replaySegments`). 이력은 \"그때 그 공장의 사실\" 로 계속 읽힌다.\n *\n * ── 왜 model 를 통째로 담나 ─────────────────────────────────────────────────\n * 재생하려면 **그때의 토폴로지 전체**가 필요하다. 차이(diff)만 담으면 리비전 1부터 다시 계산해 쌓아야\n * 하고, 그 재계산 자체가 또 하나의 규칙이 되어 어긋난다. 구조는 이벤트에 비해 드물게 바뀌므로\n * 스냅샷을 통째로 두는 편이 싸고 정직하다(보드 이력 `BoardHistory` 와 같은 선택).\n *\n * ── audit 컬럼을 다 두지 않는 이유 ──────────────────────────────────────────\n * 리비전은 **한 번 쓰이면 바뀌지 않는다** — 그 시절의 사실이라 고칠 수가 없다. 그래서 updater·\n * deletedAt 이 없다(TwinEvent 와 같은 규율). 대신 **누가 구조를 바꿨는지**는 남긴다.\n *\n * (CLAUDE.md: 모든 @ObjectType/@Field 는 영문 description 필수.)\n */\n@Entity()\n@Index('ix_twin_structure_0', (e: TwinStructure) => [e.domain, e.instanceId, e.rev], { unique: true })\n@ObjectType({\n description:\n 'One structural revision of a twin — the plant as it stood for a stretch of its journal. Events carry the revision they were produced under, so history can be replayed against the structure that was actually in force instead of being deleted when the plant changes.'\n})\nexport class TwinStructure {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier of the structure revision record.' })\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'Owning tenant domain.' })\n domain?: Domain\n\n @RelationId((e: TwinStructure) => e.domain)\n domainId?: string\n\n @Column()\n @Field({ description: 'Twin runtime instance this structure belongs to.' })\n instanceId: string\n\n /**\n * 리비전 번호 — 1부터. 이벤트의 `structureRev` 가 이것을 가리킨다.\n *\n * 0 을 안 쓰는 이유: 컬럼이 없던 시절의 행은 `structureRev` 가 비어 있고, 그 행들은 **가장 오래된\n * 리비전**에 속한다. 0 을 유효 번호로 쓰면 \"모름\" 과 \"1번 구조\" 가 같은 값이 된다.\n */\n @Column({ type: 'int' })\n @Field(type => Int, { description: 'Revision number, starting at 1. Events reference it via structureRev; absent on rows written before structure revisions existed, which belong to the oldest revision.' })\n rev: number\n\n /**\n * 구조 서명 — 이 리비전이 앞 리비전과 **정말 다른지** 판정한 근거.\n *\n * 서명이 같으면 새 리비전을 만들지 않는다. 프로비저닝은 자주 다시 도는데(부팅마다) 그때마다\n * 리비전이 하나씩 늘면 이력이 뜻 없는 마디로 잘게 쪼개진다.\n */\n @Column({ length: 64 })\n @Field({ description: 'Short hash of the structural signature (locations + equipment). A re-provision with the same signature does not create a new revision — otherwise every reboot would slice the history into meaningless segments.' })\n signature: string\n\n /* 읽을 때 어휘 세대를 흡수한다 — 옛 리비전의 모델로 지문을 다시 계산할 때 빈 공장으로 읽히면\n 구조 변경 감지가 죽는다(`engine/model-vocabulary.ts`). */\n @Column({ type: 'simple-json', transformer: modelColumnTransformer })\n @Field(type => ScalarObject, { description: 'The full TwinModelDef as it stood for this revision. Stored whole rather than as a diff: replay needs the entire topology, and rebuilding it from diffs would be a second rule that can drift.' })\n model: any\n\n /** 왜 바뀌었나 — 사람이 남기는 한 줄. 없을 수 있다(자동 프로비저닝은 이유를 모른다). */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Why the structure changed — a human note, absent for automatic provisioning.' })\n comment?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'Who changed the structure, when known.' })\n creator?: User\n\n @RelationId((e: TwinStructure) => e.creator)\n creatorId?: string\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'When this revision took effect.' })\n createdAt?: Date\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"event-subjects.js","sourceRoot":"","sources":["../../../server/service/twin-subject/event-subjects.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * **이 사건은 무엇에 대한 것인가** — 사건 하나에서 가리키는 대상들을 뽑는다.\n *\n * ── 왜 필요한가 (2026-08-27 실측) ───────────────────────────────────────────\n * 360 화면이 「이 대상의 이력」을 묻는다. 그 조회가 64초다. 대상으로 걸러 시각순으로 정렬하는 목차가\n * 없기 때문이고, 목차를 얹어도 풀리지 않는다.\n *\n * 부분일치(지금 화면) 63.998초 앞에 와일드카드가 붙어 목차를 쓸 수 없다\n * 정확일치로 바꾸면 67.316초 · 0건 품목 하나에 로트가 여럿이라 성립하지 않는다\n * 앞부분 범위로 바꾸면 68.402초 목차는 쓰지만 정렬 때문에 맞는 행을 다 가져온다\n *\n * 저장된 식별자에 품목과 로트가 한 문자열로 붙어 있는 것이 그 원인이다.\n *\n * https://seunghwa.net/class/product-M0000618-lot-L2604100071\n *\n * 그래서 문자열을 자르는 대신 **대상마다 한 줄**을 적는다. 같은 사건이 품목과 로트를 함께 가리키면\n * 두 줄이다. 자를 규칙이 필요 없다 — 넣는 쪽이 이미 둘을 안다.\n *\n * ── 왜 「뜻이 달라지는 키 칸」이 아닌가 ─────────────────────────────────────\n * `key01~05` 처럼 칸을 두고 사건 종류에 따라 뜻을 달리 쓰는 방식을 **이 표에는** 쓰지 않는다.\n *\n * 그 방식 자체가 틀린 것은 아니다. 데이터셋마다 정의가 따로 있는 프레임워크에서는 합리적이다 — 읽는\n * 쪽이 그 정의를 보고 해석한다. 우리 저널은 표 하나를 화면 여럿이 읽고 볼 정의가 없다. 그래서\n * 「M0000438 을 찾아 줘」가 어떤 줄에서는 품목, 다른 줄에서는 오더가 되고, 묻는 쪽이 사건 종류마다\n * 어느 칸에 무엇이 들어가는지 외워야 한다.\n *\n * **종류를 값으로 밝힌다.** 그러면 읽는 쪽이 사건 종류표를 들고 있지 않아도 된다.\n *\n * ── 왜 순수 모듈인가 ────────────────────────────────────────────────────────\n * 기록 경로에서 불리므로 DB·데코레이터를 알지 않는다. 그래야 테스트가 값으로 확인할 수 있다\n * (`twin-event-keys` 와 같은 이유다).\n *\n * 설계: `operato-twin/design/plans/read-models.md`\n */\nimport { twinEventKeys } from '../twin-event/twin-event-keys.js'\n\n/**\n * 대상의 종류 — **닫힌 목록**이다.\n *\n * 열어 두면 넣는 쪽마다 다른 이름을 쓰게 되고(설비를 세 가지 이름으로 부르는 식), 그러면 조회가\n * 어느 이름으로 물어야 하는지 알 수 없다. 늘려야 하면 이 목록에 더한다.\n *\n * item 선언된 품목. 로트 여럿이 같은 품목을 가리킨다\n * lot 개체 하나. 저장된 식별자 그대로다\n * order 오더\n * location 자리\n * equipment 설비\n * task 작업\n * transaction 거래(PO/SO/생산오더) — 오더와 다르다. 전에는 오더 칸에 섞여 들어와 한 오더의\n * 이력이 두 쪽으로 갈라졌다(2026-09-05)\n */\nexport const SUBJECT_KINDS = ['item', 'lot', 'order', 'location', 'equipment', 'task', 'transaction'] as const\nexport type SubjectKind = (typeof SUBJECT_KINDS)[number]\n\nexport interface EventSubject {\n kind: SubjectKind\n id: string\n}\n\n/**\n * 개체 식별자에서 **선언된 품목**을 찾아 주는 함수 — 부르는 쪽이 넘긴다.\n *\n * 이 모듈이 문자열을 자르지 않는 이유는 그 규칙이 연결된 시스템마다 다르기 때문이다. 어느 부호가\n * 로트를 가리키는지 커널 계약이 정한 적이 없고, chef 의 `-lot-` 을 여기에 굳히면 이 파일이 그\n * 시스템 위에 서게 된다(`item-ref.ts` 가 같은 판단을 적어 두었다).\n *\n * 그래서 판정은 **그 트윈의 선언**과 견주어 하고(`resolveByDeclaration`), 그 선언을 아는 것은\n * 부르는 쪽이다. 답하지 못하면 `undefined` — 그때는 품목 줄을 만들지 않는다. 짐작으로 이으면 남의\n * 품목 이력에 이 사건이 섞인다.\n */\nexport type ItemResolver = (epc: string) => string | null | undefined\n\n/** 컬럼 길이 상한 — `twin-event-keys` 와 같은 값이다(같은 표에 나란히 서는 값이므로). */\nconst MAX_ID = 255\n\nfunction put(out: EventSubject[], kind: SubjectKind, raw: unknown): void {\n if (raw === undefined || raw === null) return\n const id = String(raw).trim()\n if (!id || id.length > MAX_ID) return\n /* 같은 대상을 두 번 적지 않는다 — 사건 하나에 같은 자리가 두 번 실려 올 수 있다. */\n if (out.some(s => s.kind === kind && s.id === id)) return\n out.push({ kind, id })\n}\n\n/**\n * 사건 하나가 가리키는 대상들 — 없으면 빈 배열이다.\n *\n * 승격된 검색 키(`twinEventKeys`)를 그대로 쓴다. 그 함수가 이미 사건 종류별로 어디에서 값을 꺼낼지\n * 알고 있으므로, 여기서 다시 판단하면 두 곳이 어긋난다.\n *\n * **작업은 승격 키에 없어서 봉투에서 직접 읽는다.** 승격 컬럼을 늘리는 것은 이 작업의 범위가 아니다\n * (그것이 바로 이 파생 표로 대신하려는 일이다).\n */\nexport function subjectsOf(envelope: any, opts?: { itemOf?: ItemResolver }): EventSubject[] {\n const keys = twinEventKeys(envelope)\n const out: EventSubject[] = []\n\n /*\n * ── 개체로 온 것과 클래스로 온 것을 갈라 적는다 (2026-08-27 실측) ────────────\n *\n * `epcOf` 는 셋을 한 칸에 담는다 — `epcList[0]` · `parentID` · `quantityList[0].epcClass`.\n * 승격 컬럼이 하나라서 그렇게 되어 있고, 그 칸으로는 「개체인가 클래스인가」를 알 수 없다.\n *\n * 실측한 chef 의 사건 하나가 그 경우다. 물건을 개체로 세지 않고 무게로 담는 현장이라 `epcList` 가\n * 비어 있고 `quantityList` 에 클래스가 있다.\n *\n * epcList []\n * quantityList [{ epcClass: 'https://…/product-M0000879', quantity: 11797.6, uom: 'KGM' }]\n *\n * 그것을 로트로 적으면 뜻이 틀린다 — 개체가 아니라 품목이다. 이 표는 종류를 값으로 밝히므로\n * 갈라 적을 수 있다.\n */\n const d = envelope?.data ?? envelope ?? {}\n const instanceEpc = d.epcList?.[0] ?? d.parentID\n const classEpc = d.quantityList?.[0]?.epcClass\n\n if (instanceEpc) {\n /* 개체는 저장된 식별자 그대로다 — 자르지 않는다. */\n put(out, 'lot', instanceEpc)\n /*\n * 품목은 선언과 견주어 찾는다. 답이 개체 식별자와 같으면 품목을 찾지 못한 것이므로 줄을 만들지\n * 않는다 — `itemRefOf` 는 URI 세계에서 문자열을 그대로 돌려주기 때문에 그 값이 그대로 올 수 있다.\n */\n const item = opts?.itemOf?.(String(instanceEpc))\n if (item && item !== instanceEpc) put(out, 'item', item)\n }\n if (classEpc) {\n /*\n * 클래스로 온 것은 개체 줄을 만들지 않는다 — 그 사건에 개체가 없다. 품목 줄만 만든다.\n *\n * 선언과 견주어 품목을 찾고, 찾지 못하면 클래스 문자열을 그대로 품목으로 쓴다. 클래스는 이미 품목을\n * 가리키는 값이므로 그것이 짐작이 아니다(개체 식별자를 그대로 품목으로 쓰는 것과 다르다).\n */\n const item = opts?.itemOf?.(String(classEpc)) ?? null\n put(out, 'item', item || classEpc)\n }\n put(out, 'order', keys.orderId)\n /*\n * 거래는 오더가 아니다 — 갈라 적는다. 합쳐 적으면 EPCIS 사건의 GDTI 가 오더 이름으로 앉고,\n * 그러면 한 오더의 이력이 두 쪽이 된다(`twin-event-keys.ts` 의 그 실측).\n */\n put(out, 'transaction', keys.bizTransaction)\n put(out, 'location', keys.locationId)\n put(out, 'equipment', keys.moverId)\n\n const data = envelope?.data\n put(out, 'task', data?.taskId)\n /*\n * ── 작업이 오간 두 자리도 적는다 ────────────────────────────────────────────\n *\n * 승격 컬럼(`locationId`)은 EPCIS 자리만 담는다(`readPoint` · `bizLocation` · `location`). 작업이\n * A 에서 B 로 갈 때 그 컬럼에는 **둘 중 하나만** 넣을 수 있어서 아무것도 넣지 않는다.\n *\n * 여기서는 둘 다 적는다. 이것이 승격 컬럼과의 어긋남이 아니라 **이 표가 할 수 있는 일**이다 —\n * 자리 하나에 줄 하나이므로 두 자리를 함께 담는다. 「그 자리에서 무슨 일이 있었나」를 묻는 화면은\n * 출발지와 도착지를 둘 다 그 자리의 사건으로 본다.\n */\n put(out, 'location', data?.fromNode)\n put(out, 'location', data?.toNode)\n\n return out\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/twin-subject/index.ts"],"names":[],"mappings":";;;;AAAA,mEAA0D;AAKjD,iGALA,wCAAgB,OAKA;AAJzB,mEAA+E;AAIpD,iGAJlB,wCAAgB,OAIkB;AAAE,oGAJlB,2CAAmB,OAIkB;AAFnD,QAAA,QAAQ,GAAG,CAAC,wCAAgB,CAAC,CAAA;AAC7B,QAAA,SAAS,GAAG,CAAC,wCAAgB,EAAE,2CAAmB,CAAC,CAAA;AAEhE,8DAAmC;AACnC,6DAAkC;AAClC,mEAAwC;AACxC,qEAA0C","sourcesContent":["import { TwinSubjectEvent } from './twin-subject-event.js'\nimport { TwinSubjectQuery, TwinSubjectMutation } from './twin-subject-query.js'\n\nexport const entities = [TwinSubjectEvent]\nexport const resolvers = [TwinSubjectQuery, TwinSubjectMutation]\nexport { TwinSubjectEvent, TwinSubjectQuery, TwinSubjectMutation }\nexport * from './event-subjects.js'\nexport * from './item-resolver.js'\nexport * from './twin-subject-writer.js'\nexport * from './twin-subject-backfill.js'\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"item-resolver.js","sourceRoot":"","sources":["../../../server/service/twin-subject/item-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import { itemRefOf, resolveByDeclaration } from '../twin-model/item-ref.js'\nimport type { ItemResolver } from './event-subjects.js'\n\n/*\n * **개체 식별자 → 선언된 품목** — 대상별 사건 목록에 품목 줄을 적기 위한 것.\n *\n * ── 왜 문자열을 자르지 않나 ─────────────────────────────────────────────────\n * 저장된 식별자에 품목과 로트가 붙어 있다.\n *\n * https://seunghwa.net/class/product-M0000438-lot-L2403090386\n *\n * 여기서 품목을 꺼내려면 `-lot-` 이 로트를 가리킨다는 것을 알아야 하는데, 그 부호는 연결된 시스템마다\n * 다르고 커널 계약이 정한 적이 없다. 한 시스템의 관습을 규칙으로 굳히면 이 코드가 그 시스템 위에 서게\n * 된다(`item-ref.ts` 가 같은 판단을 적어 두었다).\n *\n * 그래서 **그 트윈의 선언과 견주어** 판정한다. 선언된 품목 중 이 식별자가 가리키는 것을 찾고, 후보가\n * 둘 이상이면 답하지 않는다 — 짐작으로 이으면 남의 품목 이력에 이 사건이 섞인다.\n *\n * ── 왜 기억해 두나 ──────────────────────────────────────────────────────────\n * 사건마다 부르는 함수다(초당 130건). 같은 개체 식별자가 계속 되풀어 오므로, 한 번 판정한 것은 기억해\n * 둔다. 트윈의 선언이 바뀌면 그 기억을 버려야 하므로 **선언의 개수와 첫 항목으로 세대**를 만든다 —\n * 선언이 바뀌면 세대가 달라져 다시 판정한다.\n */\n\n/** 트윈마다 기억해 둔 판정 — 세대가 바뀌면 버린다. */\nconst memo = new Map<string, { generation: string; declared: Map<string, string>; seen: Map<string, string | null> }>()\n\n/** 한 트윈이 기억할 개체 식별자 수 상한 — 넘으면 비우고 다시 채운다(무한히 자라지 않게). */\nconst MEMO_LIMIT = 20_000\n\n/**\n * 선언된 품목의 식별자들을 모은다 — 트윈 모델이 말하는 것만.\n *\n * `materialDefinitions` 가 이 트윈이 다루는 품목의 선언이다. 그 항목의 `id` 와, 연결된 시스템의\n * 어휘로 이어 둔 값(`binding`)을 함께 후보로 둔다 — 개체 식별자는 후자를 앞부분으로 갖는다.\n */\nfunction declaredItems(model: any): Map<string, string> {\n const out = new Map<string, string>()\n const defs = model?.productionSpec?.materialDefinitions ?? model?.materialDefinitions\n if (!Array.isArray(defs)) return out\n for (const d of defs) {\n const id = String(d?.id ?? '').trim()\n if (!id) continue\n out.set(id, id)\n const binding = d?.binding\n const bound = typeof binding === 'string' ? binding : binding?.epcClass ?? binding?.id\n const b = String(bound ?? '').trim()\n if (b) out.set(b, id)\n }\n return out\n}\n\n/** 선언이 바뀌었는지 값싸게 가리는 표식 — 개수와 첫·끝 항목이면 실제 변경을 잡는다. */\nfunction generationOf(declared: Map<string, string>): string {\n const keys = [...declared.keys()]\n return `${keys.length}:${keys[0] ?? ''}:${keys[keys.length - 1] ?? ''}`\n}\n\n/**\n * 그 트윈의 품목 판정 함수 — 선언이 없으면 `undefined`(그때는 품목 줄을 만들지 않는다).\n *\n * 선언을 모르는 트윈에서 품목을 지어내지 않는다. 개체 줄(`lot`)은 그대로 적히므로 이력 조회는 로트로\n * 답할 수 있고, 품목 축만 비어 있다. 그 사실을 조회하는 쪽이 말한다.\n */\nexport function itemResolverFor(domainId: string, instanceId: string, model: any): ItemResolver | undefined {\n const declared = declaredItems(model)\n if (!declared.size) return undefined\n\n const key = `${domainId}:${instanceId}`\n const generation = generationOf(declared)\n let entry = memo.get(key)\n if (!entry || entry.generation !== generation) {\n entry = { generation, declared, seen: new Map() }\n memo.set(key, entry)\n }\n const state = entry\n\n return (epc: string) => {\n const hit = state.seen.get(epc)\n if (hit !== undefined) return hit\n /* 판정은 선언이 한다 — 후보가 둘 이상이면 `undefined` 를 내고, 그것을 「모른다」로 기억한다. */\n const found = resolveByDeclaration(itemRefOf(epc), state.declared) ?? null\n if (state.seen.size >= MEMO_LIMIT) state.seen.clear()\n state.seen.set(epc, found)\n return found\n }\n}\n\n/** 시험이 쓰는 초기화 — 프로세스 안의 기억이므로 시험마다 비운다. */\nexport function resetItemResolverMemo(): void {\n memo.clear()\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"subject-query-shape.js","sourceRoot":"","sources":["../../../server/service/twin-subject/subject-query-shape.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SubjectKind } from './event-subjects.js'\n\n/*\n * **이 물음을 파생 테이블로 답할 수 있나** — 순수 판단만 여기 둔다.\n *\n * 데이터베이스도 커널도 부르지 않는다. 그래서 시험이 이 파일을 그대로 부를 수 있다(데코레이터가 없다).\n * 판단이 조회 코드 안에 섞여 있으면 그 판단만 확인할 방법이 없고, 그러면 「어떤 물음이 빠른 경로로\n * 가는가」가 아무도 읽지 못하는 규칙이 된다.\n *\n * 좁게 잡는다. 파생 테이블은 「대상 하나를 시각순으로」에 맞춰 생겼고, 그 모양이 아닌 물음에 억지로\n * 답하면 목록이 알리지 않고 달라진다. 맞지 않으면 `null` 이고, 부르는 쪽은 저널로 간다.\n */\n\n/** 화면이 저널에 묻던 축 → 파생 테이블의 대상 종류. */\nexport const AXIS_KIND: Record<string, SubjectKind> = {\n epc: 'item',\n orderId: 'order',\n locationId: 'location',\n moverId: 'equipment' // vocabulary-guard: allow — 저널의 승격 컬럼 이름(저장된 계약)\n}\n\n/** 이 경로가 다룰 수 있는 필터 이름 — 이 밖의 필터가 하나라도 있으면 저널로 보낸다. */\nconst ALLOWED_FILTERS = new Set(Object.keys(AXIS_KIND).concat('eventTime'))\n\nexport interface SubjectQueryShape {\n kind: SubjectKind\n /** 화면이 건넨 축 값 — 짧게 줄어 있을 수 있다(되돌리는 일은 조회하는 쪽이 한다). */\n value: string\n untilTime?: Date\n fromTime?: Date\n}\n\n/**\n * 목록 계약의 조건을 읽어 「대상 하나의 시각순 이력」인지 가린다 — 아니면 `null`.\n *\n * 받는 모양\n * 축 하나(품목·오더·자리·설비)를 `eq` 또는 `search` 로 · 시각 상한이나 구간 · 시각 내림차순 정렬\n *\n * 보내는 모양(저널로)\n * 자유 검색어 · 종류 필터 · 상관 식별자 · 축 둘 이상 · 그 밖의 정렬이나 연산자\n */\nexport function readModelShape(params: any, sortings: any[]): SubjectQueryShape | null {\n const filters: any[] = Array.isArray(params?.filters) ? params.filters : []\n if (typeof params?.search === 'string' && params.search.trim()) return null\n for (const f of filters) if (!ALLOWED_FILTERS.has(f?.name)) return null\n\n /* 정렬은 시각 내림차순 하나일 때만 — 파생 테이블의 목차가 그 축이다. */\n if (sortings.length !== 1 || sortings[0]?.name !== 'eventTime' || sortings[0]?.desc !== true) return null\n\n const axisFilters = filters.filter(f => f.name !== 'eventTime')\n if (axisFilters.length !== 1) return null\n const axis = axisFilters[0]\n const kind = AXIS_KIND[axis.name]\n if (!kind) return null\n /* 축은 값 하나여야 한다 — 여러 값(`in`)이면 목차를 한 번에 타지 못한다. */\n if (axis.operator !== 'eq' && axis.operator !== 'search') return null\n const value = typeof axis.value === 'string' ? axis.value.trim() : ''\n if (!value) return null\n\n /* 시각은 상한·구간만 받는다(시간여행). 그 밖의 연산자는 뜻이 달라지므로 저널로 보낸다. */\n let untilTime: Date | undefined\n let fromTime: Date | undefined\n for (const f of filters) {\n if (f.name !== 'eventTime') continue\n if (f.operator === 'lte' || f.operator === 'lt') {\n untilTime = toDate(f.value)\n if (!untilTime) return null\n } else if (f.operator === 'between' && Array.isArray(f.value)) {\n fromTime = toDate(f.value[0])\n untilTime = toDate(f.value[1])\n if (!fromTime || !untilTime) return null\n } else return null\n }\n\n return { kind, value, untilTime, fromTime }\n}\n\n/** 값을 날짜로 — 못 읽으면 `undefined`(지어내지 않는다). */\nexport function toDate(v: unknown): Date | undefined {\n if (v instanceof Date) return Number.isNaN(v.getTime()) ? undefined : v\n if (typeof v !== 'string' && typeof v !== 'number') return undefined\n const d = new Date(v as any)\n return Number.isNaN(d.getTime()) ? undefined : d\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"twin-subject-backfill.js","sourceRoot":"","sources":["../../../server/service/twin-subject/twin-subject-backfill.ts"],"names":[],"mappings":"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","sourcesContent":["import { getRepository } from '@things-factory/shell'\n\nimport { twinLog, twinWarn } from '../../engine/log.js'\nimport { TwinEvent } from '../twin-event/twin-event.js'\nimport { TwinInstance } from '../twin-instance/twin-instance.js'\nimport { TwinSubjectEvent } from './twin-subject-event.js'\nimport { writeSubjectRows } from './twin-subject-writer.js'\nimport { itemResolverFor } from './item-resolver.js'\n\n/*\n * **이미 저장된 사건을 이 표로 옮긴다** — 요청받은 트윈만.\n *\n * ── 왜 기본이 아닌가 ────────────────────────────────────────────────────────\n * 이 표의 규칙은 「앞으로만 채운다」다. 그래야 새 조회 축이 필요할 때마다 3,128만 줄을 다시 만드는\n * 구조가 되지 않는다(설계: read-models.md §4).\n *\n * 그런데 이미 쌓인 트윈에서 이력을 보려면 그 구간이 있어야 한다. 그래서 **부르면 채우는 도구**로 둔다 —\n * 마이그레이션이 아니고, 부팅에 자동으로 돌지도 않는다. 트윈을 지정해 부른다.\n *\n * ── 어디까지 옮겼는지 적어 둔다 (2026-08-27 · 세 번 틀린 뒤) ─────────────────\n * 저장하지 않고 표의 내용으로 진행 지점을 알아내려 했고 세 번 틀렸다.\n *\n * ① 표의 최대 리비전 뒤부터 앞으로 채워진 줄의 최댓값이 곧 저널의 끝이라 소급을 다 건너뛴다\n * ② 표의 최소 리비전 앞까지 소급이 1부터 적으면 최솟값이 1이 되어 구간이 0으로 접힌다\n * ③ 표의 최소 시각 아래로 사건이 시각순으로 오지 않는 트윈에서 곧바로 끝난다\n *\n * ③이 결정적이었다. 승화푸드 커넥터는 과거 기록을 되읽으므로 방금 들어온 사건의 시각이 2024년 10월이다.\n * 그래서 표의 가장 오래된 시각이 이미 저널의 시작과 같아진다.\n *\n * 원인은 하나다. **채워진 것은 구간이 아니라 집합이다.** 리비전으로 보든 시각으로 보든 「어디까지」를\n * 하나의 경계로 말할 수 없다. 그래서 알아내려 하지 않고 **이 프로세스가 든다**(§`inFlight`).\n *\n * 리비전 1부터 순서대로 올라간다. 목표는 **채우기를 시작한 순간의 저널 끝**이다 — 그보다 뒤는 앞으로\n * 채우는 쪽이 이미 적고 있다.\n *\n * ── 규율 ────────────────────────────────────────────────────────────────────\n * **나눠서 옮긴다.** 한 번에 통째로 읽으면 그동안 호스트가 아무 일도 못 한다(sqlite 는 쓰기가 하나다).\n * 배치 사이에 루프를 비워 준다.\n *\n * **같은 시각의 줄을 반쪽만 적지 않는다.** 한 배치의 가장 오래된 시각에 걸친 줄들은 다음 배치로 넘긴다 —\n * 반쪽만 적으면 그 시각이 「채워진 것」으로 보이면서 일부가 빠진다.\n *\n * **몇 줄을 옮겼고 어디까지 왔는지 말한다.** 알리지 않고 절반만 채우면 화면이 「그 앞에는 사건이 없다」고\n * 말하게 된다.\n */\n\n/** 한 번에 읽는 저널 행 수 — 저널 정리와 같은 규율. */\nconst READ_BATCH = 5_000\n\nexport interface BackfillResult {\n instanceId: string\n /** 읽은 저널 행 수. */\n read: number\n /** 이 표에 적은 줄 수. */\n written: number\n /** 여기까지 옮겼다(저널 리비전). */\n upToRevision: number\n /** 목표 — 채우기를 시작한 순간의 저널 끝. */\n targetRevision: number\n /** 다 옮겼나. */\n complete: boolean\n /** 예산을 다 써서 멈췄나. */\n paused: boolean\n}\n\n/**\n * 옮기는 중인 트윈의 목표와 지점 — **이 프로세스 안에서만** 든다.\n *\n * 테이블에 적지 않는다(사용자 판단, 2026-08-27). 서버가 내려가면 사라지고, 다음에 부르면 처음부터\n * 다시 읽는다. 그 대가를 아는 채로 두는 것이다.\n */\nconst inFlight = new Map<string, { upToRevision: number; targetRevision: number }>()\n\n/**\n * 한 트윈의 저장된 사건을 이 표로 옮긴다 — 예산 안에서.\n *\n * 되풀어 부르면 이어서 한다. 넘길 인자가 없다 — 진행 지점을 적어 두기 때문이다.\n */\nexport async function backfillSubjects(\n domainId: string,\n instanceId: string,\n opts: { budgetMs?: number } = {}\n): Promise<BackfillResult> {\n const budgetMs = opts.budgetMs ?? 30_000\n const startedMs = Date.now()\n\n const reg = await getRepository(TwinInstance)\n .findOne({ where: { domain: { id: domainId }, instanceId } })\n .catch(() => null)\n if (!reg?.model) {\n throw new Error(`twin \"${instanceId}\" is not registered — no model to resolve declared items with`)\n }\n const itemOf = itemResolverFor(domainId, instanceId, reg.model)\n if (!itemOf) {\n twinWarn(\n `[twin-subject] \"${instanceId}\" 선언된 품목이 없어 품목 줄은 만들지 않는다 — 개체·자리·설비 줄만 옮긴다.`\n )\n }\n\n /*\n * ── 진행 지점을 **회차 안에서만** 든다 (2026-08-27 · 사용자 판단) ────────────────\n *\n * 처음에는 테이블에 적었다. 사용자 판단으로 없앴다 — 사건이 들어올 때 파생 테이블도 같이 적히므로,\n * 옛 사건을 옮기는 일은 새 파생 테이블을 만들 때와 계산 규칙을 고쳐 다시 만들 때뿐이다. 그 두 경우가\n * 흔하지 않다.\n *\n * 대가는 이것이다. 옮기는 중에 서버가 내려가면 그 몫을 다시 읽는다. 335만 건이면 20분쯤이다.\n *\n * 그래서 **목표와 지점을 이 프로세스가 든다.** 트윈마다 한 줄이고, 서버가 내려가면 사라진다.\n */\n const events = getRepository(TwinEvent)\n const key = `${domainId}:${instanceId}`\n let held = inFlight.get(key)\n if (!held) {\n /*\n * 목표는 **처음 부를 때의 저널 끝**이다. 매번 다시 재면 그 사이에 새 사건이 들어와 끝나지 않는다.\n * 그 뒤의 사건은 저널을 쓸 때 파생 테이블에도 같이 적히므로 이 작업의 몫이 아니다.\n */\n const tip = await events\n .createQueryBuilder('e')\n .select('MAX(e.revision)', 'max')\n .where('e.domain = :domainId', { domainId })\n .andWhere('e.instanceId = :instanceId', { instanceId })\n .getRawOne<{ max: unknown }>()\n held = { upToRevision: 0, targetRevision: Number(tip?.max ?? 0) }\n inFlight.set(key, held)\n }\n\n let read = 0\n let written = 0\n let paused = false\n let cursor = held.upToRevision\n const target = held.targetRevision\n\n for (;;) {\n if (cursor >= target) break\n if (Date.now() - startedMs >= budgetMs) {\n paused = true\n break\n }\n const rows = await events\n .createQueryBuilder('e')\n .where('e.domain = :domainId', { domainId })\n .andWhere('e.instanceId = :instanceId', { instanceId })\n .andWhere('e.revision > :cursor', { cursor })\n .andWhere('e.revision <= :target', { target })\n .orderBy('e.revision', 'ASC')\n .take(READ_BATCH)\n .getMany()\n if (!rows.length) {\n cursor = target\n break\n }\n\n read += rows.length\n written += await writeRows(domainId, instanceId, rows, itemOf)\n cursor = Number(rows[rows.length - 1].revision)\n /* 진행 지점을 그때그때 든다 — 중간에 멈춰도 다음 호출이 여기서 잇는다(같은 프로세스 안에서). */\n held.upToRevision = cursor\n /* 배치 사이에 루프를 비워 준다 — 이 한 줄이 없으면 나눈 뜻이 없다. */\n await new Promise(resolve => setImmediate(resolve))\n }\n\n held.upToRevision = cursor\n const complete = cursor >= target\n /* 다 옮겼으면 들고 있을 이유가 없다 — 다시 부르면 목표를 새로 잡는다(그때는 옮길 것이 없다). */\n if (complete) inFlight.delete(key)\n twinLog(\n `[twin-subject] backfill \"${instanceId}\" — 저널 ${read}행을 읽어 ${written}줄 적었다 · ` +\n `리비전 ${cursor}/${target}` +\n `${complete ? ' · 다 옮겼다' : paused ? ' · 예산을 다 써서 멈췄다 — 다시 부르면 이어서 한다' : ''}`\n )\n\n return { instanceId, read, written, upToRevision: cursor, targetRevision: target, complete, paused }\n}\n\n/** 저널 행들을 봉투로 되돌려 적는다 — `payload` 가 봉투 전체다. */\nasync function writeRows(domainId: string, instanceId: string, rows: TwinEvent[], itemOf?: any): Promise<number> {\n return await writeSubjectRows(\n domainId,\n instanceId,\n rows.map(r => ({\n /*\n * `payload` 가 봉투 전체다(`{ eventId, eventType, eventTime, tenantId, data: {…} }`). 한 겹 더\n * 감싸면 키가 묻혀 아무것도 찾지 못한다 — 실측으로 825,000행을 읽어 0줄을 적었다.\n *\n * 시각과 종류는 승격 컬럼을 믿는다. payload 안의 값과 컬럼이 어긋난 옛 행이 있을 수 있고,\n * 이 표의 정렬 축은 컬럼이 정본이다.\n */\n envelope: { ...((r as any).payload ?? {}), eventType: r.eventType, eventTime: r.eventTime },\n revision: Number(r.revision),\n eventId: r.id\n })),\n itemOf\n )\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"twin-subject-event.js","sourceRoot":"","sources":["../../../server/service/twin-subject/twin-subject-event.ts"],"names":[],"mappings":";;;;AAAA,+CAAqD;AACrD,qCAAgH;AAChH,iDAA8C;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAkBI,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;CAmD5B,CAAA;AAnDY,4CAAgB;AAGlB;IAFR,IAAA,gCAAsB,EAAC,MAAM,CAAC;IAC9B,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC;;4CAC/B;AAGnB;IADC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,cAAM,CAAC;sCAClB,cAAM;gDAAA;AAGd;IADC,IAAA,oBAAU,EAAC,CAAC,CAAmB,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;;kDAC7B;AAIjB;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,wCAAwC,EAAE,CAAC;;oDAC/C;AAOlB;IALC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC;QACL,WAAW,EACT,qLAAqL;KACxL,CAAC;;qDACiB;AAInB;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,4CAA4C,EAAE,CAAC;;mDACpD;AAUjB;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,8FAA8F,EAAE,CAAC;sCAC5G,IAAI;mDAAA;AAIf;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACvB,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,kBAAG,EAAE,EAAE,WAAW,EAAE,2EAA2E,EAAE,CAAC;;kDACjG;AAIhB;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,uEAAuE,EAAE,CAAC;;mDAC/E;AAIjB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,wEAAwE,EAAE,CAAC;;iDACjG;AAIhB;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,4BAA4B,EAAE,CAAC;sCACzC,IAAI;mDAAA;2BAlDL,gBAAgB;IAjB5B,IAAA,gBAAM,GAAE;IACT;;;;;OAKG;;IACF,IAAA,eAAK,EACJ,yBAAyB,EACzB,CAAC,CAAmB,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,EAC1F,EAAE,MAAM,EAAE,KAAK,EAAE,CAClB;IACD,sEAAsE;;IACrE,IAAA,eAAK,EAAC,yBAAyB,EAAE,CAAC,CAAmB,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,SAAS,CAAC,EAAE;QAChG,MAAM,EAAE,KAAK;KACd,CAAC;IACD,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,gFAAgF,EAAE,CAAC;GACjG,gBAAgB,CAmD5B","sourcesContent":["import { Field, Int, ObjectType } from 'type-graphql'\nimport { Column, CreateDateColumn, Entity, Index, ManyToOne, PrimaryGeneratedColumn, RelationId } from 'typeorm'\nimport { Domain } from '@things-factory/shell'\n\n/*\n * **대상별 사건 목록** — 조회용 파생 표. 사실의 원본은 `twin_events` 다.\n *\n * ── 왜 만드나 (2026-08-27 실측) ─────────────────────────────────────────────\n * 360 화면이 「이 대상의 이력」을 묻고, 그 조회가 64초다. 원인은 「무엇으로 걸러 시각순으로 정렬해\n * 앞에서 30개」라는 모양에 맞는 목차가 없다는 것이다. 그리고 목차를 얹는 방식으로는 풀리지 않는다.\n *\n * 정렬이 목차를 무력화한다 여러 값으로 거르면(IN) 합칠 때 다시 정렬해야 한다\n * 축이 늘 때마다 목차가 늘어난다 360 의 축 넷을 다 빠르게 하면 `twin_events` 목차가 13개가 된다\n * 소급 채우기가 매번 든다 목차 하나 만드는 데 3분 36초 · 파일 +2.4GB\n * 품목 축은 그래도 안 풀린다 저장된 식별자에 품목과 로트가 한 문자열로 붙어 있다\n *\n * ── 이 표가 무엇을 담나 ─────────────────────────────────────────────────────\n * 사건 하나가 가리키는 **대상마다 한 줄**이다. 품목 이동 사건이면 품목·로트·자리로 세 줄이다.\n * 종류(`subjectKind`)를 값으로 밝히므로 읽는 쪽이 사건 종류표를 들고 있지 않아도 된다. 뜻이\n * 달라지는 키 칸을 쓰지 않는 이유이고, 그 방식이 틀렸다는 뜻은 아니다 — 정의가 함께 있는\n * 프레임워크에서는 합리적이다. 이 표는 볼 정의가 없다.\n *\n * ── 규칙 넷 (설계: read-models.md §4) ───────────────────────────────────────\n * ① 조회는 이 표가 답한다. `twin_events` 에 목차를 얹어 답하지 않는다\n * ② **앞으로만 채운다.** 소급하지 않는다 — 새 키가 필요할 때 3,128만 줄을 다시 만들지 않는다\n * ③ 준비되지 않은 구간을 화면이 말한다. 비어 있는 것을 「없다」로 그리지 않는다\n * ④ 언제든 버릴 수 있다. 사건에서 파생된 값이므로 지워도 사실이 사라지지 않는다\n *\n * ── 왜 사건 본문을 베끼지 않나 ──────────────────────────────────────────────\n * `eventId` 로 원본을 가리키고, 목록을 그리는 데 필요한 값만 함께 둔다(종류·시각·리비전). 본문까지\n * 베끼면 이 표가 원본만큼 커지고, 원본을 고칠 때 두 곳이 어긋난다.\n */\n@Entity()\n/*\n * 이 표의 유일한 목차 — 이 순서가 곧 이 표의 존재 이유다.\n *\n * 「이 트윈의 이 대상의 것을 최신순으로 30개」가 목차를 위에서 30줄 읽고 끝난다. 정렬이 없다 —\n * 거르는 값이 하나이고 그 다음이 시각이기 때문이다.\n */\n@Index(\n 'ix_twin_subject_event_0',\n (e: TwinSubjectEvent) => [e.domain, e.instanceId, e.subjectKind, e.subjectId, e.eventTime],\n { unique: false }\n)\n/* 보관 상한을 지우는 축 — 「이 트윈에서 오래된 줄」을 찾는다(§`twin-subject-writer` 의 정리). */\n@Index('ix_twin_subject_event_1', (e: TwinSubjectEvent) => [e.domain, e.instanceId, e.eventTime], {\n unique: false\n})\n@ObjectType({ description: 'One row per subject an event refers to — the read model behind entity history.' })\nexport class TwinSubjectEvent {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => String, { description: 'Row id.' })\n readonly id: string\n\n @ManyToOne(type => Domain)\n domain: Domain\n\n @RelationId((e: TwinSubjectEvent) => e.domain)\n domainId?: string\n\n @Column()\n @Field({ description: 'The twin instance this row belongs to.' })\n instanceId: string\n\n @Column()\n @Field({\n description:\n 'What kind of thing this row points at: item, lot, order, location, equipment or task. The kind is stored as a value so a reader never has to know which event types put what where.'\n })\n subjectKind: string\n\n @Column()\n @Field({ description: 'Identifier of that thing, within its kind.' })\n subjectId: string\n\n /*\n * **타입 이름을 적지 않는다** — `twin-event.ts` 의 `eventTime` 과 같은 이유다.\n *\n * `datetime` 은 sqlite·MySQL 의 이름이고 Postgres 는 `timestamp` 만 안다. 다섯 드라이버에 다\n * 통하는 이름이 없어서, 이름을 고르는 일을 TypeORM 에 맡긴다(TS 타입 `Date` 에서 고른다).\n */\n @Column()\n @Field({ description: 'When the event happened, by the twin clock. This is the axis the history list is ordered by.' })\n eventTime: Date\n\n @Column({ type: 'int' })\n @Field(type => Int, { description: 'Journal revision of the event — ties this row to its place in the record.' })\n revision: number\n\n @Column()\n @Field({ description: 'Event type, so the list can be drawn without reading the journal row.' })\n eventType: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Journal row this points at. Null when the journal row has been pruned.' })\n eventId?: string\n\n @CreateDateColumn()\n @Field({ description: 'When this row was written.' })\n createdAt?: Date\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"twin-subject-fast-path.js","sourceRoot":"","sources":["../../../server/service/twin-subject/twin-subject-fast-path.ts"],"names":[],"mappings":"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","sourcesContent":["import { decodeCursor, getRepository } from '@things-factory/shell'\n\nimport { TwinEvent } from '../twin-event/twin-event.js'\nimport { JOURNAL_COUNT_CAP as COUNT_CAP } from '../twin-event/journal-count.js'\nimport { TwinSubjectEvent } from './twin-subject-event.js'\nimport { subjectWriteFailures } from './twin-subject-writer.js'\nimport { itemResolverFor } from './item-resolver.js'\nimport type { SubjectKind } from './event-subjects.js'\nimport { readModelShape, type SubjectQueryShape as Shape } from './subject-query-shape.js'\nimport { absoluteTime } from '../../engine/read-time.js'\nimport { TwinEngine } from '../../engine/index.js'\n\n/*\n * **화면이 이미 부르는 문에서 파생 테이블을 쓴다** (2026-08-28).\n *\n * ── 무엇이 잘못되어 있었나 ──────────────────────────────────────────────────\n * 대상별 사건 목록(`twin_subject_events`)을 만들고 조회도 새로 열었는데(`twinSubjectHistory`),\n * 360 화면은 예전부터 `twinEventList` 를 부르고 있었다. 그래서 테이블은 8,382,017줄을 채우고도\n * 쓰이지 않았고, 화면의 이력 조회는 그대로 64초였다. **자리는 있고 길이 없었다.**\n *\n * 새 문으로 옮기라고 화면에 요구하지 않는다. 화면이 이미 서 있는 문 안에서 답을 어디서 가져오는지만\n * 바꾼다 — 계약(`TwinEventList`)도, 커서도, 응답 모양도 그대로다.\n *\n * ── 어떤 물음일 때만 쓰나 ───────────────────────────────────────────────────\n * 파생 테이블은 「대상 하나를 시각순으로」에 맞춰 생겼다. 그 모양이 아닌 물음은 여기서 답하지 않고\n * 저널로 보낸다(`null` 을 돌려준다). 좁게 잡는 이유는, 맞지 않는 물음에 억지로 답하면 목록이\n * 알리지 않고 달라지기 때문이다.\n *\n * 쓴다 트윈 하나 · 축 하나(품목·오더·자리·설비) · 시각 내림차순 · 시각 상한(시간여행)까지\n * 안 쓴다 여러 트윈 · 자유 검색 · 종류 필터 · 상관 식별자 · 그 밖의 정렬\n *\n * ── 어디까지 답할 수 있나 ───────────────────────────────────────────────────\n * 파생 테이블은 만들어진 뒤부터 채워진다. 그보다 앞의 사건은 저널에는 있고 여기에는 없다. 그 경계를\n * `coveredFrom`(이 트윈의 가장 오래된 파생 줄의 시각)이라 하고, **경계를 넘는 페이지는 저널로 보낸다.**\n * 커서 모양이 저널 경로와 같으므로 넘기는 자리에서 그냥 이어진다 — 앞은 빠르고, 경계 아래는 느리지만\n * 온전하다. 없는 것을 「사건이 없다」로 그리는 일은 생기지 않는다.\n *\n * 적지 못한 줄이 있으면(`subjectWriteFailures`) 그 창에 구멍이 있다는 뜻이므로 아예 저널로 보낸다.\n *\n * 설계: `operato-twin/design/plans/read-models.md`\n */\n\nexport interface FastPathResult {\n items: TwinEvent[]\n nextCursor?: string\n /** 이 트윈에서 파생 테이블이 말할 수 있는 가장 오래된 시각 — 화면이 경계를 말할 수 있게. */\n coveredFrom?: Date\n /** 파생 테이블이 저널의 처음까지 덮고 있나 — 아니면 전체 건수를 「모른다」로 낸다. */\n fullyCovered: boolean\n}\n\n/**\n * 화면이 건넨 축 값 → 파생 테이블의 키.\n *\n * 자리·오더·설비는 화면과 저장이 같은 값을 쓴다. **품목만 다르다**: 화면은 짧게 줄인 것을 들고 있고\n * (`net/class/product-M1`) 저장된 것은 전체 식별자다. 그 되돌림을 적는 쪽과 **같은 함수**로 한다\n * (`itemResolverFor`) — 두 벌로 적으면 어느 날 서로 다른 답을 내고, 그때 이력이 빈다.\n */\nasync function canonicalize(domainId: string, instanceId: string, kind: SubjectKind, value: string): Promise<string | null> {\n if (kind !== 'item') return value\n const det = await TwinEngine.detail(domainId, instanceId).catch(() => null)\n const itemOf = det?.model ? itemResolverFor(domainId, instanceId, det.model) : undefined\n if (!itemOf) return null\n return itemOf(value) ?? null\n}\n\n/**\n * 이 트윈에서 파생 테이블이 말할 수 있는 경계와, 저널의 처음까지 덮는지.\n *\n * 둘 다 목차만 읽는 질의다(`(domain, instance, event_time)` 의 첫 줄). 그래서 부를 때마다 구해도\n * 값이 싸고, 저장해 두지 않으므로 실제 데이터와 어긋날 일이 없다.\n */\nasync function coverage(domainId: string, instanceId: string): Promise<{ from?: Date; full: boolean }> {\n /*\n * ── **원시 값을 직접 날짜로 읽지 않는다** (2026-08-28 실측) ──────────────────\n *\n * `getRawOne` 은 드라이버가 준 값을 그대로 준다. sqlite 의 그 값은 시간대가 없는 문자열\n * (`2026-08-27 14:37:20.049`)이고, 그것을 `new Date()` 로 읽으면 **한국 시간으로 해석**해\n * UTC 로 9시간을 뺀다. 실측: 커서의 시각이 9시간 이르게 나가 이어 읽기가 구간을 건너뛰었다.\n *\n * `getOne` 은 엔티티로 받으므로 컬럼 변환이 걸린다 — 드라이버가 무엇이든 같은 값이 된다.\n * 조인이 없으므로 감싸지 않고, 상한이 안쪽에 걸린다(§`twinEvents` 의 실측).\n */\n const [sub, journal] = await Promise.all([\n getRepository(TwinSubjectEvent)\n .createQueryBuilder('s')\n .where('s.domain = :domainId', { domainId })\n .andWhere('s.instanceId = :instanceId', { instanceId })\n .orderBy('s.eventTime', 'ASC')\n .limit(1)\n .getOne()\n .catch(() => null),\n getRepository(TwinEvent)\n .createQueryBuilder('e')\n .where('e.domain = :domainId', { domainId })\n .andWhere('e.instanceId = :instanceId', { instanceId })\n .orderBy('e.eventTime', 'ASC')\n .limit(1)\n .getOne()\n .catch(() => null)\n ])\n const from = sub?.eventTime instanceof Date ? sub.eventTime : undefined\n const oldest = journal?.eventTime instanceof Date ? journal.eventTime : undefined\n if (!from || Number.isNaN(from.getTime())) return { from: undefined, full: false }\n const full = !!oldest && !Number.isNaN(oldest.getTime()) && from.getTime() <= oldest.getTime()\n return { from, full }\n}\n\n/**\n * 파생 테이블로 한 페이지 — 답할 수 없으면 `null`(부르는 쪽이 저널로 간다).\n *\n * 두 걸음이다. ① 파생 테이블에서 리비전 번호를 페이지만큼 읽는다(목차만 읽으므로 정렬이 없다).\n * ② 그 번호로 저널 줄을 가져온다(`(domain, instance, revision)` 목차의 키 조회 · 50줄).\n * 두 번째 걸음이 필요한 이유는 화면이 저널 줄의 내용(`payload`·`bizStep`…)을 그리기 때문이다 —\n * 파생 테이블은 목차이지 사실의 집이 아니다.\n */\nexport async function subjectFastPage(\n domainId: string,\n instanceId: string,\n shape: Shape,\n limit: number,\n after?: string\n): Promise<FastPathResult | null> {\n /* 적지 못한 줄이 있으면 이 창에 구멍이 있다 — 그때는 온전한 쪽(저널)으로 보낸다. */\n if (subjectWriteFailures(domainId, instanceId) > 0) return null\n\n const subjectId = await canonicalize(domainId, instanceId, shape.kind, shape.value)\n if (!subjectId) return null\n\n const cov = await coverage(domainId, instanceId)\n if (!cov.from) return null\n\n /* 이어 읽기 지점 — 저널 경로와 **같은 커서**를 읽는다(`[eventTime, revision]`). */\n let cursor: { at: Date; revision: number } | null = null\n if (after) {\n cursor = decodeJournalCursor(after)\n if (!cursor) return null\n }\n\n /*\n * 경계를 넘는 페이지는 여기서 답하지 않는다. 파생 테이블에는 그 아래가 없으므로, 답하면 목록이\n * 「여기서 끝」이라고 말하게 된다. 저널로 보내면 커서가 그대로 이어진다.\n *\n * 이어 읽기 지점이 경계와 **같을** 때도 보낸다 — 그 커서는 아래에서 우리가 직접 만든 「경계 아래를\n * 읽어라」는 표시이고(§`boundaryCursor`), 여기서 받으면 같은 페이지를 되풀게 된다.\n */\n if (cursor && cursor.at.getTime() <= cov.from.getTime()) return null\n if (shape.untilTime && shape.untilTime.getTime() < cov.from.getTime()) return null\n\n const qb = getRepository(TwinSubjectEvent)\n .createQueryBuilder('s')\n .select('s.revision', 'revision')\n .where('s.domain = :domainId', { domainId })\n .andWhere('s.instanceId = :instanceId', { instanceId })\n .andWhere('s.subjectKind = :kind', { kind: shape.kind })\n .andWhere('s.subjectId = :subjectId', { subjectId })\n if (shape.fromTime) qb.andWhere('s.eventTime >= :fromTime', { fromTime: shape.fromTime })\n if (shape.untilTime) qb.andWhere('s.eventTime <= :untilTime', { untilTime: shape.untilTime })\n if (cursor) {\n /* 시각이 같은 줄이 여럿이므로 리비전으로 갈라 이어 읽는다 — 건너뛰거나 되풀지 않게. */\n qb.andWhere('(s.eventTime < :curAt OR (s.eventTime = :curAt AND s.revision < :curRev))', {\n curAt: cursor.at,\n curRev: cursor.revision\n })\n }\n const picked = await qb\n .orderBy('s.eventTime', 'DESC')\n .addOrderBy('s.revision', 'DESC')\n .limit(limit)\n .getRawMany<{ revision: number }>()\n .catch(() => null)\n if (!picked) return null\n if (!picked.length) {\n /* 이 대상의 줄이 경계 안에 없다 — 경계 아래는 저널만 안다. 그 사실을 커서로 넘긴다. */\n return { items: [], nextCursor: cov.full ? undefined : boundaryCursor(cov.from), coveredFrom: cov.from, fullyCovered: cov.full }\n }\n\n const revisions = picked.map(p => Number(p.revision)).filter(n => Number.isFinite(n))\n const rows = await getRepository(TwinEvent)\n .createQueryBuilder('e')\n .where('e.domain = :domainId', { domainId })\n .andWhere('e.instanceId = :instanceId', { instanceId })\n .andWhere('e.revision IN (:...revisions)', { revisions })\n .getMany()\n .catch(() => null)\n if (!rows) return null\n\n /*\n * 저널 줄을 파생 테이블이 정한 순서로 되돌린다. `IN` 조회는 순서를 약속하지 않으므로 여기서\n * 세운다 — 50줄이라 값이 없다.\n */\n const byRevision = new Map(rows.map(r => [Number((r as any).revision), r]))\n const items = revisions.map(r => byRevision.get(r)).filter(Boolean) as TwinEvent[]\n\n /*\n * 지워진 저널 줄은 목차에 남아 있을 수 있다(보관 정리가 저널을 먼저 지운 창). 그 차이를 세어\n * 페이지가 짧아진 것을 알린다 — 그러나 여기서는 커서만 정확히 이어 주면 되고, 잃은 사실은 없다.\n */\n /*\n * ── 다음 지점 ───────────────────────────────────────────────────────────────\n * 페이지가 꽉 찼으면 이어 읽기 지점은 이번 페이지의 마지막 줄이다(저널 경로와 같은 축).\n * **시각은 저널 줄에서 가져온다** — 목차의 원시 값을 직접 읽으면 시간대가 어긋난다(§`coverage`).\n *\n * 짧게 끝났으면 이 대상의 줄을 경계 안에서 다 냈다는 뜻이다. 그때 「없다」로 끝내면 안 된다 —\n * 경계 아래에 저널만 아는 사건이 있을 수 있고, 그것을 끝으로 그리면 목록이 알리지 않고 잘린다.\n * 그래서 **경계 아래를 읽으라는 커서**를 낸다. 저널이 그 다음 페이지를 답한다(느리지만 온전하다).\n *\n * 파생 테이블이 저널의 처음까지 덮고 있으면 아래에 아무것도 없으므로 그때만 끝이다.\n */\n let edge: { at: Date; revision: number } | null = null\n if (picked.length === limit) {\n /* 지워진 저널 줄은 목차에 남아 있을 수 있다 — 줄이 있는 마지막 것에서 지점을 만든다. */\n for (let i = revisions.length - 1; i >= 0; i--) {\n const row: any = byRevision.get(revisions[i])\n if (row?.eventTime instanceof Date && !Number.isNaN(row.eventTime.getTime())) {\n edge = { at: row.eventTime, revision: revisions[i] }\n break\n }\n }\n }\n const nextCursor = edge ? encodeJournalCursor(edge.at, edge.revision) : cov.full ? undefined : boundaryCursor(cov.from)\n\n return { items, nextCursor, coveredFrom: cov.from, fullyCovered: cov.full }\n}\n\n/**\n * 「경계보다 오래된 것을 읽어라」는 지점.\n *\n * 저널 경로의 커서 조건은 `(시각 < v) 또는 (시각 = v 그리고 리비전 < v2)` 다. 그래서 리비전 자리에\n * 0 을 두면 뒷절이 성립하지 않고 **시각이 경계보다 오래된 것만** 남는다 — 경계에 있는 줄은 이미\n * 냈으므로 그것이 정확히 남은 구간이다. 이 커서를 다시 받으면 이 경로는 물러난다(위의 경계 검사).\n */\nfunction boundaryCursor(from: Date): string {\n return encodeJournalCursor(from, 0)\n}\n\n/**\n * 목록 계약 한 페이지를 파생 테이블로 답한다 — 답할 수 없으면 `null`(부르는 쪽이 저널로 간다).\n *\n * ── 전체 건수와 「더 있나」 ──────────────────────────────────────────────────\n * 두 칸을 함께 정직하게 채워야 한다. 화면은 전체 건수를 모를 때 `capped` 로 「더 있나」를 판단한다\n * (`hasMore`). 그래서 건수를 모르면서 `capped` 를 내리면 **목록이 알리지 않고 끝난 것처럼 보인다.**\n *\n * 파생 테이블이 저널의 처음까지 덮는다 → 정확한 전체 건수 · capped 내림\n * 덮지 못한다(앞이 저널에만 있다) → 전체 건수 모름 · capped 올림(가진 수는 하한이다)\n */\nexport async function readModelPage(domainId: string, instanceId: string, params: any, limit: number): Promise<TwinEventListShape | null> {\n const shape = readModelShape(params, params?.sortings ?? [])\n if (!shape) return null\n\n const page = await subjectFastPage(domainId, instanceId, shape, limit, params?.pagination?.after ?? undefined)\n if (!page) return null\n\n let total: number | undefined\n if (page.fullyCovered) {\n const counted = await countSubjectRows(domainId, instanceId, shape)\n if (counted !== null && counted <= COUNT_CAP) total = counted\n }\n /* 정확한 수를 낼 수 없으면 「모른다」로 내고, 가진 수가 하한임을 함께 말한다. */\n const capped = total === undefined\n\n return {\n items: page.items,\n total,\n capped,\n countCap: COUNT_CAP,\n nextCursor: page.nextCursor,\n oldestRevision: await oldestJournalRevision(domainId, instanceId)\n }\n}\n\n/** 목록 계약이 돌려주는 모양 — 저널 경로와 같은 칸을 채운다. */\nexport interface TwinEventListShape {\n items: TwinEvent[]\n total?: number\n capped: boolean\n countCap: number\n nextCursor?: string\n oldestRevision?: number\n}\n\n/** 이 대상의 사건 수 — 목차만 읽는다(`(도메인, 트윈, 종류, 식별자, 시각)` 의 앞부분). */\nasync function countSubjectRows(domainId: string, instanceId: string, shape: Shape): Promise<number | null> {\n const subjectId = await canonicalize(domainId, instanceId, shape.kind, shape.value)\n if (!subjectId) return null\n const qb = getRepository(TwinSubjectEvent)\n .createQueryBuilder('s')\n .where('s.domain = :domainId', { domainId })\n .andWhere('s.instanceId = :instanceId', { instanceId })\n .andWhere('s.subjectKind = :kind', { kind: shape.kind })\n .andWhere('s.subjectId = :subjectId', { subjectId })\n if (shape.fromTime) qb.andWhere('s.eventTime >= :fromTime', { fromTime: shape.fromTime })\n if (shape.untilTime) qb.andWhere('s.eventTime <= :untilTime', { untilTime: shape.untilTime })\n return qb.getCount().catch(() => null)\n}\n\n/**\n * 이 트윈에 남아 있는 가장 오래된 리비전 — 보관 정리로 앞이 지워졌는지 화면이 말할 수 있게.\n * 저널 경로가 내는 것과 같은 값이고 같은 이유다(없었던 것과 지워진 것은 다른 사실이다).\n */\nasync function oldestJournalRevision(domainId: string, instanceId: string): Promise<number | undefined> {\n const first = await getRepository(TwinEvent)\n .createQueryBuilder('e')\n .select('e.revision', 'revision')\n .where('e.domain = :domainId', { domainId })\n .andWhere('e.instanceId = :instanceId', { instanceId })\n .orderBy('e.revision', 'ASC')\n .limit(1)\n .getRawOne<{ revision: unknown }>()\n .catch(() => null)\n const at = Number(first?.revision)\n return Number.isFinite(at) && at > 0 ? at : undefined\n}\n\n/**\n * 저널 경로와 **같은 축의** 커서를 만든다 — `[eventTime, revision]`.\n *\n * 시각이 같은 줄들 사이에서 무엇이 앞인지 가리는 축이 두 경로에서 같아야 한다. 다르면 한 목록을\n * 나눠 답하는 자리에서 본 줄이 또 나온다(실측: 120건 중 15건). 저널 경로도 트윈 하나를 볼 때는\n * `revision` 으로 가린다(§`cursorTiebreak`).\n */\nfunction encodeJournalCursor(at: Date, revision: number): string {\n return Buffer.from(JSON.stringify([at, revision]), 'utf8').toString('base64url')\n}\n\n/**\n * 커서를 파생 테이블이 쓸 수 있는 지점으로 읽는다 — `[eventTime, revision]`.\n *\n * 예전에는 커서에 저널 줄의 `id` 가 실려 있어서 그 줄을 한 번 읽어 리비전을 얻었다. 그 방식은\n * **지워진 줄에서 어긋났다**: 보관 정리가 저널을 지우면 리비전을 얻지 못하고, 얻은 리비전이\n * 이어 읽기 지점의 시각과 다른 줄의 것일 수 있었다. 이제 두 경로가 같은 축을 쓰므로 커서에 실린\n * 값을 그대로 읽는다 — 조회도 필요 없다.\n */\nfunction decodeJournalCursor(cursor: string): { at: Date; revision: number } | null {\n let values: unknown[]\n try {\n values = decodeCursor(cursor)\n } catch {\n return null\n }\n if (values.length !== 2) return null\n const at = absoluteTime(values[0])\n const revision = Number(values[1])\n if (!at || !Number.isFinite(revision)) return null\n return { at, revision }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"twin-subject-query.js","sourceRoot":"","sources":["../../../server/service/twin-subject/twin-subject-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Directive, Field, Int, Mutation, ObjectType, Query, Resolver } from 'type-graphql'\nimport { getRepository } from '@things-factory/shell'\n\nimport { TwinSubjectEvent } from './twin-subject-event.js'\nimport { SUBJECT_KINDS } from './event-subjects.js'\nimport { subjectWriteFailures } from './twin-subject-writer.js'\nimport { backfillSubjects } from './twin-subject-backfill.js'\nimport { hydratedDate } from '../../engine/read-time.js'\n\n/*\n * **대상별 사건 목록 조회** — 360 화면의 이력이 이 문으로 온다.\n *\n * ── 무엇이 달라지나 (2026-08-27 실측) ───────────────────────────────────────\n * 지금 화면은 저널을 직접 묻고 그 조회가 64초다. 「무엇으로 걸러 시각순으로 정렬해 앞에서 30개」라는\n * 모양에 맞는 목차가 저널에 없고, 얹어도 풀리지 않는다(정렬이 목차를 무력화한다 · 품목과 로트가 한\n * 문자열에 붙어 있다).\n *\n * 이 표는 그 모양대로 생겼다. 목차가 `(도메인, 트윈, 종류, 식별자, 시각)` 이므로 위에서 30줄 읽고\n * 끝난다 — 정렬이 없다.\n *\n * ── 준비되지 않은 구간을 말한다 ─────────────────────────────────────────────\n * 이 표는 **앞으로만 채운다**(소급하지 않는다). 그래서 표가 만들어진 시점보다 앞의 사건은 여기에 없다.\n * 그것을 「사건이 없다」로 그리면 화면이 거짓을 말한다. `coveredFrom` 이 그 경계를 함께 낸다 —\n * 화면은 「이 목록은 …부터입니다. 그 앞은 기간을 지정해 조회하십시오」를 말할 수 있다.\n *\n * 적지 못한 줄이 있으면 그것도 함께 낸다(`writeFailures`). 알리지 않고 비면 화면이 「그 대상에는 사건이\n * 없다」고 말하게 된다.\n *\n * 설계: `operato-twin/design/plans/read-models.md`\n */\n\n@ObjectType({ description: 'One row of a subject history list.' })\nexport class TwinSubjectEventView {\n @Field({ description: 'Event type.' })\n eventType: string\n\n @Field({ description: 'When it happened, by the twin clock.' })\n eventTime: Date\n\n @Field(type => Int, { description: 'Journal revision — where this sits in the record.' })\n revision: number\n\n @Field({ nullable: true, description: 'Journal row id. Null when that row has been pruned.' })\n eventId?: string\n}\n\n@ObjectType({\n description:\n 'History of one subject, newest first, plus how far back this list can speak for. The list is a read model built forward from the journal, so it does not cover events older than coveredFrom — say so on screen instead of drawing an empty list as \"nothing happened\".'\n})\nexport class TwinSubjectHistory {\n @Field(type => [TwinSubjectEventView], { description: 'The events on this page, newest first.' })\n items: TwinSubjectEventView[]\n\n @Field({\n nullable: true,\n description:\n 'Oldest event time this list can speak for, for this twin. Events before it are in the journal but not in this list — the screen must say so rather than implying there were none. Null when the list holds nothing for this twin yet.'\n })\n coveredFrom?: Date\n\n @Field({ nullable: true, description: 'Cursor for the next page: pass it back as before. Null when this page is the last.' })\n nextBefore?: Date\n\n @Field(type => Int, {\n description:\n 'Rows this twin failed to write into the read model since start. Greater than zero means this list is incomplete for reasons other than coverage — the journal still holds those events.'\n })\n writeFailures: number\n}\n\nconst PAGE_MAX = 200\n\n@Resolver()\nexport class TwinSubjectQuery {\n @Query(returns => TwinSubjectHistory, {\n description:\n 'Events that refer to one subject (item, lot, order, location, equipment or task), newest first. Answered from a read model shaped for this question — one index lookup, no sort — instead of scanning the event journal. Paging is by time (before), not offset, because the list grows at the head. Always reports coveredFrom: this read model is built forward and does not contain events older than that, and an empty list before it means \"not covered\", not \"nothing happened\".'\n })\n async twinSubjectHistory(\n @Ctx() context: any,\n @Arg('instanceId', { description: 'The twin instance to read.' }) instanceId: string,\n @Arg('subjectKind', { description: `One of: ${SUBJECT_KINDS.join(', ')}.` }) subjectKind: string,\n @Arg('subjectId', { description: 'Identifier of the thing, within its kind.' }) subjectId: string,\n @Arg('limit', type => Int, { nullable: true, description: `How many rows, at most ${PAGE_MAX}.` }) limit?: number,\n @Arg('before', { nullable: true, description: 'Read events strictly older than this time — the previous page cursor.' })\n before?: Date\n ): Promise<TwinSubjectHistory> {\n const domain = context.state.domain\n const kind = String(subjectKind ?? '').trim()\n /*\n * 종류는 닫힌 목록이다. 모르는 값이면 빈 목록이 아니라 오류를 낸다 — 빈 목록으로 답하면 오타가\n * 「그 대상에는 사건이 없다」로 보인다.\n */\n if (!(SUBJECT_KINDS as readonly string[]).includes(kind)) {\n throw new Error(`unknown subjectKind \"${subjectKind}\" — one of: ${SUBJECT_KINDS.join(', ')}`)\n }\n const id = String(subjectId ?? '').trim()\n if (!id) throw new Error('subjectId is required — an empty subject would read the whole twin')\n\n const take = Math.min(Math.max(Number(limit) || 30, 1), PAGE_MAX)\n const repo = getRepository(TwinSubjectEvent)\n\n const qb = repo\n .createQueryBuilder('s')\n .where('s.domain = :domainId', { domainId: domain.id })\n .andWhere('s.instanceId = :instanceId', { instanceId })\n .andWhere('s.subjectKind = :kind', { kind })\n .andWhere('s.subjectId = :id', { id })\n /*\n * 같은 시각에 사건이 여럿일 수 있다. 그때 순서를 리비전으로 가른다 — 그러지 않으면 같은 목록을\n * 두 번 물었을 때 순서가 달라지고, 시각 커서로 넘기는 페이지에서 본 줄이 다시 나온다.\n */\n .orderBy('s.eventTime', 'DESC')\n .addOrderBy('s.revision', 'DESC')\n .take(take + 1)\n /* 시각으로 페이지를 넘긴다 — 머리에 계속 쌓이는 목록에서 건너뛰기(offset)는 본 줄을 다시 낸다. */\n if (before) qb.andWhere('s.eventTime < :before', { before })\n\n const rows = await qb.getMany()\n const items = rows.slice(0, take).map(r => ({\n eventType: r.eventType,\n eventTime: r.eventTime,\n revision: Number(r.revision),\n ...(r.eventId ? { eventId: r.eventId } : {})\n }))\n const nextBefore = rows.length > take ? items[items.length - 1]?.eventTime : undefined\n\n return {\n items,\n ...(await coverage(domain.id, instanceId)),\n ...(nextBefore ? { nextBefore } : {}),\n writeFailures: subjectWriteFailures(domain.id, instanceId)\n }\n }\n}\n\n/**\n * 이 표가 이 트윈에 대해 말할 수 있는 가장 오래된 시각 — 저장하지 않고 조회할 때 구한다.\n *\n * 저장하면 실제 자료와 어긋날 수 있고, 어긋나면 어느 쪽이 사실인지 알 수 없다. 목차\n * `(도메인, 트윈, 시각)` 의 첫 줄 하나라서 값이 싸다.\n */\nasync function coverage(domainId: string, instanceId: string): Promise<{ coveredFrom?: Date }> {\n const first = await getRepository(TwinSubjectEvent)\n .createQueryBuilder('s')\n .select('s.eventTime', 'eventTime')\n .where('s.domain = :domainId', { domainId })\n .andWhere('s.instanceId = :instanceId', { instanceId })\n .orderBy('s.eventTime', 'ASC')\n .limit(1)\n .getRawOne<{ eventTime: unknown }>()\n .catch(() => null)\n /* 원시 결과의 컬럼 값은 드라이버에게 해석시킨다(§`hydratedDate`). */\n const at = hydratedDate(getRepository(TwinSubjectEvent), 'eventTime', first?.eventTime)\n return at ? { coveredFrom: at } : {}\n}\n\n@ObjectType({ description: 'How far a backfill got.' })\nexport class TwinSubjectBackfillResult {\n @Field({ description: 'The twin that was filled.' })\n instanceId: string\n\n @Field(type => Int, { description: 'Journal rows read.' })\n read: number\n\n @Field(type => Int, { description: 'Rows written into the read model.' })\n written: number\n\n /*\n * 진행 상황을 리비전으로 말한다 — 옮기는 쪽이 저널을 리비전 순으로 읽고, 그 지점을 이 프로세스가 든다.\n *\n * 이 자리를 시각으로 적어 두었다가 옮기는 방식과 어긋났다. 응답의 칸과 실제로 드는 값이 다르면\n * 부르는 쪽이 진행을 판단할 수 없다.\n */\n @Field(type => Int, { description: 'Journal revision reached so far.' })\n upToRevision: number\n\n @Field(type => Int, { description: 'The target: the journal tip at the moment this fill started. Reaching it means the twin is fully filled.' })\n targetRevision: number\n\n @Field({ description: 'True when upToRevision has reached the target.' })\n complete: boolean\n\n @Field({ description: 'True when the call stopped on its time budget rather than finishing.' })\n paused: boolean\n}\n\n@Resolver()\nexport class TwinSubjectMutation {\n /* 쓰기는 정본 게이트를 단다 — 로그인만 하면 누구나 부를 수 있는 문을 두지 않는다. */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => TwinSubjectBackfillResult, {\n description:\n 'Move already-stored events of one twin into the subject read model. Not automatic: the read model is built forward, so filling old events is an explicit request for a twin that already has a journal. Works in slices on the time axis: each call fills events older than what the read model already covers, so calling it again continues downward with nothing to pass in. Blocks cannot split, and calling it twice does not duplicate rows.'\n })\n async backfillTwinSubjects(\n @Ctx() context: any,\n @Arg('instanceId', { description: 'The twin to fill.' }) instanceId: string,\n @Arg('budgetMs', type => Int, { nullable: true, description: 'How long this call may run, in milliseconds. Default 30000.' })\n budgetMs?: number\n ): Promise<TwinSubjectBackfillResult> {\n const domain = context.state.domain\n /*\n * 이어받기 인자가 없다 — 이 표의 현재 바닥(가장 오래된 시각)이 곧 이어받기 지점이다. 늘 그 아래만\n * 채우므로 덩어리가 갈라지지 않고, 부르는 쪽이 아무것도 들고 있지 않아도 이어서 한다.\n */\n return await backfillSubjects(domain.id, instanceId, { budgetMs: budgetMs ?? undefined })\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"twin-subject-writer.js","sourceRoot":"","sources":["../../../server/service/twin-subject/twin-subject-writer.ts"],"names":[],"mappings":";;AAoDA,4CA0CC;AAyBD,oDAEC;AAGD,8DAEC;AAUD,4CAoDC;AA5LD,iDAAqD;AAErD,gDAAyD;AACzD,4DAAsE;AACtE,mEAA0D;AAC1D,2DAAmE;AAEnE;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,gDAAgD;AAChD,MAAM,KAAK,GAAG,GAAG,CAAA;AAEjB,qDAAqD;AACrD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAmE,CAAA;AAE3F,2CAA2C;AAC3C,MAAM,WAAW,GAAG,MAAM,CAAA;AAS1B;;;;;GAKG;AACI,KAAK,UAAU,gBAAgB,CACpC,QAAgB,EAChB,UAAkB,EAClB,OAAqC,EACrC,MAAqB;IAErB,IAAI,CAAC,OAAO,CAAC,MAAM;QAAE,OAAO,CAAC,CAAA;IAE7B,MAAM,IAAI,GAAU,EAAE,CAAA;IACtB,KAAK,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,OAAO,EAAE,CAAC;QACtD,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAA;QAClC;;;WAGG;QACH,IAAI,CAAC,SAAS;YAAE,SAAQ;QACxB,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;QAC1D,IAAI,CAAC,SAAS;YAAE,SAAQ;QAExB,KAAK,MAAM,CAAC,IAAI,IAAA,8BAAU,EAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;YACjD,IAAI,CAAC,IAAI,CAAC;gBACR,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE;gBACxB,UAAU;gBACV,WAAW,EAAE,CAAC,CAAC,IAAI;gBACnB,SAAS,EAAE,CAAC,CAAC,EAAE;gBACf,SAAS;gBACT,QAAQ;gBACR,SAAS;gBACT,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAChC,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,MAAM;QAAE,OAAO,CAAC,CAAA;IAE1B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAA,qBAAa,EAAC,wCAAgB,CAAC,CAAA;QAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,KAAK;YAAE,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,CAAA;QACxF,OAAO,IAAI,CAAC,MAAM,CAAA;IACpB,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,WAAW,CAAC,QAAQ,EAAE,UAAU,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;QAC3E,OAAO,CAAC,CAAA;IACV,CAAC;AACH,CAAC;AAED,wDAAwD;AACxD,SAAS,MAAM,CAAC,QAAa;IAC3B,mEAAmE;IACnE,OAAO,IAAA,2BAAY,EAAC,QAAQ,EAAE,SAAS,IAAI,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,CAAA;AAChE,CAAC;AAED,SAAS,WAAW,CAAC,QAAgB,EAAE,UAAkB,EAAE,IAAY,EAAE,OAAe;IACtF,MAAM,GAAG,GAAG,GAAG,QAAQ,IAAI,UAAU,EAAE,CAAA;IACvC,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,CAAA;IACxE,CAAC,CAAC,KAAK,IAAI,IAAI,CAAA;IACf,CAAC,CAAC,SAAS,GAAG,OAAO,CAAA;IACrB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IACtB,IAAI,GAAG,GAAG,CAAC,CAAC,SAAS,IAAI,WAAW,EAAE,CAAC;QACrC,CAAC,CAAC,SAAS,GAAG,GAAG,CAAA;QACjB,IAAA,iBAAQ,EACN,mBAAmB,UAAU,0BAA0B,CAAC,CAAC,KAAK,KAAK;YACjE,yDAAyD,OAAO,EAAE,CACrE,CAAA;IACH,CAAC;IACD,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;AACtB,CAAC;AAED,gDAAgD;AAChD,SAAgB,oBAAoB,CAAC,QAAgB,EAAE,UAAkB;IACvE,OAAO,QAAQ,CAAC,GAAG,CAAC,GAAG,QAAQ,IAAI,UAAU,EAAE,CAAC,EAAE,KAAK,IAAI,CAAC,CAAA;AAC9D,CAAC;AAED,0CAA0C;AAC1C,SAAgB,yBAAyB;IACvC,QAAQ,CAAC,KAAK,EAAE,CAAA;AAClB,CAAC;AAED;;;;;;;GAOG;AACI,KAAK,UAAU,gBAAgB,CACpC,QAAgB,EAChB,UAAkB,EAClB,MAAY,EACZ,QAAgB,EAChB,KAAa;IAEb,IAAI,OAAO,GAAG,CAAC,CAAA;IACf,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IAC5B,SAAS,CAAC;QACR;;;WAGG;QACH,MAAM,IAAI,GAAG,MAAM,IAAA,qBAAa,EAAC,wCAAgB,CAAC;aAC/C,kBAAkB,CAAC,GAAG,CAAC;aACvB,MAAM,CAAC,aAAa,EAAE,WAAW,CAAC;aAClC,KAAK,CAAC,sBAAsB,EAAE,EAAE,QAAQ,EAAE,CAAC;aAC3C,QAAQ,CAAC,4BAA4B,EAAE,EAAE,UAAU,EAAE,CAAC;aACtD,QAAQ,CAAC,uBAAuB,EAAE,EAAE,MAAM,EAAE,CAAC;aAC7C,OAAO,CAAC,aAAa,EAAE,KAAK,CAAC;aAC7B,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC;aACjB,KAAK,CAAC,CAAC,CAAC;aACR,SAAS,EAA0B;aACnC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;QAEpB,iDAAiD;QACjD,MAAM,IAAI,GAAG,IAAA,2BAAY,EAAC,IAAA,qBAAa,EAAC,wCAAgB,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,CAAC,CAAA;QAExF,MAAM,GAAG,GAAG,IAAA,qBAAa,EAAC,wCAAgB,CAAC;aACxC,kBAAkB,EAAE;aACpB,MAAM,EAAE;aACR,IAAI,CAAC,wCAAgB,CAAC;aACtB,KAAK,CAAC,uBAAuB,EAAE,EAAE,QAAQ,EAAE,CAAC;aAC5C,QAAQ,CAAC,2BAA2B,EAAE,EAAE,UAAU,EAAE,CAAC,CAAA;QACxD,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI;YACrB,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,qBAAqB,EAAE,EAAE,IAAI,EAAE,CAAC;YAC/C,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,sBAAsB,EAAE,EAAE,MAAM,EAAE,CAAC,CACnD;aACE,OAAO,EAAE;aACT,KAAK,CAAC,GAAG,CAAC,EAAE;YACX,IAAA,kBAAS,EAAC,2BAA2B,EAAE,GAAG,EAAE,OAAO,IAAI,GAAG,CAAC,CAAA;YAC3D,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAA;QACxB,CAAC,CAAC,CAAA;QAEJ,MAAM,CAAC,GAAG,GAAG,CAAC,QAAQ,IAAI,CAAC,CAAA;QAC3B,OAAO,IAAI,CAAC,CAAA;QACZ,IAAI,CAAC,CAAC;YAAE,MAAK;QACb,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,IAAI,QAAQ;YAAE,MAAK;QAC7C,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAA;IACrD,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC","sourcesContent":["import { getRepository } from '@things-factory/shell'\n\nimport { twinError, twinWarn } from '../../engine/log.js'\nimport { absoluteTime, hydratedDate } from '../../engine/read-time.js'\nimport { TwinSubjectEvent } from './twin-subject-event.js'\nimport { subjectsOf, type ItemResolver } from './event-subjects.js'\n\n/*\n * **대상별 사건 목록에 적는 쪽** — 저널을 쓸 때 함께 적는다.\n *\n * ── 왜 저널 쓰기와 같은 자리인가 ────────────────────────────────────────────\n * 저널을 쓰는 문은 하나다(`flushJournal` → `persistBatch`·`persistCarried`). 그 자리에서 함께 적으면\n * 두 표가 어긋나지 않는다. 다른 자리에서 나중에 적으면 「저널에는 있고 이 표에는 없는 사건」이 생기고,\n * 그것은 화면에서 오류 없이 빠진 사건이 된다.\n *\n * ── 저널이 먼저다 ──────────────────────────────────────────────────────────\n * 이 표에 적다가 실패해도 저널 쓰기를 되돌리지 않는다. 사실의 원본은 저널이고, 이 표는 다시 만들 수\n * 있다. 반대로 두면 조회를 빠르게 하려고 만든 표가 사실을 잃는 원인이 된다.\n *\n * **그러나 알리지 않고 넘기지 않는다.** 실패를 세어 창마다 한 줄 남긴다 — 알리지 않고 비면 화면이 「그 대상에는\n * 사건이 없다」고 말하게 된다.\n *\n * ── 앞으로만 채운다 ────────────────────────────────────────────────────────\n * 이미 저장된 사건을 소급해서 채우지 않는다. 이 표가 만들어진 뒤 들어오는 사건만 적힌다. 그래서 새\n * 조회 축이 필요할 때 3,128만 줄을 다시 만들지 않는다. 준비되지 않은 구간은 조회하는 쪽이 말한다\n * (§`twin-subject-query` 의 `coveredFrom`).\n *\n * 설계: `operato-twin/design/plans/read-models.md`\n */\n\n/** 한 번에 넣는 줄 수 — 저널 쓰기와 같은 규율(드라이버 파라미터 상한). */\nconst CHUNK = 500\n\n/** 적다가 실패한 것을 센다 — 트윈마다. 알리지 않고 비는 것을 막는 유일한 신호다. */\nconst failures = new Map<string, { count: number; lastLogMs: number; lastError: string }>()\n\n/** 같은 실패를 매번 적지 않는다 — 로그가 그 자체로 부하가 된다. */\nconst FAIL_LOG_MS = 60_000\n\nexport interface SubjectWriteEntry {\n envelope: any\n revision: number\n /** 저널 줄의 id — 알면 함께 둔다. 벌크 저장에서는 모를 수 있다(그때는 비운다). */\n eventId?: string\n}\n\n/**\n * 이 배치의 사건들이 가리키는 대상을 적는다 — 적은 줄 수를 돌려준다.\n *\n * `itemOf` 는 개체 식별자에서 선언된 품목을 찾아 주는 함수다. 없으면 품목 줄을 만들지 않는다 —\n * 짐작으로 이으면 남의 품목 이력에 이 사건이 섞인다(§`event-subjects`).\n */\nexport async function writeSubjectRows(\n domainId: string,\n instanceId: string,\n entries: readonly SubjectWriteEntry[],\n itemOf?: ItemResolver\n): Promise<number> {\n if (!entries.length) return 0\n\n const rows: any[] = []\n for (const { envelope, revision, eventId } of entries) {\n const eventTime = timeOf(envelope)\n /*\n * 시각을 모르는 사건은 이 표에 넣지 않는다. 이 표의 유일한 쓸모가 「시각순 목록」이고, 시각이 없는\n * 줄은 그 목록의 어디에도 놓을 수 없다. 넣고 「지금」으로 메우면 그 사건이 최근으로 보인다.\n */\n if (!eventTime) continue\n const eventType = String(envelope?.eventType ?? '').trim()\n if (!eventType) continue\n\n for (const s of subjectsOf(envelope, { itemOf })) {\n rows.push({\n domain: { id: domainId },\n instanceId,\n subjectKind: s.kind,\n subjectId: s.id,\n eventTime,\n revision,\n eventType,\n ...(eventId ? { eventId } : {})\n })\n }\n }\n if (!rows.length) return 0\n\n try {\n const repo = getRepository(TwinSubjectEvent)\n for (let i = 0; i < rows.length; i += CHUNK) await repo.insert(rows.slice(i, i + CHUNK))\n return rows.length\n } catch (err: any) {\n noteFailure(domainId, instanceId, rows.length, err?.message ?? String(err))\n return 0\n }\n}\n\n/** 사건의 시각 — 봉투가 말하는 값만 쓴다. 없으면 `null`(지금으로 메우지 않는다). */\nfunction timeOf(envelope: any): Date | null {\n /* 봉투는 컬럼이 아니다 — 정규 계약이 절대시각을 요구하므로 그 규칙으로 읽는다(§`absoluteTime`). */\n return absoluteTime(envelope?.eventTime ?? envelope?.data?.at)\n}\n\nfunction noteFailure(domainId: string, instanceId: string, rows: number, message: string): void {\n const key = `${domainId}:${instanceId}`\n const f = failures.get(key) ?? { count: 0, lastLogMs: 0, lastError: '' }\n f.count += rows\n f.lastError = message\n const now = Date.now()\n if (now - f.lastLogMs >= FAIL_LOG_MS) {\n f.lastLogMs = now\n twinWarn(\n `[twin-subject] \"${instanceId}\" 대상 목록에 적지 못했다 — 지금까지 ${f.count}줄. ` +\n `저널에는 그 사건이 있으므로 사실은 잃지 않았지만, 이 트윈의 이력 화면에 그만큼이 비어 있다: ${message}`\n )\n }\n failures.set(key, f)\n}\n\n/** 적지 못한 줄 수 — 조회하는 쪽이 「이 표가 온전한가」를 말할 수 있게. */\nexport function subjectWriteFailures(domainId: string, instanceId: string): number {\n return failures.get(`${domainId}:${instanceId}`)?.count ?? 0\n}\n\n/** 시험이 쓰는 초기화 — 프로세스 안의 셈이므로 시험마다 비운다. */\nexport function resetSubjectWriteFailures(): void {\n failures.clear()\n}\n\n/**\n * 보관 상한 — 이 트윈에서 이 시각보다 오래된 줄을 지운다.\n *\n * 이 표는 **버릴 수 있는 값**이다. 지우면 그 구간의 이력 조회가 느려지고(저널로 답한다) 사실은 사라지지\n * 않는다. 그래서 저널보다 짧게 둘 수 있고, 그러는 편이 낫다 — 화면은 최근을 본다.\n *\n * 저널 정리와 같은 창에서 돌린다(§`TwinEngine.pruneJournal`). 나눠서 지우는 규율도 같다.\n */\nexport async function pruneSubjectRows(\n domainId: string,\n instanceId: string,\n cutoff: Date,\n budgetMs: number,\n batch: number\n): Promise<number> {\n let deleted = 0\n const startedMs = Date.now()\n for (;;) {\n /*\n * 경계를 시각으로 찾는다 — 목차가 `(domain, instance, eventTime)` 이므로 그 순서로 물어야 쓴다.\n * 저널 정리에서 같은 것을 실측으로 배웠다(리비전 순으로 물으면 목차를 못 쓴다).\n */\n const edge = await getRepository(TwinSubjectEvent)\n .createQueryBuilder('s')\n .select('s.eventTime', 'eventTime')\n .where('s.domain = :domainId', { domainId })\n .andWhere('s.instanceId = :instanceId', { instanceId })\n .andWhere('s.eventTime < :cutoff', { cutoff })\n .orderBy('s.eventTime', 'ASC')\n .offset(batch - 1)\n .limit(1)\n .getRawOne<{ eventTime: unknown }>()\n .catch(() => null)\n\n /* 원시 결과의 컬럼 값은 드라이버에게 해석시킨다(§`hydratedDate`). */\n const upTo = hydratedDate(getRepository(TwinSubjectEvent), 'eventTime', edge?.eventTime)\n\n const del = getRepository(TwinSubjectEvent)\n .createQueryBuilder()\n .delete()\n .from(TwinSubjectEvent)\n .where('domain_id = :domainId', { domainId })\n .andWhere('instance_id = :instanceId', { instanceId })\n const res = await (upTo\n ? del.andWhere('event_time <= :upTo', { upTo })\n : del.andWhere('event_time < :cutoff', { cutoff })\n )\n .execute()\n .catch(err => {\n twinError('[twin-subject] prune fail', err?.message ?? err)\n return { affected: 0 }\n })\n\n const n = res.affected ?? 0\n deleted += n\n if (!n) break\n if (Date.now() - startedMs >= budgetMs) break\n await new Promise(resolve => setImmediate(resolve))\n }\n return deleted\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/twin-target/index.ts"],"names":[],"mappings":";;;AAAA,qDAA6C;AAC7C,uEAA8D;AAEjD,QAAA,QAAQ,GAAG,CAAC,2BAAU,CAAC,CAAA;AACvB,QAAA,SAAS,GAAG,CAAC,4CAAkB,CAAC,CAAA","sourcesContent":["import { TwinTarget } from './twin-target.js'\nimport { TwinTargetResolver } from './twin-target-resolver.js'\n\nexport const entities = [TwinTarget]\nexport const resolvers = [TwinTargetResolver]\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"twin-target-resolver.js","sourceRoot":"","sources":["../../../server/service/twin-target/twin-target-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Directive, Float, Mutation, Query, Resolver } from 'type-graphql'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { TwinTarget } from './twin-target.js'\nimport { TWIN_METRIC, isTwinMetric } from '../../engine/kpi-target.js'\n\n/*\n * 운영 목표 조회·편집 — **현장 관리자가 트윈 화면에서** 고친다(사용자 결정 2026-08-02).\n *\n * 경영 KPI 관리메뉴가 아니라 여기인 이유: 목표가 숫자 옆에 있어야 맥락이 살고, 운영 임계는 자주\n * 조정된다. 조직 단위 기간 평가는 `@things-factory/kpi` 의 일이고 이쪽으로 베껴 오지 않는다.\n */\n/*\n * **쓰기는 막고 읽기는 연다** (2026-08-06 · ADR-0027).\n *\n * 이 리졸버는 트윈 리졸버 중 **유일하게** 권한 게이트를 갖는데, 처음에는 우회 표시가 없었다\n * (`@privilege(category:\"twin\", privilege:\"query\")`). 그러면 `twin/query`·`twin/mutation` 권한을\n * 실제로 부여받은 사용자만 쓸 수 있는데 — **저장소 어디에도 그 권한을 만드는 곳이 없다.** 결과는\n * 도메인 소유자도 슈퍼유저도 목표를 걸 수 없는 상태였다: 성과 화면의 목표 버튼이 \"Unauthorized\" 로\n * 떨어지고, 이 관점의 최상위 기능(§5 목표 프레임)이 **아무도 쓸 수 없는 채로** 놓여 있었다.\n *\n * 그래서 프레임워크의 통용 관용구를 따른다 — 가장 가까운 형제인 `@things-factory/kpi`(목표·지표 관리)가\n * 74곳 전부 `domainOwnerGranted: true, superUserGranted: true` 를 쓴다. 같은 성격의 조작(무엇을 잘한\n * 것으로 볼지 정하는 일)이므로 같은 자세를 쓴다: **현장을 소유한 사람은 바로 쓰고**, 다른 사람에게\n * 맡기려면 `twin` 권한을 부여한다.\n *\n * 이제 트윈의 **모든 변경**에 같은 게이트가 있다(ADR-0027) — 처음에는 이 리졸버 하나만 게이트를 갖는\n * 반쪽이었다. 조회 게이트는 걷었다: 같은 판정이 `twinKpi` 로 이미 열려 있어 지키는 것이 없었다.\n */\n@Resolver()\nexport class TwinTargetResolver {\n /* 조회에는 게이트를 두지 않는다 — 같은 판정이 `twinKpi.targets` 로 이미 열려 있어(그쪽은 게이트가\n 없다) 여기만 막으면 지키는 것 없이 화면만 깨진다. 읽기의 경계는 **테넌트 소속**이다. */\n @Query(returns => [String], {\n description:\n 'The metric ids a twin target may refer to. Fixed in one place so a target and the fact it judges can never drift into two names.'\n })\n async twinTargetMetrics(): Promise<string[]> {\n return Object.keys(TWIN_METRIC)\n }\n\n\n @Query(returns => [TwinTarget], {\n description:\n 'Operational targets in scope — site-wide targets for the space plus targets bound to the given instance. Callers judge with the narrower one when both exist.'\n })\n async twinTargets(\n @Ctx() context: ResolverContext,\n @Arg('spaceId', { nullable: true }) spaceId?: string,\n @Arg('instanceId', { nullable: true }) instanceId?: string\n ): Promise<TwinTarget[]> {\n const domainId = context.state.domain.id\n const rows = await getRepository(TwinTarget).find({ where: { domain: { id: domainId } } })\n return rows.filter(r => (spaceId && r.spaceId === spaceId) || (instanceId && r.instanceId === instanceId))\n }\n\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => Boolean, {\n description:\n 'Create or update one operational target. The metric must be one of twinTargetMetrics — an unknown name is rejected rather than stored, because a target nobody can evaluate reads as \"no target\" and hides the gap.'\n })\n async saveTwinTarget(\n @Arg('metric') metric: string,\n @Arg('target', type => Float) target: number,\n @Ctx() context: ResolverContext,\n @Arg('spaceId', { nullable: true }) spaceId?: string,\n @Arg('instanceId', { nullable: true }) instanceId?: string,\n @Arg('axis', { nullable: true }) axis?: string,\n @Arg('axisKey', { nullable: true }) axisKey?: string\n ): Promise<boolean> {\n if (!isTwinMetric(metric)) throw new Error(`unknown twin metric \"${metric}\" — see twinTargetMetrics`)\n if (!Number.isFinite(target)) throw new Error('target must be a finite number')\n if (!spaceId && !instanceId) throw new Error('a target must be scoped to a space or an instance')\n /* 축만 있고 값이 없으면 **어디에 거는 목표인지 알 수 없다** — 알리지 않고 전체 목표로 만들지 않는다\n (그러면 사용자는 구역 목표를 걸었다고 믿는데 전체가 바뀐다). */\n if (axis && !axisKey) throw new Error('axisKey is required when axis is given')\n if (!axis && axisKey) throw new Error('axis is required when axisKey is given')\n const domainId = context.state.domain.id\n const repo = getRepository(TwinTarget)\n const existing = await repo.findOne({\n where: { domain: { id: domainId }, spaceId: spaceId ?? null, instanceId: instanceId ?? null, metric, axis: axis ?? null, axisKey: axisKey ?? null } as any\n })\n await repo.save(\n repo.create({\n ...(existing ?? {}),\n domain: { id: domainId } as any,\n spaceId: spaceId ?? null as any,\n instanceId: instanceId ?? null as any,\n axis: axis ?? null as any,\n axisKey: axisKey ?? null as any,\n metric,\n target,\n ...(existing ? { updater: context.state.user } : { creator: context.state.user, updater: context.state.user })\n })\n )\n return true\n }\n\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => Boolean, { description: 'Remove an operational target. Removing a target removes the judgement, not the fact — the metric keeps being reported without a verdict.' })\n async deleteTwinTarget(\n @Arg('metric') metric: string,\n @Ctx() context: ResolverContext,\n @Arg('spaceId', { nullable: true }) spaceId?: string,\n @Arg('instanceId', { nullable: true }) instanceId?: string,\n @Arg('axis', { nullable: true }) axis?: string,\n @Arg('axisKey', { nullable: true }) axisKey?: string\n ): Promise<boolean> {\n const domainId = context.state.domain.id\n const repo = getRepository(TwinTarget)\n\n /*\n * **찾아서 그 행만 지운다.** 예전에는 조건 객체를 `delete()` 에 그대로 넘겼는데 **아무 행도\n * 안 지워지면서 `true` 를 돌려줬다.** 사용자는 목표를 지웠다고 믿고 화면을 떠나는데 판정은 그대로\n * 남는다(실화면에서 그렇게 당했다).\n *\n * 무엇이 조건을 깨뜨렸는지는 단정하지 않는다 — 이 조건에는 `domain` 관계 객체와 `null` 값들이\n * 함께 들어 있었고(`instanceId`·`axis`·`axisKey`), 둘 다 `delete()` 의 조건 해석에서 흔한 함정이다.\n * 같은 관계-객체 패턴을 쓰는 `resetJournal` 은 실제로 잘 지우므로 관계 객체 하나만으로 설명되지\n * 않는다. `null` 조건이 `IS NULL` 이 아니라 `= NULL` 로 나갔을 가능성이 크다.\n *\n * 그래서 조건을 고쳐 맞추는 대신 **행을 찾아 id 로 지운다**: 저장 경로와 **같은 조회 규칙**을\n * 쓰므로 규칙이 갈라지지 않고(`findOne` 은 `null` 을 제대로 다룬다), 지우는 대상이 정확히 한\n * 행이라 넓게 지울 위험도 없다.\n */\n const row = await repo.findOne({\n where: {\n domain: { id: domainId },\n spaceId: spaceId ?? null,\n instanceId: instanceId ?? null,\n axis: axis ?? null,\n axisKey: axisKey ?? null,\n metric\n } as any\n })\n /* 지울 것이 없으면 **없었다고 답한다.** 지우지 않고 true 를 주면 위와 같은 거짓말이 된다. */\n if (!row) return false\n await repo.delete(row.id)\n return true\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"twin-target.js","sourceRoot":"","sources":["../../../server/service/twin-target/twin-target.ts"],"names":[],"mappings":"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","sourcesContent":["import { CreateDateColumn, Entity, Index, Column, RelationId, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'\nimport { ObjectType, Field, ID, Float } from 'type-graphql'\n\nimport { Domain } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\n\n/*\n * TwinTarget — **운영 목표.** \"이 구간의 처리량이 목표에 닿았나\" 를 판단하는 기준선.\n *\n * ── 왜 트윈이 갖나 (사용자 결정 2026-08-02) ─────────────────────────────────\n * `@things-factory/kpi` 와 **경계를 나눈다**:\n * 트윈(여기) 이 구간·이 교대·이 공간의 **즉시 판단**. 현장 관리자가 트윈 화면에서 고친다.\n * 값은 저널 폴드에서 그때그때 계산된다(저장된 값을 읽지 않는다).\n * packages/kpi **기간 평가**(조직·분기·점수·가중치·알림). 값이 저장돼 있어야 도는 체계다.\n *\n * 지키는 규칙 하나: **트윈은 경영 목표를 복제하지 않는다.** 조직 단위 평가가 필요하면 트윈의 사실을\n * 내보내 그쪽이 소비하고, 그쪽 목표를 여기로 베껴 오지 않는다 — 베끼는 순간 두 개의 진실이 된다.\n *\n * ── 범위 (scope) ───────────────────────────────────────────────────────────\n * `spaceId` 로 현장 전체에, `instanceId` 로 트윈 하나에 건다. 둘 다 있으면 **인스턴스가 이긴다**\n * (좁은 선언이 넓은 선언을 덮는다 — 예외를 표현할 수 있어야 한다).\n *\n * 관점 축별 목표(구역마다 다른 목표)는 **아직 아니다** — 축 교차는 목표가 화면에서 쓰이기 시작한 뒤의\n * 일이고, 처음부터 넣으면 쓰이지 않는 구조가 먼저 굳는다(performance.md §5.4-5).\n *\n * (CLAUDE.md: 모든 @ObjectType/@Field 영문 description 필수.)\n */\n@Entity()\n@Index('ix_twin_target_0', (e: TwinTarget) => [e.domain, e.spaceId, e.instanceId, e.metric, e.axis, e.axisKey], { unique: true })\n@ObjectType({\n description:\n 'An operational performance target for a twin metric — the line that turns a measured number into a judgement. Scoped to a space or to a single instance (instance wins over space, so an exception can be expressed). Distinct from @things-factory/kpi targets, which evaluate organizations over periods; twin targets are for immediate, on-site judgement and are never copied from the management KPI system.'\n})\nexport class TwinTarget {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier of the target record.' })\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'Owning tenant domain.' })\n domain?: Domain\n\n @RelationId((e: TwinTarget) => e.domain)\n domainId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Space this target applies to (site-wide). Empty when the target is bound to a single instance.' })\n spaceId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Instance this target applies to. When both space and instance match, the instance target wins so an exception can be expressed.' })\n instanceId?: string\n\n /**\n * 관점 축과 그 값 — **\"이 구역만\", \"이 교대만\" 목표가 다르다.**\n *\n * 둘 다 비어 있으면 **전체 목표**다(창 전체의 집계를 판정한다). 축 목표는 전체 목표를 덮지 않는다 —\n * 다른 질문이기 때문이다: \"현장 전체가 100건\" 과 \"용접 구역이 30건\" 은 동시에 참일 수 있다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Perspective axis this target applies to (area, shift, resource, taskKind, location, order). Empty means the target judges the window total. An axis target does not override the total — \"the site does 100\" and \"the weld area does 30\" are both true at once.' })\n axis?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'The value on that axis (an area id, a shift name…). Required when axis is set.' })\n axisKey?: string\n\n @Column()\n @Field({ description: 'Metric id from the twin metric vocabulary (throughput.tasks, workTime.p50Ms, …). Names are fixed in one place so target and fact never drift into two dialects.' })\n metric: string\n\n @Column({ type: 'float' })\n @Field(type => Float, { description: 'The target value, in the metric\\'s own unit (count, milliseconds, ratio). Whether higher or lower is better is a property of the metric, not of this record — so a target can never be declared in the wrong direction.' })\n target: number\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'User who created this target.' })\n creator?: User\n\n @RelationId((e: TwinTarget) => e.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'User who last updated this target.' })\n updater?: User\n\n @RelationId((e: TwinTarget) => e.updater)\n updaterId?: string\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'Timestamp when the target was created.' })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true, description: 'Timestamp when the target was last updated.' })\n updatedAt?: Date\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"axis-read.js","sourceRoot":"","sources":["../shared/axis-read.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;AAGH,wBAEC;AAQD,kCAGC;AAdD,wDAAwD;AACxD,SAAgB,MAAM,CAAC,GAAQ,EAAE,IAAY;IAC3C,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;AAC3F,CAAC;AAED;;;;;GAKG;AACH,SAAgB,WAAW,CAAC,KAAU,EAAE,IAAqC;IAC3E,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,EAAE,IAAI;QAAE,OAAO,SAAS,CAAA;IAC3C,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAChE,CAAC","sourcesContent":["/*\n * 축의 값을 **어디서 읽나** — 규칙 한 곳. (design/profiles/ems.md §10 6.5단계)\n *\n * ── 왜 함수로 빼나 ─────────────────────────────────────────────────────────\n * 개념 지도는 상태 축을 `state[축이름]` 으로 읽고 있었다. 에너지의 수요 구간은 `state.energy.closed`\n * 에 살아서 그 규칙으로는 **언제나 `undefined`** 다 — 카드가 「관측 없음」으로 굳고, 원인은 화면도\n * 서버도 말해 주지 않는다(축은 선언돼 있고 상태에도 값이 있는데 이름이 안 맞을 뿐이다).\n *\n * 커널 선언이 이미 그 자리를 말한다(`TwinAxisInfo.path`). 그래서 규칙은 한 줄이다:\n * **경로가 있으면 그 경로로, 없으면 축 이름으로.** 리졸버 안에 두면 시험이 DB 를 물어야 하므로\n * 순수 함수로 빼 둔다.\n */\n\n/** 중첩 경로를 따라간다 — 중간이 없으면 `undefined`(빈 객체로 메우지 않는다). */\nexport function atPath(obj: any, path: string): any {\n return path.split('.').reduce((o: any, k: string) => (o == null ? undefined : o[k]), obj)\n}\n\n/**\n * 그 축의 값 — 배열이면 목록, 객체면 그릇, 없으면 `undefined`.\n *\n * `undefined` 와 빈 배열은 **다르다**: 앞은 「모른다·아직 안 옴」이고 뒤는 「없다」다. 이 함수는 그\n * 구별을 지우지 않는다 — 소비처가 `emptyBecause` 를 그 차이로 만든다.\n */\nexport function axisValueOf(state: any, axis: { axis: string; path?: string }): any {\n if (!state || !axis?.axis) return undefined\n return axis.path ? atPath(state, axis.path) : state[axis.axis]\n}\n"]}
|