@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* ★ 구조 리비전 — **실 DB로 경계를 넘어 본다.**
|
|
3
3
|
*
|
|
4
|
-
* 커널의 마디
|
|
4
|
+
* 커널의 마디 계산(`replaySegments`)는 그쪽 테스트가 지킨다. 여기서 지키는 것은 **호스트 배선**이다:
|
|
5
5
|
* 리비전을 언제 새로 만드는가 · 이벤트에 번호를 찍는가 · 재생이 그 번호로 마디를 나누는가 ·
|
|
6
6
|
* 컬럼이 생기기 전 행(번호 없음)을 어디에 붙이는가.
|
|
7
7
|
*
|
|
@@ -152,9 +152,9 @@ test('★ 재생이 경계를 넘는다 — 앞선 사실은 남고, 새 구조
|
|
|
152
152
|
assert.equal(state.locations.filter((n: any) => n.type === 'storage').length, 6, '늘린 자리가 안 보이면 늘린 의미가 없다')
|
|
153
153
|
})
|
|
154
154
|
|
|
155
|
-
test('구조 번호가 없는 옛 행은 **가장 오래된 구조**로
|
|
155
|
+
test('구조 번호가 없는 옛 행은 **가장 오래된 구조**로 처리한다 — 새 공장에 대고 계산하지 않는다', async () => {
|
|
156
156
|
/*
|
|
157
|
-
* 컬럼이 생기기 전에 쓰인 행은 `structureRev` 가 비어 있다. 그것을 현재 보드로
|
|
157
|
+
* 컬럼이 생기기 전에 쓰인 행은 `structureRev` 가 비어 있다. 그것을 현재 보드로 계산하면 그때 없던
|
|
158
158
|
* 설비에서 일이 있었던 것처럼 보인다 — 이 규칙이 그 행들을 제자리에 붙인다.
|
|
159
159
|
*/
|
|
160
160
|
await TwinEngine.provision(domainId, INSTANCE, 'wms', boardWith(2, 1), 'reset')
|
|
@@ -165,11 +165,11 @@ test('구조 번호가 없는 옛 행은 **가장 오래된 구조**로 접는
|
|
|
165
165
|
|
|
166
166
|
await TwinEngine.provision(domainId, INSTANCE, 'wms', boardWith(6, 3), 'reset')
|
|
167
167
|
const state = await TwinEngine.recover(domainId, INSTANCE)
|
|
168
|
-
assert.ok(state.items.length > 0, '번호 없는 옛 행도 읽혀야 한다 —
|
|
168
|
+
assert.ok(state.items.length > 0, '번호 없는 옛 행도 읽혀야 한다 — 알리지 않고 버리지 않는다')
|
|
169
169
|
assert.equal(state.equipment.length, 3, '마지막 구조가 자원의 진실이다')
|
|
170
170
|
})
|
|
171
171
|
|
|
172
|
-
test('리비전이 하나뿐이면 예전과 똑같이
|
|
172
|
+
test('리비전이 하나뿐이면 예전과 똑같이 계산한다 — 구조 전환이 결과를 바꾸지 않는다', async () => {
|
|
173
173
|
await TwinEngine.provision(domainId, INSTANCE, 'wms', boardWith(3, 2), 'reset')
|
|
174
174
|
const events = realEvents(boardWith(3, 2), 300)
|
|
175
175
|
await TwinEngine.persistBatch(domainId, INSTANCE, events, 0)
|
|
@@ -243,7 +243,7 @@ test('번호 없는 옛 행도 가장 오래된 리비전의 몫으로 센다
|
|
|
243
243
|
await TwinEngine.provision(domainId, INSTANCE, 'wms', boardWith(6, 3), 'reset')
|
|
244
244
|
|
|
245
245
|
const history = await TwinEngine.structureHistory(domainId, INSTANCE)
|
|
246
|
-
assert.equal(history[0].events, legacy.length, '재생이 rev 1 로
|
|
246
|
+
assert.equal(history[0].events, legacy.length, '재생이 rev 1 로 계산하는 행들을 세는 쪽만 빠뜨리면 두 화면이 다른 말을 한다')
|
|
247
247
|
assert.equal(history[1].events, 0)
|
|
248
248
|
})
|
|
249
249
|
|
|
@@ -270,7 +270,7 @@ test('지문 표기가 낡았을 뿐이면 리비전을 만들지 않는다 —
|
|
|
270
270
|
*
|
|
271
271
|
* 예전에는 sim-world 선언이 저널을 지우고 fresh 로 띄웠다. 실제로 없던 것은 **번호 이어 세기
|
|
272
272
|
* 하나**였다: 상태 복원은 이미 저널 재생으로 하고 있었고, 막힌 것은 커널이 0부터 다시 세어 기존
|
|
273
|
-
* 행과 겹치는 문제였다. 그 겹침은 오류를 내지 않고
|
|
273
|
+
* 행과 겹치는 문제였다. 그 겹침은 오류를 내지 않고 알리지 않고 재생 순서를 뒤섞는다.
|
|
274
274
|
*/
|
|
275
275
|
test('sim-world 로 다시 띄워도 저널이 살아남는다 — 선언이 지키는 약속', async () => {
|
|
276
276
|
await TwinEngine.provision(domainId, INSTANCE, 'wms', boardWith(2, 1), 'reset')
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ★ 화면이 이미 부르는 문에서 파생 테이블을 쓴다 — 그리고 못 답할 때는 물러난다.
|
|
3
|
+
*
|
|
4
|
+
* ── 무엇을 놓쳤던가 (2026-08-28) ────────────────────────────────────────────
|
|
5
|
+
* 대상별 사건 목록을 만들고 8,382,017줄을 채웠는데, 360 화면은 예전부터 `twinEventList` 를 부르고
|
|
6
|
+
* 있었다. 그래서 조회는 그대로 64초였다. **자리는 있고 길이 없었다** — 가장 자주 나는 결함이다.
|
|
7
|
+
*
|
|
8
|
+
* 이 시험이 지키는 것은 그 길이다.
|
|
9
|
+
* ① 어떤 물음이 파생 테이블로 가는가 — 좁게, 그리고 그 판단이 순수한가
|
|
10
|
+
* ② 못 답하는 물음에 억지로 답하지 않는가
|
|
11
|
+
* ③ 답할 수 없는 구간을 「사건이 없다」로 그리게 만들지 않는가
|
|
12
|
+
* ④ 화면이 부르는 자리에 실제로 붙어 있는가
|
|
13
|
+
*/
|
|
14
|
+
import { test } from 'node:test'
|
|
15
|
+
import assert from 'node:assert/strict'
|
|
16
|
+
import { readFileSync } from 'node:fs'
|
|
17
|
+
import { join } from 'node:path'
|
|
18
|
+
|
|
19
|
+
import { readModelShape, AXIS_KIND } from '../server/service/twin-subject/subject-query-shape.js'
|
|
20
|
+
|
|
21
|
+
const dir = (...p: string[]) => join(import.meta.dirname, '..', 'server', ...p)
|
|
22
|
+
const fastPath = readFileSync(dir('service', 'twin-subject', 'twin-subject-fast-path.ts'), 'utf8')
|
|
23
|
+
const journal = readFileSync(dir('service', 'twin-journal', 'twin-journal-query.ts'), 'utf8')
|
|
24
|
+
|
|
25
|
+
/** 360 화면이 실제로 보내는 모양(§`loadTwinEventPage`) — 축 하나를 부분일치로, 시각 내림차순. */
|
|
26
|
+
const panelParams = (axis: string, value: string, extra: any[] = []) => ({
|
|
27
|
+
filters: [{ name: axis, operator: 'search', value }, ...extra],
|
|
28
|
+
sortings: [{ name: 'eventTime', desc: true }]
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const DESC = [{ name: 'eventTime', desc: true }]
|
|
32
|
+
|
|
33
|
+
test('★ 360 화면이 보내는 네 축이 모두 파생 테이블로 간다', () => {
|
|
34
|
+
/* 이것이 이 작업의 목적이다. 하나라도 빠지면 그 축의 이력은 여전히 64초다. */
|
|
35
|
+
const expected: Record<string, string> = { epc: 'item', orderId: 'order', locationId: 'location', moverId: 'equipment' }
|
|
36
|
+
for (const [axis, kind] of Object.entries(expected)) {
|
|
37
|
+
const shape = readModelShape(panelParams(axis, 'V-1'), DESC)
|
|
38
|
+
assert.ok(shape, `${axis} 축이 파생 테이블로 가야 한다`)
|
|
39
|
+
assert.equal(shape!.kind, kind)
|
|
40
|
+
assert.equal(shape!.value, 'V-1')
|
|
41
|
+
}
|
|
42
|
+
assert.deepEqual(Object.keys(AXIS_KIND).sort(), ['epc', 'locationId', 'moverId', 'orderId'])
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('시각 상한(시간여행)이 함께 와도 간다 — 화면이 과거를 볼 때 보내는 모양', () => {
|
|
46
|
+
const shape = readModelShape(panelParams('epc', 'ITEM-1', [{ name: 'eventTime', operator: 'lte', value: '2026-08-01T00:00:00.000Z' }]), DESC)
|
|
47
|
+
assert.ok(shape)
|
|
48
|
+
assert.equal(shape!.untilTime?.toISOString(), '2026-08-01T00:00:00.000Z')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('구간으로 물으면 두 끝을 모두 읽는다', () => {
|
|
52
|
+
const shape = readModelShape(
|
|
53
|
+
panelParams('locationId', 'BUFFER-CK1', [
|
|
54
|
+
{ name: 'eventTime', operator: 'between', value: ['2026-08-01T00:00:00.000Z', '2026-08-02T00:00:00.000Z'] }
|
|
55
|
+
]),
|
|
56
|
+
DESC
|
|
57
|
+
)
|
|
58
|
+
assert.ok(shape)
|
|
59
|
+
assert.equal(shape!.fromTime?.toISOString(), '2026-08-01T00:00:00.000Z')
|
|
60
|
+
assert.equal(shape!.untilTime?.toISOString(), '2026-08-02T00:00:00.000Z')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('★ 맞지 않는 물음은 답하지 않고 저널로 보낸다 — 억지로 답하면 목록이 알리지 않고 달라진다', () => {
|
|
64
|
+
const cases: [string, any, any[]][] = [
|
|
65
|
+
['자유 검색어', { ...panelParams('epc', 'ITEM-1'), search: '냉동' }, DESC],
|
|
66
|
+
['종류 필터', panelParams('epc', 'ITEM-1', [{ name: 'eventType', operator: 'in', value: ['task.status'] }]), DESC],
|
|
67
|
+
['상관 식별자', panelParams('epc', 'ITEM-1', [{ name: 'correlationId', operator: 'eq', value: 'C1' }]), DESC],
|
|
68
|
+
['축 둘', { filters: [{ name: 'epc', operator: 'eq', value: 'A' }, { name: 'orderId', operator: 'eq', value: 'B' }], sortings: DESC }, DESC],
|
|
69
|
+
['축 없음', { filters: [], sortings: DESC }, DESC],
|
|
70
|
+
['여러 값(in)', panelParams('epc', 'A').filters && { filters: [{ name: 'epc', operator: 'in', value: ['A', 'B'] }], sortings: DESC }, DESC],
|
|
71
|
+
['리비전 정렬', panelParams('epc', 'ITEM-1'), [{ name: 'revision', desc: true }]],
|
|
72
|
+
['오름차순 정렬', panelParams('epc', 'ITEM-1'), [{ name: 'eventTime', desc: false }]],
|
|
73
|
+
['정렬 둘', panelParams('epc', 'ITEM-1'), [{ name: 'eventTime', desc: true }, { name: 'revision', desc: true }]],
|
|
74
|
+
['시각 하한만', panelParams('epc', 'ITEM-1', [{ name: 'eventTime', operator: 'gte', value: '2026-08-01T00:00:00.000Z' }]), DESC],
|
|
75
|
+
['못 읽는 시각', panelParams('epc', 'ITEM-1', [{ name: 'eventTime', operator: 'lte', value: '어제' }]), DESC],
|
|
76
|
+
['빈 축 값', panelParams('epc', ' '), DESC]
|
|
77
|
+
]
|
|
78
|
+
for (const [why, params, sortings] of cases) {
|
|
79
|
+
assert.equal(readModelShape(params, sortings), null, `${why} 는 저널로 가야 한다`)
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('★ 적지 못한 줄이 있으면 이 경로를 쓰지 않는다 — 그 창에는 구멍이 있다', () => {
|
|
84
|
+
assert.match(
|
|
85
|
+
fastPath,
|
|
86
|
+
/subjectWriteFailures\(domainId, instanceId\) > 0\) return null/,
|
|
87
|
+
'실패가 세어져 있으면 온전한 쪽(저널)으로 보내야 한다'
|
|
88
|
+
)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('★ 원시 값을 직접 날짜로 읽지 않는다 — 시간대가 어긋난다', () => {
|
|
92
|
+
/*
|
|
93
|
+
* sqlite 가 주는 값은 시간대가 없는 문자열이고, `new Date()` 로 읽으면 한국 시간으로 해석해 UTC 로
|
|
94
|
+
* 9시간을 뺀다. 실측: 커서의 시각이 9시간 이르게 나가 이어 읽기가 구간을 건너뛰고 본 줄이 또 나왔다
|
|
95
|
+
* (120건 중 15건). 그래서 경계는 엔티티로 받고, 커서의 시각은 저널 줄에서 가져온다.
|
|
96
|
+
*/
|
|
97
|
+
/* 경계는 엔티티로 받는다 — 숫자(리비전)는 원시 값이어도 뜻이 변하지 않으므로 날짜에만 걸린다. */
|
|
98
|
+
assert.doesNotMatch(fastPath, /getRawOne<\{ at:/, '날짜를 원시 값으로 받던 자리가 남아 있다')
|
|
99
|
+
assert.match(fastPath, /sub\?\.eventTime instanceof Date/)
|
|
100
|
+
assert.match(fastPath, /journal\?\.eventTime instanceof Date/)
|
|
101
|
+
assert.match(fastPath, /row\?\.eventTime instanceof Date/, '커서의 시각은 저널 줄의 날짜여야 한다')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('★ 담지 못한 구간을 「사건이 없다」로 그리게 만들지 않는다', () => {
|
|
105
|
+
/* 경계 아래를 물으면 물러난다. */
|
|
106
|
+
assert.match(fastPath, /cursor\.at\.getTime\(\) <= cov\.from\.getTime\(\)\) return null/)
|
|
107
|
+
assert.match(fastPath, /shape\.untilTime\.getTime\(\) < cov\.from\.getTime\(\)\) return null/)
|
|
108
|
+
/*
|
|
109
|
+
* 이 대상의 줄을 다 냈을 때가 가장 위험한 자리다. 커서를 내지 않으면 화면은 「여기서 끝」으로 읽고,
|
|
110
|
+
* 경계 아래에 저널만 아는 사건이 있으면 그것이 거짓이 된다.
|
|
111
|
+
*/
|
|
112
|
+
assert.match(fastPath, /boundaryCursor\(cov\.from\)/, '짧게 끝난 페이지는 경계 아래를 읽을 커서를 내야 한다')
|
|
113
|
+
assert.match(fastPath, /function boundaryCursor/, '그 커서를 만드는 곳이 있어야 한다')
|
|
114
|
+
/* 저널의 처음까지 덮을 때만 끝이다. */
|
|
115
|
+
assert.match(fastPath, /cov\.full\s*\n?\s*\?\s*undefined/, '덮고 있을 때만 커서를 내지 않는다')
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test('★ 전체 건수와 「더 있나」를 함께 정직하게 낸다 — 화면은 건수를 모를 때 capped 로 판단한다', () => {
|
|
119
|
+
/*
|
|
120
|
+
* 화면의 `hasMore` 는 전체 건수를 모르면 `capped` 를 본다. 그래서 「모른다」면서 `capped` 를 내리면
|
|
121
|
+
* 목록이 알리지 않고 끝난 것처럼 보인다. 두 칸이 한 판단에서 나와야 한다.
|
|
122
|
+
*/
|
|
123
|
+
assert.match(fastPath, /const capped = total === undefined/)
|
|
124
|
+
assert.match(fastPath, /if \(page\.fullyCovered\)/, '덮고 있을 때만 정확한 건수를 낸다')
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test('★ 화면이 부르는 자리에 붙어 있다 — 새 문을 열어 두고 기다리지 않는다', () => {
|
|
128
|
+
/* 이 한 줄이 없으면 테이블은 채워지고도 쓰이지 않는다(2026-08-27 에 실제로 그랬다). */
|
|
129
|
+
assert.match(journal, /const fast = await readModelPage\(domain\.id, instanceIds\[0\], effective, limit\)/)
|
|
130
|
+
assert.match(journal, /if \(fast\) return fast/)
|
|
131
|
+
/* 붙는 자리가 저널 질의를 만들기 **전**이어야 한다 — 뒤에 두면 느린 질의를 이미 치른 뒤다. */
|
|
132
|
+
assert.ok(
|
|
133
|
+
journal.indexOf('readModelPage(domain.id') < journal.indexOf('const qb = getQueryBuilderFromListParams'),
|
|
134
|
+
'저널 질의를 만들기 전에 물어야 한다'
|
|
135
|
+
)
|
|
136
|
+
/* 트윈 하나일 때만 — 여러 트윈을 합친 목록은 이 테이블의 물음이 아니다. */
|
|
137
|
+
assert.match(journal, /if \(instanceIds\.length === 1\) \{\s*\n\s*const fast = await readModelPage/)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test('품목만 식별자를 되돌린다 — 적는 쪽과 같은 함수로', () => {
|
|
141
|
+
/*
|
|
142
|
+
* 화면은 짧게 줄인 품목 식별자를 들고 있고(`net/class/product-M1`) 저장된 것은 전체 식별자다.
|
|
143
|
+
* 되돌림을 두 벌로 적으면 어느 날 서로 다른 답을 내고, 그때 이력이 빈다.
|
|
144
|
+
*/
|
|
145
|
+
assert.match(fastPath, /if \(kind !== 'item'\) return value/)
|
|
146
|
+
assert.match(fastPath, /itemResolverFor\(domainId, instanceId, det\.model\)/)
|
|
147
|
+
assert.match(fastPath, /if \(!itemOf\) return null/, '선언을 모르면 짐작하지 않고 물러난다')
|
|
148
|
+
})
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ★ 조회용 파생 표의 규율 — 앞으로만 채우고, 담지 않은 구간을 말한다.
|
|
3
|
+
*
|
|
4
|
+
* ── 왜 이 표를 만들었나 (2026-08-27 실측) ───────────────────────────────────
|
|
5
|
+
* 360 화면의 「이 대상의 이력」이 64초다. 「무엇으로 걸러 시각순으로 정렬해 앞에서 30개」라는 모양에
|
|
6
|
+
* 맞는 목차가 저널에 없고, 얹어도 풀리지 않는다.
|
|
7
|
+
*
|
|
8
|
+
* 여러 값으로 거르면(IN) 정렬 때문에 목차를 못 쓴다 — 실측한 질의 계획
|
|
9
|
+
* 축이 늘 때마다 저널의 목차가 늘어난다 — 360 의 축 넷이면 13개
|
|
10
|
+
* 품목과 로트가 한 문자열에 붙어 있어 품목만으로는 찾을 수 없다 — 정확일치가 0건
|
|
11
|
+
*
|
|
12
|
+
* ── 이 테스트가 무엇을 지키나 ───────────────────────────────────────────────
|
|
13
|
+
* DB 를 띄우지 않는다. 지켜야 할 것은 **규율**이고 그것은 순수하게 확인할 수 있다 — 목차가 조회 모양과
|
|
14
|
+
* 같은 순서인가, 소급 채우기를 하지 않는가, 담지 않은 구간을 말하는가, 저널이 먼저인가.
|
|
15
|
+
*
|
|
16
|
+
* 설계: `operato-twin/design/plans/read-models.md`
|
|
17
|
+
*/
|
|
18
|
+
import { test } from 'node:test'
|
|
19
|
+
import assert from 'node:assert/strict'
|
|
20
|
+
import { readFileSync } from 'node:fs'
|
|
21
|
+
import { join } from 'node:path'
|
|
22
|
+
|
|
23
|
+
const dir = (...p: string[]) => join(import.meta.dirname, '..', 'server', ...p)
|
|
24
|
+
const entity = readFileSync(dir('service', 'twin-subject', 'twin-subject-event.ts'), 'utf8')
|
|
25
|
+
const writer = readFileSync(dir('service', 'twin-subject', 'twin-subject-writer.ts'), 'utf8')
|
|
26
|
+
const query = readFileSync(dir('service', 'twin-subject', 'twin-subject-query.ts'), 'utf8')
|
|
27
|
+
const resolver = readFileSync(dir('service', 'twin-subject', 'item-resolver.ts'), 'utf8')
|
|
28
|
+
const engine = readFileSync(dir('engine', 'twin-engine.ts'), 'utf8')
|
|
29
|
+
|
|
30
|
+
test('★ 목차 순서가 조회 모양과 같다 — 이 순서가 이 표의 존재 이유다', () => {
|
|
31
|
+
/*
|
|
32
|
+
* 「이 트윈의 이 대상의 것을 최신순으로 30개」가 빠르려면 목차가 거르는 값 다음에 시각이어야 한다.
|
|
33
|
+
* 순서가 바뀌면 다시 정렬하게 되고, 그것이 저널에서 64초가 된 바로 그 원인이다.
|
|
34
|
+
*/
|
|
35
|
+
assert.match(
|
|
36
|
+
entity,
|
|
37
|
+
/ix_twin_subject_event_0'[\s\S]{0,160}?\[e\.domain, e\.instanceId, e\.subjectKind, e\.subjectId, e\.eventTime\]/,
|
|
38
|
+
'(도메인, 트윈, 종류, 식별자, 시각) 순서여야 한다'
|
|
39
|
+
)
|
|
40
|
+
/* 보관 상한을 지우는 축도 시각이 마지막이어야 한다(경계를 시각으로 찾으므로). */
|
|
41
|
+
assert.match(
|
|
42
|
+
entity,
|
|
43
|
+
/ix_twin_subject_event_1'[\s\S]{0,160}?\[e\.domain, e\.instanceId, e\.eventTime\]/,
|
|
44
|
+
'(도메인, 트윈, 시각) 목차가 있어야 한다'
|
|
45
|
+
)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('★ 시각 컬럼에 드라이버 방언을 적지 않는다 — datetime 은 Postgres 가 거절한다', () => {
|
|
49
|
+
const at = entity.indexOf('eventTime: Date')
|
|
50
|
+
assert.ok(at > 0, 'eventTime 이 있어야 한다')
|
|
51
|
+
const decl = entity.slice(Math.max(0, at - 400), at)
|
|
52
|
+
assert.doesNotMatch(decl, /type: 'datetime'/, 'sqlite·MySQL 의 이름을 적지 않는다')
|
|
53
|
+
assert.doesNotMatch(decl, /type: 'timestamp'/, 'Postgres 의 이름도 적지 않는다')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('★ 저널이 먼저다 — 이 표에 적다가 실패해도 저널 쓰기를 되돌리지 않는다', () => {
|
|
57
|
+
/* 사실의 원본은 저널이고 이 표는 다시 만들 수 있다. 반대로 두면 조회를 빠르게 하려고 만든 표가
|
|
58
|
+
사실을 잃는 원인이 된다. */
|
|
59
|
+
assert.match(writer, /catch \(err: any\)[\s\S]{0,200}?noteFailure/, '실패를 잡아 세어야 한다')
|
|
60
|
+
assert.match(writer, /return 0\s*\n\s*\}/, '실패해도 던지지 않는다')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('★ 알리지 않고 비지 않는다 — 적지 못한 줄 수를 조회가 함께 낸다', () => {
|
|
64
|
+
/* 알리지 않고 비면 화면이 「그 대상에는 사건이 없다」고 말하게 된다. 그것이 이 부류에서 가장 나쁜 결함이다. */
|
|
65
|
+
assert.match(writer, /export function subjectWriteFailures/, '적지 못한 수를 물을 수 있어야 한다')
|
|
66
|
+
assert.match(query, /writeFailures: subjectWriteFailures\(/, '조회가 그 수를 실어 보낸다')
|
|
67
|
+
assert.match(query, /writeFailures: number/, '응답에 그 칸이 있어야 한다')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('★ 담지 않은 구간을 말한다 — 앞으로만 채우므로 그 앞은 「없다」가 아니다', () => {
|
|
71
|
+
assert.match(query, /coveredFrom\?: Date/, '경계를 낼 칸이 있어야 한다')
|
|
72
|
+
assert.match(query, /orderBy\('s\.eventTime', 'ASC'\)[\s\S]{0,120}?limit\(1\)/, '가장 오래된 줄 하나로 구한다')
|
|
73
|
+
/* 저장하지 않고 조회할 때 구한다 — 저장한 값은 실제 자료와 어긋날 수 있다. */
|
|
74
|
+
assert.doesNotMatch(entity, /coveredFrom/, '경계를 표에 저장하지 않는다')
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('★ 종류가 닫힌 목록이고, 모르는 값은 빈 목록이 아니라 오류다', () => {
|
|
78
|
+
/* 빈 목록으로 답하면 오타가 「그 대상에는 사건이 없다」로 보인다. */
|
|
79
|
+
assert.match(query, /SUBJECT_KINDS as readonly string\[\]\)\.includes\(kind\)/, '닫힌 목록으로 검사한다')
|
|
80
|
+
assert.match(query, /throw new Error\(`unknown subjectKind/, '모르는 종류는 오류를 낸다')
|
|
81
|
+
assert.match(query, /subjectId is required/, '빈 식별자로 트윈 전체를 읽지 않는다')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test('★ 페이지를 시각으로 넘긴다 — 머리에 쌓이는 목록에서 건너뛰기는 본 줄을 다시 낸다', () => {
|
|
85
|
+
assert.match(query, /eventTime < :before/, '시각 커서로 넘긴다')
|
|
86
|
+
assert.doesNotMatch(query, /\.skip\(/, '건너뛰기(offset)를 쓰지 않는다')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('★ 품목은 선언과 견주어 판정한다 — 문자열을 자르지 않는다', () => {
|
|
90
|
+
/*
|
|
91
|
+
* 어느 부호가 로트를 가리키는지는 연결된 시스템마다 다르고 커널 계약이 정한 적이 없다. 한 시스템의
|
|
92
|
+
* 관습(`-lot-`)을 규칙으로 굳히면 이 코드가 그 시스템 위에 서게 된다.
|
|
93
|
+
*/
|
|
94
|
+
assert.match(resolver, /resolveByDeclaration\(itemRefOf\(epc\)/, '선언과 견주어 찾는다')
|
|
95
|
+
/* 주석은 그 부호를 설명으로 쓴다 — 코드에 있는지만 본다(주석 줄을 걷어 내고 검사한다). */
|
|
96
|
+
const code = resolver
|
|
97
|
+
.split('\n')
|
|
98
|
+
.filter(l => !/^\s*(\*|\/\*|\/\/)/.test(l))
|
|
99
|
+
.join('\n')
|
|
100
|
+
assert.doesNotMatch(code, /'-lot-'|"-lot-"|split\('-'\)|indexOf\('-lot/, '문자열을 자르는 규칙을 넣지 않는다')
|
|
101
|
+
/* 사건마다 부르므로 기억해 둔다. 선언이 바뀌면 세대가 달라져 다시 판정한다. */
|
|
102
|
+
assert.match(resolver, /generation !== generation|entry\.generation !== generation/, '선언 세대를 본다')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test('★ 저널을 쓰는 두 문이 모두 이 표에 적는다 — 한쪽만 적으면 그만큼이 화면에서 빈다', () => {
|
|
106
|
+
const carried = engine.indexOf('static async persistCarried')
|
|
107
|
+
const batch = engine.indexOf('static async persistBatch')
|
|
108
|
+
assert.ok(carried > 0 && batch > 0, '두 문이 있어야 한다')
|
|
109
|
+
assert.match(engine.slice(carried, carried + 900), /this\.writeSubjects\(/, 'persistCarried 가 적는다')
|
|
110
|
+
assert.match(engine.slice(batch, batch + 900), /this\.writeSubjects\(/, 'persistBatch 가 적는다')
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test('★ 소급해서 채우지 않는다 — 마이그레이션이 표만 만든다', () => {
|
|
114
|
+
const migration = readFileSync(dir('migrations', '1786500000000-CreateTwinSubjectEvents.ts'), 'utf8')
|
|
115
|
+
assert.match(migration, /createTable/, '표를 만든다')
|
|
116
|
+
assert.match(migration, /createIndex/, '목차를 만든다')
|
|
117
|
+
/* 채우는 코드가 있으면 새 조회 축마다 3,128만 줄을 다시 만드는 구조가 된다. */
|
|
118
|
+
assert.doesNotMatch(migration, /INSERT|insert\(|from twin_events|TwinEvent/i, '기존 사건을 옮기지 않는다')
|
|
119
|
+
const list = readFileSync(dir('migrations', 'index.ts'), 'utf8')
|
|
120
|
+
assert.match(list, /CreateTwinSubjectEvents1786500000000/, '마이그레이션 목록에 배선돼 있어야 한다')
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('★ 이 표의 보관 정리는 체크포인트 상한을 보지 않는다 — 잃을 사실이 없다', () => {
|
|
124
|
+
/*
|
|
125
|
+
* 저널의 정리는 체크포인트가 반영하지 못한 사건을 지우지 않는다(그것을 지우면 사실이 사라진다).
|
|
126
|
+
* 이 표는 사건에서 파생된 값이라 지워도 사실이 사라지지 않는다 — 그 구간의 조회가 느려질 뿐이다.
|
|
127
|
+
*/
|
|
128
|
+
assert.match(writer, /export async function pruneSubjectRows/, '정리 함수가 있어야 한다')
|
|
129
|
+
assert.doesNotMatch(writer, /revision <= :upTo/, '체크포인트 상한을 보지 않는다')
|
|
130
|
+
assert.match(writer, /orderBy\('s\.eventTime', 'ASC'\)/, '경계를 시각으로 찾는다 — 목차를 쓰려면 그래야 한다')
|
|
131
|
+
/* 저널 정리와 같은 창에서 돈다 — 두 표가 같은 보관 기간을 본다. */
|
|
132
|
+
assert.match(engine, /pruneSubjectRows\(/, '저널 정리 창에서 함께 부른다')
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('★ 어디까지 옮겼는지 이 프로세스가 든다 — 파생 테이블의 내용으로 알아내지 않는다', () => {
|
|
136
|
+
/*
|
|
137
|
+
* 저장하지 않고 파생 테이블의 내용으로 진행 지점을 알아내려 했고 세 번 틀렸다.
|
|
138
|
+
*
|
|
139
|
+
* ① 최대 리비전 뒤부터 실시간으로 채워진 줄의 최댓값이 곧 저널의 끝이라 옮길 구간을 다 건너뛴다
|
|
140
|
+
* ② 최소 리비전 앞까지 1부터 적으면 최솟값이 1이 되어 구간이 0으로 접힌다
|
|
141
|
+
* ③ 최소 시각 아래로 사건이 시각순으로 오지 않는 트윈에서 곧바로 끝난다
|
|
142
|
+
*
|
|
143
|
+
* 채워진 것은 구간이 아니라 집합이다. 어느 축으로도 하나의 경계로 말할 수 없다.
|
|
144
|
+
*
|
|
145
|
+
* 그래서 알아내려 하지 않고 든다. **테이블에 적지 않는다**(사용자 판단) — 사건이 들어올 때 파생
|
|
146
|
+
* 테이블도 같이 적히므로, 옛 사건을 옮기는 일은 새 파생 테이블을 만들 때와 계산 규칙을 고쳤을 때뿐이다.
|
|
147
|
+
* 대가는 서버가 내려가면 그 몫을 다시 읽는 것이다.
|
|
148
|
+
*/
|
|
149
|
+
const backfill = readFileSync(dir('service', 'twin-subject', 'twin-subject-backfill.ts'), 'utf8')
|
|
150
|
+
assert.match(backfill, /const inFlight = new Map</, '이 프로세스가 든다')
|
|
151
|
+
assert.doesNotMatch(backfill, /TwinProjectionProgress|progressRepo/, '테이블에 적지 않는다')
|
|
152
|
+
assert.doesNotMatch(backfill, /MIN\(s\.revision\)|MAX\(s\.revision\)|MIN\(s\.eventTime\)/, '파생 테이블의 내용으로 알아내지 않는다')
|
|
153
|
+
/* 목표는 처음 부를 때 한 번 정한다 — 매번 다시 재면 새 사건이 들어와 끝나지 않는다. */
|
|
154
|
+
assert.match(backfill, /targetRevision: Number\(tip\?\.max \?\? 0\)/, '목표를 한 번 정한다')
|
|
155
|
+
/* 다 옮기면 들고 있지 않는다 — 남겨 두면 다음 호출이 옛 목표를 본다. */
|
|
156
|
+
assert.match(backfill, /if \(complete\) inFlight\.delete\(key\)/, '끝나면 버린다')
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
test('★ 목록의 순서가 같은 시각에서도 정해진다 — 페이지가 본 줄을 다시 내지 않게', () => {
|
|
160
|
+
assert.match(query, /addOrderBy\('s\.revision', 'DESC'\)/, '같은 시각은 리비전으로 가른다')
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
test('★ 선언한 엔티티를 등록한다 — 선언만 하면 테이블이 만들어지지 않는다', () => {
|
|
164
|
+
/*
|
|
165
|
+
* 파생 테이블 엔티티를 만들고 `entities` 에 넣지 않은 적이 있다. 그러면 테이블이 만들어지지 않고,
|
|
166
|
+
* 그것을 쓰는 코드가 「그런 테이블 없다」로 실패한다. 파일이 있는 것과 등록된 것은 다르다.
|
|
167
|
+
*/
|
|
168
|
+
const index = readFileSync(dir('service', 'twin-subject', 'index.ts'), 'utf8')
|
|
169
|
+
assert.match(index, /entities = \[TwinSubjectEvent\]/, '이 폴더의 엔티티가 등록돼 있어야 한다')
|
|
170
|
+
const services = readFileSync(dir('service', 'index.ts'), 'utf8')
|
|
171
|
+
assert.match(services, /\.\.\.TwinSubjectEntities/, '서비스 목록에도 실려야 한다')
|
|
172
|
+
/* 마이그레이션의 테이블 이름은 TypeORM 이 만든 것과 글자 그대로 같아야 한다. */
|
|
173
|
+
const migration = readFileSync(dir('migrations', '1786500000000-CreateTwinSubjectEvents.ts'), 'utf8')
|
|
174
|
+
assert.match(migration, /const TABLE = 'twin_subject_events'/, '실제 테이블 이름을 쓴다')
|
|
175
|
+
})
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* ① 개체를 말한 자리를 전부 읽는다 — 관측·집약(부모/자식)·변환(입력/출력).
|
|
6
6
|
* ② 부류 수량만 말한 사건은 「모른다」다 — 그 부류의 어느 개체인지 말하지 않았다.
|
|
7
7
|
* ③ 물품을 만지지 않는다고 **선언된** 사건은 빈 범위다(작업·설비·인원·자산·오더·품질·확인·에너지).
|
|
8
|
-
* ④ 낯선 낱말은 「모른다」다 — 커널이 사건을 늘렸을 때
|
|
8
|
+
* ④ 낯선 낱말은 「모른다」다 — 커널이 사건을 늘렸을 때 알리지 않고 빠뜨리지 않는다.
|
|
9
9
|
*
|
|
10
10
|
* 「모른다」(`null`)와 「하나도 없다」(빈 집합)는 **다른 사실**이다. 앞은 전부 다시 만들게 하고 뒤는
|
|
11
11
|
* 물품을 건너뛰게 한다 — 둘을 같게 두면 둘 중 하나가 반드시 틀린다.
|
|
@@ -53,7 +53,7 @@ test('물품을 만지지 않는다고 선언된 사건은 빈 범위다 — 전
|
|
|
53
53
|
assert.equal(energy!.size, 0)
|
|
54
54
|
})
|
|
55
55
|
|
|
56
|
-
test('낯선 낱말은 「모른다」다 — 커널이 사건을 늘렸을 때
|
|
56
|
+
test('낯선 낱말은 「모른다」다 — 커널이 사건을 늘렸을 때 알리지 않고 빠뜨리지 않는다', () => {
|
|
57
57
|
assert.equal(touchedItemKeys({ eventType: 'something.new', data: { epcList: ['x'] } }), null)
|
|
58
58
|
assert.equal(touchedItemKeys({ data: { epcList: ['x'] } }), null, '낱말이 없으면 판정할 수 없다')
|
|
59
59
|
assert.equal(touchedItemKeys(undefined), null)
|
package/test/twin-audit.test.ts
CHANGED
|
@@ -63,7 +63,7 @@ test('근거는 문자열일 때만 싣는다 — 객체를 그대로 넣어 화
|
|
|
63
63
|
assert.equal(bad.reason, undefined)
|
|
64
64
|
})
|
|
65
65
|
|
|
66
|
-
test('args 가 지나치게 크면 자르되 잘랐다고 알린다 —
|
|
66
|
+
test('args 가 지나치게 크면 자르되 잘랐다고 알린다 — 알리지 않고 버리지 않는다', () => {
|
|
67
67
|
const huge = { blob: 'x'.repeat(9000) }
|
|
68
68
|
const row = buildCommandAudit({ command: { type: 't', args: huge }, ack: {}, at: AT })
|
|
69
69
|
assert.equal(row.argsTruncated, true)
|
|
@@ -62,8 +62,50 @@ test('집합 이벤트는 parentID 를, 수량 이벤트는 epcClass 를 품목
|
|
|
62
62
|
assert.equal(epcOf({ data: { quantityList: [{ epcClass: 'urn:epc:idpat:sgtin:0614141.107346.*', quantity: 40 }] } }), 'urn:epc:idpat:sgtin:0614141.107346.*')
|
|
63
63
|
})
|
|
64
64
|
|
|
65
|
-
test('운영 델타의 오더는
|
|
66
|
-
|
|
65
|
+
test('★ 운영 델타의 오더는 `orderId` 다 — 이 시험이 결함을 고정하고 있었다', () => {
|
|
66
|
+
/*
|
|
67
|
+
* ── 무엇이 잘못돼 있었나 (2026-08-24 실측) ────────────────────────────────
|
|
68
|
+
* 이 시험은 `data: { order: 'SO-10021' }` 를 단정했다. **커널은 그런 모양을 낸 적이 없다** —
|
|
69
|
+
* 오더는 `orderId` 다(`OrderStatusDelta.orderId` · `TaskStatusDelta.orderId`). 픽스처가 실제 델타
|
|
70
|
+
* 대신 **스스로 지어낸 모양**을 썼고, 추출기도 같은 이름을 찾고 있었으니 둘이 나란히 초록이었다.
|
|
71
|
+
*
|
|
72
|
+
* 그동안 실 저널에서:
|
|
73
|
+
* order.status 29,403,565 행 — order_id 100% 비어 있음
|
|
74
|
+
* task.status 191,175 행 — 100% 비어 있음
|
|
75
|
+
*
|
|
76
|
+
* 색인 `ix_twin_event_4`(「이 오더가 어디까지 갔나」)가 자기 용도로 못 쓰였고, 화면은 오더를 눌러도
|
|
77
|
+
* 「연결된 이벤트가 없습니다」를 냈다.
|
|
78
|
+
*
|
|
79
|
+
* **교훈**: 승격 키의 픽스처는 **커널이 실제로 내는 델타**여야 한다. 지어낸 모양으로 검사하면
|
|
80
|
+
* 추출기와 시험이 함께 틀린 채로 영원히 초록이다.
|
|
81
|
+
*/
|
|
82
|
+
assert.equal(orderOf({ eventType: 'order.status', data: { orderId: 'WO-260410-0133', status: 'in-progress' } }), 'WO-260410-0133')
|
|
83
|
+
assert.equal(orderOf({ eventType: 'task.status', data: { taskId: 'TK-1', orderId: 'WO-1' } }), 'WO-1')
|
|
84
|
+
/* 오더를 스스로 말하면 그 말이 이긴다 — 거래(PO/SO)와 오더는 같은 것이 아닐 수 있다. */
|
|
85
|
+
assert.equal(
|
|
86
|
+
orderOf({ data: { orderId: 'WO-1', bizTransactionList: [{ bizTransaction: 'urn:epc:id:gdti:0614141.402.2' }] } }),
|
|
87
|
+
'WO-1'
|
|
88
|
+
)
|
|
89
|
+
/* 오더를 말하지 않는 EPCIS 는 거래로 내려간다(전과 같다). */
|
|
90
|
+
assert.equal(orderOf({ data: { bizTransactionList: [{ bizTransaction: 'PO-9' }] } }), 'PO-9')
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test('★ 승격 키는 **커널이 실제로 내는 델타 모양**으로 검사한다', () => {
|
|
94
|
+
/*
|
|
95
|
+
* 위 결함의 부류는 「추출기가 찾는 이름을 발신자가 쓰지 않는다」이고, 지어낸 픽스처로는 절대 잡히지
|
|
96
|
+
* 않는다. 그래서 실 저널에서 뜬 payload 모양을 그대로 놓고 단정한다 — 세 축이 실제로 채워지는지.
|
|
97
|
+
*/
|
|
98
|
+
const orderDelta = { eventType: 'order.status', data: { orderId: 'order-1', kind: 'workorder', status: 'in-progress' } }
|
|
99
|
+
const taskDelta = {
|
|
100
|
+
eventType: 'task.status',
|
|
101
|
+
data: { taskId: 'task-1', orderId: 'order-1', kind: 'cut', status: 'in-progress', resourceRef: 'cutter-1', fromNode: 'st-a', toNode: 'st-b' }
|
|
102
|
+
}
|
|
103
|
+
const energyEq = { eventType: 'energy.equipment', data: { equipmentId: 'pv-roof', at: '2026-08-17T10:45:20.664Z', generatedKW: 426 } }
|
|
104
|
+
|
|
105
|
+
assert.equal(twinEventKeys(orderDelta).orderId, 'order-1', '오더 사건이 오더 축을 채운다')
|
|
106
|
+
assert.equal(twinEventKeys(taskDelta).orderId, 'order-1', '작업도 자기 오더를 채운다 — 오더의 이력에 작업이 실린다')
|
|
107
|
+
assert.equal(twinEventKeys(taskDelta).moverId, 'cutter-1', '작업이 쓴 자원이 설비 축에 실린다(`resourceRef`)')
|
|
108
|
+
assert.equal(twinEventKeys(energyEq).moverId, 'pv-roof', '에너지 사건의 설비가 설비 축에 실린다(`equipmentId`)')
|
|
67
109
|
})
|
|
68
110
|
|
|
69
111
|
test('없는 값은 빈 문자열이 아니라 undefined — 결측과 빈값을 섞지 않는다', () => {
|
|
@@ -81,6 +123,10 @@ test('설비·무버 축 — 운영 델타의 moverId 를 뽑는다', () => {
|
|
|
81
123
|
assert.equal(equipmentIdOf(ev), 'fork-3')
|
|
82
124
|
const k = twinEventKeys(ev)
|
|
83
125
|
assert.equal(k.moverId, 'fork-3')
|
|
126
|
+
/* 개명 세대가 섞인다 — 읽는 쪽이 흡수한다(`moverId` 옛 채널 · `resourceRef` 작업 · `equipmentId` 에너지). */
|
|
127
|
+
assert.equal(equipmentIdOf({ data: { resourceRef: 'cutter-1' } }), 'cutter-1')
|
|
128
|
+
assert.equal(equipmentIdOf({ data: { equipmentId: 'pv-roof' } }), 'pv-roof')
|
|
129
|
+
assert.equal(equipmentIdOf({ data: { moverId: 'fork-1', resourceRef: 'x' } }), 'fork-1', '앞의 이름이 이긴다')
|
|
84
130
|
/* 운영 델타는 readPoint/bizLocation 이 없다 — 평범한 location 을 위치로 받는다.
|
|
85
131
|
* 빠뜨리면 "이 설비가 어디서 무엇을 했나" 가 위치 축에서 통째로 사라진다. */
|
|
86
132
|
assert.equal(k.locationId, 'rack-1')
|
|
@@ -94,7 +140,7 @@ test('payload 가 data 로 감싸이지 않은 모양도 받는다', () => {
|
|
|
94
140
|
assert.equal(epcOf({ epcList: ['urn:epc:id:sgtin:1.2.3'] }), 'urn:epc:id:sgtin:1.2.3')
|
|
95
141
|
})
|
|
96
142
|
|
|
97
|
-
test('상한을 넘는 값은
|
|
143
|
+
test('상한을 넘는 값은 오류 없이 사라지지 않는다 — 잘리되 경고가 남는다', () => {
|
|
98
144
|
const long = 'urn:epc:id:sgtin:' + 'x'.repeat(400)
|
|
99
145
|
const warned: string[] = []
|
|
100
146
|
const orig = console.warn
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* 여기서 지키는 것:
|
|
8
8
|
* · 어떻게 셌는지 밝힌다 — 행이면 DB 가 페이징, 문서면 서버가 통째로 읽어 잘랐다(`basis`)
|
|
9
|
-
* ·
|
|
9
|
+
* · 알리지 않고 자르지 않는다 — 총 건수가 항상 함께 오고, 훑기가 상한에 닿으면 그렇다고 말한다
|
|
10
10
|
* · 모르는 축은 **거절한다** — 빈 목록으로 답하면 오타 하나가 "우리 트윈엔 그게 없다" 로 굳는다
|
|
11
11
|
* · 투영이 낡아도 항목은 열린다 — 행에 없으면 문서에서 찾는다
|
|
12
12
|
* · 이어진 것은 **양방향**이고, 선언된 관계만 따른다(선언에 없는 길은 그리지 않는다)
|
|
@@ -141,7 +141,7 @@ test('비교하지 못했으면 관측은 null 이고, 답했으면 **근거를
|
|
|
141
141
|
* 이 픽스처에는 저널이 없다(행만 있다) — 되짚을 것이 없으므로 관측은 없다. **0 이나 빈 객체로
|
|
142
142
|
* 메우면 "관측했는데 아무것도 없었다" 로 읽힌다.**
|
|
143
143
|
*
|
|
144
|
-
* 예전 이 검사는 "트윈이 안 돌면 관측은 null" 이었다. 지금은 멈춘 트윈도 저널을
|
|
144
|
+
* 예전 이 검사는 "트윈이 안 돌면 관측은 null" 이었다. 지금은 멈춘 트윈도 저널을 계산해 답할 수 있고,
|
|
145
145
|
* 그 답이 **자기 근거를 달고 온다**(`observedBasis`). 그래서 규율을 그 자리에 맞춰 다시 적는다:
|
|
146
146
|
* 답이 없으면 null, 답이 있으면 그것이 라이브인지 되짚은 것인지 말한다. 근거 없는 답은 없다.
|
|
147
147
|
*/
|
|
@@ -230,10 +230,10 @@ test('지도의 선이 진단이 된다 — 이어진 것뿐 아니라 **끊긴
|
|
|
230
230
|
* ★ 관측 열이 **자기 근거를 말한다** — 라이브인지 되짚은 것인지.
|
|
231
231
|
*
|
|
232
232
|
* 둘은 같은 모양의 값이라, 근거가 없으면 되짚은 값을 "지금 이렇다" 로 읽는다. 그래서 근거를 실을
|
|
233
|
-
* 자리가 없던 동안에는 멈춘 트윈의 저널을 아예
|
|
233
|
+
* 자리가 없던 동안에는 멈춘 트윈의 저널을 아예 계산하지 않았다(관측 열이 통째로 `—`). 자리를 만든 뒤의
|
|
234
234
|
* 규율은 하나다: **답하면 근거를 말한다.**
|
|
235
235
|
*/
|
|
236
|
-
test('멈춘 트윈도 저널을
|
|
236
|
+
test('멈춘 트윈도 저널을 계산해 답하고, 그 답은 다시 계산한 값이라고 알린다', async () => {
|
|
237
237
|
const { WmsKernel, EventJournal } = req('@operato/twin-kernel')
|
|
238
238
|
const model = {
|
|
239
239
|
spaceId: 'journal-space',
|
|
@@ -273,5 +273,5 @@ test('멈춘 트윈도 저널을 접어 답하고, 그 답은 다시 계산한
|
|
|
273
273
|
const it = await q.twinModelItem(INST, 'equipment', 'fk-1', ctx)
|
|
274
274
|
assert.ok(it?.observed, '저널이 있으면 멈춘 트윈도 마지막으로 알던 사실을 답할 수 있다')
|
|
275
275
|
assert.equal(it.observedBasis, 'journal', '라이브가 아니라 되짚은 것이라고 말해야 "지금 이렇다" 로 읽히지 않는다')
|
|
276
|
-
assert.ok(it.observedRevision > 0, '몇 번째 리비전까지
|
|
276
|
+
assert.ok(it.observedRevision > 0, '몇 번째 리비전까지 계산했는지 함께 말한다 — 규모의 한계도 그 수가 말한다')
|
|
277
277
|
})
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* 여기서 지키는 것:
|
|
9
9
|
* · 패턴을 코드가 알지 않는다 — 선언 그래프에서 자기 축으로 돌아오는 순환을 찾는다
|
|
10
|
-
* ·
|
|
10
|
+
* · **알리지 않고 고르지 않는다** — 순환이 여럿이면 후보를 내고 멈춘다(방향이 뒤집힌 전개는
|
|
11
11
|
* 틀린 답이 아니라 **반대의 답**이라 화면만 보고는 구별되지 않는다)
|
|
12
12
|
* · 수량은 곱해 내려가되, **모르면 곱하지 않는다**(1 로 메우면 윗단 합계가 통째로 거짓이 된다)
|
|
13
13
|
* · 사이클을 만나면 다시 파지 않고 말한다
|
|
@@ -153,7 +153,7 @@ test('사이클은 다시 탐색하지 않고 알린다', async () => {
|
|
|
153
153
|
await TwinEngine.ingestMaster(domainId, master(), 'reset')
|
|
154
154
|
})
|
|
155
155
|
|
|
156
|
-
test('상한에서 끊기면 끊겼다고 알린다 —
|
|
156
|
+
test('상한에서 끊기면 끊겼다고 알린다 — 알리지 않고 자르지 않는다', async () => {
|
|
157
157
|
const shallow = await explode('trailer', { maxDepth: 1 })
|
|
158
158
|
assert.equal(shallow.truncated.depth, true)
|
|
159
159
|
assert.deepEqual(shallow.items.filter((n: any) => n.depth === 2), [], '깊이 상한 아래는 안 판다')
|
|
@@ -174,3 +174,36 @@ test('선언에 없는 자재를 참조하면 그 사실이 드러난다', async
|
|
|
174
174
|
assert.equal(child.known, false, '선언에 없다는 사실을 함께 낸다(있는 척하지 않는다)')
|
|
175
175
|
await TwinEngine.ingestMaster(domainId, master(), 'reset')
|
|
176
176
|
})
|
|
177
|
+
|
|
178
|
+
test('★ 선언에 없는 축을 거치면 터지지 않고 **왜 못 하는지 말한다**', async () => {
|
|
179
|
+
/*
|
|
180
|
+
* ── 실측으로 터진 것 (2026-08-24) ────────────────────────────────────────
|
|
181
|
+
* 이 전개는 **트윈 모델의 선언**을 읽는다(`axisInfo(...).path` 로 모델 안을 찾아 들어간다). 그런데
|
|
182
|
+
* 커널 카탈로그의 축 중 셋은 **선언되는 것이 아니라 일어나는 것**이다 — `orders`·`tasks`·`items`
|
|
183
|
+
* 는 사건이 만들고 끝나면 사라진다. 모델이 그 목록을 가질 수 없으므로 `path` 가 비어 있다.
|
|
184
|
+
*
|
|
185
|
+
* Cannot read properties of undefined (reading 'split')
|
|
186
|
+
*
|
|
187
|
+
* ★ **터진 것은 요청한 축이 아니라 「거쳐 가는 축」이다.** `locations` 를 물으면 그 순환이 `tasks` 를
|
|
188
|
+
* 거치는데(작업이 `from`·`at` 으로 자리를 두 번 가리킨다) 그 `tasks` 에 경로가 없다. 그래서 요청한
|
|
189
|
+
* 축만 지켜서는 막히지 않는다 — 처음에 그렇게 고쳤다가 이 시험이 잡았다.
|
|
190
|
+
*
|
|
191
|
+
* 그리고 빈 트리로 답하면 화면이 「이어진 것이 없다」로 읽는데 그것은 거짓이다 — 작업은 자리를
|
|
192
|
+
* 가리키고 있고, 다만 이 질의가 그것을 아는 곳이 아니다.
|
|
193
|
+
*/
|
|
194
|
+
const q = new TwinModelTreeQuery()
|
|
195
|
+
const ctx = { state: { domain: { id: domainId } } } as any
|
|
196
|
+
|
|
197
|
+
/* 두 홉을 지정해야 순환이 하나로 정해진다 — 안 지정하면 앞 갈래(모호함)에서 답한다. */
|
|
198
|
+
const loc = await q.twinModelTree(INSTANCE, 'locations', 'st-a', ctx, 'twin.rel.from', 'twin.rel.at')
|
|
199
|
+
assert.ok(loc, 'locations: 터지지 않는다')
|
|
200
|
+
assert.deepEqual(loc.items, [], 'locations: 펼친 것이 없다')
|
|
201
|
+
assert.equal(loc.reason, 'not-declared-in-model', 'locations: 왜 못 하는지 말한다(거쳐 갈 축이 선언에 없다)')
|
|
202
|
+
|
|
203
|
+
/* 자기참조 순환이 아예 없는 축은 그 사실을 말한다 — 이유가 다르면 낱말도 달라야 한다. */
|
|
204
|
+
for (const axis of ['orders', 'tasks', 'items']) {
|
|
205
|
+
const r = await q.twinModelTree(INSTANCE, axis, 'anything', ctx)
|
|
206
|
+
assert.ok(r, `${axis}: 터지지 않는다`)
|
|
207
|
+
assert.equal(r.reason, 'no-recursive-relation', `${axis}: 순환이 없다는 사실을 말한다`)
|
|
208
|
+
}
|
|
209
|
+
})
|
|
@@ -75,7 +75,7 @@ test('인제스트가 원천을 각인한다 — 원천이 무엇이든 한 자
|
|
|
75
75
|
test('원천을 모르는 재프로비전이 이미 아는 것을 지우지 않는다 — 한 번 지우면 다시 못 읽는다', async () => {
|
|
76
76
|
/*
|
|
77
77
|
* 이 경로는 실제로 있다 — 원천을 모르는 호출자(스크립트·복구·옛 코드)가 같은 트윈을 다시
|
|
78
|
-
* 인제스트한다. 그때 `origin` 을 덮어 비우면 그 트윈은
|
|
78
|
+
* 인제스트한다. 그때 `origin` 을 덮어 비우면 그 트윈은 **알리지 않고 다시 읽을 수 없게 된다.**
|
|
79
79
|
* 그런 종류의 손상은 다음번에 다시 읽으려 할 때까지 아무도 모른다.
|
|
80
80
|
*/
|
|
81
81
|
await TwinEngine.ingestMaster(domainId, master(BORN, 5), 'reset')
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* ── 무엇을 단언하나 ────────────────────────────────────────────────────────
|
|
14
14
|
* 스냅샷의 오더가 남은 데맨드(requested − fulfilled)로 커널에 서고, 이행이 끝난 오더는 서지 않고,
|
|
15
|
-
* 원값이 없는 오더는
|
|
15
|
+
* 원값이 없는 오더는 알리지 않고 서지 않는다는 것. 숫자를 지어내지 않는다는 규칙이 이음매 끝까지 지켜지는가.
|
|
16
16
|
*/
|
|
17
17
|
import { test } from 'node:test'
|
|
18
18
|
import assert from 'node:assert/strict'
|