@things-factory/headless-twin 10.1.278 → 10.1.280
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/package.json +21 -7
- package/dist-server/engine/attention-digest.js.map +0 -1
- package/dist-server/engine/canonical-ingest.js.map +0 -1
- package/dist-server/engine/checkpoint-fold.js.map +0 -1
- package/dist-server/engine/command-routing.js.map +0 -1
- package/dist-server/engine/declared-stimulus.js.map +0 -1
- package/dist-server/engine/energy-topology.js.map +0 -1
- package/dist-server/engine/event-time.js.map +0 -1
- package/dist-server/engine/index.js.map +0 -1
- package/dist-server/engine/ingest-dedupe.js.map +0 -1
- package/dist-server/engine/ingest-health.js.map +0 -1
- package/dist-server/engine/integration-coverage.js.map +0 -1
- package/dist-server/engine/integration-probes.js.map +0 -1
- package/dist-server/engine/integration-runner.js.map +0 -1
- package/dist-server/engine/integration-target-profile.js.map +0 -1
- package/dist-server/engine/kpi-baseline.js.map +0 -1
- package/dist-server/engine/kpi-fold.js.map +0 -1
- package/dist-server/engine/kpi-query.js.map +0 -1
- package/dist-server/engine/kpi-target.js.map +0 -1
- package/dist-server/engine/live-attentions.js.map +0 -1
- package/dist-server/engine/live-feed-registry.js.map +0 -1
- package/dist-server/engine/load-meter.js.map +0 -1
- package/dist-server/engine/local-declarations.js.map +0 -1
- package/dist-server/engine/log.js.map +0 -1
- package/dist-server/engine/loop-lag.js.map +0 -1
- package/dist-server/engine/measured-estimator.js.map +0 -1
- package/dist-server/engine/measured-yield.js.map +0 -1
- package/dist-server/engine/model-basis.js.map +0 -1
- package/dist-server/engine/model-gap.js.map +0 -1
- package/dist-server/engine/model-refusal.js.map +0 -1
- package/dist-server/engine/model-vocabulary.js.map +0 -1
- package/dist-server/engine/oee-accumulator.js.map +0 -1
- package/dist-server/engine/operation-basis.js.map +0 -1
- package/dist-server/engine/producer-guard.js.map +0 -1
- package/dist-server/engine/property-effects.js.map +0 -1
- package/dist-server/engine/read-time.js.map +0 -1
- package/dist-server/engine/restart-policy.js.map +0 -1
- package/dist-server/engine/runtime-key.js.map +0 -1
- package/dist-server/engine/spec-coverage.js.map +0 -1
- package/dist-server/engine/stage-path.js.map +0 -1
- package/dist-server/engine/state-axes.js.map +0 -1
- package/dist-server/engine/structure-diff.js.map +0 -1
- package/dist-server/engine/travel-estimator.js.map +0 -1
- package/dist-server/engine/twin-engine.js.map +0 -1
- package/dist-server/engine/twin-state-channel.js.map +0 -1
- package/dist-server/engine/warm-start.js.map +0 -1
- package/dist-server/index.js.map +0 -1
- package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +0 -1
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +0 -1
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +0 -1
- package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js.map +0 -1
- package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js.map +0 -1
- package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js.map +0 -1
- package/dist-server/migrations/1786600000000-RenameOperatoMesAdapterType.js.map +0 -1
- package/dist-server/migrations/1786700000000-AddDispatchRetryColumns.js.map +0 -1
- package/dist-server/migrations/1786800000000-CreateTwinReferenceReconciliations.js.map +0 -1
- package/dist-server/migrations/index.js.map +0 -1
- package/dist-server/routes.js.map +0 -1
- package/dist-server/service/actuation/actuation-advisor.js.map +0 -1
- package/dist-server/service/actuation/actuation-outcome.js.map +0 -1
- package/dist-server/service/actuation/actuation-rule-resolver.js.map +0 -1
- package/dist-server/service/actuation/actuation-rule.js.map +0 -1
- package/dist-server/service/actuation/approval-gateway.js.map +0 -1
- package/dist-server/service/actuation/catch-up-effects.js.map +0 -1
- package/dist-server/service/actuation/command-dispatcher.js.map +0 -1
- package/dist-server/service/actuation/command-store.js.map +0 -1
- package/dist-server/service/actuation/dispatch-after-approval.js.map +0 -1
- package/dist-server/service/actuation/effect-match.js.map +0 -1
- package/dist-server/service/actuation/index.js.map +0 -1
- package/dist-server/service/actuation/retry-plan.js.map +0 -1
- package/dist-server/service/actuation/rule-evaluate.js.map +0 -1
- package/dist-server/service/actuation/rule-loop.js.map +0 -1
- package/dist-server/service/actuation/rule-runner.js.map +0 -1
- package/dist-server/service/actuation/rule-wiring.js.map +0 -1
- package/dist-server/service/actuation/settle-effects.js.map +0 -1
- package/dist-server/service/actuation/sweep-dispatch.js.map +0 -1
- package/dist-server/service/actuation/twin-command-resolver.js.map +0 -1
- package/dist-server/service/actuation/twin-command.js.map +0 -1
- package/dist-server/service/actuation/twin-now.js.map +0 -1
- package/dist-server/service/index.js.map +0 -1
- package/dist-server/service/reference/actuation-routing.js.map +0 -1
- package/dist-server/service/reference/actuation-target.js.map +0 -1
- package/dist-server/service/reference/actuation-verification.js.map +0 -1
- package/dist-server/service/reference/attention-lane.js.map +0 -1
- package/dist-server/service/reference/connection-portability-resolver.js.map +0 -1
- package/dist-server/service/reference/connection-portability.js.map +0 -1
- package/dist-server/service/reference/control-routing.js.map +0 -1
- package/dist-server/service/reference/discovery-result.js.map +0 -1
- package/dist-server/service/reference/fill-cadence.js.map +0 -1
- package/dist-server/service/reference/fill-dedupe.js.map +0 -1
- package/dist-server/service/reference/fill-loop.js.map +0 -1
- package/dist-server/service/reference/fill-span.js.map +0 -1
- package/dist-server/service/reference/hook-contract.js.map +0 -1
- package/dist-server/service/reference/hook-rejected.js.map +0 -1
- package/dist-server/service/reference/hook-store.js.map +0 -1
- package/dist-server/service/reference/index.js.map +0 -1
- package/dist-server/service/reference/ingest-space.js.map +0 -1
- package/dist-server/service/reference/knob-defaults.js.map +0 -1
- package/dist-server/service/reference/live-cadence.js.map +0 -1
- package/dist-server/service/reference/live-feed-lease.js.map +0 -1
- package/dist-server/service/reference/live-ingest.js.map +0 -1
- package/dist-server/service/reference/reference-adapter.js.map +0 -1
- package/dist-server/service/reference/reference-assessment.js.map +0 -1
- package/dist-server/service/reference/reference-fill-run.js.map +0 -1
- package/dist-server/service/reference/reference-fill.js.map +0 -1
- package/dist-server/service/reference/reference-hook.js.map +0 -1
- package/dist-server/service/reference/reference-link-groups.js.map +0 -1
- package/dist-server/service/reference/reference-links.js.map +0 -1
- package/dist-server/service/reference/reference-live.js.map +0 -1
- package/dist-server/service/reference/reference-master.js.map +0 -1
- package/dist-server/service/reference/reference-probe.js.map +0 -1
- package/dist-server/service/reference/reference-progress-channel.js.map +0 -1
- package/dist-server/service/reference/reference-progress-subscription.js.map +0 -1
- package/dist-server/service/reference/reference-progress.js.map +0 -1
- package/dist-server/service/reference/reference-reconciliation-query.js.map +0 -1
- package/dist-server/service/reference/reference-reconciliation-writer.js.map +0 -1
- package/dist-server/service/reference/reference-reconciliation.js.map +0 -1
- package/dist-server/service/reference/reference-registry.js.map +0 -1
- package/dist-server/service/reference/reference-resolver.js.map +0 -1
- package/dist-server/service/reference/template-registry.js.map +0 -1
- package/dist-server/service/reference/twin-reference-reconciliation.js.map +0 -1
- package/dist-server/service/reference/twin-reference.js.map +0 -1
- package/dist-server/service/twin-attention/index.js.map +0 -1
- package/dist-server/service/twin-attention/twin-attention-query.js.map +0 -1
- package/dist-server/service/twin-audit/command-audit.js.map +0 -1
- package/dist-server/service/twin-audit/index.js.map +0 -1
- package/dist-server/service/twin-audit/twin-audit-event.js.map +0 -1
- package/dist-server/service/twin-audit/twin-audit-query.js.map +0 -1
- package/dist-server/service/twin-backfill/backfill-period-facts.js.map +0 -1
- package/dist-server/service/twin-backfill/backfill-shape.js.map +0 -1
- package/dist-server/service/twin-backfill/index.js.map +0 -1
- package/dist-server/service/twin-backfill/twin-backfill-resolver.js.map +0 -1
- package/dist-server/service/twin-control/index.js.map +0 -1
- package/dist-server/service/twin-control/twin-control-mutation.js.map +0 -1
- package/dist-server/service/twin-event/index.js.map +0 -1
- package/dist-server/service/twin-event/journal-count.js.map +0 -1
- package/dist-server/service/twin-event/twin-event-keys.js.map +0 -1
- package/dist-server/service/twin-event/twin-event-type.js.map +0 -1
- package/dist-server/service/twin-event/twin-event.js.map +0 -1
- package/dist-server/service/twin-forecast/forecast-metrics.js.map +0 -1
- package/dist-server/service/twin-forecast/forecast-tuning.js.map +0 -1
- package/dist-server/service/twin-forecast/gap-analytics.js.map +0 -1
- package/dist-server/service/twin-forecast/index.js.map +0 -1
- package/dist-server/service/twin-forecast/twin-forecast-query.js.map +0 -1
- package/dist-server/service/twin-ingest-window/index.js.map +0 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +0 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +0 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window.js.map +0 -1
- package/dist-server/service/twin-instance/index.js.map +0 -1
- package/dist-server/service/twin-instance/twin-instance.js.map +0 -1
- package/dist-server/service/twin-journal/event-type-split-shape.js.map +0 -1
- package/dist-server/service/twin-journal/event-type-split.js.map +0 -1
- package/dist-server/service/twin-journal/index.js.map +0 -1
- package/dist-server/service/twin-journal/journal-order.js.map +0 -1
- package/dist-server/service/twin-journal/twin-journal-query.js.map +0 -1
- package/dist-server/service/twin-lifecycle/domain-catalog.js.map +0 -1
- package/dist-server/service/twin-lifecycle/index.js.map +0 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +0 -1
- package/dist-server/service/twin-metrics/index.js.map +0 -1
- package/dist-server/service/twin-metrics/twin-metrics-query.js.map +0 -1
- package/dist-server/service/twin-model/axis-journal-evidence.js.map +0 -1
- package/dist-server/service/twin-model/choose-recipe.js.map +0 -1
- package/dist-server/service/twin-model/epcis-coverage.js.map +0 -1
- package/dist-server/service/twin-model/external-resolver.js.map +0 -1
- package/dist-server/service/twin-model/iec61850-coverage.js.map +0 -1
- package/dist-server/service/twin-model/index.js.map +0 -1
- package/dist-server/service/twin-model/isa95-coverage.js.map +0 -1
- package/dist-server/service/twin-model/item-ref.js.map +0 -1
- package/dist-server/service/twin-model/name-index.js.map +0 -1
- package/dist-server/service/twin-model/project-structure.js.map +0 -1
- package/dist-server/service/twin-model/standard-coverage.js.map +0 -1
- package/dist-server/service/twin-model/status-tally.js.map +0 -1
- package/dist-server/service/twin-model/twin-equipment.js.map +0 -1
- package/dist-server/service/twin-model/twin-lineage-query.js.map +0 -1
- package/dist-server/service/twin-model/twin-location.js.map +0 -1
- package/dist-server/service/twin-model/twin-model-item-query.js.map +0 -1
- package/dist-server/service/twin-model/twin-model-mutation.js.map +0 -1
- package/dist-server/service/twin-model/twin-model-query.js.map +0 -1
- package/dist-server/service/twin-model/twin-model-tree-query.js.map +0 -1
- package/dist-server/service/twin-model/twin-operation.js.map +0 -1
- package/dist-server/service/twin-readiness/index.js.map +0 -1
- package/dist-server/service/twin-readiness/twin-readiness-query.js.map +0 -1
- package/dist-server/service/twin-space/area-resolver.js.map +0 -1
- package/dist-server/service/twin-space/index.js.map +0 -1
- package/dist-server/service/twin-space/move-space.js.map +0 -1
- package/dist-server/service/twin-space/representation-role.js.map +0 -1
- package/dist-server/service/twin-space/space-integrity.js.map +0 -1
- package/dist-server/service/twin-space/twin-area.js.map +0 -1
- package/dist-server/service/twin-space/twin-space-area.js.map +0 -1
- package/dist-server/service/twin-space/twin-space-representation.js.map +0 -1
- package/dist-server/service/twin-space/twin-space-resolver.js.map +0 -1
- package/dist-server/service/twin-space/twin-space.js.map +0 -1
- package/dist-server/service/twin-state/index.js.map +0 -1
- package/dist-server/service/twin-state/twin-state-subscription.js.map +0 -1
- package/dist-server/service/twin-structure/index.js.map +0 -1
- package/dist-server/service/twin-structure/twin-structure.js.map +0 -1
- package/dist-server/service/twin-subject/event-subjects.js.map +0 -1
- package/dist-server/service/twin-subject/index.js.map +0 -1
- package/dist-server/service/twin-subject/item-resolver.js.map +0 -1
- package/dist-server/service/twin-subject/subject-query-shape.js.map +0 -1
- package/dist-server/service/twin-subject/twin-subject-backfill.js.map +0 -1
- package/dist-server/service/twin-subject/twin-subject-event.js.map +0 -1
- package/dist-server/service/twin-subject/twin-subject-fast-path.js.map +0 -1
- package/dist-server/service/twin-subject/twin-subject-query.js.map +0 -1
- package/dist-server/service/twin-subject/twin-subject-writer.js.map +0 -1
- package/dist-server/service/twin-target/index.js.map +0 -1
- package/dist-server/service/twin-target/twin-target-resolver.js.map +0 -1
- package/dist-server/service/twin-target/twin-target.js.map +0 -1
- package/dist-shared/axis-read.js.map +0 -1
- package/dist-shared/entity-delta.js.map +0 -1
- package/dist-shared/kpi-broadcast.js.map +0 -1
- package/dist-shared/touched-items.js.map +0 -1
- package/dist-shared/twin-level.js.map +0 -1
- package/server/engine/attention-digest.ts +0 -102
- package/server/engine/canonical-ingest.ts +0 -511
- package/server/engine/checkpoint-fold.ts +0 -12
- package/server/engine/command-routing.ts +0 -67
- package/server/engine/declared-stimulus.ts +0 -66
- package/server/engine/energy-topology.ts +0 -189
- package/server/engine/event-time.ts +0 -96
- package/server/engine/index.ts +0 -51
- package/server/engine/ingest-dedupe.ts +0 -24
- package/server/engine/ingest-health.ts +0 -24
- package/server/engine/integration-coverage.ts +0 -147
- package/server/engine/integration-probes.ts +0 -144
- package/server/engine/integration-runner.ts +0 -95
- package/server/engine/integration-target-profile.ts +0 -103
- package/server/engine/kpi-baseline.ts +0 -202
- package/server/engine/kpi-fold.ts +0 -1516
- package/server/engine/kpi-query.ts +0 -1210
- package/server/engine/kpi-target.ts +0 -248
- package/server/engine/live-attentions.ts +0 -30
- package/server/engine/live-feed-registry.ts +0 -59
- package/server/engine/load-meter.ts +0 -393
- package/server/engine/local-declarations.ts +0 -779
- package/server/engine/log.ts +0 -72
- package/server/engine/loop-lag.ts +0 -120
- package/server/engine/measured-estimator.ts +0 -91
- package/server/engine/measured-yield.ts +0 -89
- package/server/engine/model-basis.ts +0 -162
- package/server/engine/model-gap.ts +0 -168
- package/server/engine/model-refusal.ts +0 -31
- package/server/engine/model-vocabulary.ts +0 -82
- package/server/engine/oee-accumulator.ts +0 -195
- package/server/engine/operation-basis.ts +0 -131
- package/server/engine/producer-guard.ts +0 -64
- package/server/engine/property-effects.ts +0 -216
- package/server/engine/read-time.ts +0 -77
- package/server/engine/restart-policy.ts +0 -55
- package/server/engine/runtime-key.ts +0 -58
- package/server/engine/spec-coverage.ts +0 -105
- package/server/engine/stage-path.ts +0 -172
- package/server/engine/state-axes.ts +0 -60
- package/server/engine/structure-diff.ts +0 -88
- package/server/engine/travel-estimator.ts +0 -138
- package/server/engine/twin-engine.ts +0 -4979
- package/server/engine/twin-state-channel.ts +0 -39
- package/server/engine/warm-start.ts +0 -265
- package/server/index.ts +0 -90
- package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +0 -43
- package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +0 -54
- package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +0 -53
- package/server/migrations/1786300000000-IndexEventTypeByTime.ts +0 -48
- package/server/migrations/1786400000000-IndexEventCreatedAt.ts +0 -38
- package/server/migrations/1786500000000-CreateTwinSubjectEvents.ts +0 -93
- package/server/migrations/1786600000000-RenameOperatoMesAdapterType.ts +0 -66
- package/server/migrations/1786700000000-AddDispatchRetryColumns.ts +0 -82
- package/server/migrations/1786800000000-CreateTwinReferenceReconciliations.ts +0 -39
- package/server/migrations/index.ts +0 -25
- package/server/routes.ts +0 -75
- package/server/service/actuation/actuation-advisor.ts +0 -131
- package/server/service/actuation/actuation-outcome.ts +0 -97
- package/server/service/actuation/actuation-rule-resolver.ts +0 -147
- package/server/service/actuation/actuation-rule.ts +0 -139
- package/server/service/actuation/approval-gateway.ts +0 -44
- package/server/service/actuation/catch-up-effects.ts +0 -152
- package/server/service/actuation/command-dispatcher.ts +0 -474
- package/server/service/actuation/command-store.ts +0 -236
- package/server/service/actuation/dispatch-after-approval.ts +0 -71
- package/server/service/actuation/effect-match.ts +0 -124
- package/server/service/actuation/index.ts +0 -27
- package/server/service/actuation/retry-plan.ts +0 -145
- package/server/service/actuation/rule-evaluate.ts +0 -352
- package/server/service/actuation/rule-loop.ts +0 -160
- package/server/service/actuation/rule-runner.ts +0 -216
- package/server/service/actuation/rule-wiring.ts +0 -84
- package/server/service/actuation/settle-effects.ts +0 -115
- package/server/service/actuation/sweep-dispatch.ts +0 -179
- package/server/service/actuation/twin-command-resolver.ts +0 -387
- package/server/service/actuation/twin-command.ts +0 -232
- package/server/service/actuation/twin-now.ts +0 -97
- package/server/service/index.ts +0 -100
- package/server/service/reference/actuation-routing.ts +0 -192
- package/server/service/reference/actuation-target.ts +0 -163
- package/server/service/reference/actuation-verification.ts +0 -85
- package/server/service/reference/attention-lane.ts +0 -21
- package/server/service/reference/connection-portability-resolver.ts +0 -171
- package/server/service/reference/connection-portability.ts +0 -329
- package/server/service/reference/control-routing.ts +0 -62
- package/server/service/reference/discovery-result.ts +0 -95
- package/server/service/reference/fill-cadence.ts +0 -29
- package/server/service/reference/fill-dedupe.ts +0 -66
- package/server/service/reference/fill-loop.ts +0 -153
- package/server/service/reference/fill-span.ts +0 -31
- package/server/service/reference/hook-contract.ts +0 -364
- package/server/service/reference/hook-rejected.ts +0 -53
- package/server/service/reference/hook-store.ts +0 -28
- package/server/service/reference/index.ts +0 -18
- package/server/service/reference/ingest-space.ts +0 -104
- package/server/service/reference/knob-defaults.ts +0 -59
- package/server/service/reference/live-cadence.ts +0 -129
- package/server/service/reference/live-feed-lease.ts +0 -56
- package/server/service/reference/live-ingest.ts +0 -94
- package/server/service/reference/reference-adapter.ts +0 -985
- package/server/service/reference/reference-assessment.ts +0 -250
- package/server/service/reference/reference-fill-run.ts +0 -179
- package/server/service/reference/reference-fill.ts +0 -223
- package/server/service/reference/reference-hook.ts +0 -479
- package/server/service/reference/reference-link-groups.ts +0 -21
- package/server/service/reference/reference-links.ts +0 -77
- package/server/service/reference/reference-live.ts +0 -420
- package/server/service/reference/reference-master.ts +0 -999
- package/server/service/reference/reference-probe.ts +0 -264
- package/server/service/reference/reference-progress-channel.ts +0 -26
- package/server/service/reference/reference-progress-subscription.ts +0 -78
- package/server/service/reference/reference-progress.ts +0 -95
- package/server/service/reference/reference-reconciliation-query.ts +0 -28
- package/server/service/reference/reference-reconciliation-writer.ts +0 -38
- package/server/service/reference/reference-reconciliation.ts +0 -55
- package/server/service/reference/reference-registry.ts +0 -17
- package/server/service/reference/reference-resolver.ts +0 -969
- package/server/service/reference/template-registry.ts +0 -85
- package/server/service/reference/twin-reference-reconciliation.ts +0 -27
- package/server/service/reference/twin-reference.ts +0 -190
- package/server/service/twin-attention/index.ts +0 -3
- package/server/service/twin-attention/twin-attention-query.ts +0 -70
- package/server/service/twin-audit/command-audit.ts +0 -114
- package/server/service/twin-audit/index.ts +0 -5
- package/server/service/twin-audit/twin-audit-event.ts +0 -158
- package/server/service/twin-audit/twin-audit-query.ts +0 -72
- package/server/service/twin-backfill/backfill-period-facts.ts +0 -168
- package/server/service/twin-backfill/backfill-shape.ts +0 -86
- package/server/service/twin-backfill/index.ts +0 -3
- package/server/service/twin-backfill/twin-backfill-resolver.ts +0 -35
- package/server/service/twin-control/index.ts +0 -3
- package/server/service/twin-control/twin-control-mutation.ts +0 -150
- package/server/service/twin-event/index.ts +0 -3
- package/server/service/twin-event/journal-count.ts +0 -53
- package/server/service/twin-event/twin-event-keys.ts +0 -215
- package/server/service/twin-event/twin-event-type.ts +0 -68
- package/server/service/twin-event/twin-event.ts +0 -250
- package/server/service/twin-forecast/forecast-metrics.ts +0 -106
- package/server/service/twin-forecast/forecast-tuning.ts +0 -40
- package/server/service/twin-forecast/gap-analytics.ts +0 -139
- package/server/service/twin-forecast/index.ts +0 -3
- package/server/service/twin-forecast/twin-forecast-query.ts +0 -486
- package/server/service/twin-ingest-window/index.ts +0 -7
- package/server/service/twin-ingest-window/twin-ingest-window-query.ts +0 -125
- package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +0 -57
- package/server/service/twin-ingest-window/twin-ingest-window.ts +0 -113
- package/server/service/twin-instance/index.ts +0 -3
- package/server/service/twin-instance/twin-instance.ts +0 -199
- package/server/service/twin-journal/event-type-split-shape.ts +0 -112
- package/server/service/twin-journal/event-type-split.ts +0 -144
- package/server/service/twin-journal/index.ts +0 -3
- package/server/service/twin-journal/journal-order.ts +0 -20
- package/server/service/twin-journal/twin-journal-query.ts +0 -615
- package/server/service/twin-lifecycle/domain-catalog.ts +0 -51
- package/server/service/twin-lifecycle/index.ts +0 -3
- package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +0 -285
- package/server/service/twin-metrics/index.ts +0 -3
- package/server/service/twin-metrics/twin-metrics-query.ts +0 -172
- package/server/service/twin-model/axis-journal-evidence.ts +0 -57
- package/server/service/twin-model/choose-recipe.ts +0 -31
- package/server/service/twin-model/epcis-coverage.ts +0 -251
- package/server/service/twin-model/external-resolver.ts +0 -173
- package/server/service/twin-model/iec61850-coverage.ts +0 -111
- package/server/service/twin-model/index.ts +0 -25
- package/server/service/twin-model/isa95-coverage.ts +0 -320
- package/server/service/twin-model/item-ref.ts +0 -80
- package/server/service/twin-model/name-index.ts +0 -94
- package/server/service/twin-model/project-structure.ts +0 -213
- package/server/service/twin-model/standard-coverage.ts +0 -35
- package/server/service/twin-model/status-tally.ts +0 -37
- package/server/service/twin-model/twin-equipment.ts +0 -191
- package/server/service/twin-model/twin-lineage-query.ts +0 -327
- package/server/service/twin-model/twin-location.ts +0 -147
- package/server/service/twin-model/twin-model-item-query.ts +0 -1029
- package/server/service/twin-model/twin-model-mutation.ts +0 -503
- package/server/service/twin-model/twin-model-query.ts +0 -792
- package/server/service/twin-model/twin-model-tree-query.ts +0 -211
- package/server/service/twin-model/twin-operation.ts +0 -157
- package/server/service/twin-readiness/index.ts +0 -3
- package/server/service/twin-readiness/twin-readiness-query.ts +0 -96
- package/server/service/twin-space/area-resolver.ts +0 -75
- package/server/service/twin-space/index.ts +0 -8
- package/server/service/twin-space/move-space.ts +0 -262
- package/server/service/twin-space/representation-role.ts +0 -15
- package/server/service/twin-space/space-integrity.ts +0 -293
- package/server/service/twin-space/twin-area.ts +0 -63
- package/server/service/twin-space/twin-space-area.ts +0 -66
- package/server/service/twin-space/twin-space-representation.ts +0 -99
- package/server/service/twin-space/twin-space-resolver.ts +0 -582
- package/server/service/twin-space/twin-space.ts +0 -148
- package/server/service/twin-state/index.ts +0 -3
- package/server/service/twin-state/twin-state-subscription.ts +0 -62
- package/server/service/twin-structure/index.ts +0 -3
- package/server/service/twin-structure/twin-structure.ts +0 -96
- package/server/service/twin-subject/event-subjects.ts +0 -162
- package/server/service/twin-subject/index.ts +0 -10
- package/server/service/twin-subject/item-resolver.ts +0 -92
- package/server/service/twin-subject/subject-query-shape.ts +0 -84
- package/server/service/twin-subject/twin-subject-backfill.ts +0 -196
- package/server/service/twin-subject/twin-subject-event.ts +0 -101
- package/server/service/twin-subject/twin-subject-fast-path.ts +0 -346
- package/server/service/twin-subject/twin-subject-query.ts +0 -210
- package/server/service/twin-subject/twin-subject-writer.ts +0 -189
- package/server/service/twin-target/index.ts +0 -5
- package/server/service/twin-target/twin-target-resolver.ts +0 -142
- package/server/service/twin-target/twin-target.ts +0 -97
- package/server/types/global.d.ts +0 -22
- package/shared/axis-read.ts +0 -28
- package/shared/entity-delta.ts +0 -335
- package/shared/kpi-broadcast.ts +0 -13
- package/shared/touched-items.ts +0 -73
- package/shared/twin-level.ts +0 -48
- package/test/ack-shape-alignment.test.ts +0 -97
- package/test/actuation-advisor.test.ts +0 -55
- package/test/actuation-approval-door.test.ts +0 -411
- package/test/actuation-dispatch.test.ts +0 -209
- package/test/actuation-effect.test.ts +0 -162
- package/test/actuation-outcome.test.ts +0 -152
- package/test/actuation-rule-runner.test.ts +0 -281
- package/test/actuation-rule.test.ts +0 -266
- package/test/actuation-seam.test.ts +0 -255
- package/test/actuation-settle.test.ts +0 -164
- package/test/actuation-target.test.ts +0 -145
- package/test/actuation-verification-state.test.ts +0 -139
- package/test/adopt-structure-live.test.ts +0 -133
- package/test/aggregate-not-findone.test.ts +0 -112
- package/test/area-resolver-db.test.ts +0 -110
- package/test/attention-digest.test.ts +0 -161
- package/test/axis-declaration-passthrough-db.test.ts +0 -152
- package/test/axis-journal-evidence.test.ts +0 -71
- package/test/axis-read.test.ts +0 -188
- package/test/backfill-shape.test.ts +0 -89
- package/test/boot-resume.test.ts +0 -287
- package/test/broadcast-cost-baseline.test.ts +0 -201
- package/test/broadcast-period.test.ts +0 -58
- package/test/broadcast-queue-overflow.test.ts +0 -244
- package/test/canonical-ingest-vocabularies.test.ts +0 -451
- package/test/canonical-quantity-door.test.ts +0 -61
- package/test/capability-mapping.test.ts +0 -89
- package/test/checkpoint-refuses-empty.test.ts +0 -103
- package/test/choose-recipe.test.ts +0 -117
- package/test/column-type-portability.test.ts +0 -122
- package/test/command-routing.test.ts +0 -61
- package/test/command-store-writes-columns.test.ts +0 -126
- package/test/connection-portability-doors.test.ts +0 -87
- package/test/connector-capability-declaration.test.ts +0 -195
- package/test/contract-layer-guard.test.ts +0 -48
- package/test/control-capability.test.ts +0 -98
- package/test/cursor-stall-not-read-failure.test.ts +0 -126
- package/test/declaration-reaches-model.test.ts +0 -226
- package/test/declared-location-types.test.ts +0 -89
- package/test/declared-stimulus.test.ts +0 -88
- package/test/dedupe-transformation.test.ts +0 -112
- package/test/discovery-result.test.ts +0 -75
- package/test/dispatch-after-approval-source.test.ts +0 -81
- package/test/duration-estimators.test.ts +0 -144
- package/test/energy-topology.test.ts +0 -113
- package/test/entity-delta.test.ts +0 -352
- package/test/equipment-identity-reaches.test.ts +0 -57
- package/test/event-subjects.test.ts +0 -148
- package/test/event-time-column.test.ts +0 -67
- package/test/event-type-split.test.ts +0 -178
- package/test/external-resolver.test.ts +0 -177
- package/test/fact-scope-wiring.test.ts +0 -96
- package/test/fill-dedupe.test.ts +0 -59
- package/test/fill-loop.test.ts +0 -35
- package/test/first-envelope.test.ts +0 -153
- package/test/fold-gap-speaks.test.ts +0 -53
- package/test/fold-shift-dst.test.ts +0 -110
- package/test/forecast-metrics.test.ts +0 -157
- package/test/forecast-tuning.test.ts +0 -42
- package/test/gap-analytics.test.ts +0 -113
- package/test/generated-not-counted.test.ts +0 -99
- package/test/guard-roots.ts +0 -76
- package/test/home-in-area.test.ts +0 -90
- package/test/hook-address.test.ts +0 -28
- package/test/hook-rejected-shape.test.ts +0 -86
- package/test/hook-response-sum.test.ts +0 -193
- package/test/hook-sequence.test.ts +0 -333
- package/test/ingest-bench.test.ts +0 -70
- package/test/ingest-dedupe.test.ts +0 -56
- package/test/ingest-expected-quiet.test.ts +0 -111
- package/test/ingest-health-engine.test.ts +0 -248
- package/test/ingest-health-wiring.test.ts +0 -120
- package/test/ingest-health.test.ts +0 -306
- package/test/ingest-history.test.ts +0 -247
- package/test/ingest-idempotent.test.ts +0 -211
- package/test/ingest-reconcile-four-ways.test.ts +0 -97
- package/test/ingest-rules-header.test.ts +0 -100
- package/test/ingest-running-guard.test.ts +0 -136
- package/test/ingest-space.test.ts +0 -78
- package/test/ingest-window-rolls-unobserved.test.ts +0 -72
- package/test/ingest-wiring-guard.test.ts +0 -197
- package/test/instance-cache-lifecycle.test.ts +0 -110
- package/test/intake-mapping-parity.test.ts +0 -175
- package/test/integration-probes.test.ts +0 -103
- package/test/integration-runner.test.ts +0 -95
- package/test/item-ref.test.ts +0 -78
- package/test/journal-order-axis.test.ts +0 -80
- package/test/journal-read-discipline.test.ts +0 -186
- package/test/journal-retention.test.ts +0 -275
- package/test/journal-sort-axis.test.ts +0 -142
- package/test/journal-write-door.test.ts +0 -110
- package/test/journal-write-trend.test.ts +0 -115
- package/test/kernel-kind-guard.test.ts +0 -98
- package/test/knob-defaults.test.ts +0 -72
- package/test/kpi-baseline-db.test.ts +0 -216
- package/test/kpi-baseline.test.ts +0 -196
- package/test/kpi-fold.test.ts +0 -1456
- package/test/kpi-query-bench.test.ts +0 -130
- package/test/lens-own-places.test.ts +0 -50
- package/test/lineage-survives-restart.test.ts +0 -234
- package/test/live-cadence.test.ts +0 -170
- package/test/live-cursor-wiring.test.ts +0 -87
- package/test/live-feed-lease.test.ts +0 -63
- package/test/live-feed-registry.test.ts +0 -75
- package/test/live-kernel-facts.test.ts +0 -161
- package/test/live-mirror-parity.test.ts +0 -286
- package/test/load-meter.test.ts +0 -328
- package/test/local-declarations.test.ts +0 -662
- package/test/log-stamp.test.ts +0 -59
- package/test/loop-lag.test.ts +0 -82
- package/test/master-to-twin.test.ts +0 -279
- package/test/measured-yield.test.ts +0 -90
- package/test/merge-streams.test.ts +0 -70
- package/test/mirror-resumes-from-checkpoint.test.ts +0 -192
- package/test/model-basis.test.ts +0 -141
- package/test/model-gap.test.ts +0 -153
- package/test/model-refusal.test.ts +0 -75
- package/test/model-vocabulary.test.ts +0 -114
- package/test/move-space.test.ts +0 -149
- package/test/mutation-gate.test.ts +0 -122
- package/test/oee-accumulator.test.ts +0 -204
- package/test/oee-site-calendar.test.ts +0 -146
- package/test/open-doors.test.ts +0 -197
- package/test/operation-basis.test.ts +0 -127
- package/test/operational-vocabulary.test.ts +0 -108
- package/test/operations-capability-db.test.ts +0 -165
- package/test/order-and-transaction-split.test.ts +0 -107
- package/test/order-fold-host.test.ts +0 -112
- package/test/producer-guard.test.ts +0 -66
- package/test/project-structure-db.test.ts +0 -226
- package/test/projection-reaches-screen.test.ts +0 -277
- package/test/projection-reads-declared.test.ts +0 -109
- package/test/property-effects.test.ts +0 -128
- package/test/provenance-promotion.test.ts +0 -99
- package/test/read-failure-ledger.test.ts +0 -106
- package/test/read-failure-visible.test.ts +0 -145
- package/test/read-time.test.ts +0 -118
- package/test/reference-fill.test.ts +0 -235
- package/test/reference-grounding.test.ts +0 -70
- package/test/reference-hook.test.ts +0 -177
- package/test/reference-links.test.ts +0 -56
- package/test/reference-reconciliation.test.ts +0 -39
- package/test/registry-key-guard.test.ts +0 -80
- package/test/rejection-carries-event-id.test.ts +0 -101
- package/test/rename-twin.test.ts +0 -89
- package/test/resolve-ts-siblings.mjs +0 -52
- package/test/restart-policy.test.ts +0 -111
- package/test/resync-origin-site.test.ts +0 -83
- package/test/retry-plan.test.ts +0 -139
- package/test/revision-axis.test.ts +0 -110
- package/test/runtime-key.test.ts +0 -66
- package/test/save-intake-collision.test.ts +0 -91
- package/test/scale-twin-bench.test.ts +0 -103
- package/test/secrets-at-rest.test.ts +0 -135
- package/test/snapshot-freshness.test.ts +0 -60
- package/test/source-outcome-audit.test.ts +0 -104
- package/test/space-integrity.test.ts +0 -223
- package/test/space-representation.test.ts +0 -66
- package/test/space-scope-includes-stopped.test.ts +0 -55
- package/test/spec-coverage.test.ts +0 -113
- package/test/stage-path.test.ts +0 -95
- package/test/standard-coverage.test.ts +0 -148
- package/test/state-axes.test.ts +0 -74
- package/test/status-tally.test.ts +0 -55
- package/test/streamline-e2e.test.ts +0 -76
- package/test/structure-revision-db.test.ts +0 -315
- package/test/subject-fast-path.test.ts +0 -148
- package/test/subject-read-model.test.ts +0 -175
- package/test/sweep-dispatch.test.ts +0 -191
- package/test/tenant-registry-db.test.ts +0 -149
- package/test/time-range.test.ts +0 -152
- package/test/touched-items.test.ts +0 -61
- package/test/twin-audit.test.ts +0 -83
- package/test/twin-event-keys.test.ts +0 -193
- package/test/twin-model-item-db.test.ts +0 -319
- package/test/twin-model-parent-relation-db.test.ts +0 -125
- package/test/twin-model-tree-db.test.ts +0 -209
- package/test/twin-origin-resync.test.ts +0 -133
- package/test/twin-state-channel.test.ts +0 -111
- package/test/vocabulary-guard.test.ts +0 -43
- package/test/warm-start-seam.test.ts +0 -162
- package/test/warm-start.test.ts +0 -297
- package/test/withheld-door.test.ts +0 -95
- package/test/yield-loop.test.ts +0 -199
- package/tsconfig.json +0 -15
- package/tsconfig.shared.json +0 -23
- package/tsconfig.shared.tsbuildinfo +0 -1
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* 속성 효과 등록부 — **지어낸 효과를 등록하지 않고, 있는 효과를 숨기지 않는다.**
|
|
3
|
-
*
|
|
4
|
-
* ── 왜 이 시험이 있나 (2026-08-18) ──────────────────────────────────────────
|
|
5
|
-
* 화면은 속성마다 「무엇을 바꾸나」를 말한다. 등록되지 않은 속성에는 「표시만 됩니다」라고 한다 — 그것이
|
|
6
|
-
* 정직한 기본값이다. 그런데 에너지 값들(계약전력·정격·요금)은 **오래전부터 읽히고 있었는데** 등록부에
|
|
7
|
-
* 없었다: 사용자는 계약전력을 넣고도 「이 값은 아무것도 바꾸지 않는다」는 문장을 읽었다. 들어온 것이 없는 거짓말이다.
|
|
8
|
-
*
|
|
9
|
-
* 반대 방향의 위험도 있다: 효과를 등록해 두고 **읽는 코드가 없으면** 화면이 「쓰입니다」라고 말하는데
|
|
10
|
-
* 아무 일도 일어나지 않는다. 그래서 등록부의 각 줄이 가리키는 자리를 이 시험이 확인한다.
|
|
11
|
-
*/
|
|
12
|
-
import { test } from 'node:test'
|
|
13
|
-
import assert from 'node:assert/strict'
|
|
14
|
-
import { readFileSync } from 'node:fs'
|
|
15
|
-
import { fileURLToPath } from 'node:url'
|
|
16
|
-
import { createRequire } from 'node:module'
|
|
17
|
-
|
|
18
|
-
import { PROPERTY_EFFECTS, propertyEffectsOf, declarationTargetsOf } from '../server/engine/property-effects.ts'
|
|
19
|
-
|
|
20
|
-
const req = createRequire(import.meta.url)
|
|
21
|
-
/*
|
|
22
|
-
* 커널과 계약은 각각 **한 벌로 묶인 산출물**을 내보낸다(`dist-cjs/index.cjs`) — 서브패스는 exports 로
|
|
23
|
-
* 막혀 있다. 그래서 모듈마다 읽지 않고 묶음을 읽는다.
|
|
24
|
-
*
|
|
25
|
-
* **둘 다 읽는다** — 어휘와 판별은 계약에, 그것을 쓰는 엔진은 커널에 있다(2026-08-30 층 분리).
|
|
26
|
-
* 한쪽만 읽으면 「읽는 코드가 없다」는 거짓 판정이 난다.
|
|
27
|
-
*/
|
|
28
|
-
const kernelBundle =
|
|
29
|
-
readFileSync(req.resolve('@operato/twin-kernel'), 'utf-8') + '\n' + readFileSync(req.resolve('@operato/ops-contract'), 'utf-8')
|
|
30
|
-
const hostSrc = (file: string): string => readFileSync(fileURLToPath(new URL(`../server/${file}`, import.meta.url)), 'utf-8')
|
|
31
|
-
|
|
32
|
-
test('등록부의 모든 줄이 자기 자리를 가리킨다 — 빈 항목이 없다', () => {
|
|
33
|
-
for (const e of PROPERTY_EFFECTS) {
|
|
34
|
-
assert.ok(e.id, '속성 id 가 있어야 한다')
|
|
35
|
-
assert.ok(e.axes.length, `${e.id}: 어느 축의 속성인지 말해야 한다`)
|
|
36
|
-
assert.match(e.effect, /^twin\.propeffect\./, `${e.id}: 효과 문장은 i18n 키다(서버는 사람 말을 만들지 않는다)`)
|
|
37
|
-
assert.ok(e.readBy && e.readBy.length > 10, `${e.id}: 실제로 읽는 자리를 적어야 한다`)
|
|
38
|
-
}
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
test('등록한 속성 id 를 읽는 코드가 실제로 있다 — 「쓰입니다」가 거짓이 되지 않게', () => {
|
|
42
|
-
/* 읽는 쪽 소스 — 커널(에너지·환산·주행)과 호스트(요금·주행 추정). */
|
|
43
|
-
const corpus = [kernelBundle, hostSrc('engine/kpi-query.ts'), hostSrc('engine/travel-estimator.ts')].join('\n')
|
|
44
|
-
|
|
45
|
-
const missing: string[] = []
|
|
46
|
-
for (const e of PROPERTY_EFFECTS) {
|
|
47
|
-
/* id 그대로 나오거나(문자열 리터럴), 커널 어휘 상수의 마지막 마디로 나온다(EMS_PROPERTY.contractKW). */
|
|
48
|
-
const tail = e.id.includes('.') ? e.id.split('.').pop()! : e.id
|
|
49
|
-
if (!corpus.includes(`'${e.id}'`) && !corpus.includes(`"${e.id}"`) && !new RegExp(`\\b${tail}\\b`).test(corpus)) missing.push(e.id)
|
|
50
|
-
}
|
|
51
|
-
assert.deepEqual(missing, [], `읽는 코드를 찾지 못한 등록 항목:\n${missing.join('\n')}`)
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
test('에너지 값들이 등록돼 있다 — 예전에는 「표시만 됩니다」라고 말했다', () => {
|
|
55
|
-
const ids = PROPERTY_EFFECTS.map(e => e.id)
|
|
56
|
-
for (const id of ['contract.kW', 'power.ratedKW', 'storage.capacityKWh', 'tariff.demandChargePerKW', 'tariff.currency'])
|
|
57
|
-
assert.ok(ids.includes(id), `${id} 가 등록부에 없다`)
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
test('축으로 갈라 낸다 — 그 축의 항목에만 효과를 말한다', () => {
|
|
61
|
-
const locations = propertyEffectsOf('locations')
|
|
62
|
-
const equipment = propertyEffectsOf('equipment')
|
|
63
|
-
assert.ok(locations['contract.kW'], '계약전력은 자리의 속성이다')
|
|
64
|
-
assert.equal(locations['power.ratedKW'], undefined, '설비 속성을 자리에 말하지 않는다')
|
|
65
|
-
assert.ok(equipment['speed'], '속도는 설비의 속성이다')
|
|
66
|
-
assert.equal(equipment['tariff.currency'], undefined, '요금은 자리(수전)의 속성이다')
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
test('요금은 커널이 아니라 호스트가 읽는다 — 그 사실을 readBy 가 말한다', () => {
|
|
70
|
-
const tariff = PROPERTY_EFFECTS.filter(e => e.id.startsWith('tariff.'))
|
|
71
|
-
assert.equal(tariff.length, 3)
|
|
72
|
-
for (const e of tariff) assert.match(e.readBy, /kpi-query/, '금액은 저널을 계산하는 쪽이 만든다')
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
/*
|
|
76
|
-
* ── 어느 대상에 얹히나 (2026-08-18) ────────────────────────────────────────
|
|
77
|
-
* 지게차에 계약전력을 선언할 수 있었다 — 문은 받고 화면은 「넣었습니다」라고 말하는데, 읽는 코드는
|
|
78
|
-
* 자리(수전)만 본다. 그 제한은 이미 등록부의 축이 알고 있으므로, 검사에 그 지식을 쓴다(두 곳에 적지
|
|
79
|
-
* 않는다). 등록부에 없는 속성은 **제한을 모르는 것**이므로 받아 준다 — 속성은 열려 있고 효과만 닫혀 있다.
|
|
80
|
-
*/
|
|
81
|
-
test('축이 곧 얹을 수 있는 대상이다 — 목록을 따로 적지 않는다', () => {
|
|
82
|
-
assert.deepEqual(declarationTargetsOf('contract.kW'), ['location'])
|
|
83
|
-
assert.deepEqual(declarationTargetsOf('speed'), ['equipment'])
|
|
84
|
-
assert.deepEqual(declarationTargetsOf('operation.duration'), ['operation'])
|
|
85
|
-
/* 선언 대상이 아닌 축(자재 정의)은 제한을 말하지 않는다 — 이 문의 대상이 아니다. */
|
|
86
|
-
assert.equal(declarationTargetsOf('perBaseUnit'), undefined)
|
|
87
|
-
assert.equal(declarationTargetsOf('made.up.property'), undefined, '모르는 속성은 제한도 모른다')
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
test('공정 시간이 등록돼 있고, 그 값을 실어 보내는 자리를 가리킨다', () => {
|
|
91
|
-
const e = PROPERTY_EFFECTS.find(x => x.id === 'operation.duration')!
|
|
92
|
-
assert.ok(e, '공정 시간이 등록부에 있어야 한다')
|
|
93
|
-
assert.deepEqual(e.axes, ['operations'])
|
|
94
|
-
assert.match(e.readBy, /declareDurations/, '커널의 어느 창구로 가는지 말한다')
|
|
95
|
-
/* 실어 보내는 코드가 실제로 있다 — 등록만 해 두고 아무도 나르지 않으면 화면이 거짓을 말한다. */
|
|
96
|
-
const layer = hostSrc('engine/local-declarations.ts')
|
|
97
|
-
assert.match(layer, /OPERATION_PROPERTY\.duration/, '선언 겹이 그 속성을 안다')
|
|
98
|
-
assert.match(layer, /isoDurationOf/, '커널이 읽는 표기로 바꾼다')
|
|
99
|
-
assert.match(hostSrc('engine/twin-engine.ts'), /declareDurations\(declared\)/, '기동·구조전환 때 커널에 싣는다')
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
/*
|
|
103
|
-
* 모수도 같은 배선을 지난다 (2026-08-19).
|
|
104
|
-
*
|
|
105
|
-
* 이 시험이 소스를 보는 이유: 모수를 읽는 순간은 **작업이 완료될 때**라서 실 앱에서 관측하려면 라인이
|
|
106
|
-
* 한 바퀴 돌아야 한다(복원된 25분 작업이 쌓인 트윈에서는 그 관측이 오래 걸린다 — 실제로 그랬다).
|
|
107
|
-
* 값이 커널까지 가는 **경로**는 여기서 지키고, 커널이 그 값을 쓰는지는 커널 시험이 값으로 지킨다.
|
|
108
|
-
*/
|
|
109
|
-
test('수율·셋업이 커널 창구까지 배선돼 있다 — 등록만 하고 나르지 않으면 화면이 거짓을 말한다', () => {
|
|
110
|
-
const layer = hostSrc('engine/local-declarations.ts')
|
|
111
|
-
assert.match(layer, /OPERATION_PROPERTY\.yield/, '선언 겹이 수율을 안다')
|
|
112
|
-
assert.match(layer, /OPERATION_PROPERTY\.setupDuration/, '선언 겹이 셋업을 안다')
|
|
113
|
-
/* 퍼센트 → 비율 환산은 **한 곳**이어야 한다(두 곳이면 100 이 두 번 걸린다). */
|
|
114
|
-
const divisions = layer.match(/pct \/ 100/g) ?? []
|
|
115
|
-
assert.equal(divisions.length, 1, `퍼센트 환산은 한 자리에서만 (발견 ${divisions.length})`)
|
|
116
|
-
assert.match(hostSrc('engine/twin-engine.ts'), /declareParameters\(params\)/, '기동·구조전환 때 커널에 싣는다')
|
|
117
|
-
/* 커널이 그 창구를 갖지 않은 버전이면 **말한다** — 알리지 않고 상수로 돌지 않게. */
|
|
118
|
-
assert.match(hostSrc('engine/twin-engine.ts'), /declareParameters missing/, '버전 어긋남을 경고로 드러낸다')
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
test('공정 모수가 등록부에 있고 읽는 자리를 가리킨다', () => {
|
|
122
|
-
for (const id of ['operation.yield', 'operation.setupDuration']) {
|
|
123
|
-
const e = PROPERTY_EFFECTS.find(x => x.id === id)!
|
|
124
|
-
assert.ok(e, `${id} 가 등록부에 있어야 한다`)
|
|
125
|
-
assert.deepEqual(e.axes, ['operations'])
|
|
126
|
-
assert.match(e.readBy, /declareParameters/, '어느 창구로 가는지 말한다')
|
|
127
|
-
}
|
|
128
|
-
})
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Whether a fact was declared as having actually happened, promoted out of the payload.
|
|
3
|
-
*
|
|
4
|
-
* ── What this was for (measured 2026-09-09) ───────────────────────────────
|
|
5
|
-
* The twin's journal held 1,134 rows and every one of them was produced by a generator. There
|
|
6
|
-
* was no way to ask:
|
|
7
|
-
*
|
|
8
|
-
* select count(*) from twin_events 1134
|
|
9
|
-
* select count(*) where payload like '%SIM-%' 1134
|
|
10
|
-
* select count(*) where order_id like 'WO-SIM-%' 558 ← half of them
|
|
11
|
-
*
|
|
12
|
-
* The only signal was a naming convention on the sending side (`SIM-`, `WO-SIM-`), and the
|
|
13
|
-
* column that convention reaches was empty on half the rows. Meanwhile the screens compute
|
|
14
|
-
* yield and utilisation from those rows and cannot say what they are made of.
|
|
15
|
-
*
|
|
16
|
-
* ── The property that matters is the absent case ──────────────────────────
|
|
17
|
-
* `null` means the sender did not declare, which is **not** "it actually happened". Reading
|
|
18
|
-
* those two as the same thing is the whole defect: a generator that omits the field has its
|
|
19
|
-
* output counted as production, and nothing says so.
|
|
20
|
-
*
|
|
21
|
-
* So most of what is asserted here is about absence, not about the two declared values.
|
|
22
|
-
*/
|
|
23
|
-
import { test } from 'node:test'
|
|
24
|
-
import assert from 'node:assert/strict'
|
|
25
|
-
|
|
26
|
-
import { provenanceOf, twinEventKeys } from '../server/service/twin-event/twin-event-keys.js'
|
|
27
|
-
|
|
28
|
-
const envelope = (over: Record<string, unknown> = {}) => ({
|
|
29
|
-
eventId: 'e1',
|
|
30
|
-
eventType: 'task.status',
|
|
31
|
-
eventTime: '2026-09-09T02:00:00.000Z',
|
|
32
|
-
tenantId: 't1',
|
|
33
|
-
data: { taskId: 'SIM-ECU-0001-01', status: 'completed' },
|
|
34
|
-
...over
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
test('★ a fact declared simulated says so in a column, not only inside the payload', () => {
|
|
38
|
-
assert.equal(provenanceOf(envelope({ provenance: 'simulated' })), 'simulated')
|
|
39
|
-
assert.equal(twinEventKeys(envelope({ provenance: 'simulated' })).provenance, 'simulated')
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
test('a fact declared actual says that', () => {
|
|
43
|
-
assert.equal(provenanceOf(envelope({ provenance: 'actual' })), 'actual')
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
test('★ no declaration is not a declaration of actual — this is the defect being fixed', () => {
|
|
47
|
-
/*
|
|
48
|
-
* The payload here carries `SIM-…` in its task id, which is exactly how the 1,134 rows were
|
|
49
|
-
* recognisable by hand. The column still says nothing, because the sender said nothing. That
|
|
50
|
-
* is correct: the twin does not judge, it records what it was told.
|
|
51
|
-
*
|
|
52
|
-
* A reader has to distinguish three states. If it folds the last two together, a generator
|
|
53
|
-
* that forgets the field is counted as production.
|
|
54
|
-
*/
|
|
55
|
-
assert.equal(provenanceOf(envelope()), undefined)
|
|
56
|
-
assert.equal(twinEventKeys(envelope()).provenance, undefined)
|
|
57
|
-
|
|
58
|
-
assert.notEqual(provenanceOf(envelope()), 'actual', 'absent must never resolve to actual')
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
test('a value the source made up does not reach the index', () => {
|
|
62
|
-
/*
|
|
63
|
-
* Same discipline as `actionOf`, which takes only ADD · OBSERVE · DELETE. A column with three
|
|
64
|
-
* possible values stops being answerable the moment a fourth arrives from a source we do not
|
|
65
|
-
* control.
|
|
66
|
-
*/
|
|
67
|
-
for (const bogus of ['ACTUAL', 'Simulated', 'fake', 'true', '', 'sim', 1, true, null, {}]) {
|
|
68
|
-
assert.equal(provenanceOf(envelope({ provenance: bogus })), undefined, `${JSON.stringify(bogus)} reached the column`)
|
|
69
|
-
}
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
test('the declaration is read from the envelope, not from the source data', () => {
|
|
73
|
-
/*
|
|
74
|
-
* `data` is where the source system writes. If this were read from there, a source could
|
|
75
|
-
* declare its own facts actual by writing a key, and the declaration would no longer be the
|
|
76
|
-
* connecting side's statement. The envelope is ours.
|
|
77
|
-
*/
|
|
78
|
-
const sneaky = envelope({ data: { taskId: 'x', provenance: 'actual' } })
|
|
79
|
-
assert.equal(provenanceOf(sneaky), undefined, 'a value inside data was promoted')
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
test('promoting it does not disturb the keys already promoted', () => {
|
|
83
|
-
/*
|
|
84
|
-
* This function is the write path's only promotion point, so adding one key is checked
|
|
85
|
-
* together with not disturbing the others.
|
|
86
|
-
*/
|
|
87
|
-
const full = twinEventKeys(
|
|
88
|
-
envelope({
|
|
89
|
-
provenance: 'simulated',
|
|
90
|
-
data: { action: 'OBSERVE', bizStep: 'commissioning', epcList: ['urn:epc:id:sgtin:1.2.3'], orderId: 'WO-1' }
|
|
91
|
-
})
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
assert.equal(full.action, 'OBSERVE')
|
|
95
|
-
assert.equal(full.bizStep, 'commissioning')
|
|
96
|
-
assert.equal(full.epc, 'urn:epc:id:sgtin:1.2.3')
|
|
97
|
-
assert.equal(full.orderId, 'WO-1')
|
|
98
|
-
assert.equal(full.provenance, 'simulated')
|
|
99
|
-
})
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* ★ 연결된 시스템에 닿지 못한 것과 다시 닿은 것을 인제스트 레저가 기록한다.
|
|
3
|
-
*
|
|
4
|
-
* ── 무엇이 없었나 (2026-08-27) ──────────────────────────────────────────────
|
|
5
|
-
* 커넥터는 폴링이 실패할 때 세 가지를 안다: 연달아 몇 번인지 · 언제부터인지 · 다음에 언제 다시 묻는지.
|
|
6
|
-
* 그런데 어댑터 계약에는 이유 한 줄만 받는 자리가 있었다. 그래서 커넥터가 세 가지를 **한국어 문장으로
|
|
7
|
-
* 합쳐** 이유 칸에 넣고 있었다(`ppms-sunsage.ts`).
|
|
8
|
-
*
|
|
9
|
-
* 두 가지가 틀어진다.
|
|
10
|
-
* ① 연달아 횟수와 시작 시각은 이 레저가 **이미 센다**. 두 곳에서 세면 갈라진다.
|
|
11
|
-
* ② 그 칸은 화면에 그려지고 화면은 다섯 언어로 그린다. 한 언어의 문장은 번역되지 않는다.
|
|
12
|
-
*
|
|
13
|
-
* 그리고 회복을 받는 자리가 아예 없었다. 읽기가 다시 성공하면 실패 기록만 지워졌고, 그러면 밤새 두 시간
|
|
14
|
-
* 끊겼던 연결과 한 번도 끊기지 않은 연결이 아침에 똑같이 보인다.
|
|
15
|
-
*/
|
|
16
|
-
import { test } from 'node:test'
|
|
17
|
-
import assert from 'node:assert/strict'
|
|
18
|
-
import {
|
|
19
|
-
newIngestLedger,
|
|
20
|
-
recordReadFailure,
|
|
21
|
-
recordRecovered,
|
|
22
|
-
clearReadFailure,
|
|
23
|
-
ingestHealth
|
|
24
|
-
} from '../server/engine/ingest-health.js'
|
|
25
|
-
|
|
26
|
-
const t0 = Date.UTC(2026, 7, 27, 3, 0, 0)
|
|
27
|
-
|
|
28
|
-
test('★ 연달아 실패한 횟수와 시작 시각은 레저가 센다 — 커넥터가 보내지 않는다', () => {
|
|
29
|
-
const l = newIngestLedger()
|
|
30
|
-
recordReadFailure(l, 'connect timeout', t0, 'inverters')
|
|
31
|
-
recordReadFailure(l, 'connect timeout', t0 + 60_000, 'inverters')
|
|
32
|
-
recordReadFailure(l, 'connect timeout', t0 + 120_000, 'inverters')
|
|
33
|
-
assert.equal(l.readFailure?.consecutive, 3)
|
|
34
|
-
assert.equal(l.readFailure?.sinceMs, t0, '처음 실패한 시각을 붙잡는다')
|
|
35
|
-
assert.equal(l.readFailure?.lastAtMs, t0 + 120_000)
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
test('★ 이유는 이유만 담는다 — 세는 값을 문장으로 합쳐 넣지 않는다', () => {
|
|
39
|
-
/*
|
|
40
|
-
* 이 테스트는 사람이 읽는 문장이 이유 칸에 들어가는 것을 막지 못한다(문자열은 무엇이든 담긴다).
|
|
41
|
-
* 막을 수 있는 것은 **그 값들을 담을 다른 자리가 있다**는 사실뿐이다. 그것을 테스트로 고정한다.
|
|
42
|
-
*/
|
|
43
|
-
const l = newIngestLedger()
|
|
44
|
-
recordReadFailure(l, 'connect timeout', t0, 'inverters', 480_000)
|
|
45
|
-
assert.equal(l.readFailure?.reason, 'connect timeout')
|
|
46
|
-
assert.equal(l.readFailure?.nextRetryMs, 480_000)
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
test('다음 시도까지의 시간은 있을 때만 적는다 — 0 이나 없는 값을 「곧 다시 묻는다」로 만들지 않는다', () => {
|
|
50
|
-
const l = newIngestLedger()
|
|
51
|
-
recordReadFailure(l, 'connect timeout', t0)
|
|
52
|
-
assert.equal('nextRetryMs' in (l.readFailure as any), false)
|
|
53
|
-
recordReadFailure(l, 'connect timeout', t0 + 1000, undefined, 0)
|
|
54
|
-
assert.equal('nextRetryMs' in (l.readFailure as any), false)
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
test('★ 돌아오면 실패 기록이 사라지고 돌아온 사실이 남는다', () => {
|
|
58
|
-
const l = newIngestLedger()
|
|
59
|
-
recordReadFailure(l, 'connect timeout', t0)
|
|
60
|
-
recordReadFailure(l, 'connect timeout', t0 + 60_000)
|
|
61
|
-
recordRecovered(l, 2, 120_000, t0 + 120_000)
|
|
62
|
-
assert.equal(l.readFailure, undefined, '지금은 닿는다')
|
|
63
|
-
assert.deepEqual(l.lastRecovery, { atMs: t0 + 120_000, afterFailures: 2, downMs: 120_000 })
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
test('★ 그냥 지우면 끊긴 적이 있었다는 사실이 사라진다 — 그래서 두 함수를 나눈다', () => {
|
|
67
|
-
const l = newIngestLedger()
|
|
68
|
-
recordReadFailure(l, 'connect timeout', t0)
|
|
69
|
-
clearReadFailure(l)
|
|
70
|
-
assert.equal(l.readFailure, undefined)
|
|
71
|
-
assert.equal(l.lastRecovery, undefined, '지우기만 하면 남지 않는다')
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
test('★ 다시 끊기면 새 단절이다 — 지난번 회복 기록이 그것을 가리지 않는다', () => {
|
|
75
|
-
const l = newIngestLedger()
|
|
76
|
-
recordReadFailure(l, 'connect timeout', t0)
|
|
77
|
-
recordRecovered(l, 1, 60_000, t0 + 60_000)
|
|
78
|
-
recordReadFailure(l, '401 unauthorized', t0 + 600_000)
|
|
79
|
-
assert.equal(l.readFailure?.consecutive, 1, '연달아 횟수가 1 부터 다시 센다')
|
|
80
|
-
assert.equal(l.readFailure?.sinceMs, t0 + 600_000)
|
|
81
|
-
assert.equal(l.lastRecovery?.atMs, t0 + 60_000, '지난 회복도 함께 보인다')
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
test('★ 밖으로 내는 모양에 두 사실이 다 있다 — 화면이 옮기지 못하면 아무 도움이 되지 않는다', () => {
|
|
85
|
-
const l = newIngestLedger()
|
|
86
|
-
recordReadFailure(l, 'connect timeout', t0, 'inverters', 480_000)
|
|
87
|
-
const v: any = ingestHealth(l, 'attached')
|
|
88
|
-
assert.equal(v.readFailure.consecutive, 1)
|
|
89
|
-
assert.equal(v.readFailure.reason, 'connect timeout')
|
|
90
|
-
assert.equal(v.readFailure.stream, 'inverters')
|
|
91
|
-
assert.equal(v.readFailure.nextRetryMs, 480_000)
|
|
92
|
-
assert.equal(typeof v.readFailure.since, 'string')
|
|
93
|
-
|
|
94
|
-
recordRecovered(l, 1, 480_000, t0 + 480_000)
|
|
95
|
-
const w: any = ingestHealth(l, 'attached')
|
|
96
|
-
assert.equal(w.readFailure, undefined)
|
|
97
|
-
assert.equal(w.lastRecovery.at, new Date(t0 + 480_000).toISOString())
|
|
98
|
-
assert.equal(w.lastRecovery.afterFailures, 1)
|
|
99
|
-
assert.equal(w.lastRecovery.downMs, 480_000)
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
test('닿은 적도 끊긴 적도 없으면 두 칸이 다 없다 — 없는 칸을 「끊겼다」로 읽지 않게', () => {
|
|
103
|
-
const v: any = ingestHealth(newIngestLedger(), 'attached')
|
|
104
|
-
assert.equal('readFailure' in v, false)
|
|
105
|
-
assert.equal('lastRecovery' in v, false)
|
|
106
|
-
})
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* ★ 「원본에 닿지 못한다」와 「연결된 시스템에서 들어온 것이 없다」는 **다른 사실**이다 — 상태가 그것을 가려야 한다.
|
|
3
|
-
*
|
|
4
|
-
* ── 무엇이 아무 신호도 내지 않았나 (2026-08-23 실측) ───────────────────────────────────────
|
|
5
|
-
* 실 원본이 끊겼다(`jejoup.hatiolab.com:4000` 접속 시간 초과, HTTP 000 / 8초). 그런데 트윈의
|
|
6
|
-
* **조회 가능한 상태 어디에도 그 사실이 없었다.**
|
|
7
|
-
*
|
|
8
|
-
* recordIngest `onRecords` 가 불릴 때만 도달한다 — 읽기 실패는 그 문에 오지 않는다
|
|
9
|
-
* TwinReference.lastError 마스터 동기·접속 시험에만 적힌다 — 라이브 피드는 적지 않았다
|
|
10
|
-
* 계약 `openLiveFeed` 에 「읽지 못했다」를 말할 통로가 없었다
|
|
11
|
-
*
|
|
12
|
-
* 어댑터는 **로그로는 말하고 있었다**(재시도 경고). 그러나 로그는 사람이 볼 때만 값이 있다 — 화면이
|
|
13
|
-
* 말하려면 상태에 있어야 한다. 실증 데모 중에 원본이 끊기면 사용자가 원인을 찾을 수 없다.
|
|
14
|
-
*
|
|
15
|
-
* ── 이 시험이 지키는 것 ─────────────────────────────────────────────────────
|
|
16
|
-
* ① 실패가 상태에 남는다 ② 「몇 번」과 「언제부터」를 함께 든다(한 번의 흔들림과 20분째 단절은 다르다)
|
|
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
|
-
import {
|
|
25
|
-
clearReadFailure,
|
|
26
|
-
ingestHealth,
|
|
27
|
-
newIngestLedger,
|
|
28
|
-
recordIngest,
|
|
29
|
-
recordReadFailure
|
|
30
|
-
} from '../server/engine/ingest-health.ts'
|
|
31
|
-
|
|
32
|
-
const T0 = Date.parse('2026-08-23T06:00:00.000Z')
|
|
33
|
-
|
|
34
|
-
test('실패가 상태에 남는다 — 사유와 흐름까지', () => {
|
|
35
|
-
const l = newIngestLedger()
|
|
36
|
-
assert.equal(l.readFailure, undefined, '처음에는 없다(없음이 「닿는다」는 뜻이다)')
|
|
37
|
-
recordReadFailure(l, 'connect ETIMEDOUT', T0, 'lot')
|
|
38
|
-
assert.equal(l.readFailure?.consecutive, 1)
|
|
39
|
-
assert.equal(l.readFailure?.reason, 'connect ETIMEDOUT')
|
|
40
|
-
assert.equal(l.readFailure?.stream, 'lot', '어느 표가 막혔나가 원인 찾기의 절반이다')
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
test('한 번의 흔들림과 20분째 단절을 가른다 — 시작 시각을 지킨다', () => {
|
|
44
|
-
/*
|
|
45
|
-
* 매번 새로 적으면 화면이 늘 「방금 실패했다」만 보게 되고, 계속된 단절을 대수롭지 않게 말한다.
|
|
46
|
-
*/
|
|
47
|
-
const l = newIngestLedger()
|
|
48
|
-
recordReadFailure(l, 'ETIMEDOUT', T0)
|
|
49
|
-
recordReadFailure(l, 'ETIMEDOUT', T0 + 600_000)
|
|
50
|
-
recordReadFailure(l, 'ETIMEDOUT', T0 + 1_200_000)
|
|
51
|
-
assert.equal(l.readFailure?.consecutive, 3)
|
|
52
|
-
assert.equal(l.readFailure?.sinceMs, T0, '처음 실패한 시각을 지킨다')
|
|
53
|
-
assert.equal(l.readFailure?.lastAtMs, T0 + 1_200_000, '마지막 실패는 갱신된다')
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
test('빈 읽기는 성공이다 — 들어온 것이 없는 연결을 끊긴 원본으로 만들지 않는다', () => {
|
|
57
|
-
/*
|
|
58
|
-
* ★ 이 단정이 고치려던 것의 **반대 방향으로 틀리는 것**을 막는다. 원본이 「새 것이 없다」고 답한 것은
|
|
59
|
-
* 닿았다는 뜻이다.
|
|
60
|
-
*/
|
|
61
|
-
const l = newIngestLedger()
|
|
62
|
-
recordReadFailure(l, 'ETIMEDOUT', T0)
|
|
63
|
-
clearReadFailure(l)
|
|
64
|
-
assert.equal(l.readFailure, undefined, '읽었으면 단절 기록이 사라진다')
|
|
65
|
-
/* 그리고 그 뒤 다시 실패하면 **새 단절**이다 — 옛 시작 시각을 물려받지 않는다. */
|
|
66
|
-
recordReadFailure(l, 'ETIMEDOUT', T0 + 3_600_000)
|
|
67
|
-
assert.equal(l.readFailure?.consecutive, 1, '연속이 끊겼으므로 1 부터')
|
|
68
|
-
assert.equal(l.readFailure?.sinceMs, T0 + 3_600_000, '새 단절의 시작이다')
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
test('조회 뷰로 나간다 — 상태에만 있으면 화면이 못 본다', () => {
|
|
72
|
-
const l = newIngestLedger()
|
|
73
|
-
recordReadFailure(l, 'connect ETIMEDOUT', T0, 'inventory')
|
|
74
|
-
const v = ingestHealth(l, 'attached', undefined, T0 + 60_000)
|
|
75
|
-
assert.ok(v.readFailure, '뷰에 실려야 한다')
|
|
76
|
-
assert.equal(v.readFailure?.consecutive, 1)
|
|
77
|
-
assert.equal(v.readFailure?.since, new Date(T0).toISOString())
|
|
78
|
-
assert.equal(v.readFailure?.reason, 'connect ETIMEDOUT')
|
|
79
|
-
assert.equal(v.readFailure?.stream, 'inventory')
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
test('닿고 있으면 그 필드를 만들지 않는다 — 빈 값이 「닿는다」로 읽히지 않게', () => {
|
|
83
|
-
const l = newIngestLedger()
|
|
84
|
-
recordIngest(l, 3, [], T0)
|
|
85
|
-
const v = ingestHealth(l, 'attached', undefined, T0 + 1000)
|
|
86
|
-
assert.equal(v.readFailure, undefined)
|
|
87
|
-
/* 가드가 스스로를 증명한다 — 이 장부가 실제로 무엇을 받았다. */
|
|
88
|
-
assert.equal(v.total.offered, 3, '빈 비교로 통과하지 않는다')
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
test('「유입 없음」과 「닿지 못함」은 다른 판정이다', () => {
|
|
92
|
-
/*
|
|
93
|
-
* 유입 없음은 연결된 시스템에서 들어온 것이 없는 것일 수 있고, 닿지 못함은 우리가 묻지도 못한 것이다. 두 상태가 같은
|
|
94
|
-
* 값으로 뭉개지면 화면이 두 문장을 다르게 말할 수 없다.
|
|
95
|
-
*/
|
|
96
|
-
const quiet = newIngestLedger()
|
|
97
|
-
const dark = newIngestLedger()
|
|
98
|
-
recordReadFailure(dark, 'ETIMEDOUT', T0)
|
|
99
|
-
|
|
100
|
-
const vq = ingestHealth(quiet, 'attached', undefined, T0)
|
|
101
|
-
const vd = ingestHealth(dark, 'attached', undefined, T0)
|
|
102
|
-
assert.equal(vq.lastAt, null, '들어온 것이 없는 쪽도 받은 것이 없다')
|
|
103
|
-
assert.equal(vd.lastAt, null, '끊긴 쪽도 받은 것이 없다 — 그 값만으로는 갈리지 않는다')
|
|
104
|
-
assert.notEqual(Boolean(vq.readFailure), Boolean(vd.readFailure), '이 축이 그 둘을 가른다')
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
/*
|
|
108
|
-
* ── 읽기 실패가 **피드를 끄면 안 된다** (2026-08-23, 되돌린 자리) ────────────
|
|
109
|
-
*
|
|
110
|
-
* 처음 구현에서 읽기 실패가 레퍼런스 행의 `status` 를 `'error'` 로 적었다. 그것이 **피드를 영구히
|
|
111
|
-
* 껐다**: 부팅 재부착은 `status: 'connected'` 인 레퍼런스만 되살리므로, 원본이 **잠깐** 끊긴 것이
|
|
112
|
-
* 다음 재기동에서 그 트윈을 아예 굶겼다. 실측으로 그렇게 났다 — 그 버전의 로그에 `[twin-live]` 줄이
|
|
113
|
-
* 한 줄도 없었다.
|
|
114
|
-
*
|
|
115
|
-
* 두 축을 한 칸에 섞은 것이 원인이다.
|
|
116
|
-
*
|
|
117
|
-
* status **연결 설정이 성립하나** — 접속 시험·마스터 동기가 정한다. 재부착의 **자격**이다
|
|
118
|
-
* 읽기 실패 **지금 닿나** — 오갈 수 있는 사실이고 유입 장부가 그 축을 갖는다
|
|
119
|
-
*
|
|
120
|
-
* 「사실을 말하게」 하려던 것이 「사실을 못 듣게」 만들었다. 그 방향의 실패는 들어온 것이 없다 — 화면은
|
|
121
|
-
* 「원본에 닿지 못한다」를 정직하게 말하면서, 정작 다시 닿을 기회를 잃는다.
|
|
122
|
-
*/
|
|
123
|
-
test('읽기 실패가 레퍼런스의 status 를 건드리지 않는다 — 그것이 재부착의 자격이다', () => {
|
|
124
|
-
const src = readFileSync(join(import.meta.dirname, '..', 'server', 'service', 'reference', 'reference-live.ts'), 'utf8')
|
|
125
|
-
const code = src.replace(/\/\*[\s\S]*?\*\//g, ' ').replace(/(^|[^:])\/\/.*$/gm, '$1')
|
|
126
|
-
|
|
127
|
-
const at = code.indexOf('onReadFailure:')
|
|
128
|
-
assert.ok(at > 0, 'onReadFailure 배선이 있어야 한다')
|
|
129
|
-
const body = code.slice(at, at + 600)
|
|
130
|
-
|
|
131
|
-
assert.doesNotMatch(body, /status:/, '읽기 실패는 status 를 적지 않는다 — 적으면 다음 부팅이 그 피드를 되살리지 않는다')
|
|
132
|
-
/* 대신 이유는 남긴다 — 사람이 목록에서 볼 수 있게. */
|
|
133
|
-
assert.match(body, /lastError/, '이유는 레퍼런스 행에 남긴다')
|
|
134
|
-
/* 그리고 「지금 닿나」는 장부가 답한다. */
|
|
135
|
-
assert.match(body, /recordIngestReadFailure/, '유입 장부가 그 축을 든다')
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
test('재부착의 자격은 connected 뿐이다 — 그래서 그 값을 함부로 바꾸면 안 된다', () => {
|
|
139
|
-
/*
|
|
140
|
-
* 이 단정이 위 가드의 **이유**를 못박는다. 재부착 조건이 넓어지면 위 규율의 근거가 약해지므로,
|
|
141
|
-
* 둘이 함께 움직여야 한다.
|
|
142
|
-
*/
|
|
143
|
-
const src = readFileSync(join(import.meta.dirname, '..', 'server', 'service', 'reference', 'reference-live.ts'), 'utf8')
|
|
144
|
-
assert.match(src, /find\(\{ where: \{ status: 'connected' \}/, '부팅 재부착은 connected 만 되살린다')
|
|
145
|
-
})
|
package/test/read-time.test.ts
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* ★ 저장된 값을 시각으로 읽는 규칙 — 드라이버의 문을 지난다.
|
|
3
|
-
*
|
|
4
|
-
* ── 무엇이 어긋났나 (2026-08-28 실측) ───────────────────────────────────────
|
|
5
|
-
* `getRawOne` 은 드라이버가 준 값을 그대로 준다. sqlite 가 주는 그 값에는 시간대가 없다
|
|
6
|
-
* (`2026-08-27 14:37:20.049`). 그것을 `new Date()` 로 읽으면 서버의 지역 시간으로 해석해 한국에서
|
|
7
|
-
* 9시간이 밀린다. 네 자리에서 났고, 둘은 **지우는 경계**였다(경계가 이르게 밀리면 정리가 0건이 되어
|
|
8
|
-
* 멈춘다).
|
|
9
|
-
*
|
|
10
|
-
* ── 처음에 편법을 썼다 ──────────────────────────────────────────────────────
|
|
11
|
-
* 「시간대가 없으면 UTC 로 읽는다」를 응용 코드에 직접 적었다. 그것은 드라이버가 어떤 문자열로
|
|
12
|
-
* 저장하는지를 우리가 아는 것이고, 다섯 드라이버 규칙에 어긋난다.
|
|
13
|
-
*
|
|
14
|
-
* 표준 문은 `Driver.prepareHydratedValue` 다 — 엔티티 변환이 지나는 그 함수. 이 시험은 컬럼 값을
|
|
15
|
-
* 읽을 때 **우리가 해석하지 않고 그 문에 물어보는지**를 지킨다.
|
|
16
|
-
*/
|
|
17
|
-
import { test } from 'node:test'
|
|
18
|
-
import assert from 'node:assert/strict'
|
|
19
|
-
import { readFileSync, readdirSync, statSync } from 'node:fs'
|
|
20
|
-
import { join } from 'node:path'
|
|
21
|
-
|
|
22
|
-
import { absoluteTime, hydratedDate } from '../server/engine/read-time.js'
|
|
23
|
-
|
|
24
|
-
/** 드라이버를 흉내낸 저장소 — 이 시험이 확인하는 것은 「누가 해석하나」다. */
|
|
25
|
-
const fakeRepository = (opts: { hasColumn?: boolean; hydrate?: (v: unknown) => unknown } = {}) => {
|
|
26
|
-
const calls: unknown[] = []
|
|
27
|
-
const repo = {
|
|
28
|
-
metadata: {
|
|
29
|
-
name: 'TwinEvent',
|
|
30
|
-
findColumnWithPropertyName: (name: string) => (opts.hasColumn === false ? undefined : { propertyName: name, type: Date })
|
|
31
|
-
},
|
|
32
|
-
manager: {
|
|
33
|
-
connection: {
|
|
34
|
-
driver: {
|
|
35
|
-
prepareHydratedValue: (value: unknown) => {
|
|
36
|
-
calls.push(value)
|
|
37
|
-
return opts.hydrate ? opts.hydrate(value) : new Date('2026-08-27T14:37:20.049Z')
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return { repo: repo as any, calls }
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
test('★ 컬럼 값은 우리가 해석하지 않고 드라이버에게 물어본다', () => {
|
|
47
|
-
const { repo, calls } = fakeRepository()
|
|
48
|
-
const at = hydratedDate(repo, 'createdAt', '2026-08-27 14:37:20.049')
|
|
49
|
-
assert.equal(at?.toISOString(), '2026-08-27T14:37:20.049Z')
|
|
50
|
-
assert.deepEqual(calls, ['2026-08-27 14:37:20.049'], '드라이버의 문을 그 값으로 한 번 지나야 한다')
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
test('빈 값은 드라이버를 부르지 않고 null — 없는 것은 없는 것이다', () => {
|
|
54
|
-
const { repo, calls } = fakeRepository()
|
|
55
|
-
assert.equal(hydratedDate(repo, 'createdAt', null), null)
|
|
56
|
-
assert.equal(hydratedDate(repo, 'createdAt', undefined), null)
|
|
57
|
-
assert.deepEqual(calls, [])
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
test('드라이버가 날짜를 내주지 못하면 null — 「지금」으로 메우지 않는다', () => {
|
|
61
|
-
const { repo } = fakeRepository({ hydrate: () => 'still a string' })
|
|
62
|
-
assert.equal(hydratedDate(repo, 'createdAt', 'x'), null)
|
|
63
|
-
const bad = fakeRepository({ hydrate: () => new Date('nope') })
|
|
64
|
-
assert.equal(hydratedDate(bad.repo, 'createdAt', 'x'), null)
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
test('★ 컬럼 이름을 못 찾으면 명시 실패한다 — 이름이 바뀐 것을 값으로 덮지 않는다', () => {
|
|
68
|
-
const { repo } = fakeRepository({ hasColumn: false })
|
|
69
|
-
assert.throws(() => hydratedDate(repo, 'createdAt', '2026-08-27 14:37:20.049'), /no column for property/)
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
test('★ 계약이 절대시각인 값 — 시간대가 없으면 UTC 다(지역 시간으로 읽을 근거가 없다)', () => {
|
|
73
|
-
assert.equal(absoluteTime('2026-08-27 14:37:20.049')?.toISOString(), '2026-08-27T14:37:20.049Z')
|
|
74
|
-
/* 지역 시간으로 읽었다면 한국에서 9시간이 밀린다. */
|
|
75
|
-
assert.notEqual(absoluteTime('2026-08-27 14:37:20.049')?.toISOString(), '2026-08-27T05:37:20.049Z')
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
test('시간대가 적혀 있으면 그 말을 믿는다', () => {
|
|
79
|
-
assert.equal(absoluteTime('2026-08-27T14:37:20.049Z')?.toISOString(), '2026-08-27T14:37:20.049Z')
|
|
80
|
-
assert.equal(absoluteTime('2026-08-27T23:37:20.049+09:00')?.toISOString(), '2026-08-27T14:37:20.049Z')
|
|
81
|
-
assert.equal(absoluteTime('2026-08-27T09:37:20.049-05:00')?.toISOString(), '2026-08-27T14:37:20.049Z')
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
test('날짜 객체와 밀리초도 받는다', () => {
|
|
85
|
-
const d = new Date('2026-08-27T14:37:20.049Z')
|
|
86
|
-
assert.equal(absoluteTime(d), d)
|
|
87
|
-
assert.equal(absoluteTime(1_756_305_440_049)?.getTime(), 1_756_305_440_049)
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
test('★ 읽을 수 없는 값은 null — 경계를 정하는 자리가 이 함수를 쓴다', () => {
|
|
91
|
-
for (const bad of [null, undefined, '', ' ', '어제', 'not-a-date', {}, [], NaN, new Date('x')]) {
|
|
92
|
-
assert.equal(absoluteTime(bad), null, `${String(bad)} 는 null 이어야 한다`)
|
|
93
|
-
}
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
test('★ 컬럼 값을 직접 날짜로 만드는 자리가 남아 있지 않다', () => {
|
|
97
|
-
/*
|
|
98
|
-
* 규칙이 있어도 새로 쓰는 코드가 그것을 지나지 않으면 같은 결함이 다시 난다. 그래서 자리를 세어
|
|
99
|
-
* 둔다 — 원시 결과의 컬럼을 `new Date(...)` 로 감싸는 모양을 찾는다.
|
|
100
|
-
*
|
|
101
|
-
* 밀리초 숫자(`new Date(someMs)`)는 시간대가 없으므로 이 그물에 걸리지 않는다.
|
|
102
|
-
*/
|
|
103
|
-
const root = join(import.meta.dirname, '..', 'server')
|
|
104
|
-
const offenders: string[] = []
|
|
105
|
-
const walk = (dir: string) => {
|
|
106
|
-
for (const name of readdirSync(dir)) {
|
|
107
|
-
const p = join(dir, name)
|
|
108
|
-
if (statSync(p).isDirectory()) { walk(p); continue }
|
|
109
|
-
if (!name.endsWith('.ts')) continue
|
|
110
|
-
const text = readFileSync(p, 'utf8')
|
|
111
|
-
for (const m of text.matchAll(/new Date\(\s*[A-Za-z_$][\w$?.]*\.(eventTime|createdAt|updatedAt|at)\b[^)]*\)/g)) {
|
|
112
|
-
offenders.push(`${name}: ${m[0]}`)
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
walk(root)
|
|
117
|
-
assert.deepEqual(offenders, [], '컬럼 값을 직접 날짜로 만드는 자리가 있다 — hydratedDate() 를 지나야 한다')
|
|
118
|
-
})
|