@things-factory/headless-twin 10.0.14 → 10.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-server/engine/attention-digest.d.ts +4 -4
- package/dist-server/engine/attention-digest.js +7 -7
- package/dist-server/engine/attention-digest.js.map +1 -1
- package/dist-server/engine/canonical-ingest.d.ts +11 -2
- package/dist-server/engine/canonical-ingest.js +137 -8
- package/dist-server/engine/canonical-ingest.js.map +1 -1
- package/dist-server/engine/command-routing.js +1 -1
- package/dist-server/engine/command-routing.js.map +1 -1
- package/dist-server/engine/declared-stimulus.js +1 -1
- package/dist-server/engine/declared-stimulus.js.map +1 -1
- package/dist-server/engine/energy-topology.js +1 -1
- package/dist-server/engine/energy-topology.js.map +1 -1
- package/dist-server/engine/ingest-dedupe.d.ts +38 -0
- package/dist-server/engine/ingest-dedupe.js +145 -0
- package/dist-server/engine/ingest-dedupe.js.map +1 -0
- package/dist-server/engine/ingest-health.d.ts +124 -9
- package/dist-server/engine/ingest-health.js +169 -15
- package/dist-server/engine/ingest-health.js.map +1 -1
- package/dist-server/engine/integration-probes.d.ts +3 -3
- package/dist-server/engine/integration-probes.js +6 -6
- package/dist-server/engine/integration-probes.js.map +1 -1
- package/dist-server/engine/kpi-baseline.js +4 -4
- package/dist-server/engine/kpi-baseline.js.map +1 -1
- package/dist-server/engine/kpi-fold.d.ts +122 -12
- package/dist-server/engine/kpi-fold.js +247 -18
- package/dist-server/engine/kpi-fold.js.map +1 -1
- package/dist-server/engine/kpi-query.d.ts +11 -11
- package/dist-server/engine/kpi-query.js +185 -28
- package/dist-server/engine/kpi-query.js.map +1 -1
- package/dist-server/engine/live-feed-registry.d.ts +1 -1
- package/dist-server/engine/live-feed-registry.js +1 -1
- package/dist-server/engine/live-feed-registry.js.map +1 -1
- package/dist-server/engine/load-meter.d.ts +1 -1
- package/dist-server/engine/load-meter.js.map +1 -1
- package/dist-server/engine/local-declarations.d.ts +7 -7
- package/dist-server/engine/local-declarations.js +9 -9
- package/dist-server/engine/local-declarations.js.map +1 -1
- package/dist-server/engine/log.d.ts +1 -1
- package/dist-server/engine/log.js +1 -1
- package/dist-server/engine/log.js.map +1 -1
- package/dist-server/engine/measured-estimator.js +1 -1
- package/dist-server/engine/measured-estimator.js.map +1 -1
- package/dist-server/engine/measured-yield.js +1 -1
- package/dist-server/engine/measured-yield.js.map +1 -1
- package/dist-server/engine/model-basis.d.ts +1 -1
- package/dist-server/engine/model-basis.js +1 -1
- package/dist-server/engine/model-basis.js.map +1 -1
- package/dist-server/engine/model-gap.d.ts +3 -3
- package/dist-server/engine/model-gap.js +1 -1
- package/dist-server/engine/model-gap.js.map +1 -1
- package/dist-server/engine/model-vocabulary.js +1 -1
- package/dist-server/engine/model-vocabulary.js.map +1 -1
- package/dist-server/engine/oee-accumulator.d.ts +4 -4
- package/dist-server/engine/oee-accumulator.js +3 -3
- package/dist-server/engine/oee-accumulator.js.map +1 -1
- package/dist-server/engine/property-effects.js +2 -2
- package/dist-server/engine/property-effects.js.map +1 -1
- package/dist-server/engine/read-time.d.ts +22 -0
- package/dist-server/engine/read-time.js +81 -0
- package/dist-server/engine/read-time.js.map +1 -0
- package/dist-server/engine/restart-policy.js +1 -1
- package/dist-server/engine/restart-policy.js.map +1 -1
- package/dist-server/engine/runtime-key.js +1 -1
- package/dist-server/engine/runtime-key.js.map +1 -1
- package/dist-server/engine/spec-coverage.d.ts +2 -2
- package/dist-server/engine/spec-coverage.js +1 -1
- package/dist-server/engine/spec-coverage.js.map +1 -1
- package/dist-server/engine/stage-path.d.ts +4 -4
- package/dist-server/engine/stage-path.js +1 -1
- package/dist-server/engine/stage-path.js.map +1 -1
- package/dist-server/engine/state-axes.d.ts +1 -1
- package/dist-server/engine/state-axes.js +2 -2
- package/dist-server/engine/state-axes.js.map +1 -1
- package/dist-server/engine/travel-estimator.d.ts +2 -2
- package/dist-server/engine/travel-estimator.js +1 -1
- package/dist-server/engine/travel-estimator.js.map +1 -1
- package/dist-server/engine/twin-engine.d.ts +243 -53
- package/dist-server/engine/twin-engine.js +690 -191
- package/dist-server/engine/twin-engine.js.map +1 -1
- package/dist-server/engine/warm-start.d.ts +2 -2
- package/dist-server/engine/warm-start.js +5 -5
- package/dist-server/engine/warm-start.js.map +1 -1
- package/dist-server/index.js +1 -1
- package/dist-server/index.js.map +1 -1
- package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js +1 -1
- package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +1 -1
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js +3 -3
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +1 -1
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js +2 -2
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +1 -1
- package/dist-server/migrations/1786300000000-IndexEventTypeByTime.d.ts +5 -0
- package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js +54 -0
- package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js.map +1 -0
- package/dist-server/migrations/1786400000000-IndexEventCreatedAt.d.ts +5 -0
- package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js +44 -0
- package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js.map +1 -0
- package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.d.ts +6 -0
- package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js +84 -0
- package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js.map +1 -0
- package/dist-server/migrations/index.js +7 -1
- package/dist-server/migrations/index.js.map +1 -1
- package/dist-server/routes.d.ts +1 -0
- package/dist-server/routes.js +69 -5
- package/dist-server/routes.js.map +1 -1
- package/dist-server/service/index.d.ts +2 -2
- package/dist-server/service/index.js +27 -24
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/reference/control-routing.d.ts +1 -1
- package/dist-server/service/reference/control-routing.js +1 -1
- package/dist-server/service/reference/control-routing.js.map +1 -1
- package/dist-server/service/reference/discovery-result.d.ts +2 -2
- package/dist-server/service/reference/discovery-result.js +3 -3
- package/dist-server/service/reference/discovery-result.js.map +1 -1
- package/dist-server/service/reference/hook-contract.d.ts +43 -0
- package/dist-server/service/reference/hook-contract.js +59 -0
- package/dist-server/service/reference/hook-contract.js.map +1 -0
- package/dist-server/service/reference/ingest-space.d.ts +1 -1
- package/dist-server/service/reference/ingest-space.js +4 -4
- package/dist-server/service/reference/ingest-space.js.map +1 -1
- package/dist-server/service/reference/knob-defaults.d.ts +1 -1
- package/dist-server/service/reference/knob-defaults.js +4 -4
- package/dist-server/service/reference/knob-defaults.js.map +1 -1
- package/dist-server/service/reference/reference-adapter.d.ts +140 -8
- package/dist-server/service/reference/reference-adapter.js +27 -0
- package/dist-server/service/reference/reference-adapter.js.map +1 -1
- package/dist-server/service/reference/reference-hook.d.ts +20 -0
- package/dist-server/service/reference/reference-hook.js +133 -0
- package/dist-server/service/reference/reference-hook.js.map +1 -0
- package/dist-server/service/reference/reference-live.d.ts +2 -2
- package/dist-server/service/reference/reference-live.js +88 -13
- package/dist-server/service/reference/reference-live.js.map +1 -1
- package/dist-server/service/reference/reference-master.d.ts +14 -14
- package/dist-server/service/reference/reference-master.js +34 -12
- package/dist-server/service/reference/reference-master.js.map +1 -1
- package/dist-server/service/reference/reference-resolver.d.ts +1 -1
- package/dist-server/service/reference/reference-resolver.js +10 -10
- package/dist-server/service/reference/reference-resolver.js.map +1 -1
- package/dist-server/service/reference/twin-reference.d.ts +3 -3
- package/dist-server/service/reference/twin-reference.js.map +1 -1
- package/dist-server/service/twin-attention/twin-attention-query.d.ts +1 -1
- package/dist-server/service/twin-attention/twin-attention-query.js +1 -1
- package/dist-server/service/twin-attention/twin-attention-query.js.map +1 -1
- package/dist-server/service/twin-audit/command-audit.d.ts +1 -1
- package/dist-server/service/twin-audit/command-audit.js.map +1 -1
- package/dist-server/service/twin-audit/twin-audit-event.d.ts +1 -1
- package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -1
- package/dist-server/service/twin-audit/twin-audit-query.js +1 -1
- package/dist-server/service/twin-audit/twin-audit-query.js.map +1 -1
- package/dist-server/service/twin-control/twin-control-mutation.js +1 -1
- package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
- package/dist-server/service/twin-event/twin-event-keys.d.ts +22 -2
- package/dist-server/service/twin-event/twin-event-keys.js +37 -5
- package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
- package/dist-server/service/twin-event/twin-event-type.d.ts +1 -0
- package/dist-server/service/twin-event/twin-event-type.js +8 -1
- package/dist-server/service/twin-event/twin-event-type.js.map +1 -1
- package/dist-server/service/twin-event/twin-event.d.ts +2 -2
- package/dist-server/service/twin-event/twin-event.js +32 -4
- package/dist-server/service/twin-event/twin-event.js.map +1 -1
- package/dist-server/service/twin-forecast/forecast-metrics.js +1 -1
- package/dist-server/service/twin-forecast/forecast-metrics.js.map +1 -1
- package/dist-server/service/twin-forecast/gap-analytics.d.ts +1 -1
- package/dist-server/service/twin-forecast/gap-analytics.js.map +1 -1
- package/dist-server/service/twin-forecast/twin-forecast-query.js +29 -11
- package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.d.ts +1 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js +1 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +1 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js +1 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +1 -1
- package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
- package/dist-server/service/twin-journal/event-type-split-shape.d.ts +50 -0
- package/dist-server/service/twin-journal/event-type-split-shape.js +111 -0
- package/dist-server/service/twin-journal/event-type-split-shape.js.map +1 -0
- package/dist-server/service/twin-journal/event-type-split.d.ts +15 -0
- package/dist-server/service/twin-journal/event-type-split.js +102 -0
- package/dist-server/service/twin-journal/event-type-split.js.map +1 -0
- package/dist-server/service/twin-journal/journal-order.d.ts +15 -0
- package/dist-server/service/twin-journal/journal-order.js +17 -0
- package/dist-server/service/twin-journal/journal-order.js.map +1 -0
- package/dist-server/service/twin-journal/twin-journal-query.d.ts +5 -5
- package/dist-server/service/twin-journal/twin-journal-query.js +232 -21
- package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +27 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +59 -3
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
- package/dist-server/service/twin-metrics/twin-metrics-query.js +14 -1
- package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
- package/dist-server/service/twin-model/axis-journal-evidence.js +1 -1
- package/dist-server/service/twin-model/axis-journal-evidence.js.map +1 -1
- package/dist-server/service/twin-model/epcis-coverage.js +36 -1
- package/dist-server/service/twin-model/epcis-coverage.js.map +1 -1
- package/dist-server/service/twin-model/iec61850-coverage.js +1 -1
- package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -1
- package/dist-server/service/twin-model/isa95-coverage.d.ts +15 -2
- package/dist-server/service/twin-model/isa95-coverage.js +32 -8
- package/dist-server/service/twin-model/isa95-coverage.js.map +1 -1
- package/dist-server/service/twin-model/item-ref.js +2 -2
- package/dist-server/service/twin-model/item-ref.js.map +1 -1
- package/dist-server/service/twin-model/name-index.d.ts +1 -1
- package/dist-server/service/twin-model/name-index.js +1 -1
- package/dist-server/service/twin-model/name-index.js.map +1 -1
- package/dist-server/service/twin-model/project-structure.d.ts +1 -1
- package/dist-server/service/twin-model/project-structure.js +3 -3
- package/dist-server/service/twin-model/project-structure.js.map +1 -1
- package/dist-server/service/twin-model/twin-equipment.d.ts +1 -1
- package/dist-server/service/twin-model/twin-equipment.js +1 -1
- package/dist-server/service/twin-model/twin-equipment.js.map +1 -1
- package/dist-server/service/twin-model/twin-lineage-query.js +16 -5
- package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-location.d.ts +1 -1
- package/dist-server/service/twin-model/twin-location.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-item-query.d.ts +3 -3
- package/dist-server/service/twin-model/twin-model-item-query.js +14 -14
- package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-mutation.d.ts +2 -2
- package/dist-server/service/twin-model/twin-model-mutation.js +4 -4
- package/dist-server/service/twin-model/twin-model-mutation.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-query.js +117 -28
- package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-tree-query.js +22 -0
- package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-operation.d.ts +1 -1
- package/dist-server/service/twin-model/twin-operation.js +1 -1
- package/dist-server/service/twin-model/twin-operation.js.map +1 -1
- package/dist-server/service/twin-space/move-space.js +2 -2
- package/dist-server/service/twin-space/move-space.js.map +1 -1
- package/dist-server/service/twin-space/representation-role.d.ts +2 -0
- package/dist-server/service/twin-space/representation-role.js +18 -0
- package/dist-server/service/twin-space/representation-role.js.map +1 -0
- package/dist-server/service/twin-space/space-integrity.d.ts +1 -1
- package/dist-server/service/twin-space/space-integrity.js.map +1 -1
- package/dist-server/service/twin-space/twin-space-representation.d.ts +2 -0
- package/dist-server/service/twin-space/twin-space-representation.js +16 -0
- package/dist-server/service/twin-space/twin-space-representation.js.map +1 -1
- package/dist-server/service/twin-space/twin-space-resolver.d.ts +2 -2
- package/dist-server/service/twin-space/twin-space-resolver.js +36 -7
- package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
- package/dist-server/service/twin-structure/twin-structure.js +3 -3
- package/dist-server/service/twin-structure/twin-structure.js.map +1 -1
- package/dist-server/service/twin-subject/event-subjects.d.ts +43 -0
- package/dist-server/service/twin-subject/event-subjects.js +137 -0
- package/dist-server/service/twin-subject/event-subjects.js.map +1 -0
- package/dist-server/service/twin-subject/index.d.ts +9 -0
- package/dist-server/service/twin-subject/index.js +16 -0
- package/dist-server/service/twin-subject/index.js.map +1 -0
- package/dist-server/service/twin-subject/item-resolver.d.ts +10 -0
- package/dist-server/service/twin-subject/item-resolver.js +93 -0
- package/dist-server/service/twin-subject/item-resolver.js.map +1 -0
- package/dist-server/service/twin-subject/subject-query-shape.d.ts +22 -0
- package/dist-server/service/twin-subject/subject-query-shape.js +88 -0
- package/dist-server/service/twin-subject/subject-query-shape.js.map +1 -0
- package/dist-server/service/twin-subject/twin-subject-backfill.d.ts +23 -0
- package/dist-server/service/twin-subject/twin-subject-backfill.js +159 -0
- package/dist-server/service/twin-subject/twin-subject-backfill.js.map +1 -0
- package/dist-server/service/twin-subject/twin-subject-event.d.ts +14 -0
- package/dist-server/service/twin-subject/twin-subject-event.js +111 -0
- package/dist-server/service/twin-subject/twin-subject-event.js.map +1 -0
- package/dist-server/service/twin-subject/twin-subject-fast-path.d.ts +39 -0
- package/dist-server/service/twin-subject/twin-subject-fast-path.js +295 -0
- package/dist-server/service/twin-subject/twin-subject-fast-path.js.map +1 -0
- package/dist-server/service/twin-subject/twin-subject-query.d.ts +27 -0
- package/dist-server/service/twin-subject/twin-subject-query.js +232 -0
- package/dist-server/service/twin-subject/twin-subject-query.js.map +1 -0
- package/dist-server/service/twin-subject/twin-subject-writer.d.ts +27 -0
- package/dist-server/service/twin-subject/twin-subject-writer.js +166 -0
- package/dist-server/service/twin-subject/twin-subject-writer.js.map +1 -0
- package/dist-server/service/twin-target/twin-target-resolver.js +1 -1
- package/dist-server/service/twin-target/twin-target-resolver.js.map +1 -1
- package/dist-shared/entity-delta.d.ts +1 -1
- package/dist-shared/entity-delta.js +3 -3
- package/dist-shared/entity-delta.js.map +1 -1
- package/dist-shared/touched-items.js +2 -2
- package/dist-shared/touched-items.js.map +1 -1
- package/dist-shared/twin-level.js +1 -1
- package/dist-shared/twin-level.js.map +1 -1
- package/package.json +3 -3
- package/server/engine/attention-digest.ts +9 -9
- package/server/engine/canonical-ingest.ts +170 -13
- package/server/engine/command-routing.ts +1 -1
- package/server/engine/declared-stimulus.ts +1 -1
- package/server/engine/energy-topology.ts +1 -1
- package/server/engine/ingest-dedupe.ts +149 -0
- package/server/engine/ingest-health.ts +234 -20
- package/server/engine/integration-probes.ts +7 -7
- package/server/engine/kpi-baseline.ts +4 -4
- package/server/engine/kpi-fold.ts +395 -27
- package/server/engine/kpi-query.ts +212 -46
- package/server/engine/live-feed-registry.ts +1 -1
- package/server/engine/load-meter.ts +1 -1
- package/server/engine/local-declarations.ts +13 -13
- package/server/engine/log.ts +1 -1
- package/server/engine/measured-estimator.ts +1 -1
- package/server/engine/measured-yield.ts +1 -1
- package/server/engine/model-basis.ts +2 -2
- package/server/engine/model-gap.ts +3 -3
- package/server/engine/model-vocabulary.ts +1 -1
- package/server/engine/oee-accumulator.ts +4 -4
- package/server/engine/property-effects.ts +2 -2
- package/server/engine/read-time.ts +77 -0
- package/server/engine/restart-policy.ts +1 -1
- package/server/engine/runtime-key.ts +1 -1
- package/server/engine/spec-coverage.ts +3 -3
- package/server/engine/stage-path.ts +5 -5
- package/server/engine/state-axes.ts +2 -2
- package/server/engine/travel-estimator.ts +2 -2
- package/server/engine/twin-engine.ts +764 -201
- package/server/engine/warm-start.ts +6 -6
- package/server/index.ts +1 -1
- package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +1 -1
- package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +3 -3
- package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +2 -2
- package/server/migrations/1786300000000-IndexEventTypeByTime.ts +48 -0
- package/server/migrations/1786400000000-IndexEventCreatedAt.ts +38 -0
- package/server/migrations/1786500000000-CreateTwinSubjectEvents.ts +93 -0
- package/server/migrations/index.ts +7 -1
- package/server/routes.ts +68 -5
- package/server/service/index.ts +4 -1
- package/server/service/reference/control-routing.ts +1 -1
- package/server/service/reference/discovery-result.ts +3 -3
- package/server/service/reference/hook-contract.ts +103 -0
- package/server/service/reference/ingest-space.ts +4 -4
- package/server/service/reference/knob-defaults.ts +4 -4
- package/server/service/reference/reference-adapter.ts +166 -9
- package/server/service/reference/reference-hook.ts +145 -0
- package/server/service/reference/reference-live.ts +92 -14
- package/server/service/reference/reference-master.ts +39 -17
- package/server/service/reference/reference-resolver.ts +10 -10
- package/server/service/reference/twin-reference.ts +3 -3
- package/server/service/twin-attention/twin-attention-query.ts +1 -1
- package/server/service/twin-audit/command-audit.ts +1 -1
- package/server/service/twin-audit/twin-audit-event.ts +2 -2
- package/server/service/twin-audit/twin-audit-query.ts +1 -1
- package/server/service/twin-control/twin-control-mutation.ts +1 -1
- package/server/service/twin-event/twin-event-keys.ts +37 -5
- package/server/service/twin-event/twin-event-type.ts +18 -1
- package/server/service/twin-event/twin-event.ts +32 -6
- package/server/service/twin-forecast/forecast-metrics.ts +1 -1
- package/server/service/twin-forecast/gap-analytics.ts +1 -1
- package/server/service/twin-forecast/twin-forecast-query.ts +30 -11
- package/server/service/twin-ingest-window/twin-ingest-window-query.ts +1 -1
- package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +1 -1
- package/server/service/twin-instance/twin-instance.ts +4 -4
- package/server/service/twin-journal/event-type-split-shape.ts +112 -0
- package/server/service/twin-journal/event-type-split.ts +144 -0
- package/server/service/twin-journal/journal-order.ts +20 -0
- package/server/service/twin-journal/twin-journal-query.ts +224 -20
- package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +57 -3
- package/server/service/twin-metrics/twin-metrics-query.ts +14 -1
- package/server/service/twin-model/axis-journal-evidence.ts +1 -1
- package/server/service/twin-model/epcis-coverage.ts +36 -1
- package/server/service/twin-model/iec61850-coverage.ts +1 -1
- package/server/service/twin-model/isa95-coverage.ts +45 -8
- package/server/service/twin-model/item-ref.ts +2 -2
- package/server/service/twin-model/name-index.ts +1 -1
- package/server/service/twin-model/project-structure.ts +4 -4
- package/server/service/twin-model/twin-equipment.ts +2 -2
- package/server/service/twin-model/twin-lineage-query.ts +16 -5
- package/server/service/twin-model/twin-location.ts +1 -1
- package/server/service/twin-model/twin-model-item-query.ts +14 -14
- package/server/service/twin-model/twin-model-mutation.ts +4 -4
- package/server/service/twin-model/twin-model-query.ts +117 -28
- package/server/service/twin-model/twin-model-tree-query.ts +25 -1
- package/server/service/twin-model/twin-operation.ts +2 -2
- package/server/service/twin-space/move-space.ts +3 -3
- package/server/service/twin-space/representation-role.ts +15 -0
- package/server/service/twin-space/space-integrity.ts +1 -1
- package/server/service/twin-space/twin-space-representation.ts +37 -0
- package/server/service/twin-space/twin-space-resolver.ts +39 -7
- package/server/service/twin-structure/twin-structure.ts +3 -3
- package/server/service/twin-subject/event-subjects.ts +155 -0
- package/server/service/twin-subject/index.ts +10 -0
- package/server/service/twin-subject/item-resolver.ts +92 -0
- package/server/service/twin-subject/subject-query-shape.ts +84 -0
- package/server/service/twin-subject/twin-subject-backfill.ts +196 -0
- package/server/service/twin-subject/twin-subject-event.ts +101 -0
- package/server/service/twin-subject/twin-subject-fast-path.ts +346 -0
- package/server/service/twin-subject/twin-subject-query.ts +210 -0
- package/server/service/twin-subject/twin-subject-writer.ts +189 -0
- package/server/service/twin-target/twin-target-resolver.ts +1 -1
- package/shared/entity-delta.ts +3 -3
- package/shared/touched-items.ts +2 -2
- package/shared/twin-level.ts +1 -1
- package/test/adopt-structure-live.test.ts +3 -3
- package/test/aggregate-not-findone.test.ts +112 -0
- package/test/attention-digest.test.ts +6 -6
- package/test/axis-read.test.ts +2 -2
- package/test/boot-resume.test.ts +20 -7
- package/test/broadcast-period.test.ts +2 -2
- package/test/broadcast-queue-overflow.test.ts +244 -0
- package/test/canonical-ingest-vocabularies.test.ts +179 -7
- package/test/canonical-quantity-door.test.ts +2 -2
- package/test/checkpoint-refuses-empty.test.ts +2 -2
- package/test/connector-capability-declaration.test.ts +88 -0
- package/test/control-capability.test.ts +1 -1
- package/test/cursor-stall-not-read-failure.test.ts +2 -2
- package/test/declaration-reaches-model.test.ts +5 -5
- package/test/declared-stimulus.test.ts +4 -4
- package/test/discovery-result.test.ts +2 -2
- package/test/entity-delta.test.ts +4 -4
- package/test/event-subjects.test.ts +147 -0
- package/test/event-time-column.test.ts +3 -3
- package/test/event-type-split.test.ts +155 -0
- package/test/fold-gap-speaks.test.ts +53 -0
- package/test/forecast-metrics.test.ts +1 -1
- package/test/forecast-tuning.test.ts +1 -1
- package/test/ingest-health-engine.test.ts +2 -2
- package/test/ingest-health-wiring.test.ts +7 -5
- package/test/ingest-health.test.ts +6 -6
- package/test/ingest-history.test.ts +2 -2
- package/test/ingest-idempotent.test.ts +132 -0
- package/test/ingest-space.test.ts +3 -3
- package/test/integration-probes.test.ts +3 -3
- package/test/integration-runner.test.ts +1 -1
- package/test/item-ref.test.ts +1 -1
- package/test/journal-order-axis.test.ts +80 -0
- package/test/journal-read-discipline.test.ts +16 -6
- package/test/journal-retention.test.ts +171 -29
- package/test/journal-sort-axis.test.ts +1 -1
- package/test/journal-write-door.test.ts +3 -3
- package/test/journal-write-trend.test.ts +1 -1
- package/test/kernel-kind-guard.test.ts +3 -3
- package/test/knob-defaults.test.ts +1 -1
- package/test/kpi-baseline-db.test.ts +2 -2
- package/test/kpi-fold.test.ts +185 -14
- package/test/kpi-query-bench.test.ts +3 -3
- package/test/live-cursor-wiring.test.ts +6 -6
- package/test/live-kernel-facts.test.ts +11 -11
- package/test/live-mirror-parity.test.ts +3 -3
- package/test/load-meter.test.ts +1 -1
- package/test/local-declarations.test.ts +10 -10
- package/test/log-stamp.test.ts +1 -1
- package/test/measured-yield.test.ts +2 -2
- package/test/merge-streams.test.ts +70 -0
- package/test/mirror-resumes-from-checkpoint.test.ts +13 -13
- package/test/model-basis.test.ts +2 -2
- package/test/model-gap.test.ts +4 -4
- package/test/model-vocabulary.test.ts +2 -2
- package/test/move-space.test.ts +1 -1
- package/test/oee-accumulator.test.ts +2 -2
- package/test/operational-vocabulary.test.ts +2 -2
- package/test/project-structure-db.test.ts +2 -2
- package/test/projection-reaches-screen.test.ts +2 -2
- package/test/property-effects.test.ts +3 -3
- package/test/read-failure-ledger.test.ts +106 -0
- package/test/read-failure-visible.test.ts +7 -7
- package/test/read-time.test.ts +118 -0
- package/test/reference-grounding.test.ts +1 -1
- package/test/reference-hook.test.ts +86 -0
- package/test/registry-key-guard.test.ts +1 -1
- package/test/rename-twin.test.ts +81 -0
- package/test/restart-policy.test.ts +3 -3
- package/test/resync-origin-site.test.ts +3 -3
- package/test/revision-axis.test.ts +14 -8
- package/test/runtime-key.test.ts +3 -3
- package/test/snapshot-freshness.test.ts +4 -4
- package/test/source-outcome-audit.test.ts +1 -1
- package/test/space-integrity.test.ts +1 -1
- package/test/space-representation.test.ts +66 -0
- package/test/space-scope-includes-stopped.test.ts +55 -0
- package/test/spec-coverage.test.ts +1 -1
- package/test/standard-coverage.test.ts +43 -1
- package/test/status-tally.test.ts +1 -1
- package/test/structure-revision-db.test.ts +7 -7
- package/test/subject-fast-path.test.ts +148 -0
- package/test/subject-read-model.test.ts +175 -0
- package/test/touched-items.test.ts +2 -2
- package/test/twin-audit.test.ts +1 -1
- package/test/twin-event-keys.test.ts +49 -3
- package/test/twin-model-item-db.test.ts +5 -5
- package/test/twin-model-tree-db.test.ts +35 -2
- package/test/twin-origin-resync.test.ts +1 -1
- package/test/warm-start-seam.test.ts +1 -1
- package/test/warm-start.test.ts +6 -6
- package/test/withheld-door.test.ts +95 -0
- package/test/yield-loop.test.ts +4 -4
- package/tsconfig.shared.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -54,6 +54,19 @@ export interface Isa95Concept {
|
|
|
54
54
|
* 라고만 말하면 **표가 스스로 모순된 말을 한다**(다 됐다면서 없다고 한다).
|
|
55
55
|
*
|
|
56
56
|
* 그래서 축일 수 없는 것은 갈 곳을 가리킨다. 여기가 비어 있고 축도 없으면 그것은 **진짜 빈칸**이다.
|
|
57
|
+
*
|
|
58
|
+
* ── ★ 뜻을 고쳐 적는다 (2026-08-24, 사용자 지적) ──────────────────────────
|
|
59
|
+
* 한때 이 칸을 「**어디서 보이나**」로 읽고, 화면이 그것을 「해당 축 항목에 표시됩니다」라는 문장으로
|
|
60
|
+
* 냈다. 두 가지가 잘못이었다.
|
|
61
|
+
*
|
|
62
|
+
* ① 그 문장은 **한국어가 아니었다**(사용자 지적). 그리고 칩이 이미 갈 곳을 보이므로 **군더더기**다.
|
|
63
|
+
* ② 뜻이 틀렸다. 이 칸은 「**어디에 속하나**」다 — 그 개념이 자기 목록을 갖지 않고 **어느 축의 안에
|
|
64
|
+
* 들어 있나**. 화면에 그려지는지는 `surface` 가 따로 답한다. 둘은 다른 물음이다.
|
|
65
|
+
*
|
|
66
|
+
* 그래서 **`surface` 와 짝을 맞추지 않는다.** 자기 축이 없으면 언제나 갈 곳을 적는다 — 아직 그려지지
|
|
67
|
+
* 않는 개념도 **어디에 속하는지는 사실**이고, 그것을 비우면 「어디에도 없는 것」으로 읽힌다.
|
|
68
|
+
*
|
|
69
|
+
* 여기가 비어 있고 축도 없으면 그것은 **진짜 빈칸**이다(위 규칙).
|
|
57
70
|
*/
|
|
58
71
|
shownOn?: string[]
|
|
59
72
|
}
|
|
@@ -114,7 +127,17 @@ const PART2: Isa95Concept[] = [
|
|
|
114
127
|
* `TestSpecificationID`(로트별 기준 참조 — 품목 정의가 대신 답하므로 두지 않았다) · `Status` ·
|
|
115
128
|
* `HierarchyScope` · `Version`. 채운 것만 세는 것이 이 표의 규율이다.
|
|
116
129
|
*/
|
|
117
|
-
|
|
130
|
+
/*
|
|
131
|
+
* ── 2026-08-25 — 목록이 쌓이기만 하던 것을 고쳤다 ──────────────────────────
|
|
132
|
+
* 연결된 시스템은 매 주기 현재 재고를 전부 보내는데, 트윈은 그것을 낱낱의 관측으로 받아 **더하기만
|
|
133
|
+
* 했다.** 「그리고 이것 말고는 없다」를 받는 곳이 없어서 목록에서 빠진 줄이 영원히 남았다.
|
|
134
|
+
* 실측: 실제 1,704건인데 트윈이 10,038건.
|
|
135
|
+
*
|
|
136
|
+
* 그동안 이 줄은 「기능 있음」이었다. **현재 재고를 맞게 유지하지 못하는 상태로 그렇게 적을 수
|
|
137
|
+
* 없다.** 오늘 「이 목록이 전부다」를 받는 길을 냈고(커널 0.7.63), 수량으로 온 재고도 없어질 수
|
|
138
|
+
* 있게 했다. 이제 사실이다.
|
|
139
|
+
*/
|
|
140
|
+
{ std: 'MaterialLot', part: '2', label: 'twin.isa95.MaterialLot', axis: 'items', structure: 'partial', behavior: 'full', surface: 'partial', note: 'twin.isa95.note.lot' },
|
|
118
141
|
/*
|
|
119
142
|
* ── 2026-08-24 판정 정정: `none` 이 아니었다 ──────────────────────────────
|
|
120
143
|
* 이 줄은 셋 다 `none` 이었는데 **표가 코드보다 뒤처져 있었다.** 커널은 서브로트를 든다:
|
|
@@ -124,7 +147,7 @@ const PART2: Isa95Concept[] = [
|
|
|
124
147
|
* `itemKeyOf(item)` **개체의 정체성이 이 값이다** — `subLotId ?? epc`
|
|
125
148
|
*
|
|
126
149
|
* 기능이 `full` 인 근거: 이 축이 없으면 같은 로트를 rack-1 에 100개·rack-2 에 60개 관측했을 때
|
|
127
|
-
* **뒤에 온 관측이 앞을 덮어 100개가
|
|
150
|
+
* **뒤에 온 관측이 앞을 덮어 100개가 오류 없이 사라진다**(합계 160 → 60). 실제로 그 결함을 이 축으로
|
|
128
151
|
* 고쳤다. 즉 장식이 아니라 정체성을 지탱한다.
|
|
129
152
|
*
|
|
130
153
|
* 구조가 `partial` 인 이유: 서브로트는 로트와 같은 모양이고(위 참조) 그 로트가 `partial` 이다.
|
|
@@ -134,7 +157,7 @@ const PART2: Isa95Concept[] = [
|
|
|
134
157
|
* 이 부류(코드에 있는데 표가 `none`)는 자동으로 잡히지 않는다 — 표의 가드는 반대 방향만 본다
|
|
135
158
|
* (축이 사라지면 구조를 내린다). 그래서 축을 늘릴 때 이 표를 함께 보는 것이 규율이다.
|
|
136
159
|
*/
|
|
137
|
-
{ std: 'MaterialSublot', part: '2', label: 'twin.isa95.MaterialSublot', axis: null, structure: 'partial', behavior: 'full', surface: 'partial' },
|
|
160
|
+
{ std: 'MaterialSublot', part: '2', label: 'twin.isa95.MaterialSublot', axis: null, shownOn: ['items'], structure: 'partial', behavior: 'full', surface: 'partial' },
|
|
138
161
|
{ std: 'ProcessSegment', part: '2', label: 'twin.isa95.ProcessSegment', axis: 'operations', structure: 'full', behavior: 'full', surface: 'full' },
|
|
139
162
|
/*
|
|
140
163
|
* 속성은 자원마다 붙는 확장이라 **자기 축이 아니다** — 그래서 `axis` 는 없지만 구조는 완전하다:
|
|
@@ -144,7 +167,7 @@ const PART2: Isa95Concept[] = [
|
|
|
144
167
|
* 기능은 **부분**이다: 호스트의 주행 추정기가 `speed` 를 읽어 이동 시간을 만드는 것이 유일한 소비처다
|
|
145
168
|
* (다른 속성은 아직 아무도 읽지 않는다). 그 사실을 `full` 로 올리면 표가 거짓이 된다.
|
|
146
169
|
*/
|
|
147
|
-
{ std: 'Property', part: '2', label: 'twin.isa95.Property', axis: null, structure: 'full', behavior: 'partial', surface: 'full', note: 'twin.isa95.note.property' },
|
|
170
|
+
{ std: 'Property', part: '2', label: 'twin.isa95.Property', axis: null, shownOn: ['equipment', 'persons', 'assets'], structure: 'full', behavior: 'partial', surface: 'full', note: 'twin.isa95.note.property' },
|
|
148
171
|
/*
|
|
149
172
|
* **자리의 상시 관측** — 2026-08-24 신설(커널 0.7.56). 표에 이 개념이 아예 없었다.
|
|
150
173
|
*
|
|
@@ -162,11 +185,25 @@ const PART2: Isa95Concept[] = [
|
|
|
162
185
|
* 계층이고 냉장실은 `StorageZone` 수준의 설비다. `OperationalLocationType` 에는 이 축이 없다.
|
|
163
186
|
*
|
|
164
187
|
* 구조가 `partial`: 봉투 전체(`ID`·`Description`·사건 분류·`OperationsRecord` 구조)가 아니라 관측
|
|
165
|
-
* 하나를 담는 데 필요한 칸만 든다.
|
|
188
|
+
* 하나를 담는 데 필요한 칸만 든다. 2026-08-25 에 지난 기록의 봉투가 사람이 적은 문장을 함께
|
|
189
|
+
* 나르게 됐지만(`CanonicalEnvelope.description`), 그것은 **기록에 남는 말**이고 이 타입의
|
|
190
|
+
* `Description` 칸이 아니다 — 상태에 담기지 않으므로 이 줄의 등급을 올리지 않는다.
|
|
166
191
|
* 기능이 `full`: 사건 채널(`location.measured`)이 상태를 만들고, 선언된 기준으로 판정해
|
|
167
192
|
* `observation-out-of-limit` 신호를 세운다.
|
|
168
193
|
* 화면이 `none`: 클라이언트에 이 축을 그리는 곳이 없다(실측 0곳).
|
|
169
194
|
*/
|
|
195
|
+
/*
|
|
196
|
+
* ── 2026-08-25 — 이 줄이 실제보다 높게 적혀 있었다 ────────────────────────
|
|
197
|
+
* 「기능 있음」으로 적어 두었다. 커널이 관측을 반영하고 기준으로 판단하는 것은 사실이었으므로
|
|
198
|
+
* 그렇게 적었는데, **연결된 시스템이 그 값을 보낼 길이 없었다** — 유입 경로가 `locationId` 를
|
|
199
|
+
* 보지 않아 「어느 사실인지 모른다」로 거부했다.
|
|
200
|
+
*
|
|
201
|
+
* **아무도 먹일 수 없는 기능은 동작하는 기능이 아니다.** 오늘 그 경로를 냈고(커널 0.7.62),
|
|
202
|
+
* 이제 「기능 있음」이 사실이다. 그때까지는 「일부」였어야 했다.
|
|
203
|
+
*
|
|
204
|
+
* 같은 부류를 오늘 세 번 만났다 — 표가 **할 수 있는 일을 못 한다고** 적은 것이 여섯 줄이었고,
|
|
205
|
+
* 이것은 반대로 **못 하는 일을 할 수 있다고** 적은 것이다. 뒤쪽이 더 위험하다.
|
|
206
|
+
*/
|
|
170
207
|
{ std: 'OperationsEvent', part: '2', label: 'twin.isa95.OperationsEvent', axis: null, shownOn: ['locations'], structure: 'partial', behavior: 'full', surface: 'none' }
|
|
171
208
|
]
|
|
172
209
|
|
|
@@ -189,7 +226,7 @@ const PART4: Isa95Concept[] = [
|
|
|
189
226
|
*
|
|
190
227
|
* `surface` 도 `full` 이다(같은 날 두 번째 단계): 목록·상세·관계에 더해 **과거로 다시 계산하는 길**이 화면에
|
|
191
228
|
* 있다 — 시각 커서를 옮기면 그 시점의 사실이 나오고, 무엇을 근거로 답했는지도 화면이 말한다
|
|
192
|
-
* (라이브=권위 · 저널=파생 ·
|
|
229
|
+
* (라이브=권위 · 저널=파생 · 계산한 리비전). 실측: 이른 시각 0건 · 하루 뒤 504건.
|
|
193
230
|
*/
|
|
194
231
|
{ std: 'OperationsSchedule', part: '4', label: 'twin.isa95.OperationsSchedule', axis: 'orders', structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.schedule' },
|
|
195
232
|
{ std: 'OperationsPerformance', part: '4', label: 'twin.isa95.OperationsPerformance', axis: 'tasks', structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.performance' },
|
|
@@ -244,10 +281,10 @@ export const ISA95_CONCEPTS: Isa95Concept[] = [...PART2, ...PART4]
|
|
|
244
281
|
* 적합성 보고 — **채운 것과 빠진 것을 함께.**
|
|
245
282
|
*
|
|
246
283
|
* `axis` 를 커널 축 목록과 맞춰 본다: 축이 있다고 적혀 있는데 커널에 없으면 그 선언이 낡은 것이므로
|
|
247
|
-
* **구조 없음으로 낮추고 그 사실을 말한다**(
|
|
284
|
+
* **구조 없음으로 낮추고 그 사실을 말한다**(알리지 않고 통과시키면 이 표가 거짓이 된다).
|
|
248
285
|
*/
|
|
249
286
|
/**
|
|
250
|
-
* 선언한 축을 커널의 축 목록과 맞춘다 — **어긋난 것을
|
|
287
|
+
* 선언한 축을 커널의 축 목록과 맞춘다 — **어긋난 것을 알리지 않고 지우지 않는다.**
|
|
251
288
|
*
|
|
252
289
|
* 두 자리가 어긋날 수 있다.
|
|
253
290
|
* · `axis` — 그 개념 자신의 문. 커널에 없으면 구조를 「없음」으로 낮추고 그 사실을 말한다.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* **개체에서 품목으로** — 정체성 모양이 하나가 아니다 (2026-08-23).
|
|
3
3
|
*
|
|
4
|
-
* ── 무엇이
|
|
4
|
+
* ── 무엇이 알리지 않고 비어 있었나 ───────────────────────────────────────────────
|
|
5
5
|
* 화면이 「이 개체는 선언된 자재가 아닙니다(라인 내부의 중간 산물일 수 있습니다)」라고 말했는데, 그
|
|
6
6
|
* 품목은 원본의 선언에 이름까지 있었다(「냉동그린빈스」). 대응 표(`productionSpec.binding`)도 멀쩡했다.
|
|
7
7
|
*
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* https://…/class/product-M0000438@M2R0001 → null (모르던 모양)
|
|
12
12
|
*
|
|
13
13
|
* 웹 URI 로 정체성을 말하는 원본에서는 언제나 `null` 이 나왔고, 그래서 **개체를 자재에 이을 수 없었다.**
|
|
14
|
-
* 그 결과가 화면 두 곳에서
|
|
14
|
+
* 그 결과가 화면 두 곳에서 들어온 것이 없는 빈칸이었다(계보 적합성 · 오더가 무엇을 만드나). 「없으면 붙이지
|
|
15
15
|
* 않는다」는 규율을 지키기 때문에 아무 오류도 나지 않았다.
|
|
16
16
|
*
|
|
17
17
|
* ── 무엇을 돌려주나 ─────────────────────────────────────────────────────────
|
|
@@ -16,7 +16,7 @@ export const at = (o: any, path: string) => path.split('.').reduce((a: any, k: s
|
|
|
16
16
|
*
|
|
17
17
|
* 도메인 모양을 코드가 아는 것이 아니라 **식별자 관례**를 아는 것이다. 후보를 순서대로 보고 첫
|
|
18
18
|
* 값을 쓴다. 하나도 없으면 그 항목은 가리킬 수 없으므로 `null` — 목록에서 빼지 않고 **id 없음**으로
|
|
19
|
-
* 낸다(
|
|
19
|
+
* 낸다(오류 없이 빠지면 건수가 안 맞는다).
|
|
20
20
|
*/
|
|
21
21
|
export const idOf = (o: any, info?: { idField?: string | string[] }): string | null => {
|
|
22
22
|
if (typeof o === 'string') return o
|
|
@@ -26,7 +26,7 @@ export interface ProjectionReport {
|
|
|
26
26
|
locations: number
|
|
27
27
|
equipment: number
|
|
28
28
|
operations: number
|
|
29
|
-
/** 자리를 못 찾아 기준 자리를 비운 설비 —
|
|
29
|
+
/** 자리를 못 찾아 기준 자리를 비운 설비 — **알리지 않고 넘기지 않는다.** */
|
|
30
30
|
unresolvedHome: string[]
|
|
31
31
|
/** 구역을 못 찾아 비운 자리. 구역은 model 밖(호스트 개념)이라 어긋날 수 있다. */
|
|
32
32
|
unresolvedArea: string[]
|
|
@@ -48,7 +48,7 @@ export async function projectStructure(domainId: string, instanceId: string, mod
|
|
|
48
48
|
* 실측(2026-08-18): 현장 값을 그 현장의 트윈 전부에 얹다가 한 트윈에서 투영이 터졌다. 올라온 것은
|
|
49
49
|
* `SQLITE_CONSTRAINT: UNIQUE constraint failed: twin_equipment...` 뿐이라 **무엇이 잘못됐는지 아무도
|
|
50
50
|
* 알 수 없었다.** 원인은 그 모델에 설비 `load-1` 이 두 번 들어 있던 것이다(id 는 트윈 안에서 유일해야
|
|
51
|
-
* 한다).
|
|
51
|
+
* 한다). 알리지 않고 하나만 남기지 않는다 — 그것은 모델링 오류를 감추는 일이고, 어느 쪽이 맞는지 우리는
|
|
52
52
|
* 모른다.
|
|
53
53
|
*/
|
|
54
54
|
const dupOf = (list: any[] | undefined, what: string): string | undefined => {
|
|
@@ -117,7 +117,7 @@ export async function projectStructure(domainId: string, instanceId: string, mod
|
|
|
117
117
|
parallelism: l.parallelism ?? null,
|
|
118
118
|
area: area ? ({ id: area.id } as any) : null,
|
|
119
119
|
gs1Id: l.gs1Id ?? null,
|
|
120
|
-
/* 자리의 속성도 옮긴다 — 옮기지 않으면 계약전력·요금이 항목 패널에서 사라진다(
|
|
120
|
+
/* 자리의 속성도 옮긴다 — 옮기지 않으면 계약전력·요금이 항목 패널에서 사라진다(들어온 것이 없는 누락). */
|
|
121
121
|
properties: l.properties ?? null,
|
|
122
122
|
sourceRef,
|
|
123
123
|
ingestedAt
|
|
@@ -173,7 +173,7 @@ export async function projectStructure(domainId: string, instanceId: string, mod
|
|
|
173
173
|
intent: o.intent ?? null,
|
|
174
174
|
locationType: o.locationType ?? null,
|
|
175
175
|
resourceType: o.resourceType ?? null,
|
|
176
|
-
/* 소요시간은 ISO 8601 문자열이 정본 — 분으로
|
|
176
|
+
/* 소요시간은 ISO 8601 문자열이 정본 — 분으로 줄이면 초가 오류 없이 사라진다. */
|
|
177
177
|
duration: o.duration ?? null,
|
|
178
178
|
variability: o.variability ?? null,
|
|
179
179
|
parameters: o.parameters ?? null,
|
|
@@ -14,7 +14,7 @@ import { TwinLocation } from './twin-location.js'
|
|
|
14
14
|
*
|
|
15
15
|
* ── 테이블 이름을 명시한다 ──────────────────────────────────────────────────
|
|
16
16
|
* `equipment` 는 영어에서 **불가산**이라 자동 복수화가 다른 표들과 규칙이 어긋난다
|
|
17
|
-
* (`twin_locations` 인데 `twin_equipment`).
|
|
17
|
+
* (`twin_locations` 인데 `twin_equipment`). 들어온 것이 없는 비일관은 나중에 "왜 이것만 다르지" 를
|
|
18
18
|
* 만들므로 이름을 고정한다(ADR-0032-I).
|
|
19
19
|
*/
|
|
20
20
|
@Entity({ name: 'twin_equipment' })
|
|
@@ -97,7 +97,7 @@ export class TwinEquipment {
|
|
|
97
97
|
* 그 시험들의 **결과** — 자격이 성립하는지는 이것이 말한다.
|
|
98
98
|
*
|
|
99
99
|
* 참조만 있으면 "무엇으로 검증한다" 까지다. 오늘 이 저장소에서 네 번 나온 결손과 같은 부류이므로
|
|
100
|
-
* 행에 자리를 함께 만든다 — 모델에는 있는데 행에 없으면 화면이
|
|
100
|
+
* 행에 자리를 함께 만든다 — 모델에는 있는데 행에 없으면 화면이 알리지 않고 잃는다.
|
|
101
101
|
*/
|
|
102
102
|
@Column({ type: 'simple-json', nullable: true })
|
|
103
103
|
@Field(type => ScalarObject, { nullable: true, description: 'Results of those tests (pass/fail with dates). Declared by the source — the twin does not produce them.' })
|
|
@@ -52,11 +52,22 @@ export class TwinLineageQuery {
|
|
|
52
52
|
* 변환 이벤트만 읽는다. **최신부터** 읽는다 — 계보를 묻는 개체는 대개 최근에 만들어진 것이고,
|
|
53
53
|
* 창을 넘겨 잘릴 때 잃는 쪽이 오래된 것이어야 덜 아프다.
|
|
54
54
|
*/
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
/*
|
|
56
|
+
* 관계를 **컬럼으로** 묻는다 (2026-08-28). `find({ where: { domain: { id } } })` 은 관계를 가진
|
|
57
|
+
* 엔티티에서 두 단계 질의가 되고 **안쪽에 상한이 없다** — `take` 가 있어도 조건에 맞는 행을 전부
|
|
58
|
+
* 만든 뒤 밖에서 자른다. 변환 사건이 적은 트윈에서는 표가 안 나지만, 변환을 많이 내는 트윈에서는
|
|
59
|
+
* 이 한 번이 그 종류 전체를 만든다. 질의 빌더로 물으면 상한이 안쪽에 걸린다.
|
|
60
|
+
*
|
|
61
|
+
* 종류가 정확일치이므로 `(domain, instance, event_type, event_time)` 색인이 시각 순서까지 준다.
|
|
62
|
+
*/
|
|
63
|
+
const rows: any[] = await getRepository(TwinEvent)
|
|
64
|
+
.createQueryBuilder('e')
|
|
65
|
+
.where('e.domain = :domainId', { domainId })
|
|
66
|
+
.andWhere('e.instanceId = :instanceId', { instanceId })
|
|
67
|
+
.andWhere('e.eventType = :eventType', { eventType: 'epcis.TransformationEvent' })
|
|
68
|
+
.orderBy('e.eventTime', 'DESC')
|
|
69
|
+
.limit(windowCap)
|
|
70
|
+
.getMany()
|
|
60
71
|
const truncated = rows.length >= windowCap
|
|
61
72
|
|
|
62
73
|
/* 산출 → 그 변환. 한 개체는 한 번만 만들어지지만, 데이터가 그렇지 않을 수 있으므로 목록으로 든다. */
|
|
@@ -115,7 +115,7 @@ export class TwinLocation {
|
|
|
115
115
|
* ── 왜 늦게 생겼나 (2026-08-18) ────────────────────────────────────────────
|
|
116
116
|
* 설비 행에는 이 칸이 있었는데 자리 행에는 없었다. 그래서 수전 지점에 계약전력·요금을 넣어도 **항목
|
|
117
117
|
* 패널의 「원본이 말한 값」에 그 줄이 아예 없었다** — 사용자는 값을 넣고도 화면에서 찾을 수 없었고,
|
|
118
|
-
* 「이 값이 무엇을 바꾸나」는 문장도 붙을 자리가 없었다(
|
|
118
|
+
* 「이 값이 무엇을 바꾸나」는 문장도 붙을 자리가 없었다(들어온 것이 없는 누락).
|
|
119
119
|
*/
|
|
120
120
|
@Column({ type: 'simple-json', nullable: true })
|
|
121
121
|
@Field(type => ScalarObject, { nullable: true, description: 'Properties declared on this location (ISA-95 Property: value + dataType + unit of measure). Carries locally-declared numbers too, with source: local.' })
|
|
@@ -54,7 +54,7 @@ function matches(o: any, q: string): boolean {
|
|
|
54
54
|
* 그래서 야드의 도크도어나 에너지의 계량기가 늘어도 여기 손댈 것이 없다(ADR-0031). 이 파일에
|
|
55
55
|
* 도메인 이름이 한 번도 나오지 않는 것이 그 증거다.
|
|
56
56
|
*
|
|
57
|
-
* ──
|
|
57
|
+
* ── 알리지 않고 자르지 않는다 ──────────────────────────────────────────────────────
|
|
58
58
|
* 목록은 항상 **총 건수**와 함께 온다. 그리고 어떻게 셌는지(`basis`)를 밝힌다 — 행이면 DB 가
|
|
59
59
|
* 페이징했고, 문서면 서버가 문서를 통째로 읽어 잘라 냈다. 후자는 규모에서 한계가 되므로 그 사실이
|
|
60
60
|
* 화면에 드러나야 이주 압력이 살아 있다.
|
|
@@ -118,7 +118,7 @@ export class TwinModelItemQuery {
|
|
|
118
118
|
/*
|
|
119
119
|
* 검색은 아직 DB 로 내리지 않았다. **그 사실을 밝힌다**(`basis: 'rows-scanned'`) —
|
|
120
120
|
* 흉내로 덮으면 규모가 오는 날까지 아무도 이 한계를 모른다. 훑는 건수를 상한으로 막고,
|
|
121
|
-
* 상한에 닿았으면 잘렸다고 말한다(
|
|
121
|
+
* 상한에 닿았으면 잘렸다고 말한다(알리지 않고 자르지 않는다).
|
|
122
122
|
*/
|
|
123
123
|
const SCAN_MAX = 5000
|
|
124
124
|
const repo = getRepository(row.entity)
|
|
@@ -159,11 +159,11 @@ export class TwinModelItemQuery {
|
|
|
159
159
|
* **라이브는 권위, 히스토리는 파생.**
|
|
160
160
|
*
|
|
161
161
|
* 지금 도는 커널의 상태가 이 축의 진실이다. 그런데 커널이 멈춰 있거나 사용자가 **과거 시각**을
|
|
162
|
-
* 물으면 답할 것이 없어진다 — 그때 저널을 그 시점까지
|
|
162
|
+
* 물으면 답할 것이 없어진다 — 그때 저널을 그 시점까지 계산해 답한다(`recover`). 둘은 같은 모양이지만
|
|
163
163
|
* **성질이 다르므로 `basis` 로 구별해 낸다**: 파생을 권위처럼 내면 다시 계산한 값을 보고 "지금 이렇다"
|
|
164
164
|
* 로 읽게 된다.
|
|
165
165
|
*
|
|
166
|
-
* 규모:
|
|
166
|
+
* 규모: 계산 비용은 이벤트 수에 비례한다. 감추지 않고 **어느 리비전까지 계산했는지 함께 낸다** —
|
|
167
167
|
* 이 화면이 목록마다 `basis` 를 밝혀 온 규율 그대로다.
|
|
168
168
|
*/
|
|
169
169
|
const live = TwinEngine.isRunning(domainId, instanceId) && !at_
|
|
@@ -341,9 +341,9 @@ export class TwinModelItemQuery {
|
|
|
341
341
|
*/
|
|
342
342
|
if (axisSource(axis) === 'state') {
|
|
343
343
|
/*
|
|
344
|
-
* 동작하지 않으면 **목록과 같은 길로 저널을
|
|
344
|
+
* 동작하지 않으면 **목록과 같은 길로 저널을 계산한다.**
|
|
345
345
|
*
|
|
346
|
-
* 목록만
|
|
346
|
+
* 목록만 계산하고 상세는 안 계산했더니, 목록에는 472건이 보이는데 그 줄을 열면 "자기 속성이 없습니다"
|
|
347
347
|
* 가 나왔다 — 한 화면에서 두 답이 갈리면 어느 쪽이 맞는지 사용자가 알 수 없다.
|
|
348
348
|
*/
|
|
349
349
|
const liveItem = TwinEngine.isRunning(domainId, instanceId)
|
|
@@ -471,7 +471,7 @@ export class TwinModelItemQuery {
|
|
|
471
471
|
observed,
|
|
472
472
|
/*
|
|
473
473
|
* 관측 열이 **자기 근거를 말한다** — 목록의 그 낱말 그대로(`state`=라이브 권위, `journal`=다시 계산한
|
|
474
|
-
* 파생). 없으면 비교하지 못한 것이다. 파생이면 몇 번째 리비전까지
|
|
474
|
+
* 파생). 없으면 비교하지 못한 것이다. 파생이면 몇 번째 리비전까지 계산했는지 함께 낸다.
|
|
475
475
|
*/
|
|
476
476
|
...('value' in seen
|
|
477
477
|
? { observedBasis: seen.basis, ...(seen.foldedRevision != null ? { observedRevision: seen.foldedRevision } : {}) }
|
|
@@ -631,8 +631,8 @@ export class TwinModelItemQuery {
|
|
|
631
631
|
*
|
|
632
632
|
* 근거를 값과 함께 낸다 — 목록이 오래 해 온 그대로다(§twinModelItemList). 라이브(권위)와 저널
|
|
633
633
|
* 폴드(파생)는 **같은 모양의 객체**라서, 근거가 없으면 다시 계산한 값을 "지금 이렇다" 로 읽는다. 근거를
|
|
634
|
-
* 실을 자리가 없던 동안에는 멈춘 트윈의 저널을 아예
|
|
635
|
-
* 자리가 있으니
|
|
634
|
+
* 실을 자리가 없던 동안에는 멈춘 트윈의 저널을 아예 계산하지 않고 `—` 로 두는 편이 정직했다. 이제
|
|
635
|
+
* 자리가 있으니 계산한다 — 멈춘 트윈에도 마지막으로 알던 사실이 있고, 그것을 감출 이유는 없다.
|
|
636
636
|
*
|
|
637
637
|
* 다시 계산할 것이 없으면(모델도 저널도 없다) `null` 이다 — **없는 것과 비교하지 못한 것은 다르다.**
|
|
638
638
|
*/
|
|
@@ -652,9 +652,9 @@ export class TwinModelItemQuery {
|
|
|
652
652
|
const rec = await TwinEngine.recover(domainId, instanceId)
|
|
653
653
|
folded = Number((rec as any)?.revision) || undefined
|
|
654
654
|
/*
|
|
655
|
-
* **이벤트를 하나도
|
|
655
|
+
* **이벤트를 하나도 계산하지 않은 폴드는 관측이 아니다.** 저널이 비어도 복원은 선언을 다시 투영한
|
|
656
656
|
* 기준선을 돌려준다 — 그것을 "트윈이 관측한 값" 열에 실으면 **선언이 관측으로 위장한다**
|
|
657
|
-
* (원본이 말한 값이 오른쪽에도 똑같이 앉아, 견준 것처럼 보인다).
|
|
657
|
+
* (원본이 말한 값이 오른쪽에도 똑같이 앉아, 견준 것처럼 보인다). 계산한 것이 없으면 없다고 한다.
|
|
658
658
|
*/
|
|
659
659
|
if (folded == null) return { absence: 'not-running' as const }
|
|
660
660
|
state = unwrapState(rec)
|
|
@@ -700,7 +700,7 @@ export class TwinModelItemQuery {
|
|
|
700
700
|
const model = inst.model ?? {}
|
|
701
701
|
|
|
702
702
|
/*
|
|
703
|
-
* 관측 축의 항목들 — **한 번만** 읽는다(관계마다 다시
|
|
703
|
+
* 관측 축의 항목들 — **한 번만** 읽는다(관계마다 다시 계산하면 저널을 여러 번 계산한다).
|
|
704
704
|
* 라이브가 있으면 그것, 없으면 저널 폴드. 없으면 빈 배열(없는 것을 지어내지 않는다).
|
|
705
705
|
*/
|
|
706
706
|
let observedState: any
|
|
@@ -725,7 +725,7 @@ export class TwinModelItemQuery {
|
|
|
725
725
|
*
|
|
726
726
|
* 관계 선언의 필드 이름(`locations.parentId`)은 **원본 문서의 어휘**다. 행은 같은 사실을 자기
|
|
727
727
|
* 컬럼 이름으로 담는다(자리의 구역은 `area` FK). 행을 보고 `parentId` 를 찾으면 늘 비어서
|
|
728
|
-
* "이어진 것이 없다" 로
|
|
728
|
+
* "이어진 것이 없다" 로 알리지 않고 답한다 — 실제로 그랬다.
|
|
729
729
|
*
|
|
730
730
|
* 어느 쪽이 정본인가로 구분하면 답이 분명하다: 관계를 선언한 어휘가 문서 어휘이므로 **문서에서
|
|
731
731
|
* 푼다.** 행에만 있고 문서에 없는 항목(있을 수 있다)은 이어진 것이 비지만, 그것은 사실이다.
|
|
@@ -762,7 +762,7 @@ export class TwinModelItemQuery {
|
|
|
762
762
|
const repo = getRepository(row.entity)
|
|
763
763
|
/*
|
|
764
764
|
* **FK 는 부르지 않으면 오지 않는다.** 관계 필드를 로딩하지 않고 조회하면 값이 늘 `undefined`
|
|
765
|
-
* 라서 "나를 가리키는 것이 하나도 없다" 로
|
|
765
|
+
* 라서 "나를 가리키는 것이 하나도 없다" 로 알리지 않고 답한다 — 실제로 그랬다(설비 둘이 이 자리를
|
|
766
766
|
* 기준으로 삼는데 이웃이 비어 있었다).
|
|
767
767
|
*
|
|
768
768
|
* 어느 필드가 관계인지는 **엔티티 메타데이터**가 안다. 축마다 표를 두면 축이 늘 때마다
|
|
@@ -71,7 +71,7 @@ async function spreadSpaceLayer(
|
|
|
71
71
|
t.localDeclarations = out.instanceLayerApplied as any
|
|
72
72
|
const running = TwinEngine.modeOf(domainId, t.instanceId)
|
|
73
73
|
/*
|
|
74
|
-
* **트윈 하나가 실패해도 나머지는 받는다** — 다만
|
|
74
|
+
* **트윈 하나가 실패해도 나머지는 받는다** — 다만 알리지 않고 넘기지 않는다(2026-08-18 실측).
|
|
75
75
|
*
|
|
76
76
|
* 현장 선언을 처음 퍼뜨렸을 때 그 현장의 어떤 트윈에서 구조 투영이 터졌고(그 트윈 모델에 설비 id가
|
|
77
77
|
* 겹쳐 있었다 — 선재 결함), 그 예외가 올라와 **한 트윈 때문에 현장 전체 선언이 실패**했다. 사용자는
|
|
@@ -132,12 +132,12 @@ export class TwinModelMutation {
|
|
|
132
132
|
* 계량이 흐르는 트윈에서도 **금액이 영원히 나오지 않았다** — 넣을 문이 없었기 때문이다.
|
|
133
133
|
*
|
|
134
134
|
* 값은 `model` 에 직접 쓰지 않고 **겹**으로 보관한다: 모델은 원천의 사본이므로 다시 읽기가 그것을
|
|
135
|
-
* 새로 그리면 직접 쓴 값은
|
|
135
|
+
* 새로 그리면 직접 쓴 값은 오류 없이 사라진다. 겹은 모델을 그릴 때마다 다시 얹힌다.
|
|
136
136
|
*
|
|
137
137
|
* ── 돌고 있는 트윈에 어떻게 반영하나 ────────────────────────────────────────
|
|
138
138
|
* 미러는 **동작 중에** 새 구조를 받는다(현실은 멈추지 않는다). 시뮬레이션은 받지 않는다 — 조건이
|
|
139
139
|
* 바뀐 실험은 다른 실험이고, 커널이 그렇게 거절한다. 그때는 저장만 하고 **그 사실을 말한다**:
|
|
140
|
-
*
|
|
140
|
+
* 알리지 않고 성공으로 답하면 사용자는 지금 화면의 수가 새 선언으로 계산된 줄로 읽는다.
|
|
141
141
|
*/
|
|
142
142
|
@Directive('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
143
143
|
@Mutation(returns => ScalarObject, {
|
|
@@ -165,7 +165,7 @@ export class TwinModelMutation {
|
|
|
165
165
|
if (!declarations?.length) return { ok: false, error: 'no declaration arrived — the declarations argument was empty' }
|
|
166
166
|
|
|
167
167
|
/*
|
|
168
|
-
* 검사부터 — 받지 않은 것을
|
|
168
|
+
* 검사부터 — 받지 않은 것을 알리지 않고 버리지 않는다(사용자는 값을 넣었다고 믿는다).
|
|
169
169
|
*
|
|
170
170
|
* 공정 이름은 **대조로만** 걸러진다(모델에 행이 없어도 성립하는 선언이라 다른 안전망이 없다). 아직
|
|
171
171
|
* 돌지도 않고 명세 행도 없는 트윈이면 목록이 `undefined` 이고, 그때는 받아 주되 답에 그 사실을 싣는다.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { axisSeenInJournal } from './axis-journal-evidence.js'
|
|
2
|
+
import { hydratedDate } from '../../engine/read-time.js'
|
|
2
3
|
import { statusTally } from './status-tally.js'
|
|
3
4
|
import { TwinEvent } from '../twin-event/twin-event.js'
|
|
4
5
|
import { Arg, Ctx, Query, Resolver } from 'type-graphql'
|
|
@@ -117,8 +118,8 @@ async function emptyStateReason(domainId: string, instanceId: string, axis: stri
|
|
|
117
118
|
/**
|
|
118
119
|
* 되세운 상태를 담아 둔다 — **열쇠에 저널 끝 리비전이 들어 있어** 새 사건이 오면 저절로 낡는다.
|
|
119
120
|
*
|
|
120
|
-
*
|
|
121
|
-
* 프로세스 안에만 두고 몇 개만 남긴다 — 다시 뜨면 다시
|
|
121
|
+
* 계산은 저널 전체를 훑는 일이라(리비전 12만에서 2~3초) 모델 화면을 열 때마다 그 값을 낼 수 없다.
|
|
122
|
+
* 프로세스 안에만 두고 몇 개만 남긴다 — 다시 뜨면 다시 계산한다(정확함이 캐시보다 앞선다).
|
|
122
123
|
*/
|
|
123
124
|
const RECALL_CACHE = new Map<string, { snap: any; heardAt: string | null }>()
|
|
124
125
|
const RECALL_CACHE_MAX = 8
|
|
@@ -181,20 +182,38 @@ export class TwinModelQuery {
|
|
|
181
182
|
const inst = await getRepository(TwinInstance).findOne({ where: { domain: { id: domainId }, instanceId } })
|
|
182
183
|
if (!inst) return null
|
|
183
184
|
|
|
184
|
-
/*
|
|
185
|
+
/*
|
|
186
|
+
* 어느 원본을 반영하나 — 없을 수 있다(템플릿으로 세운 트윈). 그 사실도 사실이다.
|
|
187
|
+
*
|
|
188
|
+
* ── **레퍼런스 열쇠는 인스턴스 이름이 아니다** (2026-08-24 실측) ──────────────
|
|
189
|
+
* 여기서 `source: instanceId` 로 찾고 있었다. 레퍼런스 하나가 인스턴스 하나를 낳던 시절에는
|
|
190
|
+
* 두 값이 같았지만, **레퍼런스 하나가 여러 트윈을 먹이는 순간 어긋난다.**
|
|
191
|
+
*
|
|
192
|
+
* 인스턴스 operato-chef-v1-pocheon
|
|
193
|
+
* origin { kind: 'reference', source: 'seunghwa' } ← 이것이 레퍼런스 열쇠다
|
|
194
|
+
* 레퍼런스 seunghwa
|
|
195
|
+
*
|
|
196
|
+
* 그래서 요약의 `source` 가 **언제나 `null`** 이었다. 레퍼런스로 세운 트윈인데 화면이 그 원본을
|
|
197
|
+
* 못 찾았고, 「마지막 동기화」도 흐름별 신선도도 그릴 자리가 아예 없었다 — 그런데 오류는 없었다
|
|
198
|
+
* (`ref ? … : null` 이 알리지 않고 갈렸다).
|
|
199
|
+
*
|
|
200
|
+
* 인스턴스가 자기 원본을 말하고 있으므로 **그 말을 먼저 듣는다.** 옛 트윈은 두 값이 같으므로
|
|
201
|
+
* 뒤의 것으로 여전히 찾힌다 — 가산적이다.
|
|
202
|
+
*/
|
|
203
|
+
const refKey = (inst.origin as any)?.source || instanceId
|
|
185
204
|
const ref = await getRepository(TwinReference)
|
|
186
|
-
.findOne({ where: { domain: { id: domainId }, source:
|
|
205
|
+
.findOne({ where: { domain: { id: domainId }, source: refKey } })
|
|
187
206
|
.catch(() => null)
|
|
188
207
|
|
|
189
208
|
const model = inst.model ?? {}
|
|
190
209
|
/*
|
|
191
|
-
* **한 요청에 저널은 한 번만
|
|
210
|
+
* **한 요청에 저널은 한 번만 계산한다.**
|
|
192
211
|
*
|
|
193
212
|
* 관측 축(`orders`·`tasks`)은 상태에서 센다. 그 둘이 각자 `recover()` 를 부르고 있었고, 커널이
|
|
194
|
-
* 멈춰 있으면 그것은 **저널 전체를 다시
|
|
213
|
+
* 멈춰 있으면 그것은 **저널 전체를 다시 계산하는 일**이다(23,731건짜리 트윈에서 4.6초, 두 번이면 곱절).
|
|
195
214
|
* 트윈을 전부 멈춰도 이 화면이 5~7초 걸린 이유가 이것이다 — 부하가 아니라 같은 일을 두 번 한 것이다.
|
|
196
215
|
*
|
|
197
|
-
* 한 번만
|
|
216
|
+
* 한 번만 계산해 나눠 쓴다. 값이 같아지는 것은 덤이 아니라 **옳음**이다: 두 번 계산하면 그 사이에
|
|
198
217
|
* 들어온 이벤트 때문에 오더와 작업이 서로 다른 시점의 사실이 될 수 있다.
|
|
199
218
|
*/
|
|
200
219
|
let statePromise: Promise<{ snap: any; folded: boolean; observing: boolean }> | null = null
|
|
@@ -221,29 +240,49 @@ export class TwinModelQuery {
|
|
|
221
240
|
* 그런데 읽기 커서가 정직해진 뒤로는 원본이 **지난 것을 다시 말하지 않는다.** 그래서 재고 741건이
|
|
222
241
|
* 저널에 있는데 화면은 「0」을 보였다. 사용자가 「재고현황이 없다」고 물은 자리가 이것이다.
|
|
223
242
|
*
|
|
224
|
-
* 그 0 은 사실이 아니라 **우리가 잊은 것**이다. 저널을
|
|
225
|
-
* 97,689 을 2.4초에
|
|
243
|
+
* 그 0 은 사실이 아니라 **우리가 잊은 것**이다. 저널을 계산하면 그대로 되살아난다(실측: 리비전
|
|
244
|
+
* 97,689 을 2.4초에 계산해 물품 741 · 작업 9,145 · 지시 2,856).
|
|
226
245
|
*
|
|
227
|
-
* ── 그래서
|
|
228
|
-
* 저널을
|
|
246
|
+
* ── 그래서 계산한다. 다만 **언제 들은 값인지 함께 낸다** ──────────────────────
|
|
247
|
+
* 저널을 계산하는 것은 **원천이 우리에게 말한 것을 다시 세우는 일**이지, 없는 값을 심는 일이 아니다.
|
|
229
248
|
* 그러나 물품·재고처럼 **절대값인 축**은 우리가 못 듣는 사이 떠났을 수 있다 — 그래서 값과 함께
|
|
230
|
-
* `heardAt`(그
|
|
249
|
+
* `heardAt`(그 계산의 시계)을 낸다. 사용자가 신선도를 스스로 판정할 수 있어야 하고, 화면은 그것을
|
|
231
250
|
* 값 옆에 적는다. 시각 없이 수만 내면 낡은 값을 지금으로 읽는다.
|
|
232
251
|
*
|
|
233
|
-
*
|
|
234
|
-
* 한 줄이다). 그리고 한 요청에 **한 번만**
|
|
252
|
+
* 계산하는 조건을 좁게 둔다: **관측 축이 비어 있고, 저널에 그 축의 사실이 있을 때만**(그 판정은 색인
|
|
253
|
+
* 한 줄이다). 그리고 한 요청에 **한 번만** 계산한다 — 위 `stateOnce` 와 같은 규율이다.
|
|
235
254
|
*/
|
|
236
255
|
let journalPromise: Promise<{ snap: any; heardAt: string | null } | null> | null = null
|
|
237
256
|
const journalOnce = () =>
|
|
238
257
|
(journalPromise ??= (async () => {
|
|
239
258
|
try {
|
|
240
|
-
const tip = await getRepository(TwinEvent).findOne({
|
|
241
|
-
where: { domain: { id: domainId } as any, instanceId } as any,
|
|
242
|
-
order: { revision: 'DESC' } as any
|
|
243
|
-
})
|
|
244
|
-
if (!tip?.revision) return null
|
|
245
259
|
/*
|
|
246
|
-
*
|
|
260
|
+
* 관계로 묻지 않는다 — 두 단계 질의가 되어 안쪽에 상한이 없고, 그 트윈의 저널을 전부 만든 뒤
|
|
261
|
+
* 한 줄을 고른다(2026-08-28 · 같은 모양이 현장 성과 화면에서 7.2초였다). 색인의 끝만 읽는다.
|
|
262
|
+
*/
|
|
263
|
+
const [tipRow, latestRow] = await Promise.all([
|
|
264
|
+
getRepository(TwinEvent)
|
|
265
|
+
.createQueryBuilder('e')
|
|
266
|
+
.select('MAX(e.revision)', 'max')
|
|
267
|
+
.where('e.domain = :domainId', { domainId })
|
|
268
|
+
.andWhere('e.instanceId = :instanceId', { instanceId })
|
|
269
|
+
.getRawOne<{ max: unknown }>(),
|
|
270
|
+
/*
|
|
271
|
+
* 「저널 끝의 사건 시각」은 **가장 늦은 사건 시각**이다 — 리비전이 가장 큰 행의 시각이
|
|
272
|
+
* 아니다. 그 둘은 어긋난다(연결된 시스템을 되읽는 트윈은 원본의 시각을 싣고 도착 순서로
|
|
273
|
+
* 리비전을 받는다). 색인의 끝을 집으므로 질의 둘이어도 각각 한 줄이다.
|
|
274
|
+
*/
|
|
275
|
+
getRepository(TwinEvent)
|
|
276
|
+
.createQueryBuilder('e')
|
|
277
|
+
.select('MAX(e.eventTime)', 'at')
|
|
278
|
+
.where('e.domain = :domainId', { domainId })
|
|
279
|
+
.andWhere('e.instanceId = :instanceId', { instanceId })
|
|
280
|
+
.getRawOne<{ at: unknown }>()
|
|
281
|
+
])
|
|
282
|
+
const tip = { revision: Number(tipRow?.max) || 0, eventTime: hydratedDate(getRepository(TwinEvent), 'eventTime', latestRow?.at) }
|
|
283
|
+
if (!tip.revision) return null
|
|
284
|
+
/*
|
|
285
|
+
* **한 번 계산한 것을 다시 계산하지 않는다.** 계산은 저널 전체를 훑는 일이다(리비전 12만에서 2~3초).
|
|
247
286
|
* 이 화면은 사람이 여러 번 들어오는 자리이므로 그대로 두면 매번 그 값을 낸다 — 그리고 저널이
|
|
248
287
|
* 수백만 행이 되면 그 비용이 화면을 못 쓰게 만든다.
|
|
249
288
|
*
|
|
@@ -255,14 +294,14 @@ export class TwinModelQuery {
|
|
|
255
294
|
if (hit) return hit
|
|
256
295
|
const snap = unwrapState(await TwinEngine.recover(domainId, instanceId, tip.revision))
|
|
257
296
|
if (!snap) return null
|
|
258
|
-
/* 그
|
|
259
|
-
const out = { snap, heardAt: (snap as any).nowTime ?? tip.eventTime ?? null }
|
|
297
|
+
/* 그 계산의 시계가 「언제 들은 값인가」다. 없으면 저널 끝의 사건 시각으로 답한다. */
|
|
298
|
+
const out = { snap, heardAt: (snap as any).nowTime ?? tip.eventTime?.toISOString() ?? null }
|
|
260
299
|
RECALL_CACHE.set(key, out)
|
|
261
300
|
/* 오래된 열쇠부터 버린다 — 트윈 몇 개를 번갈아 봐도 메모리가 자라지 않게. */
|
|
262
301
|
while (RECALL_CACHE.size > RECALL_CACHE_MAX) RECALL_CACHE.delete(RECALL_CACHE.keys().next().value as string)
|
|
263
302
|
return out
|
|
264
303
|
} catch {
|
|
265
|
-
/*
|
|
304
|
+
/* 계산하지 못했으면 아무 말도 만들지 않는다 — 라이브가 말한 것을 그대로 둔다. */
|
|
266
305
|
return null
|
|
267
306
|
}
|
|
268
307
|
})())
|
|
@@ -314,7 +353,7 @@ export class TwinModelQuery {
|
|
|
314
353
|
*
|
|
315
354
|
* 행으로 옮긴 축이라도 그 트윈이 투영을 거치지 않았으면 행은 0이다(인제스트 없이
|
|
316
355
|
* warm-start 된 트윈이 그렇다). 행만 보고 답하면 문서에 자리 6개가 있는데 화면은
|
|
317
|
-
* **0 이라고 말한다** —
|
|
356
|
+
* **0 이라고 말한다** — 들어온 것이 없는 거짓이고, 이 프로젝트가 내내 잡아 온 바로 그 부류다.
|
|
318
357
|
*
|
|
319
358
|
* 그래서 어느 하나로 메우지 않는다. 행 수를 답하되 문서 수를 함께 내고, 다르면
|
|
320
359
|
* `stale` 로 표시한다 — 그 차이 자체가 "이 트윈은 투영 이후 인제스트되지 않았다" 는 정보다.
|
|
@@ -348,7 +387,7 @@ export class TwinModelQuery {
|
|
|
348
387
|
/*
|
|
349
388
|
* 관측 중인지는 **런타임이 있는지**로 본다 — 캐시된 스냅샷은 관측이 아니다.
|
|
350
389
|
*
|
|
351
|
-
* 동작하지 않으면 저널을
|
|
390
|
+
* 동작하지 않으면 저널을 계산해 답한다(라이브=권위 · 히스토리=파생). 카드는 그 차이를 `basis` 로
|
|
352
391
|
* 말한다: 파생을 권위처럼 내면 다시 계산한 수를 "지금 이만큼" 으로 읽게 된다.
|
|
353
392
|
*/
|
|
354
393
|
const { snap, folded, observing } = await stateOnce()
|
|
@@ -359,7 +398,7 @@ export class TwinModelQuery {
|
|
|
359
398
|
let fromJournal = false
|
|
360
399
|
/*
|
|
361
400
|
* 라이브가 잊은 축은 **저널에서 되세운다**(§`journalOnce`). 그 축의 사실이 저널에 있을 때만
|
|
362
|
-
*
|
|
401
|
+
* 계산하고, 값과 함께 「언제 들은 값인가」를 낸다 — 시각 없이 수만 내면 낡은 값을 지금으로 읽는다.
|
|
363
402
|
*/
|
|
364
403
|
if (!folded && Array.isArray(bucket) && bucket.length === 0) {
|
|
365
404
|
const seen = await axisSeenInJournal(getRepository(TwinEvent), { domain: { id: domainId }, instanceId }, a.axis).catch(() => null)
|
|
@@ -451,7 +490,7 @@ export class TwinModelQuery {
|
|
|
451
490
|
/*
|
|
452
491
|
* **원본이 모르는데 실행 중인 것** — 커널은 마스터에 없던 자원도 이벤트로 알게 되고
|
|
453
492
|
* `origin: 'observed'` 를 단다(`observed-reducer`). 그것을 표에 밀어 넣지 않는다 — 그러면
|
|
454
|
-
* 틱 루프에서 DB 로 쓰는 **두 번째 쓰기 경로**가 생기고, 실패하면
|
|
493
|
+
* 틱 루프에서 DB 로 쓰는 **두 번째 쓰기 경로**가 생기고, 실패하면 알리지 않고 어긋난다
|
|
455
494
|
* (쓰기 경로는 인제스트 하나, ADR-0032-D).
|
|
456
495
|
*
|
|
457
496
|
* 대신 **여기서 비교해 낸다.** 이 차이는 숨겨야 할 불일치가 아니라 인스펙션이 답해야 할
|
|
@@ -523,11 +562,61 @@ export class TwinModelQuery {
|
|
|
523
562
|
recipeLabel: material?.label ?? material?.name ?? null
|
|
524
563
|
}
|
|
525
564
|
})(),
|
|
565
|
+
/*
|
|
566
|
+
* ── **식별자가 무엇에 근거하나** (2026-08-24) ──────────────────────────────
|
|
567
|
+
* 요약이 `production.companyPrefix` 를 그대로 보이는데 **그것이 발급받은 번호인지 말할 근거가
|
|
568
|
+
* 없었다.** 그래서 사용자가 긴 URI 를 보고 「GS1 을 강제하는 것인가」를 물었다 — 강제는 **이미
|
|
569
|
+
* 없고**(GS1 프리픽스가 없는 현장은 자기 도메인 아래에서 식별자를 만든다, CBV 2.0 §8.3.4)
|
|
570
|
+
* 없다는 사실이 화면에 없었던 것이 문제였다.
|
|
571
|
+
*
|
|
572
|
+
* ★ **이 축만은 무표를 쓰지 않는다.** GS1 형태의 식별자를 본 사람은 그것이 진짜 발급받은
|
|
573
|
+
* 번호라고 가정하므로, 표식이 없으면 그 가정이 곧 거짓 안심이 된다. 계약도 그렇게 적고 있다 —
|
|
574
|
+
* 「없으면 소비처는 판정하지 않는다. 기본값으로 `issued` 를 가정하면 그 화면이 곧 거짓이 된다.」
|
|
575
|
+
* 그래서 화면이 네 갈래 모두에 낱말을 붙일 수 있게 **그대로** 낸다
|
|
576
|
+
* (`claimed-issued`·`declared-namespace`·`gs1-shaped`·`kernel-constant`).
|
|
577
|
+
*
|
|
578
|
+
* 커널이 내는 것을 그대로 넘긴다 — 여기서 다시 판정하면 스냅샷과 요약이 다른 말을 한다.
|
|
579
|
+
* 트윈이 돌지 않거나 커널이 말하지 않으면 `null` 이고, 그때 화면은 **재지 않는다.**
|
|
580
|
+
*/
|
|
581
|
+
identityGrounding: (await stateOnce()).snap?.identityGrounding ?? null,
|
|
526
582
|
description: inst.description ?? null,
|
|
527
583
|
kind: inst.kind,
|
|
528
584
|
restartPolicy: inst.restartPolicy,
|
|
529
585
|
source: ref
|
|
530
|
-
? {
|
|
586
|
+
? {
|
|
587
|
+
referenceId: ref.source,
|
|
588
|
+
adapterType: ref.adapterType,
|
|
589
|
+
status: ref.status,
|
|
590
|
+
lastSyncedAt: ref.lastSyncedAt,
|
|
591
|
+
/*
|
|
592
|
+
* ── **원본의 어느 흐름이 어디까지 살아 있나** (2026-08-24) ──────────────
|
|
593
|
+
* 사용자가 「지금 데이터가 들어오는 것이 사실이냐」고 물었고, 화면이 답할 것이 「붙어 있다」
|
|
594
|
+
* 뿐이었다. 그런데 한 원본 안에서 흐름마다 신선도가 **크게 다르다**(실측: 재고 08-23 ·
|
|
595
|
+
* 계측 08-12 · 작업지시 05-10 · 공정·로트 04-15). 「붙어 있다」 하나로 뭉치면 넉 달 멈춘
|
|
596
|
+
* 흐름이 살아 있는 것으로 읽힌다.
|
|
597
|
+
*
|
|
598
|
+
* **계약에는 자리가 있다** — `LiveFeedCursor.since` 가 「그 흐름에서 마지막으로 본 시각
|
|
599
|
+
* (원본의 시각)」이고 `onCursor` 가 그것을 `TwinReference.liveCursor` 에 적는다. 없던 것은
|
|
600
|
+
* **화면으로 가는 문**이고, 그 문이 여기다.
|
|
601
|
+
*
|
|
602
|
+
* ★ **다만 지금은 비어 있다** — 실 저장값을 확인했다:
|
|
603
|
+
* `{"streams":{},"firstAttachedAt":"2026-03-25T…"}`. 즉 어댑터가 흐름별 커서를 적지 않는다.
|
|
604
|
+
* 그래서 이 문을 열어도 당장은 빈 목록이 나오고, **그것이 정직한 답이다**: 어댑터가 말하지
|
|
605
|
+
* 않는 것을 여기서 지어내지 않는다. 채우는 것은 어댑터의 몫이고 그쪽에 알렸다.
|
|
606
|
+
*
|
|
607
|
+
* 빈 목록과 「흐름이 없다」를 화면이 구별할 수 있어야 한다 — 이 값이 비면 그것은
|
|
608
|
+
* 「어댑터가 아직 말하지 않는다」이고 「원본에 흐름이 없다」가 아니다.
|
|
609
|
+
*
|
|
610
|
+
* `seen`(중복 걸러낸 행 id)은 내지 않는다 — 읽기 내부 상태이고 수천 개가 될 수 있다.
|
|
611
|
+
* 흐름 열쇠는 **어댑터가 정한다**(계약이 이름을 닫지 않는다). 그래서 이름을 번역하지 않고
|
|
612
|
+
* 그대로 낸다 — 화면이 모르는 이름을 만나면 그 이름을 보이는 것이 맞다.
|
|
613
|
+
*/
|
|
614
|
+
streams: Object.entries((ref.liveCursor as any)?.streams ?? {})
|
|
615
|
+
.map(([stream, c]: [string, any]) => ({ stream, since: c?.since ?? null }))
|
|
616
|
+
/* 시각을 말하지 않는 흐름도 낸다 — 「모른다」와 「없다」는 다르고, 빼면 그 흐름이 사라진다. */
|
|
617
|
+
.sort((a, b) => String(b.since ?? '').localeCompare(String(a.since ?? ''))),
|
|
618
|
+
firstAttachedAt: (ref.liveCursor as any)?.firstAttachedAt ?? null
|
|
619
|
+
}
|
|
531
620
|
: null,
|
|
532
621
|
/*
|
|
533
622
|
* **다시 읽을 수 있는가** — 이 트윈이 자기 원본을 기억하고 있나(`TwinInstance.origin`).
|