@things-factory/headless-twin 10.0.15 → 10.0.18
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 +18 -82
- package/dist-server/engine/canonical-ingest.js +183 -18
- 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 +4 -4
- 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 +90 -10
- package/dist-server/engine/ingest-health.js +125 -16
- 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 +212 -14
- package/dist-server/engine/kpi-fold.js +374 -21
- 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 +281 -36
- 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 +15 -14
- 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 +3 -3
- package/dist-server/engine/model-vocabulary.js.map +1 -1
- package/dist-server/engine/oee-accumulator.d.ts +6 -5
- package/dist-server/engine/oee-accumulator.js +6 -5
- 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 +307 -54
- package/dist-server/engine/twin-engine.js +822 -196
- 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 +30 -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 +116 -9
- 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 +76 -13
- package/dist-server/service/reference/reference-live.js.map +1 -1
- package/dist-server/service/reference/reference-master.d.ts +25 -16
- package/dist-server/service/reference/reference-master.js +35 -13
- 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 +12 -12
- package/dist-server/service/reference/reference-resolver.js.map +1 -1
- package/dist-server/service/reference/template-registry.d.ts +1 -1
- package/dist-server/service/reference/template-registry.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-backfill/backfill-period-facts.d.ts +24 -0
- package/dist-server/service/twin-backfill/backfill-period-facts.js +96 -0
- package/dist-server/service/twin-backfill/backfill-period-facts.js.map +1 -0
- package/dist-server/service/twin-backfill/backfill-shape.d.ts +34 -0
- package/dist-server/service/twin-backfill/backfill-shape.js +75 -0
- package/dist-server/service/twin-backfill/backfill-shape.js.map +1 -0
- package/dist-server/service/twin-backfill/index.d.ts +2 -0
- package/dist-server/service/twin-backfill/index.js +6 -0
- package/dist-server/service/twin-backfill/index.js.map +1 -0
- package/dist-server/service/twin-backfill/twin-backfill-resolver.d.ts +10 -0
- package/dist-server/service/twin-backfill/twin-backfill-resolver.js +44 -0
- package/dist-server/service/twin-backfill/twin-backfill-resolver.js.map +1 -0
- package/dist-server/service/twin-control/twin-control-mutation.js +3 -3
- package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
- package/dist-server/service/twin-event/twin-event-keys.d.ts +1 -1
- package/dist-server/service/twin-event/twin-event-keys.js +2 -2
- 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/domain-catalog.js +2 -1
- package/dist-server/service/twin-lifecycle/domain-catalog.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 +34 -3
- 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 +12 -9
- package/dist-server/service/twin-model/isa95-coverage.js +33 -9
- 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 +2 -2
- 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 +7 -6
- package/dist-server/service/twin-model/project-structure.js.map +1 -1
- package/dist-server/service/twin-model/twin-equipment.d.ts +2 -2
- package/dist-server/service/twin-model/twin-equipment.js +3 -3
- 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 +15 -15
- 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 +51 -30
- package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-tree-query.js +23 -1
- 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 +37 -8
- 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 +4 -4
- 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 +7 -6
- package/server/engine/attention-digest.ts +9 -9
- package/server/engine/canonical-ingest.ts +202 -87
- package/server/engine/command-routing.ts +1 -1
- package/server/engine/declared-stimulus.ts +1 -1
- package/server/engine/energy-topology.ts +2 -2
- package/server/engine/ingest-dedupe.ts +149 -0
- package/server/engine/ingest-health.ts +171 -22
- package/server/engine/integration-probes.ts +7 -7
- package/server/engine/kpi-baseline.ts +4 -4
- package/server/engine/kpi-fold.ts +592 -41
- package/server/engine/kpi-query.ts +307 -48
- package/server/engine/live-feed-registry.ts +1 -1
- package/server/engine/load-meter.ts +1 -1
- package/server/engine/local-declarations.ts +15 -14
- 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 +2 -2
- package/server/engine/oee-accumulator.ts +8 -6
- 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 +906 -205
- 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 +73 -5
- package/server/service/index.ts +7 -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 +145 -10
- package/server/service/reference/reference-hook.ts +145 -0
- package/server/service/reference/reference-live.ts +87 -15
- package/server/service/reference/reference-master.ts +51 -20
- package/server/service/reference/reference-resolver.ts +11 -11
- package/server/service/reference/template-registry.ts +1 -1
- 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-backfill/backfill-period-facts.ts +168 -0
- package/server/service/twin-backfill/backfill-shape.ts +86 -0
- package/server/service/twin-backfill/index.ts +3 -0
- package/server/service/twin-backfill/twin-backfill-resolver.ts +35 -0
- package/server/service/twin-control/twin-control-mutation.ts +2 -2
- package/server/service/twin-event/twin-event-keys.ts +2 -2
- 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/domain-catalog.ts +2 -1
- package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +58 -4
- 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 +34 -3
- package/server/service/twin-model/iec61850-coverage.ts +1 -1
- package/server/service/twin-model/isa95-coverage.ts +43 -16
- package/server/service/twin-model/item-ref.ts +2 -2
- package/server/service/twin-model/name-index.ts +2 -2
- package/server/service/twin-model/project-structure.ts +7 -6
- package/server/service/twin-model/twin-equipment.ts +16 -4
- 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 +15 -15
- package/server/service/twin-model/twin-model-mutation.ts +4 -4
- package/server/service/twin-model/twin-model-query.ts +49 -28
- package/server/service/twin-model/twin-model-tree-query.ts +26 -2
- 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 +40 -8
- 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 +4 -4
- 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 +6 -5
- package/test/backfill-shape.test.ts +89 -0
- package/test/boot-resume.test.ts +20 -7
- package/test/broadcast-cost-baseline.test.ts +2 -1
- package/test/broadcast-period.test.ts +2 -2
- package/test/broadcast-queue-overflow.test.ts +244 -0
- package/test/canonical-ingest-vocabularies.test.ts +259 -8
- package/test/canonical-quantity-door.test.ts +2 -2
- package/test/capability-mapping.test.ts +1 -1
- package/test/checkpoint-refuses-empty.test.ts +2 -2
- package/test/connector-capability-declaration.test.ts +88 -0
- package/test/contract-layer-guard.test.ts +48 -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 +7 -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/equipment-identity-reaches.test.ts +57 -0
- 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/fact-scope-wiring.test.ts +96 -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/generated-not-counted.test.ts +99 -0
- package/test/ingest-bench.test.ts +2 -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/ingest-wiring-guard.test.ts +197 -0
- 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 +266 -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 +9 -6
- package/test/load-meter.test.ts +1 -1
- package/test/local-declarations.test.ts +13 -12
- package/test/log-stamp.test.ts +1 -1
- package/test/master-to-twin.test.ts +2 -2
- 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 +20 -5
- package/test/operational-vocabulary.test.ts +2 -2
- package/test/project-structure-db.test.ts +2 -2
- package/test/projection-reaches-screen.test.ts +7 -2
- package/test/projection-reads-declared.test.ts +109 -0
- package/test/property-effects.test.ts +10 -6
- 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/scale-twin-bench.test.ts +2 -1
- 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 +19 -21
- 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 +1 -1
- 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/vocabulary-guard.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 +2 -2
- package/test/yield-loop.test.ts +4 -4
- package/tsconfig.shared.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* 현장 선언 — **구조는 원천이, 수는 현장이.**
|
|
3
3
|
*
|
|
4
|
-
* 이 시험이 지키는 것: 사용자가 넣은 수가 ① 검사를 거치고(단위를 잘못 읽은 값이
|
|
4
|
+
* 이 시험이 지키는 것: 사용자가 넣은 수가 ① 검사를 거치고(단위를 잘못 읽은 값이 알리지 않고 들어가지 않는다)
|
|
5
5
|
* ② 원천에서 다시 그려도 살아남고 ③ 갈 곳이 없으면 그 사실이 남는다.
|
|
6
6
|
*/
|
|
7
7
|
import { test } from 'node:test'
|
|
@@ -122,7 +122,7 @@ test('같은 속성을 다시 넣으면 새 값이 이긴다 — 현장이 값
|
|
|
122
122
|
const second = mergeLocalDeclarations(first, [
|
|
123
123
|
{ target: 'location', id: 'incoming-1', properties: [{ id: 'tariff.demandChargePerKW', value: '24' }, { id: 'tariff.currency', value: 'USD' }] }
|
|
124
124
|
])
|
|
125
|
-
assert.equal(second.length, 1, '같은 대상은 한 줄로
|
|
125
|
+
assert.equal(second.length, 1, '같은 대상은 한 줄로 합쳐진다')
|
|
126
126
|
assert.deepEqual(second[0].properties.map(p => [p.id, p.value]), [
|
|
127
127
|
['tariff.demandChargePerKW', '24'],
|
|
128
128
|
['tariff.currency', 'USD']
|
|
@@ -154,7 +154,7 @@ test('현장 값이 원천 값을 덮는다 — 고쳐 넣었다는 것은 원
|
|
|
154
154
|
assert.equal(p.source, 'local')
|
|
155
155
|
})
|
|
156
156
|
|
|
157
|
-
test('대상이 사라졌으면 그 사실을 낸다 — 갈 곳 없는 선언을
|
|
157
|
+
test('대상이 사라졌으면 그 사실을 낸다 — 갈 곳 없는 선언을 알리지 않고 버리지 않는다', () => {
|
|
158
158
|
const { missingTargets } = applyLocalDeclarations(emsModel(), [
|
|
159
159
|
{ target: 'equipment', id: 'ess-gone', properties: [{ id: 'storage.capacityKWh', value: '500' }] }
|
|
160
160
|
])
|
|
@@ -191,7 +191,7 @@ test('경고 문장이 무엇을 해야 하는지 말한다 — 우리가 시간
|
|
|
191
191
|
* 필드 이름을 틀린 호출(예: id 대신 targetId)이 「받아준 선언이 없다」는 답만 받고 rejected 는 비어
|
|
192
192
|
* 있었다. 사용자는 값을 넣었다고 믿는데 화면은 아무것도 바뀌지 않고, 고칠 단서도 없다.
|
|
193
193
|
*/
|
|
194
|
-
test('대상 id 가 없으면 이유를 말한다 —
|
|
194
|
+
test('대상 id 가 없으면 이유를 말한다 — 알리지 않고 건너뛰지 않는다', () => {
|
|
195
195
|
const { accepted, rejected } = validateLocalDeclarations('ems', [
|
|
196
196
|
{ properties: [{ id: 'contract.kW', value: '3500' }] } as any
|
|
197
197
|
])
|
|
@@ -289,7 +289,7 @@ test('기억이 없는 옛 선언은 손대지 않고 그렇게 말한다 —
|
|
|
289
289
|
assert.equal((back.model.locations[0] as any).properties[0].value, '600', '값은 그대로다 — 다음 재읽기가 바로잡는다')
|
|
290
290
|
})
|
|
291
291
|
|
|
292
|
-
test('겹에 없는 것을 뺐다고 하지 않는다 —
|
|
292
|
+
test('겹에 없는 것을 뺐다고 하지 않는다 — 성공이라 답하면서 아무 일도 하지 않는 것이 가장 비싸다', () => {
|
|
293
293
|
const { revoked, notFound } = revokeLocalDeclarations(
|
|
294
294
|
[{ target: 'location', id: 'incoming-1', properties: [{ id: 'contract.kW', value: '600', replaced: '3500' }] }],
|
|
295
295
|
[
|
|
@@ -309,7 +309,7 @@ test('겹에 없는 것을 뺐다고 하지 않는다 — 조용한 성공이
|
|
|
309
309
|
* 이고(SAP 는 `P3`, Oracle 은 `W01` 로 같은 현장을 부른다), 여기 담기는 것은 그 시스템이 **모르는**
|
|
310
310
|
* 값이다 — 이름이 내용과 정반대였다. 축은 「누가 말했나」이고 답은 `reference` ↔ `local` 둘이다.
|
|
311
311
|
*
|
|
312
|
-
* 낱말은
|
|
312
|
+
* 낱말은 알리지 않고 되돌아간다(복사·자동완성·옛 문서). 그래서 검사로 못 박는다.
|
|
313
313
|
*/
|
|
314
314
|
test('이 겹의 어휘는 local 이다 — site·facility 로 되돌아가지 않는다', () => {
|
|
315
315
|
const src = readFileSync(fileURLToPath(new URL('../server/engine/local-declarations.ts', import.meta.url)), 'utf-8')
|
|
@@ -327,7 +327,8 @@ test('이 겹의 어휘는 local 이다 — site·facility 로 되돌아가지
|
|
|
327
327
|
test('축의 두 낱말이 계약에 선언돼 있다 — 얹혀 다니는 필드가 아니다', async () => {
|
|
328
328
|
/* 예전에는 source 표식이 커널 계약에 없는 필드로 얹혀 다녔다 — 소비처가 any 로 읽고, 상태에 정말
|
|
329
329
|
실리는지 아무도 검사할 수 없었다. */
|
|
330
|
-
|
|
330
|
+
/* 속성 어휘는 계약에 있다 — 커널은 엔진만 내보낸다(2026-08-30 층 분리). */
|
|
331
|
+
const contract: any = await import('@operato/ops-contract')
|
|
331
332
|
const { model } = applyLocalDeclarations(
|
|
332
333
|
{ locations: [{ id: 'incoming-1', type: 'incoming' }] },
|
|
333
334
|
[{ target: 'location', id: 'incoming-1', properties: [{ id: 'contract.kW', value: '3500' }] }]
|
|
@@ -335,7 +336,7 @@ test('축의 두 낱말이 계약에 선언돼 있다 — 얹혀 다니는 필
|
|
|
335
336
|
const p = (model.locations[0] as any).properties[0]
|
|
336
337
|
/* 계약이 정한 두 값 외에는 쓰지 않는다. */
|
|
337
338
|
assert.ok(['reference', 'local'].includes(p.source), '표식은 reference|local 뿐이다')
|
|
338
|
-
assert.ok(
|
|
339
|
+
assert.ok(contract.EMS_PROPERTY, '계약을 실제로 읽고 있다')
|
|
339
340
|
})
|
|
340
341
|
|
|
341
342
|
test('옛 표식이 구워진 값을 원천 값으로 오인하지 않는다 — 표식의 종류를 따지지 않는다', () => {
|
|
@@ -430,7 +431,7 @@ test('구조 투영이 중복 id 를 이름으로 말한다 — 제약 위반
|
|
|
430
431
|
const src = readFileSync(fileURLToPath(new URL('../server/service/twin-model/project-structure.ts', import.meta.url)), 'utf-8')
|
|
431
432
|
assert.match(src, /has two \$\{what\} with id/, '무엇이 겹쳤는지 이름으로 말한다')
|
|
432
433
|
assert.match(src, /ids must be unique within a twin/, '규칙도 함께 말한다')
|
|
433
|
-
/*
|
|
434
|
+
/* 알리지 않고 하나만 남기지 않는다 — 어느 쪽이 맞는지 우리는 모른다. */
|
|
434
435
|
assert.equal(/dedupe|distinctBy/.test(src), false, '중복을 소리 없이 지우지 않는다')
|
|
435
436
|
})
|
|
436
437
|
|
|
@@ -508,7 +509,7 @@ test('지게차에 계약전력을 넣던 구멍이 막혔다 — 읽는 코드
|
|
|
508
509
|
assert.equal(rejected[0]?.reason, 'wrong-target')
|
|
509
510
|
})
|
|
510
511
|
|
|
511
|
-
test('확인할 수 있으면 오타 공정 이름을 거절한다 —
|
|
512
|
+
test('확인할 수 있으면 오타 공정 이름을 거절한다 — 알리지 않고 아무 일도 안 하지 않게', () => {
|
|
512
513
|
const decl = [{ target: 'operation' as const, id: 'put-away', properties: [{ id: 'operation.duration', value: '4', uom: 'MIN' }] }]
|
|
513
514
|
const checked = validateLocalDeclarations('wms', decl, { knownOperations: ['putaway', 'pick'] })
|
|
514
515
|
assert.deepEqual(checked.accepted, [])
|
|
@@ -534,7 +535,7 @@ test('철회하면 표에서 빠진다 — 원천 명세나 커널 상수가 다
|
|
|
534
535
|
assert.deepEqual(back.restored.length, 1)
|
|
535
536
|
})
|
|
536
537
|
|
|
537
|
-
test('없는 공정을 철회했다고 말하지 않는다 —
|
|
538
|
+
test('없는 공정을 철회했다고 말하지 않는다 — 성공이라 답하면서 아무 일도 하지 않는 것이 가장 비싸다', () => {
|
|
538
539
|
const back = restoreRevoked({ locations: [], equipment: [], localDurations: { pick: 'PT60S' } }, [
|
|
539
540
|
{ target: 'operation', id: 'putaway', propertyId: 'operation.duration', replaced: null }
|
|
540
541
|
])
|
|
@@ -642,7 +643,7 @@ test('모수 철회는 모수 표에서 뺀다 — 시간 선언은 그대로
|
|
|
642
643
|
assert.deepEqual((back.model as any).localDurations, { weld: 'PT180S' }, '시간은 그대로')
|
|
643
644
|
})
|
|
644
645
|
|
|
645
|
-
test('없는 모수를 철회했다고 말하지 않는다 —
|
|
646
|
+
test('없는 모수를 철회했다고 말하지 않는다 — 성공이라 답하면서 아무 일도 하지 않는 것이 가장 비싸다', () => {
|
|
646
647
|
const back = restoreRevoked({ locations: [], equipment: [], localParams: { weld: { yield: '0.9' } } }, [
|
|
647
648
|
{ target: 'operation', id: 'weld', propertyId: 'operation.setupDuration', replaced: null }
|
|
648
649
|
])
|
package/test/log-stamp.test.ts
CHANGED
|
@@ -34,7 +34,7 @@ test('오류는 스택까지 남는다 — 「어디서 났나」를 잃지 않
|
|
|
34
34
|
assert.ok(out.includes('log-stamp.test'), '스택이 있어야 어느 자리에서 났는지 알 수 있다')
|
|
35
35
|
})
|
|
36
36
|
|
|
37
|
-
test('값이 없어도 있었다는 사실은 남긴다 —
|
|
37
|
+
test('값이 없어도 있었다는 사실은 남긴다 — 알리지 않고 지우지 않는다', () => {
|
|
38
38
|
assert.equal(line(['a', undefined, null, 0]), 'a undefined null 0')
|
|
39
39
|
const circular: any = { name: 'ㄱ' }
|
|
40
40
|
circular.self = circular
|
|
@@ -7,7 +7,7 @@ import { test } from 'node:test'
|
|
|
7
7
|
import assert from 'node:assert/strict'
|
|
8
8
|
|
|
9
9
|
import { masterToTwin } from '../server/service/reference/reference-master.ts'
|
|
10
|
-
import { DOMAIN_CATALOG } from '@operato/
|
|
10
|
+
import { DOMAIN_CATALOG } from '@operato/ops-contract'
|
|
11
11
|
|
|
12
12
|
/** WMS 단위적재 템플릿이 내는 것과 동형인 현실적 master(coordinate-free). */
|
|
13
13
|
const wmsMaster = () => ({
|
|
@@ -30,7 +30,7 @@ test('locations role → 노드/영역 분기, equipment → 무버', () => {
|
|
|
30
30
|
assert.deepEqual(model.locations.map((n: any) => n.id).sort(), ['dock-recv', 'dock-ship', 'rack-1', 'rack-2'], '노드=role location')
|
|
31
31
|
assert.equal(spaceContent.areas.length, 1, '영역=role area')
|
|
32
32
|
assert.equal(spaceContent.areas[0].id, 'a-dc')
|
|
33
|
-
assert.deepEqual(model.equipment, [{ id: 'fk-1', kind: 'forklift', homeLocation: 'dock-recv', mtbfMs: undefined, mttrMs: undefined
|
|
33
|
+
assert.deepEqual(model.equipment, [{ id: 'fk-1', kind: 'forklift', homeLocation: 'dock-recv', mtbfMs: undefined, mttrMs: undefined }], '마스터 equipment→보드 equipment(homeLocationId→homeLocation, identity 통과)')
|
|
34
34
|
assert.equal(model.locations.find((n: any) => n.id === 'rack-1').capacity, 60, 'capacity 전달')
|
|
35
35
|
assert.equal(model.locations.find((n: any) => n.id === 'dock-recv').parentId, undefined, 'parentId 없으면 undefined')
|
|
36
36
|
})
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* ② 표본이 모자라면 **배우지 않는다** — 비율은 드문 사건을 재는 값이라 작은 표본이 잡음을 만든다.
|
|
12
12
|
* ③ 0 도 사실이다 — 전량 불량이면 0 을 배운다(「값이 없다」와 다르다).
|
|
13
13
|
* ④ 모르는 종류에는 `undefined` — 그러면 커널이 선언값·상수로 내려간다.
|
|
14
|
-
* ⑤ 배우지 못한 것을 **밝힌다** —
|
|
14
|
+
* ⑤ 배우지 못한 것을 **밝힌다** — 알리지 않고 일부만 반영하지 않는다.
|
|
15
15
|
*/
|
|
16
16
|
import { test } from 'node:test'
|
|
17
17
|
import assert from 'node:assert/strict'
|
|
@@ -84,7 +84,7 @@ test('사슬 넷이 이어져 있다 — 판정→축→집계→비율', () =>
|
|
|
84
84
|
assert.match(hostSrc('engine/kpi-fold.ts'), /facets\.outcome/, 'KPI 가 판정 축을 읽는다')
|
|
85
85
|
assert.match(hostSrc('engine/kpi-fold.ts'), /quality\?: \{ good: number; scrap: number \}/, '종류별로 센다')
|
|
86
86
|
const engine = hostSrc('engine/twin-engine.ts')
|
|
87
|
-
assert.match(engine, /buildYieldEstimator\(kpi\?\.groups\?\.items/, '같은 폴드에서 배운다(두 번
|
|
87
|
+
assert.match(engine, /buildYieldEstimator\(kpi\?\.groups\?\.items/, '같은 폴드에서 배운다(두 번 계산하지 않는다)')
|
|
88
88
|
assert.match(engine, /kernel\.yieldEstimator = yields\.estimator/, '커널 시임에 넣는다')
|
|
89
89
|
assert.match(engine, /yieldOf missing/, '옛 커널이면 그 사실을 말한다')
|
|
90
90
|
})
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ★ 나눠 묻고 합치는 규율 — 갈래마다 상한까지 받아야 합친 결과가 정확하다.
|
|
3
|
+
*
|
|
4
|
+
* ── 왜 이 시험이 있나 (2026-08-28) ──────────────────────────────────────────
|
|
5
|
+
* 「종류 IN + 시각순」은 색인을 못 쓴다. 그래서 종류를 하나씩 정확일치로 묻고 합치는데, 그때 함정이
|
|
6
|
+
* 하나 있다 — UX 레인이 구현 전에 짚어 주었다.
|
|
7
|
+
*
|
|
8
|
+
* 「갈래마다 상한을 걸면 합친 결과가 시각순으로 온전하지 않다.
|
|
9
|
+
* 종류 A 의 마지막이 종류 B 의 첫째보다 이를 수 있다.」
|
|
10
|
+
*
|
|
11
|
+
* 그 지적은 **갈래마다 상한보다 적게 받을 때** 참이다. 상한까지 받으면 성질이 선다: 어떤 갈래에서 빠진
|
|
12
|
+
* 행은 그 갈래의 상한 번째보다 뒤이고, 우리는 전체에서 상한 개만 취하므로 그 행이 이 페이지에 들어올 수
|
|
13
|
+
* 없다. 이 시험이 그 성질을 못박는다.
|
|
14
|
+
*/
|
|
15
|
+
import { test } from 'node:test'
|
|
16
|
+
import assert from 'node:assert/strict'
|
|
17
|
+
|
|
18
|
+
import { mergeStreams } from '../server/service/twin-journal/event-type-split-shape.js'
|
|
19
|
+
|
|
20
|
+
const byN = (a: { n: number }, b: { n: number }) => a.n - b.n
|
|
21
|
+
const seq = (from: number, count: number) => Array.from({ length: count }, (_, i) => ({ n: from + i }))
|
|
22
|
+
|
|
23
|
+
test('★ 한 갈래가 앞을 다 차지해도 전체에서 앞 limit 개가 나온다 — 짚어 준 그 경우다', () => {
|
|
24
|
+
/*
|
|
25
|
+
* A 가 1~5, B 가 100~104. limit 3 이면 답은 A 의 1,2,3 이다. B 는 한 건도 들어오지 않는 것이 맞다.
|
|
26
|
+
*/
|
|
27
|
+
const r = mergeStreams([seq(1, 5), seq(100, 5)], 3, byN)
|
|
28
|
+
assert.deepEqual(r.items.map(x => x.n), [1, 2, 3])
|
|
29
|
+
assert.equal(r.capped, true, '더 있었다는 사실을 말해야 한다')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('★ 갈래를 섞어도 시각순이 유지된다', () => {
|
|
33
|
+
const a = [{ n: 1 }, { n: 4 }, { n: 7 }]
|
|
34
|
+
const b = [{ n: 2 }, { n: 5 }, { n: 8 }]
|
|
35
|
+
const c = [{ n: 3 }, { n: 6 }, { n: 9 }]
|
|
36
|
+
const r = mergeStreams([a, b, c], 9, byN)
|
|
37
|
+
assert.deepEqual(r.items.map(x => x.n), [1, 2, 3, 4, 5, 6, 7, 8, 9])
|
|
38
|
+
assert.equal(r.capped, false, '상한에 닿지 않았으면 잘리지 않았다')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('★ 상한에 닿은 갈래가 있으면 잘렸다고 말한다 — 숫자가 알리지 않고 작아지지 않게', () => {
|
|
42
|
+
/* 한 갈래가 정확히 상한만큼 받아 왔으면 그 갈래에 더 있었을 수 있다. */
|
|
43
|
+
const r = mergeStreams([seq(1, 3), seq(10, 1)], 3, byN)
|
|
44
|
+
assert.equal(r.items.length, 3)
|
|
45
|
+
assert.equal(r.capped, true)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('상한에 못 닿았고 합쳐도 상한 미만이면 잘리지 않았다', () => {
|
|
49
|
+
const r = mergeStreams([seq(1, 2), seq(10, 2)], 10, byN)
|
|
50
|
+
assert.deepEqual(r.items.map(x => x.n), [1, 2, 10, 11])
|
|
51
|
+
assert.equal(r.capped, false)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('빈 갈래가 섞여도 답이 같다 — 그 종류가 그 구간에 없는 것은 정상이다', () => {
|
|
55
|
+
const r = mergeStreams([[], seq(5, 2), []], 10, byN)
|
|
56
|
+
assert.deepEqual(r.items.map(x => x.n), [5, 6])
|
|
57
|
+
assert.equal(r.capped, false)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('★ 갈래가 상한보다 많이 받아 오면 그 사실을 말한다 — 있을 수 없는 일이다', () => {
|
|
61
|
+
/* 부르는 쪽이 상한을 잘못 걸었다는 뜻이고, 그 상태로 답하면 합친 결과를 믿을 수 없다. */
|
|
62
|
+
const r = mergeStreams([seq(1, 5)], 3, byN)
|
|
63
|
+
assert.equal(r.underfilled, true)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('갈래가 하나면 그냥 앞에서 자른 것과 같다', () => {
|
|
67
|
+
const r = mergeStreams([seq(1, 5)], 5, byN)
|
|
68
|
+
assert.deepEqual(r.items.map(x => x.n), [1, 2, 3, 4, 5])
|
|
69
|
+
assert.equal(r.underfilled, false)
|
|
70
|
+
})
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* ★ **재기동이 저널을 처음부터 다시
|
|
2
|
+
* ★ **재기동이 저널을 처음부터 다시 계산하지 않는다** — 미러가 저장된 재개점에서 이어 선다.
|
|
3
3
|
*
|
|
4
4
|
* ── 무엇이 오래 잘못돼 있었나 (2026-08-24 실측) ─────────────────────────────
|
|
5
|
-
* 재개점을 **읽는** 쪽은 다 있었다: `recover` 는 `fold` 가 있으면 꼬리만
|
|
6
|
-
*
|
|
5
|
+
* 재개점을 **읽는** 쪽은 다 있었다: `recover` 는 `fold` 가 있으면 꼬리만 계산하고, 없으면 저널을 0부터
|
|
6
|
+
* 계산한다. 그런데 **쓰는 쪽이 없었다** — `persistSnapshot` 은 `{revision, state, structureRev}` 만
|
|
7
7
|
* 적었다. 실측으로 저장된 스냅샷 34건 전부 `fold` 가 비어 있었고, 저널은 2,960만 줄이었다.
|
|
8
8
|
*
|
|
9
9
|
* 그래서 두 가지가 함께 일어났다.
|
|
10
10
|
*
|
|
11
|
-
* ① 켤 때마다 처음부터
|
|
11
|
+
* ① 켤 때마다 처음부터 계산했다 — 규모 기준(엔티티 10만·품목 100만)에서 느린 것이 아니라 못 하는 것이다
|
|
12
12
|
* ② 미러는 되찾은 상태를 **버렸다**(`startLive`). 「다음 계측이 정정한다」는 전제였는데, 원본이 커서
|
|
13
|
-
* 증분으로 말하는 현장에서는 그 정정이 오지 않는다 — 미러가 영구히 빈 채로 남고 화면은
|
|
13
|
+
* 증분으로 말하는 현장에서는 그 정정이 오지 않는다 — 미러가 영구히 빈 채로 남고 화면은 들어온 것이 없었다
|
|
14
14
|
*
|
|
15
15
|
* ── 왜 커널 시험만으로 충분하지 않나 ────────────────────────────────────────
|
|
16
|
-
* 커널은 「재개점 + 꼬리 == 0부터
|
|
16
|
+
* 커널은 「재개점 + 꼬리 == 0부터 계산하기」를 증명한다(`observed-checkpoint.test.ts`). 그것이 참이어도
|
|
17
17
|
* 호스트가 재개점을 **적지 않거나 되세우지 않으면** 아무 일도 일어나지 않는다 — 이 저장소가 실제로
|
|
18
18
|
* 넉 달 동안 그 상태였다. 그래서 여기서 DB 를 물고 실제로 걷는다.
|
|
19
19
|
*
|
|
20
20
|
* ── 결정적인 단정 ───────────────────────────────────────────────────────────
|
|
21
|
-
* 재기동 전에 **저널을 지운다.** 그래도 상태가 돌아오면 그것은 저널을
|
|
21
|
+
* 재기동 전에 **저널을 지운다.** 그래도 상태가 돌아오면 그것은 저널을 계산해 온 것이 **아니다** —
|
|
22
22
|
* 저장된 재개점에서 온 것이다. 그리고 그 상황은 가정이 아니라 설계된 거동이다: 보존 정책이 체크포인트
|
|
23
23
|
* 리비전 이하의 행을 지운다(「그 앞은 스냅샷이 대신한다」).
|
|
24
24
|
*/
|
|
@@ -31,7 +31,7 @@ const req = createRequire(import.meta.url)
|
|
|
31
31
|
const { NamingStrategy, addDataSource, entities: shellEntities, Domain } = req('@things-factory/shell')
|
|
32
32
|
const { entities: authEntities } = req('@things-factory/auth-base')
|
|
33
33
|
const { entities: twinEntities } = req('../dist-server/service/index.js')
|
|
34
|
-
/* 체크포인트는 `cache-service` 표에 앉는다 — 그 엔티티를 빼면 저장이
|
|
34
|
+
/* 체크포인트는 `cache-service` 표에 앉는다 — 그 엔티티를 빼면 저장이 알리지 않고 실패한다(스냅샷도 씨앗도). */
|
|
35
35
|
const { entities: cacheEntities } = req('@things-factory/cache-service/dist-server/service/index.js')
|
|
36
36
|
const { TwinEngine } = req('../dist-server/engine/index.js')
|
|
37
37
|
const { TwinEvent } = req('../dist-server/service/twin-event/twin-event.js')
|
|
@@ -56,7 +56,7 @@ const master = (): any => ({
|
|
|
56
56
|
equipment: [{ id: 'fk-1', kind: 'forklift', homeLocationId: 'dock' }]
|
|
57
57
|
})
|
|
58
58
|
|
|
59
|
-
/** 원본이 말한 사실 — 오더와 작업. 미러는 이것을
|
|
59
|
+
/** 원본이 말한 사실 — 오더와 작업. 미러는 이것을 계산해 상태를 만든다. */
|
|
60
60
|
function facts(n: number): any[] {
|
|
61
61
|
const out: any[] = []
|
|
62
62
|
for (let i = 1; i <= n; i++) {
|
|
@@ -112,7 +112,7 @@ after(async () => {
|
|
|
112
112
|
await ds?.destroy()
|
|
113
113
|
})
|
|
114
114
|
|
|
115
|
-
test('체크포인트가 **이어
|
|
115
|
+
test('체크포인트가 **이어 계산할 씨앗을 함께 적는다** — 이 줄이 없어서 넉 달 동안 처음부터 계산했다', async () => {
|
|
116
116
|
const rt = await TwinEngine.bootDeclared(domainId, INSTANCE, 'resync')
|
|
117
117
|
assert.equal(rt.mode, 'live', '미러로 떠야 이 검사가 뜻이 있다')
|
|
118
118
|
|
|
@@ -124,14 +124,14 @@ test('체크포인트가 **이어 접을 씨앗을 함께 적는다** — 이
|
|
|
124
124
|
await TwinEngine.persistSnapshot(domainId, INSTANCE)
|
|
125
125
|
const stored = await TwinEngine.loadSnapshot(domainId, INSTANCE)
|
|
126
126
|
assert.ok(stored, '스냅샷이 저장됐다')
|
|
127
|
-
assert.ok(stored.fold?.reducer, '★ 재개점이 함께 적혀야 한다 — 없으면 다음 기동이 저널을 0부터
|
|
127
|
+
assert.ok(stored.fold?.reducer, '★ 재개점이 함께 적혀야 한다 — 없으면 다음 기동이 저널을 0부터 계산한다')
|
|
128
128
|
assert.ok(stored.fold?.oee, '가동 누적기도 함께 — 상태만 되돌리면 가동률이 부팅 이후로만 잡힌다')
|
|
129
129
|
/* 씨앗이 실제로 사실을 들었는지 — 빈 껍데기를 적어 두고 초록이 되지 않게. */
|
|
130
130
|
assert.ok((stored.fold.reducer as any).orders?.length, '씨앗에 오더가 담겨 있다')
|
|
131
131
|
assert.ok((stored.fold.reducer as any).tasks?.length, '씨앗에 작업이 담겨 있다')
|
|
132
132
|
})
|
|
133
133
|
|
|
134
|
-
test('★ 저널을 지워도 미러가 상태를 들고 다시 선다 — 그러므로 저널을
|
|
134
|
+
test('★ 저널을 지워도 미러가 상태를 들고 다시 선다 — 그러므로 저널을 계산한 것이 아니다', async () => {
|
|
135
135
|
/*
|
|
136
136
|
* 이 단정이 이 축의 전부다. 저널이 없는데 상태가 돌아오면 그 상태는 **저장된 재개점**에서 온 것이다.
|
|
137
137
|
* 그리고 이 상황은 가정이 아니다 — 보존 정책이 체크포인트 리비전 이하의 행을 실제로 지운다.
|
|
@@ -145,7 +145,7 @@ test('★ 저널을 지워도 미러가 상태를 들고 다시 선다 — 그
|
|
|
145
145
|
assert.equal(
|
|
146
146
|
await ds.getRepository(TwinEvent).countBy({ domain: { id: domainId } as any, instanceId: INSTANCE } as any),
|
|
147
147
|
0,
|
|
148
|
-
'저널이 비었다 — 이제
|
|
148
|
+
'저널이 비었다 — 이제 계산할 것이 없다'
|
|
149
149
|
)
|
|
150
150
|
|
|
151
151
|
await TwinEngine.bootDeclared(domainId, INSTANCE, 'resync')
|
package/test/model-basis.test.ts
CHANGED
|
@@ -131,11 +131,11 @@ test('선언이 침묵하면 기본값으로 떨어진다 — 없는 것을 지
|
|
|
131
131
|
assert.equal(modelBasisChanged(quiet(base), quiet(base * 1.3)), true, '기본 10% 밖')
|
|
132
132
|
})
|
|
133
133
|
|
|
134
|
-
test('변동폭이 바뀌면 다른 모델이다 — 판정의 잣대가
|
|
134
|
+
test('변동폭이 바뀌면 다른 모델이다 — 판정의 잣대가 알리지 않고 바뀌지 않게', () => {
|
|
135
135
|
const a = weld(70 * 60_000)
|
|
136
136
|
const b = {
|
|
137
137
|
measured: { weld: 70 * 60_000 },
|
|
138
138
|
declared: [{ key: 'weld', duration: 'PT1H10M', variability: { distribution: 'triangular', min: 'PT30M', max: 'PT3H', mode: 'PT1H5M' } }]
|
|
139
139
|
}
|
|
140
|
-
assert.equal(modelBasisChanged(a, b), true, '변동폭 선언이 바뀌었는데 같은 모델이라고 하면 잣대가
|
|
140
|
+
assert.equal(modelBasisChanged(a, b), true, '변동폭 선언이 바뀌었는데 같은 모델이라고 하면 잣대가 오류 없이 바뀐다')
|
|
141
141
|
})
|
package/test/model-gap.test.ts
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* ── 지키는 것 ───────────────────────────────────────────────────────────────
|
|
21
21
|
* ① 마스터가 말한 것과 관측으로 알게 된 것을 섞지 않는다
|
|
22
22
|
* ② 트윈이 안 돌면 `null` — 「없다」와 「세어 보지 못했다」는 다르다
|
|
23
|
-
* ③
|
|
23
|
+
* ③ 알리지 않고 자르지 않는다 — 상한에서 밀려난 수를 함께 낸다
|
|
24
24
|
* ④ 덮지 못하는 축을 밝힌다 — 계량기는 커널이 표시를 달지 않아 이 화면이 못 찾아 준다
|
|
25
25
|
*/
|
|
26
26
|
import { test } from 'node:test'
|
|
@@ -68,7 +68,7 @@ test('돌지만 관측 갭이 없으면 0 이다 — 이건 「없음」과 다
|
|
|
68
68
|
assert.equal(g!.total, 0, '세어 보니 없었다')
|
|
69
69
|
})
|
|
70
70
|
|
|
71
|
-
test('id 를
|
|
71
|
+
test('id 를 알리지 않고 자르지 않는다 — 밀려난 수를 함께 낸다', () => {
|
|
72
72
|
const many = Array.from({ length: GAP_ID_LIMIT + 7 }, (_, i) => loc(`ghost-${i}`, 'observed'))
|
|
73
73
|
const g = modelGapOf({ locations: many })
|
|
74
74
|
|
|
@@ -95,7 +95,7 @@ test('모르는 설비의 에너지 보고는 「없음」과 0 을 가른다
|
|
|
95
95
|
/*
|
|
96
96
|
* 이 값을 **루트에서 읽고 있었다**(2026-08-20에 잡음). 커널은 `EnergyState` 안에 담으므로 루트에서
|
|
97
97
|
* 읽으면 영원히 `null` 이고, 오류 없이 「에너지 트윈이 아니다」로 읽힌다. 나만 읽는 필드를 잘못된
|
|
98
|
-
* 경로에서 읽으면 이렇게
|
|
98
|
+
* 경로에서 읽으면 이렇게 오류 없이 사라진다.
|
|
99
99
|
*
|
|
100
100
|
* 그리고 커널은 0 이면 그 칸을 만들지 않는다. 그래서 `energy` 자체가 있는지로 「세었고 0」과
|
|
101
101
|
* 「에너지 트윈이 아님」을 가른다.
|
|
@@ -112,7 +112,7 @@ test('모르는 설비의 에너지 보고는 「없음」과 0 을 가른다
|
|
|
112
112
|
|
|
113
113
|
test('계량기도 덮는다 — 이 축이 가장 무겁다', () => {
|
|
114
114
|
/*
|
|
115
|
-
* 모르는 `meterId` 는
|
|
115
|
+
* 모르는 `meterId` 는 알리지 않고 새 계량기가 되고 그 kW 가 그 현장의 수요·피크·요금 판정에 더해진다.
|
|
116
116
|
* 로케이션 오타는 스키매틱이 비는 것으로 드러나지만, 이건 **그럴듯한 숫자**로 나온다.
|
|
117
117
|
* 커널 0.7.39 가 `MeterPointState.origin` 을 내면서 덮을 수 있게 됐다.
|
|
118
118
|
*/
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* ── 계기 (2026-08-06, 사용자 신고) ───────────────────────────────────────────
|
|
5
5
|
* 모델러의 엔티티 목록이 통째로 비어 있었다. **오류는 없었다.** 저장된 모델가 옛 어휘(`nodes`/`movers`)
|
|
6
6
|
* 인데 화면과 호스트가 새 어휘를 읽었기 때문이다. 같은 원인이 구조 서명(`N[]M[]` — 서로 다른 두 공장이
|
|
7
|
-
* 같아 보인다)·주행 추정기·설비 동기까지
|
|
7
|
+
* 같아 보인다)·주행 추정기·설비 동기까지 알리지 않고 망가뜨리고 있었다.
|
|
8
8
|
*
|
|
9
9
|
* 그래서 **DB에서 읽는 순간** 한 번 정규화한다. 이 테스트가 지키는 것은 그 정규화가 ① 세 세대를 다
|
|
10
10
|
* 흡수하고 ② 나머지 속성을 하나도 잃지 않고 ③ 새 모델에는 아무 일도 하지 않는 것이다.
|
|
@@ -101,7 +101,7 @@ test('두 옛 모델이 구조적으로 구별된다 — 서명이 빈 문자열
|
|
|
101
101
|
})
|
|
102
102
|
|
|
103
103
|
test('보드를 저장하는 엔티티가 **변환기를 달고 있다** — 결선이 빠지면 순수 모듈은 그린인데 화면은 빈다', () => {
|
|
104
|
-
/* 이 결함이 정확히 그랬다: 리더는 커널에 있었고 목록만 그것을 거쳤다. 결선은
|
|
104
|
+
/* 이 결함이 정확히 그랬다: 리더는 커널에 있었고 목록만 그것을 거쳤다. 결선은 알리지 않고 빠질 수 있으므로
|
|
105
105
|
기계가 지킨다. 새 엔티티가 보드를 담게 되면 여기 함께 올린다. */
|
|
106
106
|
const owners = ['server/service/twin-instance/twin-instance.ts', 'server/service/twin-structure/twin-structure.ts']
|
|
107
107
|
|
package/test/move-space.test.ts
CHANGED
|
@@ -25,7 +25,7 @@ test('잃을 것이 없으면 경고도 없다 — 정상 경로를 시끄럽게
|
|
|
25
25
|
assert.deepEqual(j.warnings, [])
|
|
26
26
|
})
|
|
27
27
|
|
|
28
|
-
test('없는 현장으로는 옮기지 않는다 —
|
|
28
|
+
test('없는 현장으로는 옮기지 않는다 — 알리지 않고 새 공간을 만들지 않는다', () => {
|
|
29
29
|
const j = judgeMoveTwinSpace({ ...CLEAN, toSpaceExists: false })
|
|
30
30
|
assert.equal(j.refusal?.code, 'space-not-found')
|
|
31
31
|
assert.equal(j.refusal?.params?.spaceId, 'rosarito-site')
|
|
@@ -64,9 +64,24 @@ test('★ OEE 누적기: downMs 정확 · busyMs=run+setup · 근사 OEE 유효'
|
|
|
64
64
|
|
|
65
65
|
// 근사 OEE 유효 + 한계 명시.
|
|
66
66
|
const oee = acc.oee('fk-1', nowMs)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
/*
|
|
68
|
+
* 총합은 **없을 수 있다.** 표준 시간(PRI)이 없으면 성능률이 안 나오고, 그러면 곱도 안 나온다
|
|
69
|
+
* (계약 0.5.0, ISO 22400 대조). 누적기는 사건에서 표준 시간을 알 수 없으므로 여기서는 없는 것이 정상이다.
|
|
70
|
+
*
|
|
71
|
+
* 예전에는 근거 없이 1 을 내서 총합이 늘 값을 가졌다. 그때 이 단언은 아무것도 지키지 않았다.
|
|
72
|
+
*/
|
|
73
|
+
if (oee.overall === undefined) {
|
|
74
|
+
assert.ok(oee.missing?.includes('planned-run-time-per-item'), `총합이 없으면 이유가 있어야 한다: ${JSON.stringify(oee.missing)}`)
|
|
75
|
+
} else {
|
|
76
|
+
assert.ok(oee.overall >= 0 && oee.overall <= 1, 'OEE overall ∈ [0,1]')
|
|
77
|
+
}
|
|
78
|
+
assert.ok((oee.availability as number) >= 0 && (oee.availability as number) <= 1, 'availability ∈ [0,1]')
|
|
79
|
+
/*
|
|
80
|
+
* WMS 는 품질 개념이 없어 산출을 기록하지 않는다. 만든 것이 없으면 양품률은 **모름**이다.
|
|
81
|
+
* 예전에는 1 을 냈고, 그래서 품질을 재지도 않는 창고 설비가 「품질 100%」로 보였다.
|
|
82
|
+
*/
|
|
83
|
+
assert.equal(oee.quality, undefined, 'WMS 는 산출 기록이 없으므로 양품률은 모름이다')
|
|
84
|
+
assert.ok(oee.missing?.includes('produced-quantity'))
|
|
70
85
|
})
|
|
71
86
|
|
|
72
87
|
test('★ OEE 누적기: quality.output 델타 소비 → quality 정확 (good/scrap 도출)', () => {
|
|
@@ -108,7 +123,7 @@ test('★ withLiveOee: projector 미러(oee 없음) + 누적기 → live 설비
|
|
|
108
123
|
/*
|
|
109
124
|
* ── 되짚은 화면에도 가동 이력이 있어야 한다 (2026-08-05) ────────────────────
|
|
110
125
|
* OEE 는 원 시스템이 누적을 보내 주지 않아 호스트가 상태 전이를 적분해 만든다. 그런데 그 누적기는
|
|
111
|
-
* **라이브에서만** 돌았고, 저널을
|
|
126
|
+
* **라이브에서만** 돌았고, 저널을 계산하는 경로는 그 계산을 하지 않았다 — 과거를 되짚으면 모든 설비가
|
|
112
127
|
* "가동 이력이 전혀 없음" 으로 보였다(실 저널: `rosarito-mes` 18대, `hatiolab-wms` 22대가 그랬다).
|
|
113
128
|
*
|
|
114
129
|
* 없는 것은 데이터가 아니라 계산이다: 입력은 저널에 다 있다. 그래서 같은 누적기에 같은 이벤트를
|
|
@@ -174,7 +189,7 @@ test('★ 계측 시점을 벽시계로 재지 않는다 — 과거 화면의
|
|
|
174
189
|
*
|
|
175
190
|
* 예전에는 `metricsSinceMs: 0` 을 돌려줬다. 시각이 epoch 밀리초라서 그 창은 곧 "1970년부터" 가 되고,
|
|
176
191
|
* 공식의 분모가 지금 시각 전체가 된다 — 화면에 `idleMs 1767226603000`(약 56,000년) 유휴가 섰다.
|
|
177
|
-
* 그 값은 availability·overall 의 분모이기도 해서 **성과 숫자 전체가
|
|
192
|
+
* 그 값은 availability·overall 의 분모이기도 해서 **성과 숫자 전체가 알리지 않고 거짓**이 됐다.
|
|
178
193
|
*/
|
|
179
194
|
test('이벤트를 못 본 설비는 계측 창을 만들지 않는다 — epoch 를 창으로 삼지 않는다', () => {
|
|
180
195
|
const acc = new OeeAccumulator()
|
|
@@ -56,7 +56,7 @@ test('거부는 이유와 함께 남는다 — 유효한 것은 함께 통과한
|
|
|
56
56
|
const { accepted, rejected } = ingestCanonicalRecords(
|
|
57
57
|
[
|
|
58
58
|
{ taskId: 'tk-1', kind: 'pick', status: 'completed' },
|
|
59
|
-
{ taskId: 'tk-2', kind: 'pick', status: 'FINISHED' } // 커널이
|
|
59
|
+
{ taskId: 'tk-2', kind: 'pick', status: 'FINISHED' } // 커널이 다룰 수 없는 낱말
|
|
60
60
|
] as any,
|
|
61
61
|
'domain-1',
|
|
62
62
|
AT
|
|
@@ -66,7 +66,7 @@ test('거부는 이유와 함께 남는다 — 유효한 것은 함께 통과한
|
|
|
66
66
|
assert.ok(rejected[0].errors.join(' ').length > 0)
|
|
67
67
|
})
|
|
68
68
|
|
|
69
|
-
test('피드 결선이 거부를
|
|
69
|
+
test('피드 결선이 거부를 알리지 않고 버리지 않는다 — 이유를 사람에게 말한다', () => {
|
|
70
70
|
const src = readFileSync(fileURLToPath(new URL('../server/service/reference/reference-live.ts', import.meta.url)), 'utf-8')
|
|
71
71
|
assert.match(src, /record\(s\) rejected/, '거부 수와 이유를 남긴다')
|
|
72
72
|
})
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* · 끊어진 참조는 **이름을 대고 보고**되고, 행 자체는 남는다(막지 않되 침묵하지 않는다)
|
|
13
13
|
* · **전량 교체** — 두 번 돌려도 늘지 않고, 원본에서 사라진 것은 행에서도 사라진다
|
|
14
14
|
* · 원본 표식(`sourceRef`·`ingestedAt`) 없는 행은 만들 수 없다(캐시라는 사실이 구조로 지켜진다)
|
|
15
|
-
* · 소요시간은 ISO 8601 **문자열 그대로** 남는다(분으로
|
|
15
|
+
* · 소요시간은 ISO 8601 **문자열 그대로** 남는다(분으로 줄이면 초가 오류 없이 사라진다)
|
|
16
16
|
*/
|
|
17
17
|
import { test, before, after, beforeEach } from 'node:test'
|
|
18
18
|
import assert from 'node:assert/strict'
|
|
@@ -161,7 +161,7 @@ test('원본 표식 없는 행은 만들 수 없다 — 캐시라는 사실이
|
|
|
161
161
|
)
|
|
162
162
|
})
|
|
163
163
|
|
|
164
|
-
test('소요시간은 ISO 8601 문자열 그대로 — 분으로
|
|
164
|
+
test('소요시간은 ISO 8601 문자열 그대로 — 분으로 줄이면 초가 오류 없이 사라진다', async () => {
|
|
165
165
|
const op = await ds.getRepository(TwinOperation).findOne({ where: { operationKey: 'cut' } })
|
|
166
166
|
assert.equal(op?.duration, 'PT1H10M30S')
|
|
167
167
|
/* 변동성·파라미터는 도메인마다 형이 달라 원형 그대로 싣는다. */
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* 것은 "이 사실이 여기 앉는다" 는 선언이고, 앉을 자리를 만들어 두고 안 채우면 그 컬럼은 영원히 빈다.
|
|
17
17
|
*
|
|
18
18
|
* 그래서 컬럼마다 셋을 요구한다:
|
|
19
|
-
* ① 픽스처가 모델에 그 값을 싣는다(값을 줄 방법을 모르면 **검사가 실패한다** —
|
|
19
|
+
* ① 픽스처가 모델에 그 값을 싣는다(값을 줄 방법을 모르면 **검사가 실패한다** — 알리지 않고 건너뛰면
|
|
20
20
|
* 그 컬럼은 아무도 안 보는 컬럼이 된다)
|
|
21
21
|
* ② 투영을 지나 행에 남는다
|
|
22
22
|
* ③ 조회 응답에 나온다(화면이 읽는 자리)
|
|
@@ -72,6 +72,11 @@ const VALUE: Record<string, unknown> = {
|
|
|
72
72
|
parallelism: 2,
|
|
73
73
|
level: 'WorkCenter',
|
|
74
74
|
gs1Id: 'urn:epc:id:sgln:0614141.00001.0',
|
|
75
|
+
/*
|
|
76
|
+
* 설비의 정체성 — **GS1 키가 아닌 것이 정상이다.** 실측한 값은 인버터의 하드웨어 식별자다
|
|
77
|
+
* (`ES100000007d8d061d-1`). 자리의 `gs1Id`(SGLN)와 다른 축이라 이름도 다르다.
|
|
78
|
+
*/
|
|
79
|
+
identity: 'ES100000007d8d061d-1',
|
|
75
80
|
homeLocation: 'cut-station',
|
|
76
81
|
mtbfMs: 600000,
|
|
77
82
|
mttrMs: 60000,
|
|
@@ -131,7 +136,7 @@ function master(): any {
|
|
|
131
136
|
assert.ok(
|
|
132
137
|
col in VALUE,
|
|
133
138
|
`픽스처가 "${c.axis}.${col}" 에 실을 값을 모른다 — VALUE 에 하나 적어라(모델의 사실이 아니면 NOT_FROM_MODEL 에 이유와 함께).\n` +
|
|
134
|
-
'
|
|
139
|
+
'알리지 않고 건너뛰면 그 컬럼은 아무도 안 보는 컬럼이 되고, 그것이 이 하네스가 막으려는 실패다.'
|
|
135
140
|
)
|
|
136
141
|
o[col] = VALUE[col]
|
|
137
142
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* **투영이 읽는 이름이 선언에 실재하는가.**
|
|
3
|
+
*
|
|
4
|
+
* ── 무엇이 있었나 (2026-08-30) ────────────────────────────────────────────
|
|
5
|
+
* 설비 투영이 `m.gs1Id` 를 읽고 있었는데 **선언에 그런 필드가 없었다.** 그래서 그 칸은 일곱 대 모두
|
|
6
|
+
* 영원히 비어 있었다. 오류는 나지 않았다 — `?? null` 이 늘 `null` 을 냈을 뿐이다.
|
|
7
|
+
*
|
|
8
|
+
* 값은 한 칸 옆에 있었다. 커넥터가 인버터의 하드웨어 식별자를 이미 보내고 있었고, 정체성 칸도
|
|
9
|
+
* 있었는데 그 사이에 길이 없었다. 인티그레이션 레인이 「값이 한 칸 옆에 있다」고 알려 와서 찾았다.
|
|
10
|
+
*
|
|
11
|
+
* ── 왜 다른 시험이 못 잡았나 ─────────────────────────────────────────────
|
|
12
|
+
* `projection-reaches-screen` 은 **마스터 → 행 → 화면**을 걷는다. 그 길은 멀쩡했다 — 참조 계층이
|
|
13
|
+
* `gs1Id` 를 실어 보내고 있었기 때문이다. 끊긴 것은 **선언 → 행** 쪽이었고, 아무도 그 길을 걷지
|
|
14
|
+
* 않았다.
|
|
15
|
+
*
|
|
16
|
+
* 이 시험은 이름만 본다. 값이 흐르는지는 저 시험이 보고, 여기서는 **읽는 이름이 실재하는지**만 본다.
|
|
17
|
+
* 둘이 함께 있어야 「자리는 있고 길이 없다」가 막힌다.
|
|
18
|
+
*/
|
|
19
|
+
import { test } from 'node:test'
|
|
20
|
+
import assert from 'node:assert/strict'
|
|
21
|
+
import { readFileSync } from 'node:fs'
|
|
22
|
+
import { createRequire } from 'node:module'
|
|
23
|
+
import { fileURLToPath } from 'node:url'
|
|
24
|
+
import { dirname, join } from 'node:path'
|
|
25
|
+
|
|
26
|
+
const here = dirname(fileURLToPath(import.meta.url))
|
|
27
|
+
const req = createRequire(import.meta.url)
|
|
28
|
+
|
|
29
|
+
const projection = readFileSync(join(here, '..', 'server', 'service', 'twin-model', 'project-structure.ts'), 'utf-8')
|
|
30
|
+
|
|
31
|
+
/** 계약이 배포한 선언 타입 원문 — 이름이 실재하는지 여기서 확인한다. */
|
|
32
|
+
const contractTypes = readFileSync(join(dirname(req.resolve('@operato/ops-contract')), '..', 'dist', 'contract.d.ts'), 'utf-8')
|
|
33
|
+
const domainTypes = readFileSync(
|
|
34
|
+
join(dirname(req.resolve('@operato/ops-contract')), '..', 'dist', 'domain-definition.d.ts'),
|
|
35
|
+
'utf-8'
|
|
36
|
+
)
|
|
37
|
+
const declared = contractTypes + '\n' + domainTypes
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 투영 안의 변수 이름 → 그 변수가 무엇의 선언인가.
|
|
41
|
+
*
|
|
42
|
+
* 이름을 손으로 적는다. 투영이 변수를 바꾸면 이 목록이 낡는데, 그때 아래 「대상이 실재한다」 검사가
|
|
43
|
+
* 먼저 빨갛게 난다 — 낡은 채로 초록이 되지 않는다.
|
|
44
|
+
*/
|
|
45
|
+
const SUBJECTS: { varName: string; what: string }[] = [
|
|
46
|
+
{ varName: 'l', what: '자리' },
|
|
47
|
+
{ varName: 'm', what: '설비' },
|
|
48
|
+
{ varName: 'o', what: '공정' }
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
/** 선언에서 오지 않는 이름 — 투영이 스스로 만드는 값이다. 이유를 함께 적는다. */
|
|
52
|
+
const NOT_FROM_DECLARATION: Record<string, string> = {
|
|
53
|
+
sourceRef: '투영이 붙인다 — 어느 참조에서 왔나',
|
|
54
|
+
ingestedAt: '투영이 붙인다 — 언제 채웠나',
|
|
55
|
+
domain: '테넌트 — 호출부가 준다',
|
|
56
|
+
instanceId: '어느 트윈인가 — 호출부가 준다'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 그 변수에서 읽는 필드들.
|
|
61
|
+
*
|
|
62
|
+
* **형 단언을 지나서 본다** — `(m as any).x` 도 읽는 것이다. 처음에는 `m.x` 만 봤고, 그래서
|
|
63
|
+
* 유령 이름을 넣어도 잡지 못했다(2026-08-30). 실제 코드가 그 모양을 쓰고 있었으므로 그 자리를
|
|
64
|
+
* 통째로 놓치고 있었다.
|
|
65
|
+
*
|
|
66
|
+
* 단언을 쓰는 자리가 특히 위험하다 — 타입 검사가 이미 꺼져 있어서 컴파일러도 안 잡는다.
|
|
67
|
+
*/
|
|
68
|
+
function readsOf(varName: string): { field: string; line: number }[] {
|
|
69
|
+
const out: { field: string; line: number }[] = []
|
|
70
|
+
const plain = new RegExp(`(?<![A-Za-z0-9_.)])${varName}\\.([a-zA-Z][A-Za-z0-9]*)`, 'g')
|
|
71
|
+
const cast = new RegExp(`\\(\\s*${varName}\\s+as\\s+[^)]*\\)\\.([a-zA-Z][A-Za-z0-9]*)`, 'g')
|
|
72
|
+
projection.split('\n').forEach((text, i) => {
|
|
73
|
+
for (const re of [plain, cast]) for (const m of text.matchAll(re)) out.push({ field: m[1], line: i + 1 })
|
|
74
|
+
})
|
|
75
|
+
return out
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
test('★ 검사할 대상이 실재한다 — 없으면 이 시험은 아무것도 지키지 않는다', () => {
|
|
79
|
+
assert.ok(declared.length > 10_000, '계약 선언 원문을 못 읽었다')
|
|
80
|
+
for (const s of SUBJECTS) {
|
|
81
|
+
assert.ok(readsOf(s.varName).length > 0, `${s.what}(${s.varName}) 를 읽는 자리가 없다 — 투영이 바뀌었다`)
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('★ 투영이 읽는 이름이 선언에 실재한다 — 없는 이름을 읽으면 그 칸은 영원히 빈다', () => {
|
|
86
|
+
const ghosts: string[] = []
|
|
87
|
+
for (const s of SUBJECTS) {
|
|
88
|
+
for (const { field, line } of readsOf(s.varName)) {
|
|
89
|
+
if (field in NOT_FROM_DECLARATION) continue
|
|
90
|
+
/* 선언 어딘가에 그 이름이 필드로 있는가 — 타입을 가리지 않고 이름만 본다. */
|
|
91
|
+
if (!new RegExp(`\\b${field}\\??:`).test(declared)) {
|
|
92
|
+
ghosts.push(`project-structure.ts:${line} ${s.what}.${field}`)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
assert.deepEqual(
|
|
97
|
+
[...new Set(ghosts)],
|
|
98
|
+
[],
|
|
99
|
+
`선언에 없는 이름을 읽는다:\n${[...new Set(ghosts)].join('\n')}\n\n` +
|
|
100
|
+
'그 칸은 늘 null 이 되고 오류는 나지 않는다. 선언에 그 필드를 열거나, 투영이 읽는 이름을 고쳐라.\n' +
|
|
101
|
+
'선언에서 오는 값이 아니면 NOT_FROM_DECLARATION 에 이유와 함께 올려라.'
|
|
102
|
+
)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test('선언에서 오지 않는 이름에 이유가 적혀 있다', () => {
|
|
106
|
+
for (const [name, why] of Object.entries(NOT_FROM_DECLARATION)) {
|
|
107
|
+
assert.ok(why.trim().length > 5, `${name} 에 이유가 없다 — 이유 없이 올리면 이 시험이 아무것도 지키지 않는다`)
|
|
108
|
+
}
|
|
109
|
+
})
|