@things-factory/headless-twin 10.1.278 → 10.1.279
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,110 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* ★ 리비전은 **두 셈**이고, 둘이 같은 것을 세지 않는다 — 그 사실을 코드가 지키는지 본다.
|
|
3
|
-
*
|
|
4
|
-
* ── 무엇이 어긋나 있었나 (2026-08-23 실측) ──────────────────────────────────
|
|
5
|
-
* 포천 미러의 체크포인트가 뜻이 다른 두 수를 나란히 적었다.
|
|
6
|
-
*
|
|
7
|
-
* 바깥 revision 12,972 저널에 적힌 줄 번호
|
|
8
|
-
* state.revision 6,478 커널이 처리한 봉투 수
|
|
9
|
-
*
|
|
10
|
-
* 차이 6,494 는 그 트윈을 **다시 세운 시각**(09:11)에 저널이 이미 갖고 있던 줄 수와 정확히 같았다
|
|
11
|
-
* (줄 6494 = 08:23:59 · 줄 6495 = 09:11:13). 산수가 맞았으니 결함은 「수가 틀렸다」가 아니다.
|
|
12
|
-
*
|
|
13
|
-
* 결함은 **비대칭**이었다. 재기동 경로(`start` → `resumeRevision`)는 커널에 저널 머리를 알려 주는데
|
|
14
|
-
* 미러를 세우는 경로(`startLive`)는 알려 주지 않았다. 그래서 같은 저장물을 읽는 사람이 같은 이름의 두
|
|
15
|
-
* 수를 같은 축으로 견주게 되고, 실제로 그렇게 읽혔다(반나절이 그 오독에서 나갔다).
|
|
16
|
-
*
|
|
17
|
-
* ── 왜 알려 주어야 하나 ─────────────────────────────────────────────────────
|
|
18
|
-
* 커널의 계약이 그 이유를 적어 두었다(`resumeRevision`): 「저널이 비어 있지 않은데 0부터 다시 세면 같은
|
|
19
|
-
* 트윈에 같은 리비전이 둘 생기고, 재생 순서가 뒤섞이며, 시간여행이 엉뚱한 시점을 답한다 — 오류는 안 난다.」
|
|
20
|
-
* 알리지 않고 깨지는 종류이므로 시험이 지킨다.
|
|
21
|
-
*
|
|
22
|
-
* ── 이 시험이 무엇을 재는가 ─────────────────────────────────────────────────
|
|
23
|
-
* DB 도 커널도 띄우지 않는다. 이 규율은 **어느 경로가 무엇을 부르나**에 있고 그것은 소스에서 확인된다.
|
|
24
|
-
*/
|
|
25
|
-
import { test } from 'node:test'
|
|
26
|
-
import assert from 'node:assert/strict'
|
|
27
|
-
import { readFileSync } from 'node:fs'
|
|
28
|
-
import { join } from 'node:path'
|
|
29
|
-
|
|
30
|
-
const ENGINE = join(import.meta.dirname, '..', 'server', 'engine', 'twin-engine.ts')
|
|
31
|
-
const src = readFileSync(ENGINE, 'utf8')
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* 그 함수 본문만 — 다른 경로의 호출이 섞여 판정이 흐려지지 않게.
|
|
35
|
-
*
|
|
36
|
-
* 끝은 **다음 멤버 선언**으로 자른다. 글자 수로 자르면 주석이 늘어난 날 알리지 않고 짧아져서, 있는 것을
|
|
37
|
-
* 없다고 말한다(거짓 빨강을 내는 가드는 없는 것보다 나쁘다).
|
|
38
|
-
*/
|
|
39
|
-
function bodyOf(signature: string): string {
|
|
40
|
-
const at = src.indexOf(signature)
|
|
41
|
-
assert.ok(at > 0, `${signature} 이 있어야 한다`)
|
|
42
|
-
const rest = src.slice(at + signature.length)
|
|
43
|
-
const end = rest.search(/\n (?:static|private static|\/\*\*)/)
|
|
44
|
-
const body = end === -1 ? rest : rest.slice(0, end)
|
|
45
|
-
/* 잘라낸 본문이 비면 판정이 성립하지 않는다 — 그것을 통과로 읽지 않는다. */
|
|
46
|
-
assert.ok(body.length > 200, `${signature} 본문을 잘라내지 못했다(${body.length}자)`)
|
|
47
|
-
return body
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
test('미러를 세우는 경로도 커널에 저널 머리를 알려 준다', () => {
|
|
51
|
-
const body = bodyOf('static startLive(')
|
|
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\)'/, '저널 머리를 읽어야 한다')
|
|
60
|
-
/* 그리고 **커널에도** 알려 준다 — 이 한 줄이 없어서 두 수가 갈렸다. */
|
|
61
|
-
assert.match(body, /resumeRevision\(head\)/, '커널에도 그 번호를 알려 준다')
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
test('재기동 경로는 예전부터 알려 주고 있었다 — 두 경로가 같아졌는지 본다', () => {
|
|
65
|
-
/*
|
|
66
|
-
* 이 단정은 「고쳤다」가 아니라 **두 경로가 대칭인지**를 지킨다. 한쪽만 남으면 같은 결함이 다른
|
|
67
|
-
* 문으로 되살아난다.
|
|
68
|
-
*/
|
|
69
|
-
const body = bodyOf('static start(')
|
|
70
|
-
assert.match(body, /resumeRevision\(resumeFrom\)/, '재기동 경로도 알려 준다')
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
test('저널이 비어 있으면 알려 주지 않는다 — 새 트윈은 0 부터 세는 것이 맞다', () => {
|
|
74
|
-
const body = bodyOf('static startLive(')
|
|
75
|
-
assert.match(body, /if \(!head \|\| typeof/, '머리가 0 이면 아무 일도 하지 않는다')
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
test('뒤로 되돌리려는 거절을 삼키지 않는다', () => {
|
|
79
|
-
/*
|
|
80
|
-
* 조회가 비동기라 그 사이에 이벤트가 들어와 있을 수 있다. 그때 커널은 뒤로 가기를 거절한다(겹치는
|
|
81
|
-
* 번호를 막는 그 판정이다). 그 거절을 알리지 않고 버리면 「왜 두 수가 아직 다르지」를 답할 계기가 없다.
|
|
82
|
-
*/
|
|
83
|
-
const body = bodyOf('static startLive(')
|
|
84
|
-
assert.match(body, /catch \(err[^)]*\)[\s\S]{0,200}twinWarn/, '거절을 로그에 남긴다')
|
|
85
|
-
assert.match(body, /is behind the kernel/, '무엇이 어긋났는지 말한다')
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
test('체크포인트는 두 수를 나란히 적는다 — 그 사실 자체는 옳다', () => {
|
|
89
|
-
/*
|
|
90
|
-
* 바깥은 저널 줄 번호(이어 쓸 자리), 안쪽은 커널이 계산한 봉투 수다. **둘을 하나로 합치지 않는다** —
|
|
91
|
-
* 합치면 「어디까지 적었나」와 「어디까지 반영했나」를 구별할 수 없고, 그 구별이 미러의 정직성이다.
|
|
92
|
-
* 두 경로가 대칭이면 값이 같아지지만, **자리는 둘이어야 한다.**
|
|
93
|
-
*/
|
|
94
|
-
const at = src.indexOf('static async persistSnapshot')
|
|
95
|
-
assert.ok(at > 0, 'persistSnapshot 이 있어야 한다')
|
|
96
|
-
/*
|
|
97
|
-
* 창을 넓혀 왔다 — 이 함수 앞쪽에 「빈 상태로 좋은 스냅샷을 덮지 않는다」 가드가 들어오고(§
|
|
98
|
-
* `checkpoint-refuses-empty`), 뒤쪽에 「이어 계산할 씨앗을 함께 적는다」가 들어왔다(§
|
|
99
|
-
* `mirror-resumes-from-checkpoint`). 검사하는 **사실은 그대로**다.
|
|
100
|
-
*
|
|
101
|
-
* 그리고 **줄 모양이 아니라 두 자리를 검사한다**: 예전에는 `{ revision, state` 라는 한 줄 모양을
|
|
102
|
-
* 봤는데, 저장 인자가 여러 줄로 나뉘자 사실이 그대로인데도 빨강이 됐다. 서식이 검사를 깨면 다음
|
|
103
|
-
* 사람이 검사를 지우게 된다 — 그래서 이름 둘이 함께 실리는지만 본다.
|
|
104
|
-
*/
|
|
105
|
-
const body = src.slice(at, at + 5000)
|
|
106
|
-
assert.match(body, /inst\.revision \?\? state\.revision/, '바깥은 저널 줄 번호를 먼저 쓴다')
|
|
107
|
-
const call = body.slice(body.indexOf('setInCache'))
|
|
108
|
-
assert.match(call, /revision/, '저널 줄 번호를 싣는다')
|
|
109
|
-
assert.match(call, /state/, '커널이 계산한 상태를 함께 싣는다 — 두 자리를 하나로 합치지 않는다')
|
|
110
|
-
})
|
package/test/runtime-key.test.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* 런타임 키 — **트윈의 정체성은 `(도메인, 인스턴스 id)`.**
|
|
3
|
-
*
|
|
4
|
-
* 계기(2026-08-06): 런타임 레지스트리가 `instanceId` 하나로 키를 잡고 있었다. 레퍼런스 경로가 소스
|
|
5
|
-
* 이름을 그대로 id 로 쓰므로(`sap-ewm-1710`) 두 테넌트가 같은 벤더 시스템에 연결하면 같은 id 가 된다 —
|
|
6
|
-
* 그때 남의 커널에 이벤트가 적용되거나, 웜스타트 씨앗이 남의 스냅샷이 됐다.
|
|
7
|
-
*
|
|
8
|
-
* 이 테스트가 지키는 것은 **키가 모호해지지 않는 것** 하나다. 모호한 키는 오류 없이 틀린 값을 낸다.
|
|
9
|
-
*/
|
|
10
|
-
import { test } from 'node:test'
|
|
11
|
-
import assert from 'node:assert/strict'
|
|
12
|
-
|
|
13
|
-
import { RUNTIME_KEY_SEP, isOfDomain, parseRuntimeKey, runtimeKey } from '../server/engine/runtime-key.ts'
|
|
14
|
-
|
|
15
|
-
const D1 = '11111111-1111-1111-1111-111111111111'
|
|
16
|
-
const D2 = '22222222-2222-2222-2222-222222222222'
|
|
17
|
-
|
|
18
|
-
test('같은 인스턴스 id 라도 테넌트가 다르면 다른 키다 — 이 작업의 요점', () => {
|
|
19
|
-
assert.notEqual(runtimeKey(D1, 'sap-ewm-1710'), runtimeKey(D2, 'sap-ewm-1710'))
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
test('같은 테넌트·같은 id 는 같은 키다 — 재기동이 자기 자리를 찾는다', () => {
|
|
23
|
-
assert.equal(runtimeKey(D1, 'hatiolab-wms'), runtimeKey(D1, 'hatiolab-wms'))
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
test('되돌리면 원래 둘이 나온다', () => {
|
|
27
|
-
const { domainId, instanceId } = parseRuntimeKey(runtimeKey(D1, 'hatiolab-wms'))
|
|
28
|
-
|
|
29
|
-
assert.equal(domainId, D1)
|
|
30
|
-
assert.equal(instanceId, 'hatiolab-wms')
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
test('인스턴스 id 안의 구분자를 보존한다 — 첫 구분자에서만 자른다', () => {
|
|
34
|
-
/* 레퍼런스 소스 이름에 `:` 가 들어갈 수 있다. 마지막 구분자에서 자르면 id 가 잘려 다른 트윈이 된다. */
|
|
35
|
-
const weird = 'urn:epc:id:sgln:0614141.12345'
|
|
36
|
-
const { domainId, instanceId } = parseRuntimeKey(runtimeKey(D1, weird))
|
|
37
|
-
|
|
38
|
-
assert.equal(domainId, D1)
|
|
39
|
-
assert.equal(instanceId, weird)
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
test('도메인 id 에 구분자가 있으면 오류를 낸다 — 알리지 않고 모호한 키를 만들지 않는다', () => {
|
|
43
|
-
/* 알리지 않고 만들면 그게 곧 이 결함의 재발이다(어느 테넌트인지 알 수 없는 키). */
|
|
44
|
-
assert.throws(() => runtimeKey(`a${RUNTIME_KEY_SEP}b`, 'x'), /ambiguous/)
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
test('빈 값은 키가 될 수 없다 — 테넌트 없는 런타임은 없다', () => {
|
|
48
|
-
assert.throws(() => runtimeKey('', 'x'), /domainId is required/)
|
|
49
|
-
assert.throws(() => runtimeKey(D1, ''), /instanceId is required/)
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
test('키가 아닌 것을 되돌리려 하면 오류를 낸다', () => {
|
|
53
|
-
for (const bad of ['', 'no-separator', `${RUNTIME_KEY_SEP}leading`, `trailing${RUNTIME_KEY_SEP}`]) {
|
|
54
|
-
assert.throws(() => parseRuntimeKey(bad), /is not a runtime key/, `입력: "${bad}"`)
|
|
55
|
-
}
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
test('도메인으로 걸러낸다 — 접두사가 아니라 경계까지 본다', () => {
|
|
59
|
-
const key = runtimeKey(D1, 'wms')
|
|
60
|
-
|
|
61
|
-
assert.equal(isOfDomain(key, D1), true)
|
|
62
|
-
assert.equal(isOfDomain(key, D2), false)
|
|
63
|
-
/* 도메인 id 의 앞부분만 같은 것에 걸리지 않는다(구분자까지 붙여 본다). */
|
|
64
|
-
assert.equal(isOfDomain(key, D1.slice(0, 8)), false)
|
|
65
|
-
assert.equal(isOfDomain(key, ''), false)
|
|
66
|
-
})
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* ★ **화면 저장도 받는 사이트 충돌을 거부한다** (ADR-0046 ②-1, 2026-09-16)
|
|
3
|
-
*
|
|
4
|
-
* 들여오기에는 충돌 거부가 있는데 화면 저장(`saveTwinReference`)에는 없었다. `intakeEndpoint::intakeSiteId`
|
|
5
|
-
* 가 같은 연결 둘을 만들면 받는 문은 둘 다 200 으로 받고 경고가 0 이다 — 잘못된 공장이 실제로 만든다.
|
|
6
|
-
* 이 검사는 저장의 문이 쓰는 판정(`refuseIntakeCollision`)을 값으로 못 박는다.
|
|
7
|
-
*/
|
|
8
|
-
import { test } from 'node:test'
|
|
9
|
-
import assert from 'node:assert/strict'
|
|
10
|
-
import { readFileSync } from 'node:fs'
|
|
11
|
-
|
|
12
|
-
import { intakeCollisions, refuseIntakeCollision } from '../server/service/reference/connection-portability.js'
|
|
13
|
-
|
|
14
|
-
const intake = { intakeEndpoint: 'https://plant.example/intake/', intakeSiteId: 'system' }
|
|
15
|
-
const existing = [
|
|
16
|
-
{ source: 'mes-line-a', siteName: 'Line A', connectionConfig: { baseUrl: 'https://plant.example', ...intake } }
|
|
17
|
-
]
|
|
18
|
-
|
|
19
|
-
test('★ 같은 받는 사이트를 가리키는 둘째 연결은 거부되고, 부딪힌 연결의 이름을 말한다', () => {
|
|
20
|
-
const r = refuseIntakeCollision('mes-line-b', { baseUrl: 'https://plant.example', ...intake }, existing)
|
|
21
|
-
assert.ok(r, '베낀 연결이 그대로 저장된다')
|
|
22
|
-
assert.equal(r.code, 'intake-site-taken')
|
|
23
|
-
assert.equal(r.params.site, 'system')
|
|
24
|
-
assert.equal(r.params.connection, 'Line A', '무엇과 부딪혔는지 말하지 않는다')
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
test('주소 끝의 / 하나로 비껴가지 않는다', () => {
|
|
28
|
-
const r = refuseIntakeCollision('mes-line-b', { intakeEndpoint: 'https://plant.example/intake', intakeSiteId: 'system' }, existing)
|
|
29
|
-
assert.ok(r)
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
test('자기 자신을 다시 저장하는 것은 충돌이 아니다', () => {
|
|
33
|
-
assert.equal(refuseIntakeCollision('mes-line-a', existing[0].connectionConfig, existing), null)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
test('사이트를 고치면 저장된다 — 한 글자 고친 사람은 그 자리에서 지난다', () => {
|
|
37
|
-
assert.equal(refuseIntakeCollision('mes-line-b', { ...intake, intakeSiteId: 'line-b' }, existing), null)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
test('조치 주소가 없는 연결은 부딪힐 것이 없다', () => {
|
|
41
|
-
assert.equal(refuseIntakeCollision('wms-x', { baseUrl: 'https://plant.example', siteId: 'system' }, existing), null)
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
test('★ 저장의 문이 그 판정을 부르고, 거부하면 쓰기 전에 돌아간다', () => {
|
|
45
|
-
const src = readFileSync(new URL('../server/service/reference/reference-resolver.ts', import.meta.url), 'utf8')
|
|
46
|
-
const body = src.slice(src.indexOf('async saveTwinReference('), src.indexOf('async deleteTwinReference('))
|
|
47
|
-
const check = body.indexOf('refuseIntakeCollision(')
|
|
48
|
-
const write = body.indexOf('repo.save(')
|
|
49
|
-
assert.ok(check > 0, 'saveTwinReference 가 충돌 판정을 부르지 않는다')
|
|
50
|
-
assert.ok(check < write, '판정이 쓰기 뒤에 있다 — 이미 저장된 뒤의 거부는 거부가 아니다')
|
|
51
|
-
assert.match(body, /errorCode: taken\.code/)
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
/*
|
|
55
|
-
* ★ **같은 충돌을 두 모양으로 건다** (ADR-0053 ②, 2026-09-16).
|
|
56
|
-
*
|
|
57
|
-
* 패키지는 `config`, `TwinReference` 행은 `connectionConfig` 다. 전에는 `config` 만 읽어서 행을 그대로
|
|
58
|
-
* 넘기면 `[]` 였다 — 오류도 경고도 없이 저장이 통과했다. 부르는 둘이 마침 옮겨서 부른 덕에 멀쩡했는데,
|
|
59
|
-
* 그건 계약이 아니라 우연이다(이 함수는 화면이 쓰라고 리졸버에서 다시 내보내진다).
|
|
60
|
-
*
|
|
61
|
-
* 옮기기가 다시 갈리는 날 **빨개지는 자리가 여기 하나**다. 그래서 「두 모양이 같은 답을 낸다」를
|
|
62
|
-
* 값으로 센다 — 결정의 내용이 그것이므로.
|
|
63
|
-
*/
|
|
64
|
-
const pair = [
|
|
65
|
-
{ source: 'mes-line-a', ...intake },
|
|
66
|
-
{ source: 'mes-line-b', ...intake }
|
|
67
|
-
]
|
|
68
|
-
|
|
69
|
-
test('★ DB 행 모양(connectionConfig)으로 걸어도 같은 충돌이 나온다 — 「없다」가 편한 답인 자리다', () => {
|
|
70
|
-
const rows = intakeCollisions(pair.map(({ source, ...cfg }) => ({ source, connectionConfig: cfg })))
|
|
71
|
-
|
|
72
|
-
assert.equal(rows.length, 1, '행 모양을 못 알아보고 조용히 넘어갔다')
|
|
73
|
-
assert.deepEqual(rows[0].sources, ['mes-line-a', 'mes-line-b'])
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
test('★ 두 모양이 같은 답을 낸다 — 옮기기가 갈리면 여기가 빨개진다', () => {
|
|
77
|
-
const asConfig = intakeCollisions(pair.map(({ source, ...cfg }) => ({ source, config: cfg })))
|
|
78
|
-
const asRow = intakeCollisions(pair.map(({ source, ...cfg }) => ({ source, connectionConfig: cfg })))
|
|
79
|
-
|
|
80
|
-
assert.deepEqual(asRow, asConfig)
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
test('섞여 들어와도 한 충돌로 본다 — 들여오기는 실제로 행과 패키지를 함께 넘긴다', () => {
|
|
84
|
-
const mixed = intakeCollisions([
|
|
85
|
-
{ source: 'mes-line-a', connectionConfig: { ...intake } },
|
|
86
|
-
{ source: 'mes-line-b', config: { ...intake } }
|
|
87
|
-
])
|
|
88
|
-
|
|
89
|
-
assert.equal(mixed.length, 1, '섞인 목록에서 한쪽만 세었다')
|
|
90
|
-
assert.deepEqual(mixed[0].sources, ['mes-line-a', 'mes-line-b'])
|
|
91
|
-
})
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* 스케일 테스트 트윈 벤치 — 미국 공장(WMS+MES+YMS) 형태를 대규모로 스케일업, 5종 동기 엔티티 전부 부하.
|
|
3
|
-
* 동기 엔티티 = locations·items(재고)·equipment·tasks·orders (노드/무버뿐이 아님). area 는 계층(건물>층>존).
|
|
4
|
-
* 측정(가정 아닌 실측): masterToTwin(구조 생성)·apply(혼합 이벤트 처리량)·snapshot(전체 투영)·buildEntityDeltas(브로드캐스팅, 't1')·heap.
|
|
5
|
-
* 순수 커널+호스트 매핑(shell·DB 무관) → node:test. area 는 공간층(projector State 밖)이라 처리량엔 무영향 — 구조 생성 비용에만.
|
|
6
|
-
*/
|
|
7
|
-
import { test } from 'node:test'
|
|
8
|
-
import assert from 'node:assert/strict'
|
|
9
|
-
|
|
10
|
-
import { StateProjector } from '@operato/twin-kernel'
|
|
11
|
-
import { objectEvent, BIZSTEP, DISP, OP_EVENT } from '@operato/ops-contract'
|
|
12
|
-
import { masterToTwin } from '../server/service/reference/reference-master.ts'
|
|
13
|
-
import { buildEntityDeltas } from '../shared/entity-delta.ts'
|
|
14
|
-
|
|
15
|
-
const T0 = '2026-07-20T00:00:00.000Z'
|
|
16
|
-
const MB = (b: number) => Math.round(b / 1048576)
|
|
17
|
-
|
|
18
|
-
/** 미국 공장형 대규모 master 생성: 건물>층>존 area 계층 + 노드(storage/dock/station) + 무버(forklift/cutter/welder/hostler). */
|
|
19
|
-
function bigMaster(tier: { buildings: number; floorsPer: number; zonesPer: number; nodesPerZone: number; equipment: number }) {
|
|
20
|
-
const locations: any[] = []
|
|
21
|
-
let nodeN = 0
|
|
22
|
-
for (let b = 0; b < tier.buildings; b++) {
|
|
23
|
-
locations.push({ id: `bld-${b}`, type: 'building', role: 'area', name: `건물 ${b}` })
|
|
24
|
-
for (let f = 0; f < tier.floorsPer; f++) {
|
|
25
|
-
locations.push({ id: `bld-${b}-flr-${f}`, type: 'floor', role: 'area', name: `층 ${b}-${f}`, parentId: `bld-${b}` })
|
|
26
|
-
for (let z = 0; z < tier.zonesPer; z++) {
|
|
27
|
-
const zid = `bld-${b}-flr-${f}-zn-${z}`
|
|
28
|
-
locations.push({ id: zid, type: 'zone', role: 'area', name: `존 ${b}-${f}-${z}`, parentId: `bld-${b}-flr-${f}` })
|
|
29
|
-
for (let n = 0; n < tier.nodesPerZone; n++) {
|
|
30
|
-
const t = n % 4 === 0 ? 'dock' : n % 4 === 1 ? 'staging' : 'storage'
|
|
31
|
-
locations.push({ id: `nd-${nodeN}`, type: t, role: 'location', name: `노드 ${nodeN}`, parentId: zid, capacity: 500, footprint: { x: nodeN % 100, y: (nodeN / 100) | 0, w: 1, h: 1 } })
|
|
32
|
-
nodeN++
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
const KINDS = ['forklift', 'cutter', 'welder', 'hostler']
|
|
38
|
-
const equipment = Array.from({ length: tier.equipment }, (_, i) => ({ id: `mv-${i}`, kind: KINDS[i % 4], homeLocationId: `nd-${i % nodeN}` }))
|
|
39
|
-
return {
|
|
40
|
-
master: { source: 'scale-us', system: 'wms' as const, siteName: 'US 공장(스케일)', space: { width: 500, depth: 300, unit: 'm', geo: { lat: 32.7, lon: -117.1 } }, locations, equipment },
|
|
41
|
-
locationCount: nodeN,
|
|
42
|
-
areaCount: locations.filter(l => l.role === 'area').length
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function itemEnv(i: number, location: string): any {
|
|
47
|
-
const ev = objectEvent({ eventTime: T0, action: 'OBSERVE' as any, bizStep: BIZSTEP.storing, disposition: DISP.sellable, epcList: [`urn:epc:id:sscc:0614141.${i}`], readPoint: location })
|
|
48
|
-
return { eventId: `i${i}`, eventType: `epcis.${ev.type}`, eventTime: T0, tenantId: 't', data: ev }
|
|
49
|
-
}
|
|
50
|
-
const equipmentEnv = (id: string, kind: string, loc: string): any => ({ eventId: id, eventType: OP_EVENT.equipment, eventTime: T0, tenantId: 't', data: { moverId: id, kind, status: 'running', location: loc } })
|
|
51
|
-
const taskEnv = (id: string, to: string): any => ({ eventId: id, eventType: OP_EVENT.task, eventTime: T0, tenantId: 't', data: { taskId: id, kind: 'move', status: 'in-progress', toNode: to } })
|
|
52
|
-
const orderEnv = (id: string): any => ({ eventId: id, eventType: OP_EVENT.order, eventTime: T0, tenantId: 't', data: { orderId: id, kind: 'inbound', status: 'in-progress', requested: 100, fulfilled: 50 } })
|
|
53
|
-
|
|
54
|
-
const TIERS = {
|
|
55
|
-
medium: { buildings: 3, floorsPer: 3, zonesPer: 4, nodesPerZone: 8, equipment: 80, items: 30000, tasks: 2000, orders: 500 },
|
|
56
|
-
large: { buildings: 6, floorsPer: 4, zonesPer: 6, nodesPerZone: 12, equipment: 300, items: 120000, tasks: 10000, orders: 2000 }
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
for (const [name, tier] of Object.entries(TIERS)) {
|
|
60
|
-
test(`스케일 트윈 [${name}] — 구조 생성 + 5종 엔티티 부하 실측`, () => {
|
|
61
|
-
// 1) 구조 생성(masterToTwin) — 노드·무버·area 계층
|
|
62
|
-
const { master, locationCount, areaCount } = bigMaster(tier)
|
|
63
|
-
let t = performance.now()
|
|
64
|
-
const { model, spaceContent } = masterToTwin(master)
|
|
65
|
-
const tMaster = performance.now() - t
|
|
66
|
-
console.log(`\n [${name}] 구조: 노드 ${locationCount.toLocaleString()} · 무버 ${tier.equipment} · area ${areaCount}(계층) · masterToTwin ${tMaster.toFixed(0)}ms`)
|
|
67
|
-
assert.equal(model.locations.length, locationCount)
|
|
68
|
-
assert.equal((spaceContent.areas ?? []).length, areaCount, 'area 계층 생성')
|
|
69
|
-
|
|
70
|
-
const proj = new StateProjector(model)
|
|
71
|
-
const equipment = model.equipment as any[]
|
|
72
|
-
|
|
73
|
-
// 2) apply 처리량 — 5종 혼합(items·equipment·tasks·orders)
|
|
74
|
-
t = performance.now()
|
|
75
|
-
for (let i = 0; i < tier.items; i++) proj.apply(itemEnv(i, `nd-${i % locationCount}`))
|
|
76
|
-
for (let i = 0; i < tier.equipment; i++) proj.apply(equipmentEnv(equipment[i].id, equipment[i].kind, `nd-${i % locationCount}`))
|
|
77
|
-
for (let i = 0; i < tier.tasks; i++) proj.apply(taskEnv(`tk-${i}`, `nd-${i % locationCount}`))
|
|
78
|
-
for (let i = 0; i < tier.orders; i++) proj.apply(orderEnv(`or-${i}`))
|
|
79
|
-
const total = tier.items + tier.equipment + tier.tasks + tier.orders
|
|
80
|
-
const tApply = performance.now() - t
|
|
81
|
-
console.log(` [${name}] apply: ${total.toLocaleString()} 이벤트(items+equipment+tasks+orders) ${tApply.toFixed(0)}ms = ${Math.round((total / tApply) * 1000).toLocaleString()} ev/s`)
|
|
82
|
-
|
|
83
|
-
// 3) snapshot(전체 투영) — 5종 전부
|
|
84
|
-
let s: any
|
|
85
|
-
const iters = 30
|
|
86
|
-
t = performance.now()
|
|
87
|
-
for (let k = 0; k < iters; k++) s = proj.snapshot()
|
|
88
|
-
const tSnap = (performance.now() - t) / iters
|
|
89
|
-
console.log(` [${name}] snapshot: ${tSnap.toFixed(2)}ms/call (items ${s.items.length.toLocaleString()}·locations ${s.locations.length.toLocaleString()}·equipment ${s.equipment.length}·tasks ${s.tasks.length}·orders ${s.orders.length}) → 최대 ${Math.round(1000 / tSnap).toLocaleString()} snap/s`)
|
|
90
|
-
|
|
91
|
-
// 4) 브로드캐스팅 payload(buildEntityDeltas)
|
|
92
|
-
t = performance.now()
|
|
93
|
-
for (let k = 0; k < iters; k++) buildEntityDeltas(s, 't1')
|
|
94
|
-
const tDelta = (performance.now() - t) / iters
|
|
95
|
-
console.log(` [${name}] buildEntityDeltas: ${tDelta.toFixed(2)}ms/call`)
|
|
96
|
-
console.log(` [${name}] heap: ${MB(process.memoryUsage().heapUsed)}MB`)
|
|
97
|
-
|
|
98
|
-
// 정합 sanity
|
|
99
|
-
assert.equal(s.equipment.length, tier.equipment)
|
|
100
|
-
assert.equal(s.orders.length, tier.orders)
|
|
101
|
-
assert.ok(s.items.length > 0)
|
|
102
|
-
})
|
|
103
|
-
}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* A column that can hold a credential has to be encrypted at rest.
|
|
3
|
-
*
|
|
4
|
-
* ── What this catches, and what it cost to find by hand (2026-09-09) ──────
|
|
5
|
-
* `TwinReference.connectionConfig` was a plain `simple-json` column holding a signing secret
|
|
6
|
-
* and a live access token. Two shell commands read both:
|
|
7
|
-
*
|
|
8
|
-
* sqlite3 -readonly db.sqlite "select connection_config from twin_references"
|
|
9
|
-
* {"endpoint":"…","hookSecret":"dev-only-localhost-hook-2026","token":"eyJhbGciOi…"}
|
|
10
|
-
*
|
|
11
|
-
* The read path was already guarded — the detail resolver masks fields an adapter declares
|
|
12
|
-
* `secret: true`. So the leak was not on the way out, it was the stored bytes: the database
|
|
13
|
-
* file, its backups, its replicas. **Two different protections, and having one reads exactly
|
|
14
|
-
* like having both.**
|
|
15
|
-
*
|
|
16
|
-
* ── Why a guard and not a review habit ────────────────────────────────────
|
|
17
|
-
* The framework has had the pieces for a while — `encryptJsonTransformer` and
|
|
18
|
-
* `longTextColumn` — and nothing used them together. Two packages imported the transformer and
|
|
19
|
-
* left it commented out. A rule that depends on someone remembering two exports is a rule that
|
|
20
|
-
* holds until the next new column.
|
|
21
|
-
*
|
|
22
|
-
* So the rule is measured instead: any column whose name says it can carry a credential must
|
|
23
|
-
* carry an encrypting transformer. New entities are covered the day they are written.
|
|
24
|
-
*
|
|
25
|
-
* ── What this does not claim ──────────────────────────────────────────────
|
|
26
|
-
* Encryption at rest does not protect a value from anyone who can run this process — that
|
|
27
|
-
* person holds the key. It protects the stored copy, which is a different set of people.
|
|
28
|
-
*/
|
|
29
|
-
import { test } from 'node:test'
|
|
30
|
-
import assert from 'node:assert/strict'
|
|
31
|
-
import { readFileSync, readdirSync, statSync } from 'node:fs'
|
|
32
|
-
import { fileURLToPath } from 'node:url'
|
|
33
|
-
import { join } from 'node:path'
|
|
34
|
-
|
|
35
|
-
const SERVER = fileURLToPath(new URL('../server', import.meta.url))
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* 이름이 「자격 증명이 들 수 있다」고 말하는 칸.
|
|
39
|
-
*
|
|
40
|
-
* 이름으로 판정하는 것의 한계를 적어 둔다 — `connectionConfig` 처럼 **이름이 비밀을 말하지 않는**
|
|
41
|
-
* 칸이 실제로 비밀을 담고 있었다. 그래서 그 이름을 목록에 넣는다. 새 칸이 같은 부류이면 여기 더한다.
|
|
42
|
-
*/
|
|
43
|
-
const CREDENTIAL_NAMES = /^(secret|token|password|passwd|apiKey|credential|credentials|privateKey|connectionConfig)\??$/i
|
|
44
|
-
|
|
45
|
-
/** 암호화를 붙이는 방법 — 둘 중 하나면 된다(shell 이 내놓는 것). */
|
|
46
|
-
const ENCRYPTED = /encryptedJsonColumn\(\)|encryptTransformer|encryptJsonTransformer/
|
|
47
|
-
|
|
48
|
-
function entityFiles(dir: string, out: string[] = []): string[] {
|
|
49
|
-
for (const name of readdirSync(dir)) {
|
|
50
|
-
const full = join(dir, name)
|
|
51
|
-
if (statSync(full).isDirectory()) {
|
|
52
|
-
entityFiles(full, out)
|
|
53
|
-
continue
|
|
54
|
-
}
|
|
55
|
-
if (!full.endsWith('.ts') || full.endsWith('.d.ts')) continue
|
|
56
|
-
const src = readFileSync(full, 'utf-8')
|
|
57
|
-
if (src.includes('@Entity(')) out.push(full)
|
|
58
|
-
}
|
|
59
|
-
return out
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** 한 파일에서 `@Column` 선언과 그것이 붙은 속성 이름의 짝. */
|
|
63
|
-
function columnsOf(src: string): { property: string; declaration: string; line: number }[] {
|
|
64
|
-
const lines = src.split('\n')
|
|
65
|
-
const out: { property: string; declaration: string; line: number }[] = []
|
|
66
|
-
|
|
67
|
-
for (let i = 0; i < lines.length; i++) {
|
|
68
|
-
if (!/^\s*@Column\(/.test(lines[i])) continue
|
|
69
|
-
|
|
70
|
-
/* 선언이 여러 줄일 수 있다 — 괄호가 닫힐 때까지 모은다. */
|
|
71
|
-
let declaration = ''
|
|
72
|
-
let depth = 0
|
|
73
|
-
let j = i
|
|
74
|
-
do {
|
|
75
|
-
declaration += lines[j]
|
|
76
|
-
for (const ch of lines[j]) {
|
|
77
|
-
if (ch === '(') depth++
|
|
78
|
-
else if (ch === ')') depth--
|
|
79
|
-
}
|
|
80
|
-
j++
|
|
81
|
-
} while (depth > 0 && j < lines.length && j - i < 12)
|
|
82
|
-
|
|
83
|
-
/* 데코레이터 다음의 첫 속성 줄 — 사이에 @Field 등이 끼어든다. */
|
|
84
|
-
for (let k = j; k < Math.min(j + 6, lines.length); k++) {
|
|
85
|
-
const m = /^\s{2}(?:readonly\s+)?([A-Za-z_][A-Za-z0-9_]*)\??\s*[:!]/.exec(lines[k])
|
|
86
|
-
if (m) {
|
|
87
|
-
out.push({ property: m[1], declaration, line: i + 1 })
|
|
88
|
-
break
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return out
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
test('★ a column that can hold a credential is encrypted at rest', () => {
|
|
96
|
-
const offenders: string[] = []
|
|
97
|
-
let examined = 0
|
|
98
|
-
|
|
99
|
-
for (const file of entityFiles(SERVER)) {
|
|
100
|
-
const src = readFileSync(file, 'utf-8')
|
|
101
|
-
for (const column of columnsOf(src)) {
|
|
102
|
-
examined++
|
|
103
|
-
if (!CREDENTIAL_NAMES.test(column.property)) continue
|
|
104
|
-
if (ENCRYPTED.test(column.declaration)) continue
|
|
105
|
-
offenders.push(`${file.slice(SERVER.length + 1)}:${column.line} ${column.property}`)
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
assert.ok(examined > 40, `본 칸이 ${examined}개다 — 찾는 방법이 깨졌다`)
|
|
110
|
-
assert.deepEqual(
|
|
111
|
-
offenders,
|
|
112
|
-
[],
|
|
113
|
-
'these columns can hold a credential and are stored in the clear:\n ' +
|
|
114
|
-
offenders.join('\n ') +
|
|
115
|
-
'\nAdd ...encryptedJsonColumn() (json) or transformer: encryptTransformer (string) from @things-factory/shell.\n' +
|
|
116
|
-
'Note the column type has to change with it — encryptedJsonColumn() carries both, which is why it exists.'
|
|
117
|
-
)
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
test('this guard reads real entities, so a broken scan is red and not green', () => {
|
|
121
|
-
/*
|
|
122
|
-
* 위 검사는 위반이 없으면 통과한다. **아무것도 못 읽어도** 통과하므로, 읽었다는 사실을 따로 잰다 —
|
|
123
|
-
* 오늘 이 저장소의 다른 검사가 견준 것이 0인데 초록이던 부류가 넷이었다.
|
|
124
|
-
*/
|
|
125
|
-
const files = entityFiles(SERVER)
|
|
126
|
-
assert.ok(files.length > 10, `엔티티 파일이 ${files.length}개다 — 순회가 깨졌다`)
|
|
127
|
-
|
|
128
|
-
const reference = files.find(f => f.endsWith('twin-reference.ts'))
|
|
129
|
-
assert.ok(reference, 'twin-reference.ts 를 못 찾았다 — 이 검사가 생긴 이유가 그 파일이다')
|
|
130
|
-
|
|
131
|
-
const found = columnsOf(readFileSync(reference!, 'utf-8'))
|
|
132
|
-
const config = found.find(c => c.property === 'connectionConfig')
|
|
133
|
-
assert.ok(config, 'connectionConfig 칸을 못 읽었다 — 짝짓기가 깨졌다')
|
|
134
|
-
assert.match(config!.declaration, ENCRYPTED, 'connectionConfig 가 암호화되어 있지 않다')
|
|
135
|
-
})
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* 영속 스냅샷이 **사실을 이기지 않는다** — 쓸 수 있는 조건을 못 박는다.
|
|
3
|
-
*
|
|
4
|
-
* 멈춘 트윈을 조회할 때마다 저널을 전량 계산하고 있었다(23,731건짜리 트윈에서 4.6초). 스냅샷은 같은
|
|
5
|
-
* 폴드의 결과이므로 그 뒤로 아무 일도 없었다면 다시 계산해 봐야 같은 값이다 — 그래서 쓴다.
|
|
6
|
-
*
|
|
7
|
-
* 위험은 하나다: **낡은 스냅샷을 "지금" 이라고 내는 것.** 그래서 쓰는 조건을 좁게 잡았고, 그 조건을
|
|
8
|
-
* 여기서 고정한다. 엔진 본체는 DB 를 물고 있어 부를 수 없으므로 판정식만 떼어 시험한다 —
|
|
9
|
-
* 엔진이 쓰는 식과 **같은 식**이어야 의미가 있으므로, 식이 바뀌면 이 시험이 먼저 깨진다.
|
|
10
|
-
*/
|
|
11
|
-
import { test } from 'node:test'
|
|
12
|
-
import assert from 'node:assert/strict'
|
|
13
|
-
|
|
14
|
-
/** 엔진의 조건과 같은 식(twin-engine.recover 의 스냅샷 사용 조건). */
|
|
15
|
-
const usable = (
|
|
16
|
-
cached: { revision?: number; structureRev?: number | null; state?: any } | null,
|
|
17
|
-
tip: { revision: number; structureRev: number | null },
|
|
18
|
-
asOfNow: boolean
|
|
19
|
-
): boolean =>
|
|
20
|
-
!!(asOfNow && cached?.state && (cached.revision ?? 0) === tip.revision && (cached.structureRev ?? null) === tip.structureRev)
|
|
21
|
-
|
|
22
|
-
const TIP = { revision: 23731, structureRev: 2 }
|
|
23
|
-
const FRESH = { revision: 23731, structureRev: 2, state: { locations: [] } }
|
|
24
|
-
|
|
25
|
-
test('끝에 있고 구조도 같으면 쓴다 — 다시 계산해도 같은 값이다', () => {
|
|
26
|
-
assert.equal(usable(FRESH, TIP, true), true)
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
test('이벤트가 더 있으면 쓰지 않는다 — 그 사이의 사실이 빠진다', () => {
|
|
30
|
-
assert.equal(usable({ ...FRESH, revision: 23730 }, TIP, true), false)
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
test('구조가 바뀌었으면 쓰지 않는다 — 이벤트가 안 늘어도 공장이 달라졌다', () => {
|
|
34
|
-
/* 재프로비저닝이 정확히 이 모양이다: 부스를 넷 늘렸는데 새 이벤트는 아직 없다.
|
|
35
|
-
리비전만 보면 "끝" 이라 새 설비가 없는 옛 상태를 지금으로 내게 된다. */
|
|
36
|
-
assert.equal(usable({ ...FRESH, structureRev: 1 }, TIP, true), false)
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
test('구조 이력이 없던 시절의 스냅샷은 쓰지 않는다 — 모르는 것을 같다고 하지 않는다', () => {
|
|
40
|
-
const legacy = { revision: 23731, state: { locations: [] } } // structureRev 없음
|
|
41
|
-
assert.equal(usable(legacy, TIP, true), false)
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
test('구조가 양쪽 다 없으면 그것은 같은 것이다 — 없는 것끼리는 어긋나지 않는다', () => {
|
|
45
|
-
assert.equal(usable({ revision: 5, structureRev: null, state: {} }, { revision: 5, structureRev: null }, true), true)
|
|
46
|
-
assert.equal(usable({ revision: 5, state: {} }, { revision: 5, structureRev: null }, true), true)
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
test('시간여행에는 쓰지 않는다 — 그 시점까지 계산해야 한다', () => {
|
|
50
|
-
assert.equal(usable(FRESH, TIP, false), false)
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
test('상태가 비어 있는 스냅샷은 쓰지 않는다 — 빈 공장을 지금으로 내지 않는다', () => {
|
|
54
|
-
assert.equal(usable({ revision: 23731, structureRev: 2, state: null }, TIP, true), false)
|
|
55
|
-
assert.equal(usable(null, TIP, true), false)
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
test('이벤트가 하나도 없는 트윈도 성립한다 — 0 과 0 은 같다', () => {
|
|
59
|
-
assert.equal(usable({ revision: 0, structureRev: null, state: { locations: [] } }, { revision: 0, structureRev: null }, true), true)
|
|
60
|
-
})
|