@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
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ★ 연결된 시스템이 밀어 주는 사실을 받는 자리 — 규약을 시험이 지킨다.
|
|
3
|
+
*
|
|
4
|
+
* ── 왜 만들었나 (2026-08-26 · 사용자 지시) ──────────────────────────────────
|
|
5
|
+
* 지금 모든 커넥터가 주기적으로 물어봅니다. 그러면 연결된 시스템이 값을 갱신하는 주기보다 자주 묻게
|
|
6
|
+
* 되고, 그쪽 서버에 그만큼 부담을 줍니다. 태양광 발전소에 10초마다 물으면서 10분마다 바뀌는 값을
|
|
7
|
+
* 받아 왔고, 그날 그쪽 서버가 부하로 멈췄습니다.
|
|
8
|
+
*
|
|
9
|
+
* ── 무엇을 지키나 ───────────────────────────────────────────────────────────
|
|
10
|
+
* 응답 코드가 규약입니다. 밀어 주는 쪽은 그 코드로 재시도를 정하므로, 우리가 아무렇게나 주면 고칠 수
|
|
11
|
+
* 없는 것을 영원히 다시 보내거나, 잠깐의 문제로 사라진 사실을 아무도 모릅니다.
|
|
12
|
+
*/
|
|
13
|
+
import { test } from 'node:test'
|
|
14
|
+
import assert from 'node:assert/strict'
|
|
15
|
+
import { HOOK_STATUS, secretFromHeaders, secretMatches, producedInstance } from '../server/service/reference/hook-contract.js'
|
|
16
|
+
|
|
17
|
+
test('★ 비밀값을 두 가지 방식으로 받는다 — 밀어 주는 쪽의 관습이 갈린다', () => {
|
|
18
|
+
assert.equal(secretFromHeaders({ 'x-twin-hook-secret': 'abc' }), 'abc')
|
|
19
|
+
assert.equal(secretFromHeaders({ authorization: 'Bearer abc' }), 'abc')
|
|
20
|
+
assert.equal(secretFromHeaders({ authorization: 'bearer abc ' }), 'abc')
|
|
21
|
+
assert.equal(secretFromHeaders({}), '')
|
|
22
|
+
assert.equal(secretFromHeaders(undefined), '')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('★ 전용 헤더가 먼저다 — 둘 다 오면 헷갈리지 않는다', () => {
|
|
26
|
+
assert.equal(secretFromHeaders({ 'x-twin-hook-secret': 'first', authorization: 'Bearer second' }), 'first')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('★ 빈 비밀값은 어떤 것과도 맞지 않는다 — 선언하지 않은 연결은 훅을 받지 않는다', () => {
|
|
30
|
+
assert.equal(secretMatches('', ''), false)
|
|
31
|
+
assert.equal(secretMatches('anything', ''), false)
|
|
32
|
+
assert.equal(secretMatches('', 'expected'), false)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('★ 비밀값 비교는 길이가 같으면 전부를 본다 — 응답 시간으로 알아내지 못하게', () => {
|
|
36
|
+
assert.equal(secretMatches('secret-value', 'secret-value'), true)
|
|
37
|
+
assert.equal(secretMatches('secret-valuX', 'secret-value'), false)
|
|
38
|
+
assert.equal(secretMatches('Xecret-value', 'secret-value'), false)
|
|
39
|
+
assert.equal(secretMatches('short', 'secret-value'), false)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('★ 그 연결이 만든 트윈만 받는다 — 남의 트윈에 밀어 넣지 못한다', () => {
|
|
43
|
+
const ref = {
|
|
44
|
+
scopeSpec: {
|
|
45
|
+
produced: [
|
|
46
|
+
{ siteId: 'site-a', instanceId: 'twin-a' },
|
|
47
|
+
{ siteId: 'site-b', instanceId: 'twin-b' }
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
assert.deepEqual(producedInstance(ref, 'twin-b'), { siteId: 'site-b' })
|
|
52
|
+
assert.equal(producedInstance(ref, 'twin-c'), undefined)
|
|
53
|
+
assert.equal(producedInstance(null, 'twin-a'), undefined)
|
|
54
|
+
assert.equal(producedInstance({}, 'twin-a'), undefined)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('★ 응답 코드가 재시도로 풀리는 것과 그렇지 않은 것을 가른다', () => {
|
|
58
|
+
/*
|
|
59
|
+
* 재시도해도 같은 것: 비밀값이 다르다 · 모르는 대상 · 옮길 수 없는 본문 · 커넥터가 훅을 모른다
|
|
60
|
+
* 사람이 손대야 하는 것: 트윈이 돌지 않는다
|
|
61
|
+
* 재시도하면 될 수 있는 것: 우리 쪽 오류
|
|
62
|
+
*/
|
|
63
|
+
assert.equal(HOOK_STATUS.ok, 200)
|
|
64
|
+
assert.equal(HOOK_STATUS.badPayload, 400)
|
|
65
|
+
assert.equal(HOOK_STATUS.badSecret, 401)
|
|
66
|
+
assert.equal(HOOK_STATUS.unknownTarget, 404)
|
|
67
|
+
assert.equal(HOOK_STATUS.notLive, 409)
|
|
68
|
+
assert.equal(HOOK_STATUS.failed, 500)
|
|
69
|
+
assert.equal(HOOK_STATUS.unsupported, 501)
|
|
70
|
+
|
|
71
|
+
/* 재시도해도 같은 것에 5xx 를 주면 안 된다 — 그쪽이 영원히 다시 보낸다. */
|
|
72
|
+
for (const code of [HOOK_STATUS.badPayload, HOOK_STATUS.badSecret, HOOK_STATUS.unknownTarget, HOOK_STATUS.notLive]) {
|
|
73
|
+
assert.ok(code < 500, `${code} 는 4xx 여야 한다`)
|
|
74
|
+
}
|
|
75
|
+
/* 우리 쪽 문제에는 5xx 를 준다 — 그쪽이 다시 보내야 그 사실이 살아남는다. */
|
|
76
|
+
assert.ok(HOOK_STATUS.failed >= 500)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('★ 트윈이 돌지 않으면 200 을 주지 않는다 — 그쪽이 그 사실을 버린다', () => {
|
|
80
|
+
/*
|
|
81
|
+
* 200 은 「받아서 반영했다」는 뜻입니다. 트윈이 실시간으로 돌지 않아 한 건도 반영되지 않았는데
|
|
82
|
+
* 200 을 주면, 밀어 주는 쪽은 전달된 줄 알고 그 사실을 버립니다. 다시 보내 주지도 않습니다.
|
|
83
|
+
*/
|
|
84
|
+
assert.notEqual(HOOK_STATUS.notLive, HOOK_STATUS.ok)
|
|
85
|
+
assert.equal(HOOK_STATUS.notLive, 409)
|
|
86
|
+
})
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 그래서 도메인 확인이 자리마다 제각각이었고 한 곳은 아예 없었다.
|
|
7
7
|
*
|
|
8
8
|
* 키를 `(domainId, instanceId)` 로 고쳤지만, **직접 색인이 남아 있으면 같은 부류가 다시 자란다** — 밖에서
|
|
9
|
-
* `instances[id]` 를 쓰면 이제는 항상 `undefined` 가 나와
|
|
9
|
+
* `instances[id]` 를 쓰면 이제는 항상 `undefined` 가 나와 알리지 않고 "안 돌고 있다" 로 읽힌다(더 나쁘다:
|
|
10
10
|
* 오류가 아니라 거짓 사실이 된다). 그래서 접근자(`owns`·`runtime`·`kernel`·`metrics`·`snapshot`)만 쓴다.
|
|
11
11
|
*
|
|
12
12
|
* 이 부류는 이미 한 번 물었다 — `twin-lifecycle-mutation.ts` 주석이 증언한다("과거엔
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ★ 트윈이 첫날의 이름에 갇혀 있었다 — 바꿀 문이 없었다.
|
|
3
|
+
*
|
|
4
|
+
* 이름은 처음 세울 때 한 번 정해지고(`provision`) 다시 읽기는 건드리지 않는다. 그 자체는 안전하다 —
|
|
5
|
+
* 사람이 붙인 이름을 연결이 덮어쓰면 안 된다. 그런데 그래서 첫날 이름에서 벗어날 길이 없었다.
|
|
6
|
+
*
|
|
7
|
+
* 실제로 그런 일이 났다(2026-08-25 · 태양광 발전소). 커넥터가 처음 아는 이름이 연결된 시스템의
|
|
8
|
+
* 코드뿐이었다.
|
|
9
|
+
*
|
|
10
|
+
* 현장 이름 호현에너지
|
|
11
|
+
* 트윈 이름 ES202307202202 ← 화면의 트윈 목록에 이렇게 보였다
|
|
12
|
+
*
|
|
13
|
+
* 뒤에 사람이 부르는 이름이 어디 있는지 알아내 커넥터를 고쳤는데, 트윈 이름은 그대로였다. 남은
|
|
14
|
+
* 유일한 길이 트윈을 지우고 다시 세우는 것이었고, 그러면 그날 실제 발전소에서 받은 246건이
|
|
15
|
+
* 사라진다 — **이름 하나 때문에 사실을 버리는 선택**이다.
|
|
16
|
+
*
|
|
17
|
+
* 연결된 시스템을 따라가는 트윈에서는 이름이 나중에 좋아진다. 커넥터가 나아질 때마다 나는 일이다.
|
|
18
|
+
*
|
|
19
|
+
* 이 테스트는 소스를 전수 조회해 규율을 지킨다(실제 뮤테이션은 DB 와 도메인 맥락이 필요하다).
|
|
20
|
+
*/
|
|
21
|
+
import { test } from 'node:test'
|
|
22
|
+
import assert from 'node:assert/strict'
|
|
23
|
+
import { readFileSync } from 'node:fs'
|
|
24
|
+
import { join } from 'node:path'
|
|
25
|
+
|
|
26
|
+
const FILE = join(import.meta.dirname, '..', 'server', 'service', 'twin-lifecycle', 'twin-lifecycle-mutation.ts')
|
|
27
|
+
const src = readFileSync(FILE, 'utf8')
|
|
28
|
+
const body = (() => {
|
|
29
|
+
const at = src.indexOf('async renameTwinInstance')
|
|
30
|
+
assert.ok(at > 0, '이름을 바꾸는 문이 있어야 한다')
|
|
31
|
+
return src.slice(at, src.indexOf('async deleteTwinInstance'))
|
|
32
|
+
})()
|
|
33
|
+
|
|
34
|
+
test('★ 이름을 바꾸는 문이 있다 — 없어서 지우고 다시 세우는 것이 유일한 길이었다', () => {
|
|
35
|
+
assert.ok(/@Mutation/.test(src.slice(0, src.indexOf('async renameTwinInstance'))))
|
|
36
|
+
assert.ok(/@Arg\('instanceId'\)/.test(body))
|
|
37
|
+
assert.ok(/@Arg\('name'\)/.test(body))
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('★ 빈 이름은 받지 않는다 — 지우는 것과 바꾸는 것은 다른 일이다', () => {
|
|
41
|
+
assert.ok(/\.trim\(\)/.test(body), '앞뒤 공백은 이름이 아니다')
|
|
42
|
+
assert.ok(/if \(!next\) throw/.test(body), '빈 값이면 거절한다')
|
|
43
|
+
/* 빈 이름을 받아 두면 그 트윈은 목록에서 식별자만 남는데, 그것을 「바꿨다」로 보고하면 거짓이다. */
|
|
44
|
+
assert.ok(/left as it was/.test(body), '무엇도 바뀌지 않았다는 것을 말한다')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('★ 남의 도메인 트윈은 바꾸지 못한다 — 테넌트 격리', () => {
|
|
48
|
+
assert.ok(/where: \{ domain: \{ id: domainId \}, instanceId \}/.test(body), '도메인으로 좁혀 찾는다')
|
|
49
|
+
assert.ok(/unknown twin/.test(body), '없으면 이유를 말한다')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('바꾸지 않은 경우를 바꿨다고 말하지 않는다', () => {
|
|
53
|
+
assert.ok(/changed: false/.test(body), '같은 이름이면 changed 를 false 로 낸다')
|
|
54
|
+
assert.ok(/previous/.test(body), '무엇에서 무엇으로 바뀌었는지 함께 낸다')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('바꾼 사람을 남긴다 — 이름은 사람이 하는 선언이다', () => {
|
|
58
|
+
assert.ok(/updater: context\.state\.user/.test(body))
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('이름만 바꾼다 — 트윈을 멈추거나 다시 읽지 않는다', () => {
|
|
62
|
+
/*
|
|
63
|
+
* 도는 런타임은 식별자로 동작하고 이름을 쓰지 않는다. 정지·기동을 끼우면 그 사이의 사실이 어디로
|
|
64
|
+
* 갔는지 설명해야 하는데, 이름 때문에 그 값을 치를 이유가 없다.
|
|
65
|
+
*/
|
|
66
|
+
assert.ok(!/TwinEngine\.(stop|start|remove|resync)/.test(body), '런타임을 건드리지 않는다')
|
|
67
|
+
assert.ok(!/adoptStructure|reprovision/.test(body), '구조를 다시 읽지 않는다')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('연결된 시스템이 이름을 덮어쓰지는 않는다 — 다시 읽기는 그대로다', () => {
|
|
71
|
+
/*
|
|
72
|
+
* 「사람이 손대지 않았으면 따라간다」가 옳아 보이지만, 그러려면 「손댔다」는 표시를 새로 만들어야
|
|
73
|
+
* 한다. 이름 하나 때문에 지기에 무거운 빚이므로 사람이 바꾸는 길만 낸다. 그 결정을 여기서 고정한다.
|
|
74
|
+
*
|
|
75
|
+
* 이름을 정하는 자리는 **처음 세울 때 하나**다. 그 수가 늘면 다시 읽기가 이름을 다시 정하기
|
|
76
|
+
* 시작했다는 뜻이고, 그때는 이 결정을 다시 해야 한다.
|
|
77
|
+
*/
|
|
78
|
+
const engine = readFileSync(join(import.meta.dirname, '..', 'server', 'engine', 'twin-engine.ts'), 'utf8')
|
|
79
|
+
const places = engine.match(/name: master\.siteName/g) ?? []
|
|
80
|
+
assert.equal(places.length, 1, `이름을 정하는 자리가 ${places.length}곳이다 — 처음 세울 때 하나여야 한다`)
|
|
81
|
+
})
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* 이름이 바뀌었다.
|
|
9
9
|
*
|
|
10
10
|
* ── 지키는 규율 둘 (계획서 §6) ──────────────────────────────────────────────
|
|
11
|
-
* ① **모르는 값은 오류를 낸다.** 예전에는 미선언이
|
|
11
|
+
* ① **모르는 값은 오류를 낸다.** 예전에는 미선언이 알리지 않고 `sim-experiment`(=저널 초기화)로 떨어졌다 — 값이
|
|
12
12
|
* 하나 어긋나는 순간 이력을 지우는 길이다. 기본값을 없앴다.
|
|
13
13
|
* ② **옛 어휘를 번역하지 않는다.** 번역을 두면 옛 값이 살아남고 다시 저장된다. 저장된 값은 한 번
|
|
14
14
|
* 옮기고(데이터 이관), 코드는 새 어휘만 안다 — 다만 무엇으로 옮겨야 하는지는 오류 문장이 말한다.
|
|
@@ -67,7 +67,7 @@ test('묻는 자리도 있다 — 화면·검사는 오류를 내지 않고 판
|
|
|
67
67
|
/*
|
|
68
68
|
* ── 옛 어휘가 코드로 되돌아오지 않게 (계획서 §6 의 검사) ────────────────────
|
|
69
69
|
* ADR-0034 의 `local-declarations.test.ts` 가 `site*` 에 대해 하는 것과 같은 방식이다. 개명은 저장값·타입·
|
|
70
|
-
* 화면에 걸쳐 있어서, 한 곳만 되돌아와도 그 자리가
|
|
70
|
+
* 화면에 걸쳐 있어서, 한 곳만 되돌아와도 그 자리가 알리지 않고 다르게 재기동한다.
|
|
71
71
|
*/
|
|
72
72
|
const OLD_TOKENS = [/\brealityMode\b/, /\bRealityMode\b/, /'sim-world'/, /'sim-experiment'/, /DEFAULT_REALITY_MODE/]
|
|
73
73
|
|
|
@@ -106,6 +106,6 @@ test('기본값 상수가 없다 — 미선언을 우리가 고르지 않는다'
|
|
|
106
106
|
.split('\n')
|
|
107
107
|
.filter(line => !/^\s*(\*|\/\/|\/\*)/.test(line))
|
|
108
108
|
.join('\n')
|
|
109
|
-
assert.equal(/DEFAULT_RESTART_POLICY/.test(code), false, '기본값 상수가 되살아나면 미선언이 다시
|
|
109
|
+
assert.equal(/DEFAULT_RESTART_POLICY/.test(code), false, '기본값 상수가 되살아나면 미선언이 다시 들어온 것이 없어진다')
|
|
110
110
|
assert.match(code, /readRestartPolicy\(/, '읽는 자리는 엄격한 리더를 지난다')
|
|
111
111
|
})
|
|
@@ -35,14 +35,14 @@ test('기억이 없으면 그렇게 말한다 — 첫 사이트로 때우지 않
|
|
|
35
35
|
test('기억한 사이트가 사라졌으면 다른 사이트로 덮지 않는다', () => {
|
|
36
36
|
const { site, reason } = pickOriginSite(sites, { siteId: 'plant-9' })
|
|
37
37
|
assert.equal(site, undefined)
|
|
38
|
-
assert.equal(reason, 'site-gone', '다른 현장의 구조를 이 트윈에 덮는 것은
|
|
38
|
+
assert.equal(reason, 'site-gone', '다른 현장의 구조를 이 트윈에 덮는 것은 오류 없이 틀린 공장이 된다')
|
|
39
39
|
})
|
|
40
40
|
|
|
41
41
|
test('빈 문자열은 기억이 아니다', () => {
|
|
42
42
|
assert.equal(pickOriginSite(sites, { siteId: ' ' }).reason, 'no-remembered-site')
|
|
43
43
|
})
|
|
44
44
|
|
|
45
|
-
/* 배선이 사라지면
|
|
45
|
+
/* 배선이 사라지면 알리지 않고 예전으로 돌아간다 — 소스 모양으로 못 박는다(리졸버는 DB·어댑터를 물고 있다). */
|
|
46
46
|
test('다시 읽기는 인스턴스 정체로 견주고, 가져오기는 사이트 정체를 남긴다', () => {
|
|
47
47
|
const src = readFileSync(fileURLToPath(new URL('../server/service/reference/reference-resolver.ts', import.meta.url)), 'utf-8')
|
|
48
48
|
/* 옛 비교식은 **설명문에 남아 있어야 한다**(왜 고쳤는지가 사라지면 되돌아간다) — 그래서 코드 줄만 본다. */
|
|
@@ -60,7 +60,7 @@ test('다시 읽기는 인스턴스 정체로 견주고, 가져오기는 사이
|
|
|
60
60
|
/*
|
|
61
61
|
* ── 다시 읽기가 **현장 소속**을 지킨다 (2026-08-18 실측) ─────────────────────
|
|
62
62
|
* 한 현장에 합쳐 둔 EMS 트윈 여덟을 재동기했더니 각자 공간으로 흩어졌다 — 템플릿이 낸 기본 공간으로
|
|
63
|
-
* 돌아갔기 때문이다.
|
|
63
|
+
* 돌아갔기 때문이다. 들어온 것이 없다: 오류도 경고도 없고, 트윈은 멀쩡히 돌아간다. 그런데 그 순간 **그 현장의
|
|
64
64
|
* 값(요금·통화)을 함께 잃는다** — 현장 겹은 소속으로 얹히기 때문이다.
|
|
65
65
|
*
|
|
66
66
|
* 원인은 origin 이 「어느 현장에 합쳤나」를 기억하지 않은 것이다(구조만 기억했다).
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* ── 왜 알려 주어야 하나 ─────────────────────────────────────────────────────
|
|
18
18
|
* 커널의 계약이 그 이유를 적어 두었다(`resumeRevision`): 「저널이 비어 있지 않은데 0부터 다시 세면 같은
|
|
19
19
|
* 트윈에 같은 리비전이 둘 생기고, 재생 순서가 뒤섞이며, 시간여행이 엉뚱한 시점을 답한다 — 오류는 안 난다.」
|
|
20
|
-
*
|
|
20
|
+
* 알리지 않고 깨지는 종류이므로 시험이 지킨다.
|
|
21
21
|
*
|
|
22
22
|
* ── 이 시험이 무엇을 재는가 ─────────────────────────────────────────────────
|
|
23
23
|
* DB 도 커널도 띄우지 않는다. 이 규율은 **어느 경로가 무엇을 부르나**에 있고 그것은 소스에서 확인된다.
|
|
@@ -33,7 +33,7 @@ const src = readFileSync(ENGINE, 'utf8')
|
|
|
33
33
|
/**
|
|
34
34
|
* 그 함수 본문만 — 다른 경로의 호출이 섞여 판정이 흐려지지 않게.
|
|
35
35
|
*
|
|
36
|
-
* 끝은 **다음 멤버 선언**으로 자른다. 글자 수로 자르면 주석이 늘어난 날
|
|
36
|
+
* 끝은 **다음 멤버 선언**으로 자른다. 글자 수로 자르면 주석이 늘어난 날 알리지 않고 짧아져서, 있는 것을
|
|
37
37
|
* 없다고 말한다(거짓 빨강을 내는 가드는 없는 것보다 나쁘다).
|
|
38
38
|
*/
|
|
39
39
|
function bodyOf(signature: string): string {
|
|
@@ -49,8 +49,14 @@ function bodyOf(signature: string): string {
|
|
|
49
49
|
|
|
50
50
|
test('미러를 세우는 경로도 커널에 저널 머리를 알려 준다', () => {
|
|
51
51
|
const body = bodyOf('static startLive(')
|
|
52
|
-
/*
|
|
53
|
-
|
|
52
|
+
/*
|
|
53
|
+
* 저널 머리를 읽는다 — 이 조회는 예전부터 있었다(inst.revision 시드용).
|
|
54
|
+
*
|
|
55
|
+
* 2026-08-28: 모양을 `findOne(order: revision DESC)` 에서 `MAX(e.revision)` 으로 바꿨다. 앞의 것은
|
|
56
|
+
* 관계로 물어 두 단계 질의가 되고 안쪽에 상한이 없어 그 트윈의 저널을 전부 만든 뒤 한 줄을 고른다.
|
|
57
|
+
* **묻는 것은 같다** — 저널 머리다. 그래서 이 단정은 「머리를 읽는가」로 남고 모양만 따라간다.
|
|
58
|
+
*/
|
|
59
|
+
assert.match(body, /select\('MAX\(e\.revision\)'/, '저널 머리를 읽어야 한다')
|
|
54
60
|
/* 그리고 **커널에도** 알려 준다 — 이 한 줄이 없어서 두 수가 갈렸다. */
|
|
55
61
|
assert.match(body, /resumeRevision\(head\)/, '커널에도 그 번호를 알려 준다')
|
|
56
62
|
})
|
|
@@ -72,7 +78,7 @@ test('저널이 비어 있으면 알려 주지 않는다 — 새 트윈은 0 부
|
|
|
72
78
|
test('뒤로 되돌리려는 거절을 삼키지 않는다', () => {
|
|
73
79
|
/*
|
|
74
80
|
* 조회가 비동기라 그 사이에 이벤트가 들어와 있을 수 있다. 그때 커널은 뒤로 가기를 거절한다(겹치는
|
|
75
|
-
* 번호를 막는 그 판정이다). 그 거절을
|
|
81
|
+
* 번호를 막는 그 판정이다). 그 거절을 알리지 않고 버리면 「왜 두 수가 아직 다르지」를 답할 계기가 없다.
|
|
76
82
|
*/
|
|
77
83
|
const body = bodyOf('static startLive(')
|
|
78
84
|
assert.match(body, /catch \(err[^)]*\)[\s\S]{0,200}twinWarn/, '거절을 로그에 남긴다')
|
|
@@ -81,7 +87,7 @@ test('뒤로 되돌리려는 거절을 삼키지 않는다', () => {
|
|
|
81
87
|
|
|
82
88
|
test('체크포인트는 두 수를 나란히 적는다 — 그 사실 자체는 옳다', () => {
|
|
83
89
|
/*
|
|
84
|
-
* 바깥은 저널 줄 번호(이어 쓸 자리), 안쪽은 커널이
|
|
90
|
+
* 바깥은 저널 줄 번호(이어 쓸 자리), 안쪽은 커널이 계산한 봉투 수다. **둘을 하나로 합치지 않는다** —
|
|
85
91
|
* 합치면 「어디까지 적었나」와 「어디까지 반영했나」를 구별할 수 없고, 그 구별이 미러의 정직성이다.
|
|
86
92
|
* 두 경로가 대칭이면 값이 같아지지만, **자리는 둘이어야 한다.**
|
|
87
93
|
*/
|
|
@@ -89,7 +95,7 @@ test('체크포인트는 두 수를 나란히 적는다 — 그 사실 자체는
|
|
|
89
95
|
assert.ok(at > 0, 'persistSnapshot 이 있어야 한다')
|
|
90
96
|
/*
|
|
91
97
|
* 창을 넓혀 왔다 — 이 함수 앞쪽에 「빈 상태로 좋은 스냅샷을 덮지 않는다」 가드가 들어오고(§
|
|
92
|
-
* `checkpoint-refuses-empty`), 뒤쪽에 「이어
|
|
98
|
+
* `checkpoint-refuses-empty`), 뒤쪽에 「이어 계산할 씨앗을 함께 적는다」가 들어왔다(§
|
|
93
99
|
* `mirror-resumes-from-checkpoint`). 검사하는 **사실은 그대로**다.
|
|
94
100
|
*
|
|
95
101
|
* 그리고 **줄 모양이 아니라 두 자리를 검사한다**: 예전에는 `{ revision, state` 라는 한 줄 모양을
|
|
@@ -100,5 +106,5 @@ test('체크포인트는 두 수를 나란히 적는다 — 그 사실 자체는
|
|
|
100
106
|
assert.match(body, /inst\.revision \?\? state\.revision/, '바깥은 저널 줄 번호를 먼저 쓴다')
|
|
101
107
|
const call = body.slice(body.indexOf('setInCache'))
|
|
102
108
|
assert.match(call, /revision/, '저널 줄 번호를 싣는다')
|
|
103
|
-
assert.match(call, /state/, '커널이
|
|
109
|
+
assert.match(call, /state/, '커널이 계산한 상태를 함께 싣는다 — 두 자리를 하나로 합치지 않는다')
|
|
104
110
|
})
|
package/test/runtime-key.test.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 이름을 그대로 id 로 쓰므로(`sap-ewm-1710`) 두 테넌트가 같은 벤더 시스템에 연결하면 같은 id 가 된다 —
|
|
6
6
|
* 그때 남의 커널에 이벤트가 적용되거나, 웜스타트 씨앗이 남의 스냅샷이 됐다.
|
|
7
7
|
*
|
|
8
|
-
* 이 테스트가 지키는 것은 **키가 모호해지지 않는 것** 하나다. 모호한 키는
|
|
8
|
+
* 이 테스트가 지키는 것은 **키가 모호해지지 않는 것** 하나다. 모호한 키는 오류 없이 틀린 값을 낸다.
|
|
9
9
|
*/
|
|
10
10
|
import { test } from 'node:test'
|
|
11
11
|
import assert from 'node:assert/strict'
|
|
@@ -39,8 +39,8 @@ test('인스턴스 id 안의 구분자를 보존한다 — 첫 구분자에서
|
|
|
39
39
|
assert.equal(instanceId, weird)
|
|
40
40
|
})
|
|
41
41
|
|
|
42
|
-
test('도메인 id 에 구분자가 있으면 오류를 낸다 —
|
|
43
|
-
/*
|
|
42
|
+
test('도메인 id 에 구분자가 있으면 오류를 낸다 — 알리지 않고 모호한 키를 만들지 않는다', () => {
|
|
43
|
+
/* 알리지 않고 만들면 그게 곧 이 결함의 재발이다(어느 테넌트인지 알 수 없는 키). */
|
|
44
44
|
assert.throws(() => runtimeKey(`a${RUNTIME_KEY_SEP}b`, 'x'), /ambiguous/)
|
|
45
45
|
})
|
|
46
46
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* 영속 스냅샷이 **사실을 이기지 않는다** — 쓸 수 있는 조건을 못 박는다.
|
|
3
3
|
*
|
|
4
|
-
* 멈춘 트윈을 조회할 때마다 저널을 전량
|
|
5
|
-
* 폴드의 결과이므로 그 뒤로 아무 일도 없었다면 다시
|
|
4
|
+
* 멈춘 트윈을 조회할 때마다 저널을 전량 계산하고 있었다(23,731건짜리 트윈에서 4.6초). 스냅샷은 같은
|
|
5
|
+
* 폴드의 결과이므로 그 뒤로 아무 일도 없었다면 다시 계산해 봐야 같은 값이다 — 그래서 쓴다.
|
|
6
6
|
*
|
|
7
7
|
* 위험은 하나다: **낡은 스냅샷을 "지금" 이라고 내는 것.** 그래서 쓰는 조건을 좁게 잡았고, 그 조건을
|
|
8
8
|
* 여기서 고정한다. 엔진 본체는 DB 를 물고 있어 부를 수 없으므로 판정식만 떼어 시험한다 —
|
|
@@ -22,7 +22,7 @@ const usable = (
|
|
|
22
22
|
const TIP = { revision: 23731, structureRev: 2 }
|
|
23
23
|
const FRESH = { revision: 23731, structureRev: 2, state: { locations: [] } }
|
|
24
24
|
|
|
25
|
-
test('끝에 있고 구조도 같으면 쓴다 — 다시
|
|
25
|
+
test('끝에 있고 구조도 같으면 쓴다 — 다시 계산해도 같은 값이다', () => {
|
|
26
26
|
assert.equal(usable(FRESH, TIP, true), true)
|
|
27
27
|
})
|
|
28
28
|
|
|
@@ -46,7 +46,7 @@ test('구조가 양쪽 다 없으면 그것은 같은 것이다 — 없는 것
|
|
|
46
46
|
assert.equal(usable({ revision: 5, state: {} }, { revision: 5, structureRev: null }, true), true)
|
|
47
47
|
})
|
|
48
48
|
|
|
49
|
-
test('시간여행에는 쓰지 않는다 — 그 시점까지
|
|
49
|
+
test('시간여행에는 쓰지 않는다 — 그 시점까지 계산해야 한다', () => {
|
|
50
50
|
assert.equal(usable(FRESH, TIP, false), false)
|
|
51
51
|
})
|
|
52
52
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* **허락과 일어남 사이를 같은 행에 닫는다** — ADR-0029 §1 결정 C (2026-08-19).
|
|
3
3
|
*
|
|
4
4
|
* ── 무엇이 열려 있었나 ──────────────────────────────────────────────────────
|
|
5
|
-
* 명령이 원본으로 가면 감사는 「허락했다」까지만 증명한다: 어댑터가
|
|
5
|
+
* 명령이 원본으로 가면 감사는 「허락했다」까지만 증명한다: 어댑터가 오류 없이 실패하면 원장에는 승인만 남는다.
|
|
6
6
|
* 그리고 구동 문(`controlTwinSource`)은 감사를 **아예 남기지 않았다** — 자극을 바꾸고 배속을 올리는 것은
|
|
7
7
|
* 그 트윈의 사실을 바꾸는 행위인데, 나중에 「무엇이 이 공장을 흔들었나」에 답할 수 없었다.
|
|
8
8
|
*
|
|
@@ -217,7 +217,7 @@ test('정지한 트윈은 세지 않는다 — 확인하지 않은 것을 정합
|
|
|
217
217
|
assert.equal(f.find(x => x.code === 'twin-unhandled-vocabulary'), undefined)
|
|
218
218
|
})
|
|
219
219
|
|
|
220
|
-
test('빈 배열은 결함이 아니다 — 받은 것이 없으면
|
|
220
|
+
test('빈 배열은 결함이 아니다 — 받은 것이 없으면 들어온 것이 없다', () => {
|
|
221
221
|
const f = checkSpaceIntegrity({ ...SOUND, instances: [{ ...SOUND.instances[0], unhandled: [] }] })
|
|
222
222
|
assert.equal(f.find(x => x.code === 'twin-unhandled-vocabulary'), undefined)
|
|
223
223
|
})
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ★ 표현을 묶고 가른다 — 목록에 나올 것과 다른 표현에서 열리는 것.
|
|
3
|
+
*
|
|
4
|
+
* ── 왜 (2026-08-27 · 사용자 지시, UX 레인 요청) ─────────────────────────────
|
|
5
|
+
* 「각 표현이 많아지게 되면 관리가 어려우니, 그루핑과 정렬 기능도 필요하다.」
|
|
6
|
+
* 「표현 인덱스에 포함될 표현과 팝업 같은 파생 표현이 구분될 수 있어야 한다.」
|
|
7
|
+
*
|
|
8
|
+
* 칸 둘을 더한다.
|
|
9
|
+
*
|
|
10
|
+
* group 자유 문자열. 묶는 축이 현장마다 다르다(건물별·용도별·층별)
|
|
11
|
+
* role index(기본) · derived. 닫힌 목록이다
|
|
12
|
+
*/
|
|
13
|
+
import { test } from 'node:test'
|
|
14
|
+
import assert from 'node:assert/strict'
|
|
15
|
+
import { readFileSync } from 'node:fs'
|
|
16
|
+
import { join } from 'node:path'
|
|
17
|
+
import { REPRESENTATION_ROLES } from '../server/service/twin-space/representation-role.js'
|
|
18
|
+
|
|
19
|
+
const dir = (...p: string[]) => join(import.meta.dirname, '..', 'server', 'service', 'twin-space', ...p)
|
|
20
|
+
const entity = readFileSync(dir('twin-space-representation.ts'), 'utf8')
|
|
21
|
+
const resolver = readFileSync(dir('twin-space-resolver.ts'), 'utf8')
|
|
22
|
+
|
|
23
|
+
test('★ 묶는 이름은 자유 문자열이다 — 묶는 축이 현장마다 다르다', () => {
|
|
24
|
+
assert.match(entity, /group\?: string/, 'group 칸이 있어야 한다')
|
|
25
|
+
/* 고정 목록으로 두면 현장마다 맞지 않는다 — 값을 검사하지 않는다. */
|
|
26
|
+
assert.doesNotMatch(resolver, /GROUPS as readonly|REPRESENTATION_GROUPS/, '묶는 이름을 목록으로 닫지 않는다')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('★ 그룹 자체의 순서는 칸으로 두지 않는다 — 그 그룹의 최소 seq 가 그 순서다', () => {
|
|
30
|
+
/* 칸을 두면 그룹 이름을 바꿀 때 두 곳을 맞춰야 하고, 어긋나면 목록 순서가 오류 없이 달라진다. */
|
|
31
|
+
assert.doesNotMatch(entity, /groupSeq|groupOrder/, '그룹 순서 칸을 만들지 않는다')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('★ role 은 닫힌 목록이고, 모르는 값은 저장하지 않는다', () => {
|
|
35
|
+
assert.deepEqual([...REPRESENTATION_ROLES], ['index', 'derived'])
|
|
36
|
+
/*
|
|
37
|
+
* 모르는 값을 그냥 저장하면 화면이 그것을 어느 쪽으로도 그리지 못하고, 그 표현이 목록에서 알리지 않고
|
|
38
|
+
* 사라진다. 저장하는 자리에서 거절한다.
|
|
39
|
+
*/
|
|
40
|
+
assert.match(resolver, /REPRESENTATION_ROLES as readonly string\[\]\)\.includes\(role\)/, '닫힌 목록으로 검사한다')
|
|
41
|
+
assert.match(resolver, /unknown representation role/, '거절 사유를 말한다')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('★ 비어 있으면 index 다 — 흔한 쪽을 기본으로 둔다', () => {
|
|
45
|
+
assert.match(entity, /role\?: string/, 'role 은 선택 칸이다')
|
|
46
|
+
assert.match(entity, /default when absent/, '비어 있을 때의 뜻이 설명에 있어야 한다')
|
|
47
|
+
/* 공백만 있는 값은 「없음」과 같다 — 그것을 값으로 저장하면 검사를 지나며 뜻이 없는 값이 남는다. */
|
|
48
|
+
assert.match(resolver, /String\(rep\.role\)\.trim\(\) === ''/, '공백만 있는 값은 비운다')
|
|
49
|
+
assert.match(resolver, /String\(rep\.group\)\.trim\(\) === ''/, '묶는 이름도 같다')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('★ isPrimary 에 얹지 않는다 — 다른 질문에 답하는 칸이다', () => {
|
|
53
|
+
/*
|
|
54
|
+
* `isPrimary` 는 「현장 현황에 나올 하나」를 정한다. `role` 은 「목록에 나오나」를 정한다. 한 칸이
|
|
55
|
+
* 두 질문에 답하면 한쪽을 고칠 때 다른 쪽이 오류 없이 바뀐다.
|
|
56
|
+
*/
|
|
57
|
+
assert.match(entity, /isPrimary\?: boolean/, 'isPrimary 는 그대로 있어야 한다')
|
|
58
|
+
assert.match(entity, /Kept separate from isPrimary/, '왜 따로 두는지 설명에 있어야 한다')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('★ 조회가 두 값을 실어 보낸다 — 화면이 묶을 수 있어야 한다', () => {
|
|
62
|
+
assert.match(resolver, /group: rep\.group, role: rep\.role/, '표현 목록에 두 값을 싣는다')
|
|
63
|
+
assert.match(resolver, /order: \{ group: 'ASC', seq: 'ASC' \}/, '묶은 이름 다음에 순서로 정렬한다')
|
|
64
|
+
/* 빈 값이 앞에 오는지 뒤에 오는지는 드라이버마다 다르다 — 그 순서에 기대지 않는다. */
|
|
65
|
+
assert.match(resolver, /드라이버마다\s*\n?\s*\*?\s*다르므로/, '그 순서에 기대지 않는다는 것을 적어 둔다')
|
|
66
|
+
})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ★ 현장으로 물으면 멈춘 트윈의 기록도 나온다 — 저널은 기록이고, 지금 도는지는 다른 축이다.
|
|
3
|
+
*
|
|
4
|
+
* ── 무엇이 났나 (2026-08-28 실측) ───────────────────────────────────────────
|
|
5
|
+
* 대상 해소에 `running` 조건이 있었다. 그래서 현장으로 물으면 멈춘 트윈이 대상에서 빠지고, 화면은
|
|
6
|
+
* 「기록이 없습니다」라고 말했다 — 저널에는 있는데.
|
|
7
|
+
*
|
|
8
|
+
* twinEventList(instanceId: "ems-plant-1") 1,792건
|
|
9
|
+
* twinEventList(spaceId: "ems-plant-1-site") 0건 ← 같은 트윈 · 같은 도메인
|
|
10
|
+
*
|
|
11
|
+
* 규모로 보면 예외가 아니라 기본값이었다: 등록된 트윈 30개 중 도는 것 2개, 멈춘 것 28개.
|
|
12
|
+
*
|
|
13
|
+
* ── 왜 지웠나 ──────────────────────────────────────────────────────────────
|
|
14
|
+
* 옛 주석이 대는 이유(「벤치 사본은 사용자가 보는 현장이 아니다」)는 벤치 제외만 설명한다. `running` 에는
|
|
15
|
+
* 근거가 적혀 있지 않았다. 그리고 바로 다음 줄이 반대 원칙을 적어 두었다 — 「정지·비등록이어도 저널은
|
|
16
|
+
* 남아 있을 수 있다」. 그 원칙이 현장 경로에만 적용되지 않고 있었다.
|
|
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 KPI = join(import.meta.dirname, '..', 'server', 'engine', 'kpi-query.ts')
|
|
24
|
+
const src = readFileSync(KPI, 'utf8')
|
|
25
|
+
|
|
26
|
+
/** 대상을 고르는 그 한 줄만 떼어 본다. */
|
|
27
|
+
const targetLine = (() => {
|
|
28
|
+
const from = src.indexOf('async function resolveTargets')
|
|
29
|
+
const body = src.slice(from, from + 1200)
|
|
30
|
+
const m = /: list\.filter\(\(x: any\) => [^\n]+\)/.exec(body)
|
|
31
|
+
return m?.[0] ?? ''
|
|
32
|
+
})()
|
|
33
|
+
|
|
34
|
+
test('★ 현장 경로가 「지금 도는가」로 걸러내지 않는다', () => {
|
|
35
|
+
assert.ok(targetLine, '대상을 고르는 줄을 찾아야 한다')
|
|
36
|
+
assert.doesNotMatch(targetLine, /running/, '멈춘 트윈을 현장에서 빼면 화면이 거짓 「없다」를 낸다')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('벤치 사본은 계속 뺀다 — 그 제외에는 근거가 있다', () => {
|
|
40
|
+
/* 부하 시험용 사본은 사용자가 보는 현장이 아니다. 이 제외는 지우지 않는다. */
|
|
41
|
+
assert.match(targetLine, /purpose !== 'bench'/)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('현장과 트윈 두 경로가 같은 원칙을 쓴다 — 저널은 기록이다', () => {
|
|
45
|
+
/* 트윈을 지정한 경로는 처음부터 「정지·비등록이어도 저널은 남아 있을 수 있다」였다. */
|
|
46
|
+
assert.match(src, /정지·비등록\)? ?지정된 트윈은 그대로 조회한다|정지·비등록/)
|
|
47
|
+
/* 왜 지웠는지가 코드에 남아 있어야 한다 — 다음 사람이 성능을 이유로 되넣지 않게. */
|
|
48
|
+
assert.match(src, /「지금 도는가」로 걸러내지 않는다/)
|
|
49
|
+
assert.match(src, /1,792건/, '무엇을 재서 알았는지 적혀 있어야 한다')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('★ 「지금 도는 것만」이 필요하면 이름 있는 축으로 받는다 — 여기 숨기지 않는다', () => {
|
|
53
|
+
/* 한 칸에 두 물음을 담으면 어느 화면이 무엇을 보는지 아무도 모른다. */
|
|
54
|
+
assert.match(src, /\*\*이름 있는 축\*\*으로 받는다/)
|
|
55
|
+
})
|
|
@@ -32,7 +32,7 @@ test('전부 선언되면 절대 예측 시도 가능 — 단 정확도는 별
|
|
|
32
32
|
assert.equal(r!.varyingDurations, 2)
|
|
33
33
|
})
|
|
34
34
|
|
|
35
|
-
test('섞이면 partial — 한쪽만
|
|
35
|
+
test('섞이면 partial — 한쪽만 계산하지된 사실을 감추지 않는다', () => {
|
|
36
36
|
const r = classifySpecCoverage({
|
|
37
37
|
operations: [
|
|
38
38
|
{ kind: 'cut', duration: 'declared' },
|
|
@@ -26,6 +26,7 @@ const req = createRequire(import.meta.url)
|
|
|
26
26
|
const { standardsForKind } = req('../dist-server/service/twin-model/standard-coverage.js')
|
|
27
27
|
const { iec61850Coverage } = req('../dist-server/service/twin-model/iec61850-coverage.js')
|
|
28
28
|
const { epcisCoverage } = req('../dist-server/service/twin-model/epcis-coverage.js')
|
|
29
|
+
const { isa95Coverage } = req('../dist-server/service/twin-model/isa95-coverage.js')
|
|
29
30
|
import { readFileSync } from 'node:fs'
|
|
30
31
|
import { fileURLToPath } from 'node:url'
|
|
31
32
|
|
|
@@ -75,7 +76,7 @@ test('개폐·발전·저장이 세 층 모두 올라갔다 — 값이 먼저,
|
|
|
75
76
|
assert.equal(iec.totals.behavior, 5, '다섯 논리 노드가 모두 실제로 돈다')
|
|
76
77
|
})
|
|
77
78
|
|
|
78
|
-
test('커널 축이 사라지면 구조가 없는 것으로 떨어진다 — 오타·개명이
|
|
79
|
+
test('커널 축이 사라지면 구조가 없는 것으로 떨어진다 — 오타·개명이 알리지 않고 통과하지 않게', () => {
|
|
79
80
|
const iec = iec61850Coverage(['equipment']) // demandWindows 축이 없는 세상
|
|
80
81
|
const mmxu = iec.concepts.find(c => c.std === 'MMXU')!
|
|
81
82
|
assert.equal(mmxu.structure, 'none')
|
|
@@ -104,3 +105,44 @@ test('계측은 EPCIS 센서 필드로 오지 않는다 — 그 사실을 표가
|
|
|
104
105
|
assert.equal(sensor.behavior, 'none')
|
|
105
106
|
assert.ok(sensor.note, '에너지가 다른 층으로 들어온다는 사실을 근거로 적는다')
|
|
106
107
|
})
|
|
108
|
+
|
|
109
|
+
test('★ 자기 축이 없는 개념은 **어디에 속하는지** 말한다 — 「어디에도 없다」로 읽히지 않게', () => {
|
|
110
|
+
/*
|
|
111
|
+
* ── 사용자 지적 (2026-08-24) ──────────────────────────────────────────────
|
|
112
|
+
* 화면이 그런 줄을 「개념 축이 없습니다」로만 냈다. 자재 로트·자원 속성·로케이션 관측 기록이 그렇게
|
|
113
|
+
* 보였는데, 셋 다 **어디엔가 속해 있다** — 로트는 물품 목록이고, 속성은 설비·사람·자산 안에 붙고,
|
|
114
|
+
* 관측은 로케이션에 붙는다. 「축이 없다」는 사실이지만 사용자에게는 **없는 것**으로 읽힌다.
|
|
115
|
+
*
|
|
116
|
+
* 그래서 `shownOn` 의 뜻을 고쳤다 — 「어디서 보이나」가 아니라 **「어디에 속하나」**다. 화면에
|
|
117
|
+
* 그려지는지는 `surface` 가 따로 답하고, 둘은 다른 물음이다.
|
|
118
|
+
*
|
|
119
|
+
* (그리고 화면의 「해당 축 항목에 표시됩니다」 문장은 걷었다 — 칩이 이미 갈 곳을 보인다.)
|
|
120
|
+
*/
|
|
121
|
+
const AXES = [
|
|
122
|
+
'items', 'locations', 'equipment', 'persons', 'assets', 'tasks', 'orders', 'operations', 'recipes',
|
|
123
|
+
'testSpecifications', 'materialDefinitions', 'materialClasses', 'personnelClasses', 'equipmentClasses',
|
|
124
|
+
'assetClasses', 'productionSpec', 'demandWindows', 'meters'
|
|
125
|
+
]
|
|
126
|
+
const tables: [string, { concepts: any[] }][] = [
|
|
127
|
+
['ISA-95', isa95Coverage(AXES)],
|
|
128
|
+
['EPCIS', epcisCoverage(AXES)],
|
|
129
|
+
['IEC 61850', iec61850Coverage(AXES)]
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
/*
|
|
133
|
+
* 규칙: **세 층 중 하나라도 있는 개념은 어디에 속하는지 말해야 한다.** 셋 다 없으면 진짜 빈칸이고,
|
|
134
|
+
* 그때만 「축이 없다」가 정직한 답이다.
|
|
135
|
+
*/
|
|
136
|
+
const mute: string[] = []
|
|
137
|
+
for (const [name, table] of tables) {
|
|
138
|
+
for (const c of table.concepts) {
|
|
139
|
+
const lives = c.structure !== 'none' || c.behavior !== 'none' || c.surface !== 'none'
|
|
140
|
+
if (lives && !c.axis && !c.shownOn?.length) mute.push(`${name}/${c.std}`)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
assert.deepEqual(mute, [], `있는데 어디에 속하는지 말하지 않는 줄 — 「어디에도 없다」로 읽힌다:\n${mute.join('\n')}`)
|
|
144
|
+
|
|
145
|
+
/* 빈 비교로 통과하지 않게 — 실제로 그런 줄이 여럿 있다(자재 로트·서브로트·속성·관측·시험 결과). */
|
|
146
|
+
const attached = tables.flatMap(([, t]) => t.concepts).filter(c => !c.axis && c.shownOn?.length)
|
|
147
|
+
assert.ok(attached.length >= 4, `속해 있다고 말하는 줄이 실제로 있어야 한다 (실제 ${attached.length})`)
|
|
148
|
+
})
|