@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
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 실제로 받는 판정은 그 앞에 한 층이 더 있다: **피드 사실을 어디서 얻는가**(`liveFeedStateOf` — 이 트윈이
|
|
6
6
|
* 미러인가 시뮬인가, 지금 도는가) 그리고 **장부를 어느 트윈의 것으로 찾는가**(도메인+인스턴스 키).
|
|
7
7
|
*
|
|
8
|
-
* 그 층이 틀리면 순수 시험은 전부 초록인 채로 화면만 거짓말한다. 실제로 두 가지가
|
|
8
|
+
* 그 층이 틀리면 순수 시험은 전부 초록인 채로 화면만 거짓말한다. 실제로 두 가지가 알리지 않고 틀릴 수 있다:
|
|
9
9
|
* ① 시뮬 트윈이 「연결 안 됨」으로 보인다 — 거짓 경보이고, 거짓 경보는 참 경보를 죽인다
|
|
10
10
|
* ② 남의 도메인 장부를 집는다 — 키를 도메인 없이 만들면 그렇게 된다
|
|
11
11
|
*
|
|
@@ -206,7 +206,7 @@ test('멈춘 미러는 「해당 없음」이다 — 고치려고 멈춰 둔 트
|
|
|
206
206
|
|
|
207
207
|
test('멈춘 트윈에 계속 들어오면 「버려지는 중」이라 말한다 — 실제로 겪은 거짓 안심', async () => {
|
|
208
208
|
/*
|
|
209
|
-
* 겪은 모습: 트윈이 멈췄는데 피드는 살아 레코드를 나르고, 커널은
|
|
209
|
+
* 겪은 모습: 트윈이 멈췄는데 피드는 살아 레코드를 나르고, 커널은 알리지 않고 버렸다. 화면은 「해당 없음 ·
|
|
210
210
|
* 150 통과 · 100%」였다. 여기서는 그 상황을 그대로 만들어 판정과 수치를 확인한다.
|
|
211
211
|
*/
|
|
212
212
|
/* 사람이 트윈을 멈춘다 — 그런데 피드는 남는다. 그 조합이 사실을 잃게 만든다. */
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* ── 왜 기계가 지켜야 하나 ───────────────────────────────────────────────────
|
|
5
5
|
* 피드를 여는 경로가 둘이다(커넥터 `openLiveFeed` · 시나리오 `twin-ingest` 태스크). 한쪽만 장부에
|
|
6
6
|
* 적으면 그 경로로 먹이는 트윈이 **「한 번도 받지 않은 것」**으로 보인다. 그 판정은 `unknown` 이고,
|
|
7
|
-
* `unknown` 은 화면에서 「정상」과 구별되지 않는다 — 즉
|
|
7
|
+
* `unknown` 은 화면에서 「정상」과 구별되지 않는다 — 즉 **오류 없이 틀린 답**이 된다.
|
|
8
8
|
*
|
|
9
9
|
* 세 번째 경로가 생길 때 사람이 이 사실을 기억할 것이라고 믿지 않는다. 인제스트를 부르는 자리를
|
|
10
10
|
* 소스에서 찾아 장부 기록이 함께 있는지 본다.
|
|
@@ -86,7 +86,7 @@ test('검사가 실제로 경로를 보고 있다 — 대상이 사라지면 초
|
|
|
86
86
|
|
|
87
87
|
test('유입 경로는 커널이 실제로 받은 수를 견준다 — 버려진 것을 통과로 세지 않게', () => {
|
|
88
88
|
/*
|
|
89
|
-
* 겪은 일: 트윈이 멈춘 뒤에도 피드는 레코드를 나르고 커널은
|
|
89
|
+
* 겪은 일: 트윈이 멈춘 뒤에도 피드는 레코드를 나르고 커널은 알리지 않고 버렸는데, 장부는 그것을 「통과」로
|
|
90
90
|
* 셌다. 화면은 멈춘 트윈 옆에 「150 통과 · 100%」라고 적었다 — 사실이 사라지는 동안 안심시킨 것이다.
|
|
91
91
|
*
|
|
92
92
|
* 고친 방법은 `ingestLive` 가 **넣은 수를 돌려주는** 것이다. 새 경로가 그 반환을 안 쓰고 0 을 넘기면
|
|
@@ -100,9 +100,11 @@ test('유입 경로는 커널이 실제로 받은 수를 견준다 — 버려진
|
|
|
100
100
|
missing,
|
|
101
101
|
[],
|
|
102
102
|
`커널이 받은 수를 견주지 않는 유입 경로 ${missing.length}곳:\n ${missing.join('\n ')}\n\n` +
|
|
103
|
-
`TwinEngine.ingestLive(...) 의 반환(넣은 수)을 받아 accepted.length 와의
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
`TwinEngine.ingestLive(...) 또는 ingestLiveResult(...) 의 반환(넣은 수)을 받아 accepted.length 와의 ` +
|
|
104
|
+
`차이를 undelivered 라는 이름으로 recordIngestResult(..., nowMs, undelivered) 에 넘긴다. ` +
|
|
105
|
+
`그러지 않으면 트윈이 멈춘 사이 버려진 레코드가 「통과」로 집계되어 화면이 100% 라고 말한다.\n\n` +
|
|
106
|
+
`이 검사는 **그 이름이 있는지**만 본다(소스를 글자로 본다). 이름을 적고 0 을 넘기면 통과한다 — ` +
|
|
107
|
+
`그 경우는 사람이 보는 수밖에 없다.`
|
|
106
108
|
)
|
|
107
109
|
})
|
|
108
110
|
|
|
@@ -53,7 +53,7 @@ test('오래 끊긴 피드는 건강했던 옛 창으로 초록을 내지 않는
|
|
|
53
53
|
assert.equal(
|
|
54
54
|
syncVerdictOf({ feedState: 'attached', recent: healthy, lastAtMs: 0, nowMs: STALE_AFTER_MS }),
|
|
55
55
|
'syncing',
|
|
56
|
-
'경계까지는 아직
|
|
56
|
+
'경계까지는 아직 들어온 것이 없다고 하지 않는다'
|
|
57
57
|
)
|
|
58
58
|
})
|
|
59
59
|
|
|
@@ -73,7 +73,7 @@ test('붙었지만 한 번도 받지 않은 것을 「유입 없음」이라 단
|
|
|
73
73
|
)
|
|
74
74
|
})
|
|
75
75
|
|
|
76
|
-
test('시뮬 트윈은 오래
|
|
76
|
+
test('시뮬 트윈은 오래 들어온 것이 없어도 「유입 없음」이 아니다', () => {
|
|
77
77
|
assert.equal(
|
|
78
78
|
syncVerdictOf({ feedState: 'not-applicable', recent: null, lastAtMs: -1, nowMs: STALE_AFTER_MS * 100 }),
|
|
79
79
|
'not-applicable'
|
|
@@ -82,7 +82,7 @@ test('시뮬 트윈은 오래 조용해도 「유입 없음」이 아니다', ()
|
|
|
82
82
|
|
|
83
83
|
test('들어오는데 트윈이 안 돌면 「버려지는 중」이다 — 「해당 없음」으로 안심시키지 않는다', () => {
|
|
84
84
|
/*
|
|
85
|
-
* 실제로 겪었다: 트윈이 멈춘 뒤에도 피드는 남아 레코드를 나르고, 커널은 그것을
|
|
85
|
+
* 실제로 겪었다: 트윈이 멈춘 뒤에도 피드는 남아 레코드를 나르고, 커널은 그것을 알리지 않고 버렸다.
|
|
86
86
|
* 그런데 화면은 「해당 없음 · 150 통과 · 100%」였다 — 사실이 사라지는 동안 화면이 안심시킨 것이다.
|
|
87
87
|
*
|
|
88
88
|
* 멈춘 트윈의 피드 사실은 `not-applicable` 이므로, 미전달을 **그보다 먼저** 봐야 이 상태가 드러난다.
|
|
@@ -141,7 +141,7 @@ test('채우는 중인 창을 「최근」이라 부르지 않는다', () => {
|
|
|
141
141
|
recordIngest(l, 100, [], 0)
|
|
142
142
|
recordIngest(l, 100, [], 1000)
|
|
143
143
|
|
|
144
|
-
/* 읽는 시각을 넘긴다 — 넘기지 않으면 벽시계와 견주어 「오래
|
|
144
|
+
/* 읽는 시각을 넘긴다 — 넘기지 않으면 벽시계와 견주어 「오래 들어온 것이 없음」으로 읽힌다(1970년 장부이므로). */
|
|
145
145
|
const v = ingestHealth(l, 'attached', undefined, 1000)
|
|
146
146
|
assert.equal(v.recent, null, '창이 아직 닫히지 않았다')
|
|
147
147
|
assert.equal(v.verdict, 'unknown', '그래서 판정도 모름이다')
|
|
@@ -167,7 +167,7 @@ test('추이 상한을 넘으면 오래된 창이 밀려나고, 밀려난 수를
|
|
|
167
167
|
|
|
168
168
|
const v = ingestHealth(l, 'attached')
|
|
169
169
|
assert.equal(v.trend.length, TREND_LIMIT)
|
|
170
|
-
assert.ok(v.trendDropped > 0, `밀려난 창을
|
|
170
|
+
assert.ok(v.trendDropped > 0, `밀려난 창을 알리지 않고 버리지 않는다 — 실제 ${v.trendDropped}`)
|
|
171
171
|
})
|
|
172
172
|
|
|
173
173
|
/* ── ③ 0 과 없음 ────────────────────────────────────────────────────────── */
|
|
@@ -213,7 +213,7 @@ test('사유 종류가 무한히 자라지 않는다 — 문구에 변수가 섞
|
|
|
213
213
|
assert.equal(v.reasonsCapped, true, '접혔다는 사실을 말한다')
|
|
214
214
|
assert.ok(
|
|
215
215
|
v.reasons.some(r => r.reason === OTHER_REASON),
|
|
216
|
-
'
|
|
216
|
+
'모은 것들이 갈 칸이 있다'
|
|
217
217
|
)
|
|
218
218
|
assert.equal(v.total.rejected, REASON_LIMIT * 5, '건수는 하나도 잃지 않는다')
|
|
219
219
|
})
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 거짓이 된다. 겹치면 메모리 쪽을 쓴다(같은 사실의 원본이다).
|
|
13
13
|
* ② 어디서부터 메모리인지 밝힌다(`liveFrom`) — 화면이 근거를 말할 수 있게. 재기동 직후에는 `null`
|
|
14
14
|
* 이고, 그때 보이는 것은 전부 표에서 온 것이다.
|
|
15
|
-
* ③
|
|
15
|
+
* ③ **알리지 않고 자르지 않는다** — 상한에서 밀려난 창 수와, 물은 기간이 잘렸다는 사실을 함께 낸다.
|
|
16
16
|
* ④ 남의 도메인·남의 트윈의 창을 집지 않는다.
|
|
17
17
|
* ⑤ 제시가 0 인 창의 통과율은 `null` 이다 — 빈 창은 완벽한 창이 아니다.
|
|
18
18
|
*/
|
|
@@ -157,7 +157,7 @@ test('기간 상한을 넘겨 물으면 자르고, 자른 사실을 낸다', asy
|
|
|
157
157
|
const r = await ask(TWIN, HISTORY_HOURS_MAX + 100)
|
|
158
158
|
|
|
159
159
|
assert.equal(r.hours, HISTORY_HOURS_MAX, '상한까지만 본다')
|
|
160
|
-
assert.equal(r.hoursClamped, HISTORY_HOURS_MAX + 100, '얼마를 물었는지 밝힌다 —
|
|
160
|
+
assert.equal(r.hoursClamped, HISTORY_HOURS_MAX + 100, '얼마를 물었는지 밝힌다 — 알리지 않고 자르지 않는다')
|
|
161
161
|
})
|
|
162
162
|
|
|
163
163
|
test('상한 안에서는 잘렸다고 말하지 않는다 — 없는 문제를 만들지 않는다', async () => {
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ★ 같은 사실을 두 번 받으면 한 번만 반영한다.
|
|
3
|
+
*
|
|
4
|
+
* ── 왜 이것이 핵심인가 (2026-08-26 · 사용자) ─────────────────────────────────
|
|
5
|
+
* 「웹훅 이벤트이건 폴링 데이터이건 아이뎀포턴트를 유지할 수 있도록 로직을 세우는 것」이 핵심이다.
|
|
6
|
+
* 그것이 서면 가져오는 방식이 무엇이든 트윈이 안전하고, 커넥터마다 각자 조심하지 않아도 된다.
|
|
7
|
+
*
|
|
8
|
+
* ── 무엇이 났나 ─────────────────────────────────────────────────────────────
|
|
9
|
+
* 지금까지 중복은 커넥터마다 각자 막았다. 태양광 커넥터가 10초마다 물으면서 10분마다 바뀌는 값을 매번
|
|
10
|
+
* 다시 보냈고, 한 주기에 76건이 되풀이됐다(커넥터를 고쳐 2건이 됐다). 다음 커넥터가 잊으면 또 난다.
|
|
11
|
+
* 밀어 주는 방식으로 받으면 더 자주 난다 — 재전송이 정상이다.
|
|
12
|
+
*
|
|
13
|
+
* ── 정체를 무엇으로 보나 ────────────────────────────────────────────────────
|
|
14
|
+
* 봉투의 `eventId` 로는 판단할 수 없다. 보낸 쪽이 매번 새로 만드는 순번이고, 호출마다 1부터 다시
|
|
15
|
+
* 시작한다. 그래서 사실 자체로 판단한다 — 대상 · 사건 종류 · 연결된 시스템이 준 시각.
|
|
16
|
+
*/
|
|
17
|
+
import { test } from 'node:test'
|
|
18
|
+
import assert from 'node:assert/strict'
|
|
19
|
+
import { factIdentity, FactDeduper } from '../server/engine/ingest-dedupe.js'
|
|
20
|
+
|
|
21
|
+
const at = '2026-08-26T00:00:00.000Z'
|
|
22
|
+
|
|
23
|
+
const energy = (equipmentId: string, kWh: number, when = at, eventId = 'x') => ({
|
|
24
|
+
eventId,
|
|
25
|
+
eventType: 'energy.generated',
|
|
26
|
+
eventTime: when,
|
|
27
|
+
tenantId: 't',
|
|
28
|
+
data: { equipmentId, kWh, at: when }
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const status = (moverId: string, s: string, when = at) => ({
|
|
32
|
+
eventId: `s-${moverId}`,
|
|
33
|
+
eventType: 'equipment.status',
|
|
34
|
+
eventTime: when,
|
|
35
|
+
tenantId: 't',
|
|
36
|
+
data: { moverId, kind: 'pv-array', status: s, at: when }
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('★ 같은 사실이 두 번 오면 한 번만 지난다 — 보낸 쪽이 되풀이 보내도 트윈이 틀리지 않는다', () => {
|
|
40
|
+
const d = new FactDeduper()
|
|
41
|
+
const { fresh, duplicates } = d.filter([energy('inv-1', 574.8), energy('inv-1', 574.8)])
|
|
42
|
+
assert.equal(fresh.length, 1)
|
|
43
|
+
assert.equal(duplicates, 1)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('★ 봉투의 순번이 달라도 같은 사실이면 한 번이다 — eventId 로는 판단할 수 없다', () => {
|
|
47
|
+
/* 같은 측정을 두 번 보내면 보낸 쪽이 새 순번을 붙인다. 그것으로 판단하면 중복을 못 잡는다. */
|
|
48
|
+
const d = new FactDeduper()
|
|
49
|
+
const { fresh, duplicates } = d.filter([energy('inv-1', 574.8, at, 't-energy-1'), energy('inv-1', 574.8, at, 't-energy-2')])
|
|
50
|
+
assert.equal(fresh.length, 1)
|
|
51
|
+
assert.equal(duplicates, 1)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('★ 다른 사실은 지난다 — 대상이 다르면 다른 사실이다', () => {
|
|
55
|
+
const d = new FactDeduper()
|
|
56
|
+
const { fresh, duplicates } = d.filter([energy('inv-1', 574.8), energy('inv-2', 577.2)])
|
|
57
|
+
assert.equal(fresh.length, 2)
|
|
58
|
+
assert.equal(duplicates, 0)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('★ 잰 시각이 나아가면 다른 사실이다 — 그것이 새 측정이다', () => {
|
|
62
|
+
const d = new FactDeduper()
|
|
63
|
+
const { fresh } = d.filter([energy('inv-1', 574.8, at), energy('inv-1', 580.1, '2026-08-26T00:10:00.000Z')])
|
|
64
|
+
assert.equal(fresh.length, 2)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('★ 사건 종류가 다르면 다른 사실이다 — 같은 설비의 같은 시각이어도', () => {
|
|
68
|
+
const d = new FactDeduper()
|
|
69
|
+
const { fresh } = d.filter([energy('inv-1', 574.8), status('inv-1', 'busy')])
|
|
70
|
+
assert.equal(fresh.length, 2)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('★ 정체를 알 수 없으면 거르지 않는다 — 모르는 것을 같다고 하면 다른 사실이 하나로 합쳐진다', () => {
|
|
74
|
+
const d = new FactDeduper()
|
|
75
|
+
const noTarget = { eventId: 'a', eventType: 'energy.demand.window', eventTime: at, tenantId: 't', data: { maxKW: 3 } }
|
|
76
|
+
const noTime = { eventId: 'b', eventType: 'equipment.status', tenantId: 't', data: { moverId: 'inv-9', status: 'idle' } }
|
|
77
|
+
assert.equal(factIdentity(noTarget), undefined)
|
|
78
|
+
assert.equal(factIdentity(noTime), undefined)
|
|
79
|
+
const { fresh, duplicates } = d.filter([noTarget, noTarget, noTime, noTime])
|
|
80
|
+
assert.equal(fresh.length, 4, '거르지 않고 통과시킨다')
|
|
81
|
+
assert.equal(duplicates, 0)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test('★ 늦게 도착한 옛 사실은 버리지 않는다 — 그것이 왔다는 것도 사실이다', () => {
|
|
85
|
+
/*
|
|
86
|
+
* 상태에 반영하지 않는 판단은 커널이 한다(시각 비교). 여기서 버리면 지난 기록에서 사라지고,
|
|
87
|
+
* 「늦게 왔다」는 사실 자체를 아무도 알 수 없게 된다.
|
|
88
|
+
*/
|
|
89
|
+
const d = new FactDeduper()
|
|
90
|
+
const { fresh, duplicates } = d.filter([
|
|
91
|
+
energy('inv-1', 580.1, '2026-08-26T00:10:00.000Z'),
|
|
92
|
+
energy('inv-1', 574.8, at)
|
|
93
|
+
])
|
|
94
|
+
assert.equal(fresh.length, 2)
|
|
95
|
+
assert.equal(duplicates, 0)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test('★ 창을 넘으면 잊는다 — 전부 기억할 수는 없다', () => {
|
|
99
|
+
const window = FactDeduper.WINDOW
|
|
100
|
+
try {
|
|
101
|
+
FactDeduper.WINDOW = 3
|
|
102
|
+
const d = new FactDeduper()
|
|
103
|
+
d.filter([energy('inv-1', 1, '2026-08-26T00:00:00.000Z')])
|
|
104
|
+
d.filter([
|
|
105
|
+
energy('inv-1', 2, '2026-08-26T00:01:00.000Z'),
|
|
106
|
+
energy('inv-1', 3, '2026-08-26T00:02:00.000Z'),
|
|
107
|
+
energy('inv-1', 4, '2026-08-26T00:03:00.000Z')
|
|
108
|
+
])
|
|
109
|
+
/* 첫 사실은 창 밖으로 밀려났으므로 다시 오면 통과한다 — 그 사실을 시험으로 못 박는다. */
|
|
110
|
+
const { duplicates } = d.filter([energy('inv-1', 1, '2026-08-26T00:00:00.000Z')])
|
|
111
|
+
assert.equal(duplicates, 0)
|
|
112
|
+
assert.ok(d.size() <= 3)
|
|
113
|
+
} finally {
|
|
114
|
+
FactDeduper.WINDOW = window
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test('버린 수를 함께 낸다 — 알리지 않고 버리지 않는다', () => {
|
|
119
|
+
const d = new FactDeduper()
|
|
120
|
+
const same = [energy('inv-1', 1), energy('inv-1', 1), energy('inv-1', 1), energy('inv-2', 2)]
|
|
121
|
+
const { fresh, duplicates } = d.filter(same)
|
|
122
|
+
assert.equal(fresh.length, 2)
|
|
123
|
+
assert.equal(duplicates, 2)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test('트윈마다 따로 센다 — 한 트윈의 되풀이가 다른 트윈을 막지 않는다', () => {
|
|
127
|
+
const a = new FactDeduper()
|
|
128
|
+
const b = new FactDeduper()
|
|
129
|
+
a.filter([energy('inv-1', 1)])
|
|
130
|
+
const { duplicates } = b.filter([energy('inv-1', 1)])
|
|
131
|
+
assert.equal(duplicates, 0)
|
|
132
|
+
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* 인제스트 공간 판정 — "이 현장에 더한다" 가
|
|
2
|
+
* 인제스트 공간 판정 — "이 현장에 더한다" 가 알리지 않고 빗나가지 않게.
|
|
3
3
|
*
|
|
4
4
|
* 엔진 본체는 DB·TypeORM 을 물고 있어 단위테스트로 부를 수 없다. 그래서 **결정만** 순수 모듈로
|
|
5
5
|
* 떼어 놓고 여기서 본다(엔진은 공간 존재 여부를 조회해 넘기는 일만 한다).
|
|
@@ -26,7 +26,7 @@ test('마스터가 이미 그 공간이면 합치기가 아니다 — 같은 공
|
|
|
26
26
|
assert.deepEqual(c, { spaceId: 'rosarito-site', joined: false })
|
|
27
27
|
})
|
|
28
28
|
|
|
29
|
-
test('없는 현장은 거절한다 —
|
|
29
|
+
test('없는 현장은 거절한다 — 알리지 않고 새 공간을 만들지 않는다', () => {
|
|
30
30
|
/* 여기서 새 공간을 만들어 주면 사용자는 합쳤다고 믿고 화면은 따로 논다. 그때는 이미 양쪽에
|
|
31
31
|
이벤트가 쌓여 있어 손으로 합치는 것이 훨씬 비싸다. */
|
|
32
32
|
assert.throws(
|
|
@@ -62,7 +62,7 @@ test('사이트 하나면 그 현장에 넣는다 — 렌즈를 더하는 정상
|
|
|
62
62
|
assert.equal(refuseImportSpace(1, 'rosarito-site'), undefined)
|
|
63
63
|
})
|
|
64
64
|
|
|
65
|
-
test('사이트 여럿을 한 현장에 넣는 것은 거절한다 — 서로 다른 현장이
|
|
65
|
+
test('사이트 여럿을 한 현장에 넣는 것은 거절한다 — 서로 다른 현장이 알리지 않고 합쳐진다', () => {
|
|
66
66
|
const r = refuseImportSpace(3, 'rosarito-site')
|
|
67
67
|
assert.ok(r, '거절해야 한다')
|
|
68
68
|
assert.equal(r.code, 'space-with-multiple-sites')
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* ★ 연결 적합성 하네스 — **절반만 돌린 스위트가 초록을 주장하지 못하게.**
|
|
3
3
|
*
|
|
4
4
|
* 지키는 것:
|
|
5
|
-
* ① 돌리지 않은 프로브는 **「모른다」**로
|
|
5
|
+
* ① 돌리지 않은 프로브는 **「모른다」**로 처리된다 → 판정이 그 단계를 달성으로 만들지 않는다.
|
|
6
6
|
* ② **실패한 프로브의 수를 0 으로 읽지 않는다.** 「0건이었다」와 「세지 못했다」는 다른 사실이다.
|
|
7
7
|
* ③ **쓰기 프로브는 동의 없이 계획에 들어오지 않는다** — 남의 운영 시스템에 명령을 넣는 일이다.
|
|
8
8
|
* ④ **게이팅 실패 뒤는 돌리지 않는다** — 빨간 칸을 늘리는 것은 원인을 알려 주지 않는다.
|
|
@@ -29,7 +29,7 @@ test('시퀀스는 읽기부터 시작하고 쓰기 프로브는 하나뿐이다
|
|
|
29
29
|
assert.deepEqual(PROBE_SEQUENCE.filter(p => p.writes).map(p => p.id), ['actuate-effect'])
|
|
30
30
|
})
|
|
31
31
|
|
|
32
|
-
test('아무것도 돌리지 않았으면 첫 칸부터 「모른다」다 — 실패로
|
|
32
|
+
test('아무것도 돌리지 않았으면 첫 칸부터 「모른다」다 — 실패로 계산하지 않는다', () => {
|
|
33
33
|
const p = assessStagePath(foldProbeResults([]))
|
|
34
34
|
assert.equal(p.reached, null)
|
|
35
35
|
assert.deepEqual(p.next?.stage, 'structure')
|
|
@@ -64,7 +64,7 @@ test('측정하지 못한 프로브는 수를 들지 않는다 — 0 을 지어
|
|
|
64
64
|
assert.deepEqual(assessStagePath(e).stages[1].unmeasured, ['removals'])
|
|
65
65
|
})
|
|
66
66
|
|
|
67
|
-
test('세어서 0 을 얻은 것은 사실이다 — 모른다로
|
|
67
|
+
test('세어서 0 을 얻은 것은 사실이다 — 모른다로 계산하지 않는다', () => {
|
|
68
68
|
const rs: ProbeResult[] = [...readOnlyAllPass().filter(r => r.id !== 'live-removal'),
|
|
69
69
|
{ id: 'live-removal', ran: true, passed: false, count: 0 }]
|
|
70
70
|
const e = foldProbeResults(rs)
|
|
@@ -39,7 +39,7 @@ test('시뮬 트윈은 라이브가 해당 없음이고, 그 사유를 말한다
|
|
|
39
39
|
const a = assessSuite({ ...live(), subject: 'sim', journalTotal: null, removalsJournaled: null })
|
|
40
40
|
assert.deepEqual(a.notApplicable.map(n => n.id), ['live-ingest', 'live-removal'])
|
|
41
41
|
assert.deepEqual(a.path.stages[1].blockedBy, ['subject-is-simulation'])
|
|
42
|
-
assert.deepEqual(a.path.stages[1].unmeasured, [], '해당 없음을 미측정으로
|
|
42
|
+
assert.deepEqual(a.path.stages[1].unmeasured, [], '해당 없음을 미측정으로 계산하지 않는다')
|
|
43
43
|
assert.equal(a.path.reached, 'structure')
|
|
44
44
|
assert.ok(!a.planned.includes('live-removal'), '해당 없는 프로브는 계획하지 않는다')
|
|
45
45
|
})
|
package/test/item-ref.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* 개체 → 품목 참조 — **정체성 모양이 하나가 아니고, 구분자는 우리가 정하지 않는다** (2026-08-23).
|
|
3
3
|
*
|
|
4
|
-
* ── 무엇이
|
|
4
|
+
* ── 무엇이 알리지 않고 비어 있었나 ───────────────────────────────────────────────
|
|
5
5
|
* 화면이 「이 개체는 선언된 자재가 아닙니다」라고 말했다. 그런데 그 품목은 원본의 선언에 이름까지 있었고
|
|
6
6
|
* (「냉동그린빈스」), 대응 표도 멀쩡했다. 원인은 참조를 꺼내는 규칙이 GS1 SGTIN 하나만 알았던 것이다.
|
|
7
7
|
*
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ★ 필터가 없을 때도 시각순으로 낸다 — 상한이 시간축의 임의 구간을 잘라내던 자리.
|
|
3
|
+
*
|
|
4
|
+
* ── 무엇이 났나 (2026-08-25 실측 · 승화푸드) ─────────────────────────────────
|
|
5
|
+
* 공정 타임라인은 필터 없이 상한만 걸고 지난 기록을 부른다.
|
|
6
|
+
*
|
|
7
|
+
* loadTwinEvents(id, { eventType: 'task.status', limit: TASK_CAP })
|
|
8
|
+
*
|
|
9
|
+
* 그 갈래의 정렬이 `revision DESC` 였다. `revision` 은 **적힌 순서**다. 연결된 시스템을 되읽는
|
|
10
|
+
* 트윈에서는 적힌 순서와 일어난 순서가 다르다 — 커넥터가 연결된 시스템의 갱신 순서를 걷기 때문에,
|
|
11
|
+
* 그쪽에서 나중에 손댄 행이 4월 사실을 담고 있으면 그 행이 큰 revision 을 받는다.
|
|
12
|
+
*
|
|
13
|
+
* 작업 축에 있는 가장 늦은 사실 2026-08-23 03:20
|
|
14
|
+
* revision 이 가장 큰 작업 사실 2026-04-15 10:49 ← 상한이 잡던 값
|
|
15
|
+
*
|
|
16
|
+
* 상한 안에 8월 두 달치가 들어오지 않았고, 화면은 4월 사실을 최근 것으로 그렸다. 사용자가 세 번
|
|
17
|
+
* 짚었다. 화면이 낸 「앞부분이 빠져 있습니다」도 사실과 달랐다 — 빠진 것은 앞부분이 아니라 시간축의
|
|
18
|
+
* 임의 구간이었다.
|
|
19
|
+
*
|
|
20
|
+
* ── 성능은 이유가 되지 않는다 ───────────────────────────────────────────────
|
|
21
|
+
* 오히려 시각 정렬이 인덱스(`ix_twin_event_1`)를 순서대로 타서 빠르다. 같은 파일의 실측 기록:
|
|
22
|
+
* `revision DESC` 200건 33.4초 · `eventTime DESC, revision DESC` 200건 0.007초.
|
|
23
|
+
*
|
|
24
|
+
* ── `revision` 순서를 없애지는 않는다 ───────────────────────────────────────────
|
|
25
|
+
* 부르는 쪽이 `revision` 범위로 물었으면 그 순서가 물음의 답이다. 거르는 축과 정렬 축을 맞추는
|
|
26
|
+
* 규칙과도 같다(`journal-sort-axis.test.ts`).
|
|
27
|
+
*/
|
|
28
|
+
import { test } from 'node:test'
|
|
29
|
+
import assert from 'node:assert/strict'
|
|
30
|
+
import { journalOrder } from '../server/service/twin-journal/journal-order.js'
|
|
31
|
+
|
|
32
|
+
test('★ 아무 필터도 없으면 시각순이다 — 타임라인이 부르는 방식이 이것이다', () => {
|
|
33
|
+
assert.deepEqual(journalOrder({}), { eventTime: 'DESC', revision: 'DESC' })
|
|
34
|
+
assert.deepEqual(journalOrder({ untilTime: null, fromRevision: null, toRevision: null }), {
|
|
35
|
+
eventTime: 'DESC',
|
|
36
|
+
revision: 'DESC'
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('★ 시각으로 물으면 시각순이다 — 거르는 축과 정렬 축이 맞아야 상한이 일한다', () => {
|
|
41
|
+
assert.deepEqual(journalOrder({ untilTime: '2026-08-25T00:00:00.000Z' }), { eventTime: 'DESC', revision: 'DESC' })
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('★ `revision` 범위로 물으면 그 순서로 낸다 — 그때는 그것이 물음의 답이다', () => {
|
|
45
|
+
assert.deepEqual(journalOrder({ fromRevision: 100, toRevision: 200 }), { revision: 'DESC' })
|
|
46
|
+
assert.deepEqual(journalOrder({ toRevision: 200 }), { revision: 'DESC' })
|
|
47
|
+
assert.deepEqual(journalOrder({ fromRevision: 100 }), { revision: 'DESC' })
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('시각과 `revision` 이 함께 오면 시각이 이긴다 — 거르는 쪽이 그렇게 정해져 있다', () => {
|
|
51
|
+
/*
|
|
52
|
+
* `where` 절이 `untilTime` 을 먼저 보고 `revision` 조건을 쓰지 않는다. 정렬만 `revision` 으로 두면 축이
|
|
53
|
+
* 어긋나고, 그 어긋남이 바로 위 실측의 33.4초다.
|
|
54
|
+
*/
|
|
55
|
+
assert.deepEqual(journalOrder({ untilTime: '2026-08-25T00:00:00.000Z', fromRevision: 1, toRevision: 9 }), {
|
|
56
|
+
eventTime: 'DESC',
|
|
57
|
+
revision: 'DESC'
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('동시각의 순서는 `revision` 이 정한다 — 둘째 축을 없애지 않는다', () => {
|
|
62
|
+
/* 같은 시각에 여러 사실이 들어오는 것은 흔하다(한 번의 폴링이 여러 행을 낸다). 그때 순서가 없으면
|
|
63
|
+
화면이 새로 고칠 때마다 줄 순서가 바뀐다. */
|
|
64
|
+
for (const q of [{}, { untilTime: '2026-08-25T00:00:00.000Z' }]) {
|
|
65
|
+
assert.equal(journalOrder(q).revision, 'DESC')
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('구간으로 물으면 시각순이다 — 화면이 그리는 구간을 그대로 묻는 길', () => {
|
|
70
|
+
assert.deepEqual(journalOrder({ fromTime: '2026-08-22T00:00:00.000Z' }), { eventTime: 'DESC', revision: 'DESC' })
|
|
71
|
+
assert.deepEqual(
|
|
72
|
+
journalOrder({ fromTime: '2026-08-22T00:00:00.000Z', untilTime: '2026-08-23T00:00:00.000Z' }),
|
|
73
|
+
{ eventTime: 'DESC', revision: 'DESC' }
|
|
74
|
+
)
|
|
75
|
+
/* 구간과 `revision` 범위가 함께 와도 시각이 이긴다 — 거르는 쪽이 그렇게 정해져 있다. */
|
|
76
|
+
assert.deepEqual(
|
|
77
|
+
journalOrder({ fromTime: '2026-08-22T00:00:00.000Z', fromRevision: 1, toRevision: 9 }),
|
|
78
|
+
{ eventTime: 'DESC', revision: 'DESC' }
|
|
79
|
+
)
|
|
80
|
+
})
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* ── 규율 ────────────────────────────────────────────────────────────────────
|
|
13
13
|
* 저널(`TwinEvent`)을 `find()` 로 읽는 자리는 둘 중 하나여야 한다.
|
|
14
14
|
* ① **상한이 있다**(`take:`) — 목록·표본처럼 화면이 쓰는 만큼만 읽는 경우.
|
|
15
|
-
* ② **폴드다** — 사실을 하나씩
|
|
15
|
+
* ② **폴드다** — 사실을 하나씩 계산하는 것이 목적이라 그 구간의 행이 필요한 경우. 이때는 바로 앞에
|
|
16
16
|
* `journal-fold:` 로 시작하는 주석을 두어 **왜 필요한지**, 그리고 상한이 없다면 **없어질 조건**을
|
|
17
17
|
* 함께 적는다.
|
|
18
18
|
*
|
|
@@ -101,7 +101,7 @@ test('저널을 통째로 읽는 자리는 폴드라고 밝히고 이유를 적
|
|
|
101
101
|
`이유 없이 저널 전체를 읽는 자리 ${bare.length}곳:\n ${bare.join('\n ')}\n\n` +
|
|
102
102
|
`둘 중 하나로 만든다.\n` +
|
|
103
103
|
` ① 화면이 쓰는 만큼만 읽는다면 \`take:\` 를 준다.\n` +
|
|
104
|
-
` ② 사실을
|
|
104
|
+
` ② 사실을 계산하는 것이 목적이라면 바로 앞에 \`journal-fold:\` 주석으로 왜 행이 필요한지,\n` +
|
|
105
105
|
` 상한이 없다면 그 예외가 없어질 조건까지 적는다.\n\n` +
|
|
106
106
|
`수 몇 개만 필요하다면 셋째 길이 맞다: 집계는 DB 가 한다(MIN/MAX/COUNT + createQueryBuilder).\n` +
|
|
107
107
|
`\`timeRange\` 가 min/max 두 수를 구하려고 275,882행·payload 131MB 를 4초마다 파싱했던 자리다.`
|
|
@@ -111,10 +111,20 @@ test('저널을 통째로 읽는 자리는 폴드라고 밝히고 이유를 적
|
|
|
111
111
|
test('검사가 실제로 저널 읽기를 보고 있다 — 대상이 사라지면 초록이 거짓이 된다', () => {
|
|
112
112
|
const sites = journalFindSites()
|
|
113
113
|
|
|
114
|
+
/*
|
|
115
|
+
* ── 문턱을 셋에서 둘로 내렸다 (2026-08-28) ─────────────────────────────────
|
|
116
|
+
* 자리 하나를 **질의 빌더로 옮겨서** 줄었다(계보 조회 — 관계로 물어 두 단계 질의가 되고 `take` 가
|
|
117
|
+
* 있어도 안쪽에 상한이 없던 자리다). 그 방향이 이 검사가 바라는 방향이므로 문턱을 따라 내린다.
|
|
118
|
+
*
|
|
119
|
+
* 이 단정의 뜻은 「자리가 많아야 한다」가 아니라 **「검사가 무언가를 보고 있어야 한다」**다. 남은 둘도
|
|
120
|
+
* 옮겨지면 이 시험이 빨개지는데, 그때가 바로 사람이 알아야 하는 순간이다 — 검사가 아무것도 보지 않는
|
|
121
|
+
* 채로 초록이면 그 초록이 거짓이다. 그때는 검사의 패턴을 질의 빌더 쪽으로 옮긴다.
|
|
122
|
+
*/
|
|
114
123
|
assert.ok(
|
|
115
|
-
sites.length >=
|
|
116
|
-
`저널 \`find()\` 를 ${sites.length}곳 찾았다 —
|
|
117
|
-
|
|
124
|
+
sites.length >= 2,
|
|
125
|
+
`저널 \`find()\` 를 ${sites.length}곳 찾았다 — 둘보다 적으면 이 검사가 보는 대상이 없어졌다는 뜻이다. ` +
|
|
126
|
+
`호출을 질의 빌더로 옮겼다면 이 검사의 패턴도 그쪽으로 옮긴다(리포지토리를 변수에 담아 부르는 ` +
|
|
127
|
+
`형태도 함께 본다).`
|
|
118
128
|
)
|
|
119
129
|
})
|
|
120
130
|
|
|
@@ -173,5 +183,5 @@ test('건수 상한이 값으로 선언돼 있고, 넘으면 전체를 비운다
|
|
|
173
183
|
assert.match(src, /take\(COUNT_CAP \+ 1\)/, '상한을 넘었는지 알려면 하나 더 읽어야 한다')
|
|
174
184
|
/* 상한을 넘으면 **전체를 비운다** — 상한값을 전체로 적으면 화면이 거짓을 말한다. */
|
|
175
185
|
assert.match(src, /capped \? undefined : counted\.length/)
|
|
176
|
-
assert.match(src, /capped/, '넘었다는 사실을 함께 낸다(
|
|
186
|
+
assert.match(src, /capped/, '넘었다는 사실을 함께 낸다(알리지 않고 자르지 않는다)')
|
|
177
187
|
})
|