@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
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ★ 여러 종류를 시각순으로 — 거르는 축과 정렬 축이 어긋나면 색인이 버려진다.
|
|
3
|
+
*
|
|
4
|
+
* ── 실측 (2026-08-28 · 승화푸드 저널 3,374,894행) ───────────────────────────
|
|
5
|
+
* 종류 8개 IN + 시각순 50건 SEARCH USING INDEX ix_twin_event_1 (domain, instance) 37.181초 · 0건
|
|
6
|
+
* 같은 조건 · 정렬 없음 SEARCH USING INDEX ix_twin_event_7 (…, event_type=?) 0.003초
|
|
7
|
+
* 종류 하나 + 시각순 SEARCH USING INDEX ix_twin_event_7 (…, event_type=?) 0.003초
|
|
8
|
+
*
|
|
9
|
+
* 정렬을 붙이는 순간 여러 종류의 순서를 합칠 방법이 없어 시각 색인으로 337만 행을 걸으며 행마다 종류를
|
|
10
|
+
* 확인한다. 그 트윈에 에너지 사건은 **0건**이다 — 없는 것을 찾느라 37초를 쓴다.
|
|
11
|
+
*
|
|
12
|
+
* 360 이력이 64초였던 것과 같은 부류다. 고침도 같은 성질이다: **있는 색인을 쓰게 한다.**
|
|
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 { splitShape } from '../server/service/twin-journal/event-type-split-shape.js'
|
|
20
|
+
|
|
21
|
+
const dir = (...p: string[]) => join(import.meta.dirname, '..', 'server', ...p)
|
|
22
|
+
const split = readFileSync(dir('service', 'twin-journal', 'event-type-split.ts'), 'utf8')
|
|
23
|
+
const shape = readFileSync(dir('service', 'twin-journal', 'event-type-split-shape.ts'), 'utf8')
|
|
24
|
+
const journal = readFileSync(dir('service', 'twin-journal', 'twin-journal-query.ts'), 'utf8')
|
|
25
|
+
|
|
26
|
+
const DESC = [{ name: 'eventTime', desc: true }]
|
|
27
|
+
const ENERGY = ['energy.measured', 'energy.equipment', 'energy.demand.window', 'energy.peak']
|
|
28
|
+
|
|
29
|
+
test('★ 에너지 원장이 보내는 모양이 이 갈래로 간다', () => {
|
|
30
|
+
const shape = splitShape({ filters: [{ name: 'eventType', operator: 'in', value: ENERGY }] }, DESC)
|
|
31
|
+
assert.ok(shape)
|
|
32
|
+
assert.deepEqual(shape!.eventTypes, ENERGY)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('시간창이 함께 와도 간다 — 화면이 구간을 지정한다', () => {
|
|
36
|
+
const shape = splitShape(
|
|
37
|
+
{
|
|
38
|
+
filters: [
|
|
39
|
+
{ name: 'eventType', operator: 'in', value: ENERGY },
|
|
40
|
+
{ name: 'eventTime', operator: 'between', value: ['2026-08-01T00:00:00.000Z', '2026-08-02T00:00:00.000Z'] }
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
DESC
|
|
44
|
+
)
|
|
45
|
+
assert.ok(shape)
|
|
46
|
+
assert.equal(shape!.fromTime?.toISOString(), '2026-08-01T00:00:00.000Z')
|
|
47
|
+
assert.equal(shape!.untilTime?.toISOString(), '2026-08-02T00:00:00.000Z')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('★ 맞지 않는 물음은 일반 경로로 보낸다 — 억지로 답하면 목록이 알리지 않고 달라진다', () => {
|
|
51
|
+
const cases: [string, any, any[]][] = [
|
|
52
|
+
['종류가 하나', { filters: [{ name: 'eventType', operator: 'in', value: ['a'] }] }, DESC],
|
|
53
|
+
['종류가 너무 많다', { filters: [{ name: 'eventType', operator: 'in', value: Array.from({ length: 13 }, (_, i) => `t${i}`) }] }, DESC],
|
|
54
|
+
['종류 필터가 없다', { filters: [] }, DESC],
|
|
55
|
+
['정확일치', { filters: [{ name: 'eventType', operator: 'eq', value: 'a' }] }, DESC],
|
|
56
|
+
['제외 목록', { filters: [{ name: 'eventType', operator: 'notin_with_null', value: ENERGY }] }, DESC],
|
|
57
|
+
['자유 검색어', { search: '전력', filters: [{ name: 'eventType', operator: 'in', value: ENERGY }] }, DESC],
|
|
58
|
+
['다른 필터가 섞였다', { filters: [{ name: 'eventType', operator: 'in', value: ENERGY }, { name: 'epc', operator: 'search', value: 'x' }] }, DESC],
|
|
59
|
+
['리비전 정렬', { filters: [{ name: 'eventType', operator: 'in', value: ENERGY }] }, [{ name: 'revision', desc: true }]],
|
|
60
|
+
['오름차순', { filters: [{ name: 'eventType', operator: 'in', value: ENERGY }] }, [{ name: 'eventTime', desc: false }]],
|
|
61
|
+
['못 읽는 시각', { filters: [{ name: 'eventType', operator: 'in', value: ENERGY }, { name: 'eventTime', operator: 'lte', value: '어제' }] }, DESC]
|
|
62
|
+
]
|
|
63
|
+
for (const [why, params, sortings] of cases) {
|
|
64
|
+
assert.equal(splitShape(params, sortings), null, `${why} 는 일반 경로로 가야 한다`)
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('★ 종류를 정확일치로 묻는다 — 이 한 줄이 색인을 쓰게 한다', () => {
|
|
69
|
+
assert.match(split, /andWhere\('e\.eventType = :eventType', \{ eventType \}\)/)
|
|
70
|
+
assert.doesNotMatch(split, /eventType IN/, '나눈 뒤에 다시 여러 값으로 묻지 않는다')
|
|
71
|
+
/* 정렬 축이 색인의 마지막 칸과 같아야 한다. */
|
|
72
|
+
assert.match(split, /orderBy\('e\.eventTime', 'DESC'\)/)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('★ 한 갈래라도 못 읽으면 반쪽을 전부인 것처럼 내지 않는다', () => {
|
|
76
|
+
assert.match(split, /if \(!page\) return null/)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('★ 이어 읽기 지점을 모든 갈래에 같이 건다', () => {
|
|
80
|
+
/* 갈래마다 다르게 걸면 어떤 종류는 이미 지나간 구간을 다시 낸다. */
|
|
81
|
+
assert.match(split, /e\.eventTime < :curAt OR \(e\.eventTime = :curAt AND e\.\$\{tiebreak\} < :curTie\)/)
|
|
82
|
+
assert.match(split, /if \(!cursor\) return null/, '못 읽는 커서로는 답하지 않는다')
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('★ 나눌 수를 제한한다 — 나누는 것이 손해가 되는 지점이 있다', () => {
|
|
86
|
+
assert.match(shape, /export const MAX_SPLIT = 24/)
|
|
87
|
+
assert.match(split, /if \(!pairs \|\| pairs > MAX_SPLIT\) return null/)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test('★ 화면이 부르는 자리에 붙어 있다', () => {
|
|
91
|
+
assert.match(journal, /const split = splitShape\(effective, effective\.sortings \?\? \[\]\)/)
|
|
92
|
+
assert.match(journal, /await eventTypeSplitPage\(domain\.id, instanceIds, split, limit, tiebreak, effective\.pagination\?\.after\)/)
|
|
93
|
+
/* 파생 테이블 갈래보다 뒤, 일반 저널 질의보다 앞이어야 한다. */
|
|
94
|
+
assert.ok(journal.indexOf('readModelPage(domain.id') < journal.indexOf('splitShape(effective'))
|
|
95
|
+
assert.ok(journal.indexOf('splitShape(effective') < journal.indexOf('const qb = getQueryBuilderFromListParams'))
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test('★ 걷을 조건이 적혀 있다 — 부르는 데가 없어지면 지운다', () => {
|
|
99
|
+
/*
|
|
100
|
+
* 앱에서 종류를 여러 값으로 보내는 곳은 에너지 원장 한 곳뿐이다. 그 화면이 종류를 하나씩 고르는
|
|
101
|
+
* 모양으로 바뀌면 이 갈래는 아무도 부르지 않는다 — 그때 지워야 한다. 그 조건을 코드에 적어 두지
|
|
102
|
+
* 않으면 아무것도 하지 않는 갈래가 무언가를 받쳐 주는 것처럼 남는다.
|
|
103
|
+
*/
|
|
104
|
+
assert.match(split, /이 갈래를 걷을 조건/)
|
|
105
|
+
assert.match(split, /그때 \*\*지운다\.\*\*/)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
/* ── 공정 단계 칩 (2026-08-29) ─────────────────────────────────────────────
|
|
109
|
+
*
|
|
110
|
+
* ★ 칩을 고른 순간 이 갈래가 꺼져서, **고르지 않은 쪽이 더 빨랐다.**
|
|
111
|
+
*
|
|
112
|
+
* 원장은 종류 넷을 담아 묻고 그 위에 단계 칩을 겹친다. 앞 판은 `bizStep` 필터를 만나면 모양을
|
|
113
|
+
* 알아보지 못해 예전 경로로 떨어졌다 — 좁혀 물으면 느려지는 화면이 된다.
|
|
114
|
+
*/
|
|
115
|
+
|
|
116
|
+
test('공정 단계 하나로 좁혀도 갈래 경로가 답한다', () => {
|
|
117
|
+
const shape = splitShape(
|
|
118
|
+
{
|
|
119
|
+
filters: [
|
|
120
|
+
{ name: 'eventType', operator: 'in', value: ['epcis.ObjectEvent', 'epcis.AggregationEvent'] },
|
|
121
|
+
{ name: 'bizStep', operator: 'eq', value: 'shipping' }
|
|
122
|
+
]
|
|
123
|
+
} as any,
|
|
124
|
+
[{ name: 'eventTime', desc: true }] as any
|
|
125
|
+
)
|
|
126
|
+
assert.ok(shape, '이 모양을 알아봐야 한다')
|
|
127
|
+
assert.equal(shape?.bizStep, 'shipping')
|
|
128
|
+
assert.deepEqual(shape?.eventTypes, ['epcis.ObjectEvent', 'epcis.AggregationEvent'])
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
test('단계를 여러 값으로 주면 답하지 않는다 — 갈래가 곱해진다', () => {
|
|
132
|
+
const shape = splitShape(
|
|
133
|
+
{
|
|
134
|
+
filters: [
|
|
135
|
+
{ name: 'eventType', operator: 'in', value: ['epcis.ObjectEvent', 'epcis.AggregationEvent'] },
|
|
136
|
+
{ name: 'bizStep', operator: 'in', value: ['shipping', 'receiving'] }
|
|
137
|
+
]
|
|
138
|
+
} as any,
|
|
139
|
+
[{ name: 'eventTime', desc: true }] as any
|
|
140
|
+
)
|
|
141
|
+
assert.equal(shape, null)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
test('빈 단계 값은 받지 않는다 — 모든 행을 뜻하는지 없는 단계를 뜻하는지 알 수 없다', () => {
|
|
145
|
+
const shape = splitShape(
|
|
146
|
+
{
|
|
147
|
+
filters: [
|
|
148
|
+
{ name: 'eventType', operator: 'in', value: ['epcis.ObjectEvent', 'epcis.AggregationEvent'] },
|
|
149
|
+
{ name: 'bizStep', operator: 'eq', value: ' ' }
|
|
150
|
+
]
|
|
151
|
+
} as any,
|
|
152
|
+
[{ name: 'eventTime', desc: true }] as any
|
|
153
|
+
)
|
|
154
|
+
assert.equal(shape, null)
|
|
155
|
+
})
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 마감된 구간 사실의 **범위 배선** — 유입을 부르는 곳은 모두 어느 트윈인지 말해야 한다.
|
|
3
|
+
*
|
|
4
|
+
* ── 왜 이 시험이 있나 (2026-08-30) ────────────────────────────────────────
|
|
5
|
+
* 커널은 `new Kernel(domainId, …)` 으로 서므로 자기가 어느 트윈인지 모른다. 설비 번호·계량 지점 번호는
|
|
6
|
+
* 트윈 안에서만 통하는 이름표라, 범위를 넘기지 않으면 **서로 다른 설비의 같은 날이 한 사실**이 된다.
|
|
7
|
+
* 실측으로 그 일이 났다 — 두 발전소의 `002` 가 겹쳐, 저널에 6줄이 있는데 서로 다른 이름은 5개였다.
|
|
8
|
+
* 여러 현장을 함께 계산하는 성과 화면은 이름으로 중복을 걷어내므로 한쪽 발전량이 사라진다.
|
|
9
|
+
*
|
|
10
|
+
* 배선은 조용히 빠진다. 인자를 안 넘겨도 컴파일되고, 시험도 초록이고, 실 서버에서 값이 하나 사라질
|
|
11
|
+
* 뿐이다. 그래서 **부르는 자리를 세어서** 지킨다 — `ingest-wiring-guard` 와 같은 부류다.
|
|
12
|
+
*/
|
|
13
|
+
import { test } from 'node:test'
|
|
14
|
+
import assert from 'node:assert/strict'
|
|
15
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs'
|
|
16
|
+
import { fileURLToPath } from 'node:url'
|
|
17
|
+
import { dirname, join } from 'node:path'
|
|
18
|
+
|
|
19
|
+
import { resolveSubject, ingestEnergyGenerationPeriodRecords } from '@operato/ops-contract'
|
|
20
|
+
|
|
21
|
+
const here = dirname(fileURLToPath(import.meta.url))
|
|
22
|
+
const serverDir = join(here, '..', 'server')
|
|
23
|
+
|
|
24
|
+
function allTs(dir: string, out: string[] = []): string[] {
|
|
25
|
+
for (const name of readdirSync(dir)) {
|
|
26
|
+
const full = join(dir, name)
|
|
27
|
+
if (statSync(full).isDirectory()) allTs(full, out)
|
|
28
|
+
else if (name.endsWith('.ts')) out.push(full)
|
|
29
|
+
}
|
|
30
|
+
return out
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 주석을 걷어낸다 — **설명에 적힌 이름을 호출로 세지 않는다.**
|
|
35
|
+
*
|
|
36
|
+
* 처음 이 시험을 만들었을 때 `reference-live.ts` 의 흐름 설명 한 줄(`ingestCanonicalRecords(정규→봉투)`)을
|
|
37
|
+
* 배선 빠진 호출로 잡았다. 주석을 고치게 만드는 시험은 아무것도 지키지 않는다.
|
|
38
|
+
*/
|
|
39
|
+
function withoutComments(src: string): string {
|
|
40
|
+
return src.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:])\/\/[^\n]*/g, '$1')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
test('★ 유입을 부르는 곳은 모두 어느 트윈인지 말한다 — 안 말하면 두 현장의 같은 번호가 겹친다', () => {
|
|
44
|
+
const bad: string[] = []
|
|
45
|
+
for (const file of allTs(serverDir)) {
|
|
46
|
+
if (file.endsWith('canonical-ingest.ts')) continue // 정의하는 자리
|
|
47
|
+
const src = withoutComments(readFileSync(file, 'utf-8'))
|
|
48
|
+
let i = src.indexOf('ingestCanonicalRecords(')
|
|
49
|
+
while (i >= 0) {
|
|
50
|
+
/* 호출 하나를 괄호 균형으로 잘라 낸다 — 줄바꿈이 있어도 본다. */
|
|
51
|
+
let depth = 0
|
|
52
|
+
let j = i + 'ingestCanonicalRecords'.length
|
|
53
|
+
for (; j < src.length; j++) {
|
|
54
|
+
if (src[j] === '(') depth++
|
|
55
|
+
else if (src[j] === ')') {
|
|
56
|
+
depth--
|
|
57
|
+
if (depth === 0) break
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const call = src.slice(i, j + 1)
|
|
61
|
+
if (!call.includes('factScope')) {
|
|
62
|
+
bad.push(`${file.slice(serverDir.length + 1)}: ${call.replace(/\s+/g, ' ').slice(0, 90)}`)
|
|
63
|
+
}
|
|
64
|
+
i = src.indexOf('ingestCanonicalRecords(', j)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
assert.deepEqual(
|
|
68
|
+
bad,
|
|
69
|
+
[],
|
|
70
|
+
`범위를 넘기지 않는 유입 호출:\n${bad.join('\n')}\n` +
|
|
71
|
+
'TwinEngine.factScope(domainId, instanceId) 를 넷째 인자로 넘겨라 — ' +
|
|
72
|
+
'넘기지 않으면 다른 현장의 같은 설비 번호가 한 사실로 겹치고, 성과 화면에서 한쪽이 사라진다.'
|
|
73
|
+
)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('★ 범위를 넘기면 두 현장의 같은 번호가 다른 사실이다', () => {
|
|
77
|
+
const rec = { equipmentId: '002', from: '2026-08-28T15:00:00.000Z', to: '2026-08-29T15:00:00.000Z', kWh: 400.6 }
|
|
78
|
+
const a = ingestEnergyGenerationPeriodRecords(rec, { tenantId: 'd', scopeId: 'twin-a' }).accepted[0]
|
|
79
|
+
const b = ingestEnergyGenerationPeriodRecords(rec, { tenantId: 'd', scopeId: 'twin-b' }).accepted[0]
|
|
80
|
+
assert.notEqual(a.eventId, b.eventId)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('선언된 정체성이 있으면 범위와 무관하게 한 사실이다 — 그릇이 정체성을 정하지 않는다', () => {
|
|
84
|
+
const rec = { equipmentId: '002', from: '2026-08-28T15:00:00.000Z', to: '2026-08-29T15:00:00.000Z', kWh: 400.6 }
|
|
85
|
+
const identityOf = () => 'https://example.com/obj/inv-002'
|
|
86
|
+
const a = ingestEnergyGenerationPeriodRecords(rec, { tenantId: 'd', scopeId: 'twin-a', identityOf }).accepted[0]
|
|
87
|
+
const b = ingestEnergyGenerationPeriodRecords(rec, { tenantId: 'd', scopeId: 'twin-b', identityOf }).accepted[0]
|
|
88
|
+
assert.equal(a.eventId, b.eventId)
|
|
89
|
+
assert.equal((a.data as any).subjectBasis, 'declared')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('커널이 범위를 답할 자리가 있다 — 호스트가 그것을 넘긴다', () => {
|
|
93
|
+
const r = resolveSubject('equipment', '002', { tenantId: 'd', scopeId: 'ppms-호현' })
|
|
94
|
+
assert.equal(r.basis, 'twin-local')
|
|
95
|
+
assert.equal(r.subject, 'ppms-호현/002')
|
|
96
|
+
})
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ★ 저장본 뒤의 사건 — 계산했든 못 했든 **말한다**.
|
|
3
|
+
*
|
|
4
|
+
* ── 무엇이 침묵했나 (2026-08-28 실측) ───────────────────────────────────────
|
|
5
|
+
* 미러는 저장본에서 상태를 되세우고 그 뒤의 사건만 따라 계산한다. 그 자리가 이랬다.
|
|
6
|
+
*
|
|
7
|
+
* 10:34:37 found cached state → revision 3372228
|
|
8
|
+
* 10:34:37 resumed from the stored fold seed at revision 3372228 — items 2665 · orders 4794
|
|
9
|
+
*
|
|
10
|
+
* 그 시각 저널의 머리는 3,374,894였다. 2,666건이 저장본보다 앞서 있었는데 **계산했다는 줄도, 못
|
|
11
|
+
* 했다는 줄도 없었다.** 갈래가 셋인데 말하는 갈래가 하나뿐이었기 때문이다.
|
|
12
|
+
*
|
|
13
|
+
* ① 계산해서 나아갔다 → 말한다
|
|
14
|
+
* ② 계산이 오류로 실패했다 → 말한다
|
|
15
|
+
* ③ 오류 없이 끝났는데 그대로 → **아무 말도 없었다** ← 여기
|
|
16
|
+
* ④ 저널 머리를 못 읽었다 → **아무 말도 없었다** ← 여기
|
|
17
|
+
*
|
|
18
|
+
* ③④에서 트윈은 그 사건들의 결과 없이 뜨고, 화면은 그것을 「없는 사실」로 그린다.
|
|
19
|
+
*/
|
|
20
|
+
import { test } from 'node:test'
|
|
21
|
+
import assert from 'node:assert/strict'
|
|
22
|
+
import { readFileSync } from 'node:fs'
|
|
23
|
+
import { join } from 'node:path'
|
|
24
|
+
|
|
25
|
+
const engine = readFileSync(join(import.meta.dirname, '..', 'server', 'engine', 'twin-engine.ts'), 'utf8')
|
|
26
|
+
|
|
27
|
+
test('★ 저널 머리를 못 읽었으면 그 사실을 말하고 그대로 뜬다', () => {
|
|
28
|
+
assert.match(engine, /if \(!tip\) \{/, '못 읽은 갈래가 있어야 한다')
|
|
29
|
+
assert.match(engine, /could not read the journal tip/, '무엇을 못 했는지 말해야 한다')
|
|
30
|
+
assert.match(engine, /without folding what came after it/, '무엇을 잃는지 말해야 한다')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('★ 계산이 아무 일도 하지 않았으면 그것도 말한다', () => {
|
|
34
|
+
assert.match(engine, /\} else if \(!failed\) \{/, '오류가 아닌데 나아가지 않은 갈래가 있어야 한다')
|
|
35
|
+
assert.match(engine, /left the seed at revision/)
|
|
36
|
+
/* 오류로 실패한 것을 두 번 말하지 않는다 — 이미 위에서 말했다. */
|
|
37
|
+
assert.match(engine, /let failed: string \| undefined/)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('★ 부팅마다 부르는 질의를 관계로 묻지 않는다 — 이 파일이 스스로 금지한 모양이다', () => {
|
|
41
|
+
/*
|
|
42
|
+
* `findOne({ where: { domain: { id } } })` 는 안쪽에 상한이 없는 두 단계 질의가 된다(실측 12초,
|
|
43
|
+
* 저널 375만 행). `tipOf` 는 부팅마다 트윈마다 불리므로 그 한 번이 부팅을 붙잡는다.
|
|
44
|
+
*/
|
|
45
|
+
const from = engine.indexOf('private static async tipOf')
|
|
46
|
+
assert.ok(from > 0, 'tipOf 가 있어야 한다')
|
|
47
|
+
const tipOf = engine.slice(from, from + 2600)
|
|
48
|
+
/* 주석에 인용된 나쁜 모양과 헷갈리지 않게, 실제 코드 모양만 본다. */
|
|
49
|
+
assert.doesNotMatch(tipOf, /findOne\(\{ where: \{ domain: \{ id: domainId \}/, '관계로 묻는 모양이 남아 있다')
|
|
50
|
+
assert.match(tipOf, /createQueryBuilder\('e'\)/)
|
|
51
|
+
assert.match(tipOf, /orderBy\('e\.revision', 'DESC'\)/)
|
|
52
|
+
assert.match(tipOf, /\.limit\(1\)/)
|
|
53
|
+
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* 예측 틱 크기 — **지평선이 정하고, 양끝은 묶인다.**
|
|
3
3
|
*
|
|
4
|
-
* 이 규칙이 틀리면 예측이
|
|
4
|
+
* 이 규칙이 틀리면 예측이 알리지 않고 비싸지거나(1초 고정 시절 9,000틱·28.5초) 알리지 않고 성글어진다
|
|
5
5
|
* (짧은 작업이 한 틱에 삼켜진다). 둘 다 화면에는 그럴듯한 숫자로 나오므로 시험이 지킨다.
|
|
6
6
|
*/
|
|
7
7
|
import { test } from 'node:test'
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* **창에 다 들어오지 않은 발전 기간을 조용히 버리지 않는다.**
|
|
3
|
+
*
|
|
4
|
+
* ── 무엇이 일어났나 (2026-08-30) ──────────────────────────────────────────
|
|
5
|
+
* 어제 발전량이 저널에 멀쩡히 있는데 화면이 비어 있었다. 발전 기간은 하루 단위이고(`08-28 15:00 →
|
|
6
|
+
* 08-29 15:00`), 성과 구간은 「지금부터 24시간 전」이라 한낮에서 시작한다(`08-29 02:53 → 08-30 02:53`).
|
|
7
|
+
* 하루 기간이 그 구간에 **온전히 들어오지 않는다.**
|
|
8
|
+
*
|
|
9
|
+
* 더하지 않는 것 자체는 맞다 — 걸친 기간을 더하면 다른 날의 값이 이 창의 값인 척한다. 틀린 것은
|
|
10
|
+
* **아무 말도 하지 않은 것**이다. 사용자는 빈 화면을 「발전량이 없다」로 읽는다. 그 둘은 완전히
|
|
11
|
+
* 다른 상황이다.
|
|
12
|
+
*/
|
|
13
|
+
import { test } from 'node:test'
|
|
14
|
+
import assert from 'node:assert/strict'
|
|
15
|
+
|
|
16
|
+
import { foldKpi } from '../server/engine/kpi-fold.ts'
|
|
17
|
+
|
|
18
|
+
const T = (iso: string) => Date.parse(iso)
|
|
19
|
+
|
|
20
|
+
/** 실측값 — 오창의 8월 29일 하루. */
|
|
21
|
+
const DAY_START = '2026-08-28T15:00:00.000Z'
|
|
22
|
+
const DAY_END = '2026-08-29T15:00:00.000Z'
|
|
23
|
+
|
|
24
|
+
const period = (equipmentId: string, kWh: number) => ({
|
|
25
|
+
eventType: 'energy.generation.period',
|
|
26
|
+
eventTime: DAY_END,
|
|
27
|
+
payload: {
|
|
28
|
+
eventId: `id|${equipmentId}`,
|
|
29
|
+
data: {
|
|
30
|
+
equipmentId,
|
|
31
|
+
kWh,
|
|
32
|
+
periodStart: DAY_START,
|
|
33
|
+
periodEnd: DAY_END,
|
|
34
|
+
observedFrom: DAY_START,
|
|
35
|
+
observedTo: DAY_END,
|
|
36
|
+
accumulation: 'daily',
|
|
37
|
+
boundary: 'offset'
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const EVENTS = [period('002', 127.1), period('003', 104.1), period('004', 312.4), period('005', 102.3)] as any
|
|
43
|
+
|
|
44
|
+
test('★ 창이 기간을 온전히 담으면 더한다', () => {
|
|
45
|
+
const r = foldKpi(EVENTS, { fromMs: T(DAY_START), toMs: T(DAY_END) })
|
|
46
|
+
const g = r.energy?.generated
|
|
47
|
+
assert.ok(g, '발전량이 있어야 한다')
|
|
48
|
+
assert.equal(g.counted, 4)
|
|
49
|
+
assert.equal(Math.round(g.kWh * 10) / 10, 645.9)
|
|
50
|
+
assert.equal(g.notCounted, undefined, '버린 것이 없으면 말하지 않는다')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('★ 창이 기간을 반만 담으면 더하지 않되 **버렸다고 말한다**', () => {
|
|
54
|
+
/* 실제로 났던 구간 — 하루 기간의 한가운데에서 시작한다. */
|
|
55
|
+
const r = foldKpi(EVENTS, { fromMs: T('2026-08-29T02:53:00.000Z'), toMs: T('2026-08-30T02:53:00.000Z') })
|
|
56
|
+
const g = r.energy?.generated
|
|
57
|
+
assert.ok(g, '값이 없다고 아무것도 내지 않으면 화면은 「발전량 없음」으로 읽는다')
|
|
58
|
+
assert.equal(g.kWh, 0, '걸친 기간은 더하지 않는다 — 더하면 다른 날 값이 섞인다')
|
|
59
|
+
assert.equal(g.counted, 0)
|
|
60
|
+
assert.ok(g.notCounted, '버린 것을 말해야 한다')
|
|
61
|
+
assert.equal(g.notCounted.periods, 4)
|
|
62
|
+
assert.equal(Math.round(g.notCounted.kWh * 10) / 10, 645.9, '얼마를 못 세었는지 말한다')
|
|
63
|
+
assert.equal(g.notCounted.earliestStart, DAY_START)
|
|
64
|
+
assert.equal(g.notCounted.latestEnd, DAY_END)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('창이 기간과 아예 겹치지 않으면 말할 것이 없다 — 없는 것을 만들지 않는다', () => {
|
|
68
|
+
const r = foldKpi(EVENTS, { fromMs: T('2026-07-01T00:00:00.000Z'), toMs: T('2026-07-02T00:00:00.000Z') })
|
|
69
|
+
assert.equal(r.energy?.generated, undefined)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('온전히 든 것과 걸친 것이 섞이면 더한 것과 못 센 것을 함께 낸다', () => {
|
|
73
|
+
const earlier = {
|
|
74
|
+
...period('002', 100),
|
|
75
|
+
eventTime: '2026-08-28T15:00:00.000Z',
|
|
76
|
+
payload: {
|
|
77
|
+
eventId: 'id|earlier',
|
|
78
|
+
data: {
|
|
79
|
+
equipmentId: '002',
|
|
80
|
+
kWh: 100,
|
|
81
|
+
periodStart: '2026-08-27T15:00:00.000Z',
|
|
82
|
+
periodEnd: '2026-08-28T15:00:00.000Z',
|
|
83
|
+
observedFrom: '2026-08-27T15:00:00.000Z',
|
|
84
|
+
observedTo: '2026-08-28T15:00:00.000Z',
|
|
85
|
+
accumulation: 'daily',
|
|
86
|
+
boundary: 'offset'
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const r = foldKpi([earlier, ...EVENTS] as any, {
|
|
91
|
+
fromMs: T('2026-08-27T15:00:00.000Z'),
|
|
92
|
+
toMs: T('2026-08-29T02:00:00.000Z')
|
|
93
|
+
})
|
|
94
|
+
const g = r.energy?.generated
|
|
95
|
+
assert.ok(g)
|
|
96
|
+
assert.equal(g.counted, 1, '27→28 하루만 온전히 들었다')
|
|
97
|
+
assert.equal(g.kWh, 100)
|
|
98
|
+
assert.equal(g.notCounted?.periods, 4, '28→29 넷은 창의 끝을 넘는다')
|
|
99
|
+
})
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
import { test } from 'node:test'
|
|
8
8
|
import assert from 'node:assert/strict'
|
|
9
9
|
|
|
10
|
-
import { StateProjector
|
|
10
|
+
import { StateProjector } from '@operato/twin-kernel'
|
|
11
|
+
import { objectEvent, BIZSTEP, DISP } from '@operato/ops-contract'
|
|
11
12
|
import { buildEntityDeltas } from '../shared/entity-delta.ts'
|
|
12
13
|
|
|
13
14
|
const T0 = '2026-07-19T00:00:00.000Z'
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 실제로 받는 판정은 그 앞에 한 층이 더 있다: **피드 사실을 어디서 얻는가**(`liveFeedStateOf` — 이 트윈이
|
|
6
6
|
* 미러인가 시뮬인가, 지금 도는가) 그리고 **장부를 어느 트윈의 것으로 찾는가**(도메인+인스턴스 키).
|
|
7
7
|
*
|
|
8
|
-
* 그 층이 틀리면 순수 시험은 전부 초록인 채로 화면만 거짓말한다. 실제로 두 가지가
|
|
8
|
+
* 그 층이 틀리면 순수 시험은 전부 초록인 채로 화면만 거짓말한다. 실제로 두 가지가 알리지 않고 틀릴 수 있다:
|
|
9
9
|
* ① 시뮬 트윈이 「연결 안 됨」으로 보인다 — 거짓 경보이고, 거짓 경보는 참 경보를 죽인다
|
|
10
10
|
* ② 남의 도메인 장부를 집는다 — 키를 도메인 없이 만들면 그렇게 된다
|
|
11
11
|
*
|
|
@@ -206,7 +206,7 @@ test('멈춘 미러는 「해당 없음」이다 — 고치려고 멈춰 둔 트
|
|
|
206
206
|
|
|
207
207
|
test('멈춘 트윈에 계속 들어오면 「버려지는 중」이라 말한다 — 실제로 겪은 거짓 안심', async () => {
|
|
208
208
|
/*
|
|
209
|
-
* 겪은 모습: 트윈이 멈췄는데 피드는 살아 레코드를 나르고, 커널은
|
|
209
|
+
* 겪은 모습: 트윈이 멈췄는데 피드는 살아 레코드를 나르고, 커널은 알리지 않고 버렸다. 화면은 「해당 없음 ·
|
|
210
210
|
* 150 통과 · 100%」였다. 여기서는 그 상황을 그대로 만들어 판정과 수치를 확인한다.
|
|
211
211
|
*/
|
|
212
212
|
/* 사람이 트윈을 멈춘다 — 그런데 피드는 남는다. 그 조합이 사실을 잃게 만든다. */
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* ── 왜 기계가 지켜야 하나 ───────────────────────────────────────────────────
|
|
5
5
|
* 피드를 여는 경로가 둘이다(커넥터 `openLiveFeed` · 시나리오 `twin-ingest` 태스크). 한쪽만 장부에
|
|
6
6
|
* 적으면 그 경로로 먹이는 트윈이 **「한 번도 받지 않은 것」**으로 보인다. 그 판정은 `unknown` 이고,
|
|
7
|
-
* `unknown` 은 화면에서 「정상」과 구별되지 않는다 — 즉
|
|
7
|
+
* `unknown` 은 화면에서 「정상」과 구별되지 않는다 — 즉 **오류 없이 틀린 답**이 된다.
|
|
8
8
|
*
|
|
9
9
|
* 세 번째 경로가 생길 때 사람이 이 사실을 기억할 것이라고 믿지 않는다. 인제스트를 부르는 자리를
|
|
10
10
|
* 소스에서 찾아 장부 기록이 함께 있는지 본다.
|
|
@@ -86,7 +86,7 @@ test('검사가 실제로 경로를 보고 있다 — 대상이 사라지면 초
|
|
|
86
86
|
|
|
87
87
|
test('유입 경로는 커널이 실제로 받은 수를 견준다 — 버려진 것을 통과로 세지 않게', () => {
|
|
88
88
|
/*
|
|
89
|
-
* 겪은 일: 트윈이 멈춘 뒤에도 피드는 레코드를 나르고 커널은
|
|
89
|
+
* 겪은 일: 트윈이 멈춘 뒤에도 피드는 레코드를 나르고 커널은 알리지 않고 버렸는데, 장부는 그것을 「통과」로
|
|
90
90
|
* 셌다. 화면은 멈춘 트윈 옆에 「150 통과 · 100%」라고 적었다 — 사실이 사라지는 동안 안심시킨 것이다.
|
|
91
91
|
*
|
|
92
92
|
* 고친 방법은 `ingestLive` 가 **넣은 수를 돌려주는** 것이다. 새 경로가 그 반환을 안 쓰고 0 을 넘기면
|
|
@@ -100,9 +100,11 @@ test('유입 경로는 커널이 실제로 받은 수를 견준다 — 버려진
|
|
|
100
100
|
missing,
|
|
101
101
|
[],
|
|
102
102
|
`커널이 받은 수를 견주지 않는 유입 경로 ${missing.length}곳:\n ${missing.join('\n ')}\n\n` +
|
|
103
|
-
`TwinEngine.ingestLive(...) 의 반환(넣은 수)을 받아 accepted.length 와의
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
`TwinEngine.ingestLive(...) 또는 ingestLiveResult(...) 의 반환(넣은 수)을 받아 accepted.length 와의 ` +
|
|
104
|
+
`차이를 undelivered 라는 이름으로 recordIngestResult(..., nowMs, undelivered) 에 넘긴다. ` +
|
|
105
|
+
`그러지 않으면 트윈이 멈춘 사이 버려진 레코드가 「통과」로 집계되어 화면이 100% 라고 말한다.\n\n` +
|
|
106
|
+
`이 검사는 **그 이름이 있는지**만 본다(소스를 글자로 본다). 이름을 적고 0 을 넘기면 통과한다 — ` +
|
|
107
|
+
`그 경우는 사람이 보는 수밖에 없다.`
|
|
106
108
|
)
|
|
107
109
|
})
|
|
108
110
|
|
|
@@ -53,7 +53,7 @@ test('오래 끊긴 피드는 건강했던 옛 창으로 초록을 내지 않는
|
|
|
53
53
|
assert.equal(
|
|
54
54
|
syncVerdictOf({ feedState: 'attached', recent: healthy, lastAtMs: 0, nowMs: STALE_AFTER_MS }),
|
|
55
55
|
'syncing',
|
|
56
|
-
'경계까지는 아직
|
|
56
|
+
'경계까지는 아직 들어온 것이 없다고 하지 않는다'
|
|
57
57
|
)
|
|
58
58
|
})
|
|
59
59
|
|
|
@@ -73,7 +73,7 @@ test('붙었지만 한 번도 받지 않은 것을 「유입 없음」이라 단
|
|
|
73
73
|
)
|
|
74
74
|
})
|
|
75
75
|
|
|
76
|
-
test('시뮬 트윈은 오래
|
|
76
|
+
test('시뮬 트윈은 오래 들어온 것이 없어도 「유입 없음」이 아니다', () => {
|
|
77
77
|
assert.equal(
|
|
78
78
|
syncVerdictOf({ feedState: 'not-applicable', recent: null, lastAtMs: -1, nowMs: STALE_AFTER_MS * 100 }),
|
|
79
79
|
'not-applicable'
|
|
@@ -82,7 +82,7 @@ test('시뮬 트윈은 오래 조용해도 「유입 없음」이 아니다', ()
|
|
|
82
82
|
|
|
83
83
|
test('들어오는데 트윈이 안 돌면 「버려지는 중」이다 — 「해당 없음」으로 안심시키지 않는다', () => {
|
|
84
84
|
/*
|
|
85
|
-
* 실제로 겪었다: 트윈이 멈춘 뒤에도 피드는 남아 레코드를 나르고, 커널은 그것을
|
|
85
|
+
* 실제로 겪었다: 트윈이 멈춘 뒤에도 피드는 남아 레코드를 나르고, 커널은 그것을 알리지 않고 버렸다.
|
|
86
86
|
* 그런데 화면은 「해당 없음 · 150 통과 · 100%」였다 — 사실이 사라지는 동안 화면이 안심시킨 것이다.
|
|
87
87
|
*
|
|
88
88
|
* 멈춘 트윈의 피드 사실은 `not-applicable` 이므로, 미전달을 **그보다 먼저** 봐야 이 상태가 드러난다.
|
|
@@ -141,7 +141,7 @@ test('채우는 중인 창을 「최근」이라 부르지 않는다', () => {
|
|
|
141
141
|
recordIngest(l, 100, [], 0)
|
|
142
142
|
recordIngest(l, 100, [], 1000)
|
|
143
143
|
|
|
144
|
-
/* 읽는 시각을 넘긴다 — 넘기지 않으면 벽시계와 견주어 「오래
|
|
144
|
+
/* 읽는 시각을 넘긴다 — 넘기지 않으면 벽시계와 견주어 「오래 들어온 것이 없음」으로 읽힌다(1970년 장부이므로). */
|
|
145
145
|
const v = ingestHealth(l, 'attached', undefined, 1000)
|
|
146
146
|
assert.equal(v.recent, null, '창이 아직 닫히지 않았다')
|
|
147
147
|
assert.equal(v.verdict, 'unknown', '그래서 판정도 모름이다')
|
|
@@ -167,7 +167,7 @@ test('추이 상한을 넘으면 오래된 창이 밀려나고, 밀려난 수를
|
|
|
167
167
|
|
|
168
168
|
const v = ingestHealth(l, 'attached')
|
|
169
169
|
assert.equal(v.trend.length, TREND_LIMIT)
|
|
170
|
-
assert.ok(v.trendDropped > 0, `밀려난 창을
|
|
170
|
+
assert.ok(v.trendDropped > 0, `밀려난 창을 알리지 않고 버리지 않는다 — 실제 ${v.trendDropped}`)
|
|
171
171
|
})
|
|
172
172
|
|
|
173
173
|
/* ── ③ 0 과 없음 ────────────────────────────────────────────────────────── */
|
|
@@ -213,7 +213,7 @@ test('사유 종류가 무한히 자라지 않는다 — 문구에 변수가 섞
|
|
|
213
213
|
assert.equal(v.reasonsCapped, true, '접혔다는 사실을 말한다')
|
|
214
214
|
assert.ok(
|
|
215
215
|
v.reasons.some(r => r.reason === OTHER_REASON),
|
|
216
|
-
'
|
|
216
|
+
'모은 것들이 갈 칸이 있다'
|
|
217
217
|
)
|
|
218
218
|
assert.equal(v.total.rejected, REASON_LIMIT * 5, '건수는 하나도 잃지 않는다')
|
|
219
219
|
})
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 거짓이 된다. 겹치면 메모리 쪽을 쓴다(같은 사실의 원본이다).
|
|
13
13
|
* ② 어디서부터 메모리인지 밝힌다(`liveFrom`) — 화면이 근거를 말할 수 있게. 재기동 직후에는 `null`
|
|
14
14
|
* 이고, 그때 보이는 것은 전부 표에서 온 것이다.
|
|
15
|
-
* ③
|
|
15
|
+
* ③ **알리지 않고 자르지 않는다** — 상한에서 밀려난 창 수와, 물은 기간이 잘렸다는 사실을 함께 낸다.
|
|
16
16
|
* ④ 남의 도메인·남의 트윈의 창을 집지 않는다.
|
|
17
17
|
* ⑤ 제시가 0 인 창의 통과율은 `null` 이다 — 빈 창은 완벽한 창이 아니다.
|
|
18
18
|
*/
|
|
@@ -157,7 +157,7 @@ test('기간 상한을 넘겨 물으면 자르고, 자른 사실을 낸다', asy
|
|
|
157
157
|
const r = await ask(TWIN, HISTORY_HOURS_MAX + 100)
|
|
158
158
|
|
|
159
159
|
assert.equal(r.hours, HISTORY_HOURS_MAX, '상한까지만 본다')
|
|
160
|
-
assert.equal(r.hoursClamped, HISTORY_HOURS_MAX + 100, '얼마를 물었는지 밝힌다 —
|
|
160
|
+
assert.equal(r.hoursClamped, HISTORY_HOURS_MAX + 100, '얼마를 물었는지 밝힌다 — 알리지 않고 자르지 않는다')
|
|
161
161
|
})
|
|
162
162
|
|
|
163
163
|
test('상한 안에서는 잘렸다고 말하지 않는다 — 없는 문제를 만들지 않는다', async () => {
|