@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,186 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* ★ 저널을 통째로 읽는 자리는 **코드에 그 이유가 적혀 있어야 한다** — 소스 규율 검사.
|
|
3
|
-
*
|
|
4
|
-
* ── 왜 이 검사가 필요한가 (2026-08-20) ──────────────────────────────────────
|
|
5
|
-
* `TwinEngine.timeRange` 는 min/max **두 수**를 구하려고 공간의 인스턴스마다 저널을 `find()` 로 전부
|
|
6
|
-
* 실어 왔다. 개발 서버에서 한 트윈이 275,882행 · `payload` 131MB 였고, 화면 상단의 컨텍스트 띠가 그
|
|
7
|
-
* 함수를 **4초마다** 불렀다. 프로세스 CPU 81~152%, 아무 일도 하지 않는 질의가 8~14초였다.
|
|
8
|
-
*
|
|
9
|
-
* **사람 리뷰가 이것을 잡지 못한 이유는 코드가 맞았기 때문이다.** min/max 는 정확히 나왔다. 틀린 것은
|
|
10
|
-
* 답이 아니라 비용이고, 비용은 읽어서는 보이지 않는다. 그래서 사람에게 다시 맡기지 않고 기계에 맡긴다.
|
|
11
|
-
*
|
|
12
|
-
* ── 규율 ────────────────────────────────────────────────────────────────────
|
|
13
|
-
* 저널(`TwinEvent`)을 `find()` 로 읽는 자리는 둘 중 하나여야 한다.
|
|
14
|
-
* ① **상한이 있다**(`take:`) — 목록·표본처럼 화면이 쓰는 만큼만 읽는 경우.
|
|
15
|
-
* ② **폴드다** — 사실을 하나씩 계산하는 것이 목적이라 그 구간의 행이 필요한 경우. 이때는 바로 앞에
|
|
16
|
-
* `journal-fold:` 로 시작하는 주석을 두어 **왜 필요한지**, 그리고 상한이 없다면 **없어질 조건**을
|
|
17
|
-
* 함께 적는다.
|
|
18
|
-
*
|
|
19
|
-
* 둘 다 아니면 이 검사가 빨개진다. 「두 수만 필요한데 표를 다 읽는」 코드가 다시 들어오는 것을 막는
|
|
20
|
-
* 자리가 여기다. 집계(MIN/MAX/COUNT)는 DB 가 하고 행은 실어 오지 않는다.
|
|
21
|
-
*/
|
|
22
|
-
import { test } from 'node:test'
|
|
23
|
-
import assert from 'node:assert/strict'
|
|
24
|
-
import { readFileSync, readdirSync, statSync } from 'node:fs'
|
|
25
|
-
import { join } from 'node:path'
|
|
26
|
-
|
|
27
|
-
import { guardRoots } from './guard-roots.ts'
|
|
28
|
-
|
|
29
|
-
/** 훑는 곳 — 저널을 읽을 수 있는 층 둘. */
|
|
30
|
-
const ROOTS = guardRoots(import.meta.url, ['../server'])
|
|
31
|
-
|
|
32
|
-
const READ = 'getRepository(TwinEvent)'
|
|
33
|
-
/** 폴드라고 밝히는 표식. 이유를 함께 적는 것이 이 표식의 값이다. */
|
|
34
|
-
const FOLD_PRAGMA = 'journal-fold:'
|
|
35
|
-
/** 표식을 찾아 볼 범위 — 바로 앞 주석 블록. 멀리 두면 무엇에 붙은 표식인지 알 수 없다. */
|
|
36
|
-
const PRAGMA_LOOKBACK = 900
|
|
37
|
-
|
|
38
|
-
/** 주석을 지운 소스 — 규율을 **설명하는 글**이 규율 위반으로 잡히지 않게. */
|
|
39
|
-
function stripComments(src: string): string {
|
|
40
|
-
return src.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^[ \t]*\/\/.*$/gm, '')
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function sources(dir: string): string[] {
|
|
44
|
-
let entries: string[]
|
|
45
|
-
try {
|
|
46
|
-
entries = readdirSync(dir)
|
|
47
|
-
} catch {
|
|
48
|
-
return []
|
|
49
|
-
}
|
|
50
|
-
return entries.flatMap(f => {
|
|
51
|
-
const p = join(dir, f)
|
|
52
|
-
return statSync(p).isDirectory() ? sources(p) : p.endsWith('.ts') && !p.endsWith('.d.ts') ? [p] : []
|
|
53
|
-
})
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
interface Site {
|
|
57
|
-
file: string
|
|
58
|
-
line: number
|
|
59
|
-
bounded: boolean
|
|
60
|
-
fold: boolean
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** 저널을 `find()` 로 읽는 자리들. 쓰기(`insert`/`save`)와 집계(`createQueryBuilder`)는 대상이 아니다. */
|
|
64
|
-
function journalFindSites(): Site[] {
|
|
65
|
-
const out: Site[] = []
|
|
66
|
-
for (const root of ROOTS) {
|
|
67
|
-
for (const f of sources(root)) {
|
|
68
|
-
const src = readFileSync(f, 'utf-8')
|
|
69
|
-
let at = src.indexOf(READ)
|
|
70
|
-
while (at >= 0) {
|
|
71
|
-
const tail = src.slice(at + READ.length, at + READ.length + 600)
|
|
72
|
-
/* 이 자리가 어떤 호출인가 — 첫 메서드만 본다. */
|
|
73
|
-
const call = /^\s*\.?\s*(find|findOne|count|countBy|createQueryBuilder|save|insert|delete|create)\b/.exec(tail)
|
|
74
|
-
if (call && call[1] === 'find') {
|
|
75
|
-
/* 옵션 객체 안에 상한이 있나 — 괄호 균형까지 보지 않고 넉넉히 잘라 본다(실패 시 폴드 표식이 받는다). */
|
|
76
|
-
const opts = tail.slice(0, 500)
|
|
77
|
-
const before = src.slice(Math.max(0, at - PRAGMA_LOOKBACK), at)
|
|
78
|
-
out.push({
|
|
79
|
-
file: f.slice(root.length + 1),
|
|
80
|
-
line: src.slice(0, at).split('\n').length,
|
|
81
|
-
bounded: /\btake\s*:/.test(opts),
|
|
82
|
-
fold: before.includes(FOLD_PRAGMA)
|
|
83
|
-
})
|
|
84
|
-
}
|
|
85
|
-
at = src.indexOf(READ, at + 1)
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return out
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
test('저널을 통째로 읽는 자리는 폴드라고 밝히고 이유를 적는다', () => {
|
|
93
|
-
const bare = journalFindSites()
|
|
94
|
-
.filter(s => !s.bounded && !s.fold)
|
|
95
|
-
.map(s => `${s.file}:${s.line}`)
|
|
96
|
-
|
|
97
|
-
assert.deepEqual(
|
|
98
|
-
bare,
|
|
99
|
-
[],
|
|
100
|
-
`이유 없이 저널 전체를 읽는 자리 ${bare.length}곳:\n ${bare.join('\n ')}\n\n` +
|
|
101
|
-
`둘 중 하나로 만든다.\n` +
|
|
102
|
-
` ① 화면이 쓰는 만큼만 읽는다면 \`take:\` 를 준다.\n` +
|
|
103
|
-
` ② 사실을 계산하는 것이 목적이라면 바로 앞에 \`journal-fold:\` 주석으로 왜 행이 필요한지,\n` +
|
|
104
|
-
` 상한이 없다면 그 예외가 없어질 조건까지 적는다.\n\n` +
|
|
105
|
-
`수 몇 개만 필요하다면 셋째 길이 맞다: 집계는 DB 가 한다(MIN/MAX/COUNT + createQueryBuilder).\n` +
|
|
106
|
-
`\`timeRange\` 가 min/max 두 수를 구하려고 275,882행·payload 131MB 를 4초마다 파싱했던 자리다.`
|
|
107
|
-
)
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
test('검사가 실제로 저널 읽기를 보고 있다 — 대상이 사라지면 초록이 거짓이 된다', () => {
|
|
111
|
-
const sites = journalFindSites()
|
|
112
|
-
|
|
113
|
-
/*
|
|
114
|
-
* ── 문턱을 셋에서 둘로 내렸다 (2026-08-28) ─────────────────────────────────
|
|
115
|
-
* 자리 하나를 **질의 빌더로 옮겨서** 줄었다(계보 조회 — 관계로 물어 두 단계 질의가 되고 `take` 가
|
|
116
|
-
* 있어도 안쪽에 상한이 없던 자리다). 그 방향이 이 검사가 바라는 방향이므로 문턱을 따라 내린다.
|
|
117
|
-
*
|
|
118
|
-
* 이 단정의 뜻은 「자리가 많아야 한다」가 아니라 **「검사가 무언가를 보고 있어야 한다」**다. 남은 둘도
|
|
119
|
-
* 옮겨지면 이 시험이 빨개지는데, 그때가 바로 사람이 알아야 하는 순간이다 — 검사가 아무것도 보지 않는
|
|
120
|
-
* 채로 초록이면 그 초록이 거짓이다. 그때는 검사의 패턴을 질의 빌더 쪽으로 옮긴다.
|
|
121
|
-
*/
|
|
122
|
-
assert.ok(
|
|
123
|
-
sites.length >= 2,
|
|
124
|
-
`저널 \`find()\` 를 ${sites.length}곳 찾았다 — 둘보다 적으면 이 검사가 보는 대상이 없어졌다는 뜻이다. ` +
|
|
125
|
-
`호출을 질의 빌더로 옮겼다면 이 검사의 패턴도 그쪽으로 옮긴다(리포지토리를 변수에 담아 부르는 ` +
|
|
126
|
-
`형태도 함께 본다).`
|
|
127
|
-
)
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
test('시각축은 집계로 답한다 — 행을 실어 오는 방식으로 되돌아가지 않는다', () => {
|
|
131
|
-
/*
|
|
132
|
-
* `timeRange` 만은 이름으로 못 박는다. 이 함수가 이 결함의 출처이고, 되돌아가도 **답은 맞기 때문에**
|
|
133
|
-
* 결과를 보는 시험으로는 잡히지 않는다.
|
|
134
|
-
*/
|
|
135
|
-
const src = readFileSync(new URL('../server/engine/twin-engine.ts', import.meta.url).pathname, 'utf-8')
|
|
136
|
-
const at = src.indexOf('static async timeRange(')
|
|
137
|
-
assert.ok(at > 0, 'timeRange 를 찾지 못했다 — 이름이 바뀌면 이 검사도 함께 옮긴다')
|
|
138
|
-
|
|
139
|
-
const body = src.slice(at, at + 2500)
|
|
140
|
-
assert.ok(body.includes('MIN(e.eventTime)'), '가장 이른 시각은 DB 가 고른다')
|
|
141
|
-
assert.ok(body.includes('MAX(e.eventTime)'), '가장 늦은 시각도 그렇다')
|
|
142
|
-
assert.ok(!body.includes('getRepository(TwinEvent).find('), '저널을 행 단위로 실어 오지 않는다')
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
/*
|
|
146
|
-
* ── 세는 것도 읽는 것이다 (2026-08-22 실측) ─────────────────────────────────
|
|
147
|
-
* 위 규율은 **행을 실어 오는 것**을 봤다. 그런데 `getManyAndCount()` 는 행을 싣지 않으면서도 조건에
|
|
148
|
-
* 맞는 행을 **전부 센다**. 실측: 저널 358만 행인 트윈에서 한 번에 5.32초, 상한 1만이면 0.04초.
|
|
149
|
-
* 이력 화면을 열 때마다 그것을 물었고 그 5초 동안 이벤트 루프가 다른 요청을 처리하지 못했다.
|
|
150
|
-
*
|
|
151
|
-
* 그래서 저널에는 **상한 없는 세기를 두지 않는다.** 정확한 수를 모르는 것은 사실이므로 그때는
|
|
152
|
-
* 「모른다」로 낸다(목록 규약의 `capped`) — 상한값을 전체로 적으면 화면이 거짓을 말한다.
|
|
153
|
-
*/
|
|
154
|
-
test('저널을 상한 없이 세지 않는다 — 답은 맞지만 비용이 틀린 자리', () => {
|
|
155
|
-
/*
|
|
156
|
-
* **주석과 남의 표를 세지 않는다.** 처음 만든 검사는 「파일이 `TwinEvent` 를 언급하고 어딘가에
|
|
157
|
-
* `.count(` 가 있으면」 이었다. 그래서 공간·구조·대표표현을 세는 자리와 **이 규율을 설명하는 주석**
|
|
158
|
-
* 까지 잡았다 — 거짓 경보는 참 경보를 죽인다. 규칙은 하나다: **저널 저장소에서 세는 자리**만 본다.
|
|
159
|
-
*/
|
|
160
|
-
const offenders: string[] = []
|
|
161
|
-
for (const file of ROOTS.flatMap(r => sources(r))) {
|
|
162
|
-
const src = stripComments(readFileSync(file, 'utf-8'))
|
|
163
|
-
for (const m of src.matchAll(/getRepository\(TwinEvent\)|repository: getRepository\(TwinEvent\)/g)) {
|
|
164
|
-
const after = src.slice(m.index ?? 0, (m.index ?? 0) + 900)
|
|
165
|
-
const counted = after.match(/getManyAndCount\(|getCount\(|\.count\(/)
|
|
166
|
-
if (!counted) continue
|
|
167
|
-
/* 상한이 함께 적혀 있으면 규율을 지킨 것이다(상한까지만 센다). */
|
|
168
|
-
if (/take\(|COUNT_CAP/.test(after.slice(0, (counted.index ?? 0) + 80))) continue
|
|
169
|
-
offenders.push(`${file.split('/packages/')[1]} ${counted[0]}`)
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
assert.deepEqual(
|
|
173
|
-
offenders,
|
|
174
|
-
[],
|
|
175
|
-
`저널을 상한 없이 세는 자리가 있다 — 상한까지만 세고 넘으면 「모른다」로 낸다:\n ${offenders.join('\n ')}`
|
|
176
|
-
)
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
test('건수 상한이 값으로 선언돼 있고, 넘으면 전체를 비운다', () => {
|
|
180
|
-
const src = readFileSync(new URL('../server/service/twin-journal/twin-journal-query.ts', import.meta.url).pathname, 'utf-8')
|
|
181
|
-
assert.match(src, /const COUNT_CAP = JOURNAL_COUNT_CAP/, '상한은 저널 세기의 문에서 온다 — 값이 두 곳에 흩어지지 않게')
|
|
182
|
-
assert.match(src, /take\(COUNT_CAP \+ 1\)/, '상한을 넘었는지 알려면 하나 더 읽어야 한다')
|
|
183
|
-
/* 상한을 넘으면 **전체를 비운다** — 상한값을 전체로 적으면 화면이 거짓을 말한다. */
|
|
184
|
-
assert.match(src, /capped \? undefined : counted\.length/)
|
|
185
|
-
assert.match(src, /capped/, '넘었다는 사실을 함께 낸다(알리지 않고 자르지 않는다)')
|
|
186
|
-
})
|
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* ★ 저널 보존 — **선언이 있을 때만 지운다. 그리고 도는 트윈에서만 체크포인트를 상한으로 본다.**
|
|
3
|
-
*
|
|
4
|
-
* ── 왜 필요한가 (2026-08-22 실측) ───────────────────────────────────────────
|
|
5
|
-
* 개발 저널이 시간당 **581,794행 · 0.85GB**(하루 20GB)로 자랐다. 데모 MES 트윈 셋이 전체의 **97%** 를
|
|
6
|
-
* 만들고 지우는 것이 없었다.
|
|
7
|
-
*
|
|
8
|
-
* ── 체크포인트를 전제 조건으로 두었던 것이 결함이었다 (2026-08-27) ──────────
|
|
9
|
-
* 예전 규율은 「체크포인트가 없는 인스턴스는 건드리지 않는다」였다. 근거는 「그때 저널이 유일한 복구
|
|
10
|
-
* 수단이다」였고, 그 근거가 틀렸다. 트윈이 자기 저널과 어떤 관계인지는 `restartPolicy` 가 선언한다.
|
|
11
|
-
*
|
|
12
|
-
* resync 연결된 시스템에서 다시 읽는다 저널은 복구 수단이 아니다
|
|
13
|
-
* reset 씨앗부터 다시 돌린다 저널은 복구 수단이 아니다
|
|
14
|
-
* resume 저널을 이어 세운다 보관된 구간에서 세운다
|
|
15
|
-
*
|
|
16
|
-
* 셋 다 보관 기간 밖의 사건을 요구하지 않는다. 보존 기간을 7일로 선언한 것이 곧 「7일 전 상태로는
|
|
17
|
-
* 세우지 않는다」는 뜻이다. 그 전제 때문에 멈춘 트윈 셋에 2,900만 행이 영구히 남았다.
|
|
18
|
-
*
|
|
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
|
-
/** `pruneJournal` 본문만 — 다른 자리의 조건이 섞여 판정이 흐려지지 않게. */
|
|
34
|
-
function pruneBody(): string {
|
|
35
|
-
const at = src.indexOf('static async pruneJournal')
|
|
36
|
-
assert.ok(at > 0, 'pruneJournal 이 있어야 한다')
|
|
37
|
-
const rest = src.slice(at)
|
|
38
|
-
const end = rest.indexOf('\n /** 보존 정리 주기 기동')
|
|
39
|
-
return rest.slice(0, end === -1 ? 4000 : end)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
test('선언이 없으면 아무것도 지우지 않는다 — 기본값은 없음이다', () => {
|
|
43
|
-
/* 계약의 기본값 — 여기에 숫자가 박히면 아무도 정하지 않은 채로 지워진다. */
|
|
44
|
-
assert.match(
|
|
45
|
-
src,
|
|
46
|
-
/static JOURNAL_RETENTION_DAYS\?: number = undefined/,
|
|
47
|
-
'보존 기간의 기본값은 undefined 여야 한다(알리지 않고 지우는 것보다 알리지 않고 쌓는 편이 낫다)'
|
|
48
|
-
)
|
|
49
|
-
/* 그리고 그 값이 없으면 즉시 돌아간다. */
|
|
50
|
-
assert.match(pruneBody(), /if \(!days \|\| days <= 0\) return/, '선언이 없으면 지우지 않고 돌아간다')
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
test('★ 체크포인트는 상한이지 전제 조건이 아니다 — 없다는 이유로 건너뛰지 않는다', () => {
|
|
54
|
-
const body = pruneBody()
|
|
55
|
-
/*
|
|
56
|
-
* 한 줄이 이 규율의 전부다. 있으면 그 리비전이 상한이고, 없으면 상한이 없다.
|
|
57
|
-
*
|
|
58
|
-
* 예전에는 없으면 그 트윈을 건너뛰었다. 그래서 멈춘 트윈 셋에 2,900만 행이 영구히 남았다 —
|
|
59
|
-
* 멈춘 트윈은 체크포인트를 다시 쓸 주체가 없어서 만료된 뒤 영원히 조건을 못 넘긴다.
|
|
60
|
-
*/
|
|
61
|
-
assert.match(
|
|
62
|
-
body,
|
|
63
|
-
/const upTo = at > 0 \? at : Number\.MAX_SAFE_INTEGER/,
|
|
64
|
-
'체크포인트가 없으면 상한 없이 지운다'
|
|
65
|
-
)
|
|
66
|
-
assert.doesNotMatch(body, /if \(!upTo\)[\s\S]{0,200}?continue/, '없다는 이유로 건너뛰지 않는다')
|
|
67
|
-
assert.doesNotMatch(body, /skipped/, '건너뛰는 갈래가 아예 없다')
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
test('지우는 조건이 넷 다 있다 — 하나라도 빠지면 지나친 삭제가 된다', () => {
|
|
71
|
-
const body = pruneBody()
|
|
72
|
-
for (const [what, re] of [
|
|
73
|
-
['도메인', /domain_id = :domainId/],
|
|
74
|
-
['인스턴스', /instance_id = :instanceId/],
|
|
75
|
-
['체크포인트 리비전 이하(도는 트윈에서는 상한, 멈춘 트윈에서는 무한)', /revision <= :upTo/],
|
|
76
|
-
['보존 기간 이전(실제 시계)', /created_at < :cutoff/]
|
|
77
|
-
] as [string, RegExp][]) {
|
|
78
|
-
assert.match(body, re, `${what} 조건이 있어야 한다`)
|
|
79
|
-
}
|
|
80
|
-
/* 시각을 모르는 옛 행은 「오래됐다」로 읽지 않는다. */
|
|
81
|
-
assert.match(body, /created_at IS NOT NULL/, '시각이 빈 행은 지우지 않는다')
|
|
82
|
-
/*
|
|
83
|
-
* **`event_time` 을 기준으로 쓰지 않는다** — 그것은 트윈의 시계다(실측: order-check 의 가장 늦은
|
|
84
|
-
* eventTime 이 2027-06-08 로 실제보다 10개월 앞). 보존은 저장 나이의 문제이고 그것은 실제 시계다.
|
|
85
|
-
*/
|
|
86
|
-
assert.doesNotMatch(body, /event_time\s*<\s*:cutoff/, '도메인 시각을 보존 기준으로 쓰지 않는다')
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
test('도메인의 정책이 프로세스 기본값보다 먼저다 — 그리고 엔진은 그것이 어디서 오는지 모른다', () => {
|
|
90
|
-
const body = pruneBody()
|
|
91
|
-
/*
|
|
92
|
-
* 보존 기간은 **테넌트의 정책**이다(한 고객은 30일, 다른 고객은 7일). 그래서 도메인에 먼저 묻고,
|
|
93
|
-
* 답이 없을 때만 프로세스 기본값으로 내려간다. 순서가 뒤집히면 설정 화면에서 고친 값이 먹지 않는다.
|
|
94
|
-
*/
|
|
95
|
-
assert.match(body, /this\.retentionDaysOf\(domainId\)/, '도메인에 먼저 묻는다')
|
|
96
|
-
assert.match(body, /perDomain \?\? this\.JOURNAL_RETENTION_DAYS/, '도메인 답이 없을 때만 프로세스 기본값이다')
|
|
97
|
-
/*
|
|
98
|
-
* 그리고 그 답이 **어디서 오는지 엔진은 모른다** — 시임일 뿐이다. 엔진이 `Setting` 을 직접 읽으면
|
|
99
|
-
* 커널의 호스트가 화면 관심사를 알게 된다(방향이 거꾸로다).
|
|
100
|
-
*/
|
|
101
|
-
assert.doesNotMatch(src, /from '@things-factory\/setting-base'/, '엔진은 Setting 을 알지 않는다')
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
test('★ 보존 기간을 스냅샷 TTL 로 올리지 않는다 — 두 값은 다른 것을 정한다', () => {
|
|
105
|
-
/*
|
|
106
|
-
* 예전에는 `max(보존 기간, 스냅샷 TTL)` 이었다. 둘 다 7일이라 상향은 없었지만, 그 식은 두 값을 묶어
|
|
107
|
-
* 놓아서 보존 기간을 줄이면 스냅샷 TTL 이 따라오게 만든다.
|
|
108
|
-
*
|
|
109
|
-
* 보존 기간 이력을 며칠 두나
|
|
110
|
-
* 스냅샷 TTL 빠르게 세우기 위한 저장본이 며칠 사나
|
|
111
|
-
*
|
|
112
|
-
* 저장본이 만료되어도 트윈은 보관된 구간에서 세워진다. 그래서 보존 기간이 더 짧아도 성립한다.
|
|
113
|
-
*/
|
|
114
|
-
const body = pruneBody()
|
|
115
|
-
assert.doesNotMatch(body, /Math\.max\(days, minDays\)/, '두 값을 묶지 않는다')
|
|
116
|
-
assert.match(body, /const effectiveDays = days/, '선언한 보존 기간을 그대로 쓴다')
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
test('★ 멈출 때는 만료 없이 적는다 — 그러지 않으면 7일 뒤 그 상태를 아는 것이 없다', () => {
|
|
120
|
-
/*
|
|
121
|
-
* 멈춘 트윈은 아무도 스냅샷을 다시 쓰지 않아서 7일 뒤 만료된다. 멈추는 순간이 그 상태가 메모리에
|
|
122
|
-
* 있는 마지막 순간이므로 그때 만료 없이 적는다. 그러면 다시 뜰 때 보관된 구간을 계산하지 않아도 된다.
|
|
123
|
-
*/
|
|
124
|
-
assert.match(src, /static SNAPSHOT_KEEP_S = /, '만료 없이 두는 기간에 이름이 있어야 한다')
|
|
125
|
-
const at = src.indexOf('static async stop(')
|
|
126
|
-
assert.ok(at > 0, 'stop 이 있어야 한다')
|
|
127
|
-
const stopBody = src.slice(at, at + 1600)
|
|
128
|
-
assert.match(stopBody, /persistSnapshot\(domainId, id, true\)/, '멈출 때는 만료 없이 적는다')
|
|
129
|
-
assert.match(src, /keepForever \? this\.SNAPSHOT_KEEP_S : this\.SNAPSHOT_TTL_S/, '그 값이 실제로 쓰인다')
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
test('지운 것을 말한다 — 알리지 않고 줄어든 저널은 없었던 일과 구별되지 않는다', () => {
|
|
133
|
-
const body = pruneBody()
|
|
134
|
-
assert.match(body, /journal pruned/, '지운 건수를 로그에 남긴다')
|
|
135
|
-
assert.match(body, /revision ≤/, '어디까지 지웠는지 함께 남긴다')
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
test('raw SQL 을 쓰지 않는다 — 드라이버 다섯을 다 지나야 한다', () => {
|
|
139
|
-
const body = pruneBody()
|
|
140
|
-
assert.doesNotMatch(body, /\.query\(/, '드라이버 방언을 직접 적지 않는다')
|
|
141
|
-
assert.match(body, /createQueryBuilder\(\)[\s\S]{0,80}\.delete\(\)/, '조건 삭제는 쿼리빌더로 한다')
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
test('선언도 없고 물을 길도 없으면 주기 정리를 걸지 않는다', () => {
|
|
145
|
-
const at = src.indexOf('static startRetentionLoop')
|
|
146
|
-
assert.ok(at > 0, 'startRetentionLoop 이 있어야 한다')
|
|
147
|
-
const body = src.slice(at, at + 900)
|
|
148
|
-
/*
|
|
149
|
-
* 조건이 **둘**이다. 프로세스 기본값(`JOURNAL_RETENTION_DAYS`)만 보면 안 된다 — 도메인마다 다른 값을
|
|
150
|
-
* 앱이 답하는 시임(`retentionDaysOf`)이 있고, 그것이 심겨 있으면 기본값이 없어도 걸어야 한다
|
|
151
|
-
* (그 도메인이 자기 값을 가질 수 있다). 반대로 **둘 다 없으면 물을 곳이 없으므로** 걸지 않는다.
|
|
152
|
-
*
|
|
153
|
-
* 기본값만 보게 두면 시임만 심은 배포에서 정리가 알리지 않고 돌지 않는다 — 「보존을 켰는데 왜 안 줄어드나」.
|
|
154
|
-
*/
|
|
155
|
-
assert.match(
|
|
156
|
-
body,
|
|
157
|
-
/if \(!this\.JOURNAL_RETENTION_DAYS && !this\.retentionDaysOf\) return/,
|
|
158
|
-
'기본값도 없고 도메인 시임도 없을 때만 타이머를 걸지 않는다'
|
|
159
|
-
)
|
|
160
|
-
/* 타이머가 프로세스를 붙잡지 않게 — 지우는 일이 종료를 막을 이유가 없다. */
|
|
161
|
-
assert.match(body, /unref/, '타이머는 unref 한다')
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
/*
|
|
165
|
-
* ★ 나눠서 지운다 — 한 문장으로 통째 지우면 그동안 호스트가 멈춘다.
|
|
166
|
-
*
|
|
167
|
-
* 실측(2026-08-25 · 개발 환경 44GB): 보관 기간 7일에 13일치가 쌓여 있었고, 그 밀린 몫이 한 번에
|
|
168
|
-
* 나왔다. 한 번의 지우기가 74초·79초였고 그 뒤에 한 줄짜리 `domains` 조회가 67초로 찍혔다 — 그
|
|
169
|
-
* 질의의 문제가 아니라 줄을 선 값이다. sqlite 는 쓰기가 하나다.
|
|
170
|
-
*
|
|
171
|
-
* 이 테스트는 소스를 전수 조회해 규율을 지킨다: 배치 경계를 먼저 찾는가 · 한 창의 시간 상한이 있는가 ·
|
|
172
|
-
* 배치 사이에 루프를 비워 주는가. 실제 지우기는 큰 기록이 있어야 재현되므로 여기서 재지 않는다.
|
|
173
|
-
*/
|
|
174
|
-
test('★ 지우기를 배치로 나눈다 — 쓰기 잠금을 한 번에 오래 잡지 않는다', () => {
|
|
175
|
-
const src = readFileSync(ENGINE, 'utf8')
|
|
176
|
-
assert.ok(/JOURNAL_PRUNE_BATCH\s*=\s*[0-9_]+/.test(src), '한 배치의 행 수가 이름을 갖고 있어야 한다')
|
|
177
|
-
assert.ok(/JOURNAL_PRUNE_BUDGET_MS\s*=\s*[0-9_]+/.test(src), '한 창의 시간 상한이 이름을 갖고 있어야 한다')
|
|
178
|
-
const prune = src.slice(src.indexOf('static async pruneJournal'))
|
|
179
|
-
const body = prune.slice(0, prune.indexOf('static startRetentionLoop'))
|
|
180
|
-
assert.ok(body.includes('JOURNAL_PRUNE_BATCH'), '배치 경계를 찾는 데 그 수를 쓴다')
|
|
181
|
-
assert.ok(body.includes('JOURNAL_PRUNE_BUDGET_MS'), '한 창의 시간 상한을 본다')
|
|
182
|
-
assert.ok(/setImmediate/.test(body), '배치 사이에 루프를 비워 준다 — 없으면 나눈 뜻이 없다')
|
|
183
|
-
/* `DELETE … LIMIT` 은 드라이버마다 다르다 — 경계 `revision` 을 찾아 그 아래만 지운다. */
|
|
184
|
-
assert.ok(!/delete[\s\S]{0,200}limit\(/i.test(body), 'DELETE 에 LIMIT 을 붙이지 않는다')
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
/*
|
|
188
|
-
* ★ 앞부분이 지워진 것을 목록이 말한다.
|
|
189
|
-
*
|
|
190
|
-
* 정리가 오래된 사건을 지우면 목록을 아무리 내려가도 어느 지점에서 기록이 끝난다. 화면이 그것을 말하지
|
|
191
|
-
* 않으면 사람은 「이 트윈은 그때 시작했다」로 읽는다. 없었던 것과 지워진 것은 다른 사실이다.
|
|
192
|
-
*/
|
|
193
|
-
test('★ 남아 있는 가장 오래된 리비전을 목록이 함께 낸다 — 지워진 것을 없었던 것으로 보이지 않게', () => {
|
|
194
|
-
const type = readFileSync(
|
|
195
|
-
join(import.meta.dirname, '..', 'server', 'service', 'twin-event', 'twin-event-type.ts'),
|
|
196
|
-
'utf8'
|
|
197
|
-
)
|
|
198
|
-
assert.match(type, /oldestRevision\?: number/, '목록 응답에 그 칸이 있어야 한다')
|
|
199
|
-
assert.match(type, /retention policy/, '무슨 뜻인지 설명이 있어야 한다(화면이 문구를 만든다)')
|
|
200
|
-
|
|
201
|
-
const query = readFileSync(
|
|
202
|
-
join(import.meta.dirname, '..', 'server', 'service', 'twin-journal', 'twin-journal-query.ts'),
|
|
203
|
-
'utf8'
|
|
204
|
-
)
|
|
205
|
-
/* 저장하지 않고 조회할 때 구한다 — 저장한 값은 실제 데이터와 어긋날 수 있다. */
|
|
206
|
-
assert.match(query, /orderBy\('e\.revision', 'ASC'\)[\s\S]{0,120}?limit\(1\)/, '가장 작은 리비전 한 행만 읽는다')
|
|
207
|
-
/* 트윈 하나를 물었을 때만 — 여러 트윈을 합치면 리비전은 트윈마다 따로 세는 번호다. */
|
|
208
|
-
assert.match(query, /if \(instanceIds\.length === 1\)/, '트윈 하나일 때만 낸다')
|
|
209
|
-
assert.match(query, /nextCursor, oldestRevision \}/, '응답에 실어 보낸다')
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
/*
|
|
213
|
-
* ★ 경계를 시각순으로 찾는다 — 그러지 않으면 색인이 쓰이지 않는다.
|
|
214
|
-
*
|
|
215
|
-
* ── 무엇이 났나 (2026-08-27 실측 · 개발 파일 54GB · 3,128만 행) ───────────────
|
|
216
|
-
* 배치 경계를 `ORDER BY revision` 으로 찾았다. 그러면 계획기가 리비전 색인을 고르고, 지울 대상인지는
|
|
217
|
-
* 행마다 `created_at` 을 열어 확인한다. 지울 것이 적으면 그 트윈의 저널을 끝까지 걸어야 「한 배치를
|
|
218
|
-
* 못 채웠다」를 알 수 있다 — 1,029만 행에서 그것이 2초 예산을 다 썼고 44건만 지우고 멈췄다.
|
|
219
|
-
*
|
|
220
|
-
* 실측한 질의 계획.
|
|
221
|
-
*
|
|
222
|
-
* ORDER BY revision SEARCH USING INDEX ix_twin_event_0 ← 행마다 테이블을 연다
|
|
223
|
-
* ORDER BY createdAt SEARCH USING COVERING INDEX ix_twin_event_8 ← 테이블을 열지 않는다
|
|
224
|
-
* DELETE (정렬 없음) SEARCH USING INDEX ix_twin_event_8 ← 색인만으로 이미 고쳐졌다
|
|
225
|
-
*
|
|
226
|
-
* 그리고 `created_at` 축 색인을 얹었다(`ix_twin_event_8`). 7일 밖 건수 조회가 208초에서 1.68초가 됐다.
|
|
227
|
-
*/
|
|
228
|
-
test('★ 배치 경계를 createdAt 순으로 찾는다 — revision 순으로 찾으면 색인을 못 쓴다', () => {
|
|
229
|
-
const body = pruneBody()
|
|
230
|
-
assert.match(body, /\.orderBy\('e\.createdAt', 'ASC'\)/, '시각순으로 물어야 색인을 쓴다')
|
|
231
|
-
assert.doesNotMatch(body, /\.orderBy\('e\.revision', 'ASC'\)/, '리비전 순으로 되돌아가지 않는다')
|
|
232
|
-
/* 같은 시각의 행이 여럿일 수 있으므로 경계는 `<=` 다 — `<` 로 두면 제자리걸음이 된다. */
|
|
233
|
-
assert.match(body, /created_at <= :batchCutoff/, '경계 시각 이하까지 지운다')
|
|
234
|
-
})
|
|
235
|
-
|
|
236
|
-
test('★ createdAt 축 색인이 선언돼 있다 — 없으면 정리가 전량 주사한다', () => {
|
|
237
|
-
const entity = readFileSync(
|
|
238
|
-
join(import.meta.dirname, '..', 'server', 'service', 'twin-event', 'twin-event.ts'),
|
|
239
|
-
'utf8'
|
|
240
|
-
)
|
|
241
|
-
assert.match(
|
|
242
|
-
entity,
|
|
243
|
-
/@Index\('ix_twin_event_8', \(e: TwinEvent\) => \[e\.domain, e\.instanceId, e\.createdAt\]/,
|
|
244
|
-
'(domain, instanceId, createdAt) 색인이 있어야 한다'
|
|
245
|
-
)
|
|
246
|
-
/* 그리고 그것을 만드는 마이그레이션이 배선돼 있어야 한다 — 선언만 있으면 배포에 생기지 않는다. */
|
|
247
|
-
const list = readFileSync(join(import.meta.dirname, '..', 'server', 'migrations', 'index.ts'), 'utf8')
|
|
248
|
-
assert.match(list, /IndexEventCreatedAt1786400000000/, '마이그레이션 목록에 있어야 한다')
|
|
249
|
-
})
|
|
250
|
-
|
|
251
|
-
test('★ 한 문장이 짧아야 화면이 오래 멈추지 않는다 — 실측이 그 수를 정한다', () => {
|
|
252
|
-
/*
|
|
253
|
-
* ── 무엇이 났나 (2026-08-28 실측) ──────────────────────────────────────────
|
|
254
|
-
* 배치가 20,000 이었고, 한 문장이 그만큼 지우는 데 4~5.6초였다(서버 기록: hatio-mx2 20,130줄
|
|
255
|
-
* 5.2초). sqlite 는 연결이 하나라 그동안 **화면의 모든 질의가 줄을 선다** — 첫 화면 조회가
|
|
256
|
-
* 12.2초였다(정상 상태 40밀리초).
|
|
257
|
-
*
|
|
258
|
-
* 그리고 예산이 **문장이 끝난 뒤에** 검사되므로 한 문장이 2초 예산을 2~3배 넘겼다. 배치를 나눈 뜻이
|
|
259
|
-
* 그만큼 없어진다.
|
|
260
|
-
*
|
|
261
|
-
* 고칠 축은 총 시간이 아니라 **한 번에 멈추는 길이**다. 그래서 배치를 줄이고 예산을 늘렸다 —
|
|
262
|
-
* 지우는 양은 같고 잠금만 잘게 나뉜다.
|
|
263
|
-
*/
|
|
264
|
-
const src = readFileSync(ENGINE, 'utf8')
|
|
265
|
-
const batch = /JOURNAL_PRUNE_BATCH\s*=\s*([0-9_]+)/.exec(src)?.[1]?.replace(/_/g, '')
|
|
266
|
-
const budget = /JOURNAL_PRUNE_BUDGET_MS\s*=\s*([0-9_]+)/.exec(src)?.[1]?.replace(/_/g, '')
|
|
267
|
-
assert.ok(batch && budget)
|
|
268
|
-
/* 줄당 약 0.26밀리초로 재었다 — 한 문장이 1초를 넘지 않아야 한다. */
|
|
269
|
-
const perStatementMs = Number(batch) * 0.26
|
|
270
|
-
assert.ok(perStatementMs <= 1000, `한 문장이 ${Math.round(perStatementMs)}밀리초로 추정된다 — 1초를 넘으면 화면이 그만큼 멈춘다`)
|
|
271
|
-
/* 예산이 문장 여럿을 담아야 나눈 뜻이 있다 — 하나만 담으면 예산 검사가 아무 일도 하지 않는다. */
|
|
272
|
-
assert.ok(Number(budget) / perStatementMs >= 3, '예산이 한 문장에 삼켜지면 배치를 나눈 뜻이 없다')
|
|
273
|
-
/* 실측 근거가 코드에 적혀 있어야 한다 — 다음 사람이 다시 재지 않고 바꾸지 않게. */
|
|
274
|
-
assert.match(src, /20,130줄/, '무엇을 재서 이 수가 나왔는지 적혀 있어야 한다')
|
|
275
|
-
})
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* ★ 저널을 시간으로 거르면 **시간으로 정렬한다** — 축이 어긋나면 상한이 무력해진다.
|
|
3
|
-
*
|
|
4
|
-
* ── 무엇을 재서 알았나 (2026-08-22) ─────────────────────────────────────────
|
|
5
|
-
* 서버가 간헐적으로 아주 느렸다. 원인은 저널 질의 둘이었고, 둘 다 같은 모양이었다: **거르는 축은
|
|
6
|
-
* `eventTime`(시간창)이고 정렬 축은 `revision`.**
|
|
7
|
-
*
|
|
8
|
-
* 그러면 sqlite 는 조건에 맞는 행을 **전부 찾아 전부 정렬한 뒤** 상한을 적용한다. 상한이 정렬 **뒤에**
|
|
9
|
-
* 걸리므로 아무것도 줄여 주지 않는다. 실측(인스턴스 하나가 375만 행):
|
|
10
|
-
*
|
|
11
|
-
* 성과 폴드 ORDER BY revision LIMIT 20000 → 32.2초 · LIMIT 5000 → 28.3초 · 2000 → 28.2초
|
|
12
|
-
* ORDER BY eventTime, revision LIMIT 20000 → 0.41초 (78배)
|
|
13
|
-
* 저널 목록 ORDER BY revision DESC 200건 → 33.4초
|
|
14
|
-
* ORDER BY eventTime DESC, revision 200건 → 0.007초 (4,700배)
|
|
15
|
-
*
|
|
16
|
-
* **상한을 낮추는 것으로는 고쳐지지 않는다** — 위 숫자가 그것을 말한다. 축이 원인이다.
|
|
17
|
-
*
|
|
18
|
-
* 계획도 그렇게 말한다: 어긋나면 `USE TEMP B-TREE FOR ORDER BY`(전체 정렬), 맞으면
|
|
19
|
-
* `... FOR LAST TERM OF ORDER BY` — 시각은 인덱스(`ix_twin_event_1`)에서 순서대로 나오고 동시각만
|
|
20
|
-
* 임시 정렬한다. 즉 범위를 훑다가 상한에서 멈춘다.
|
|
21
|
-
*
|
|
22
|
-
* ── 이 가드가 지키는 것 ─────────────────────────────────────────────────────
|
|
23
|
-
* 사람은 이 어긋남을 **눈으로 못 본다.** 두 줄이 멀리 떨어져 있고, 작은 저널에서는 밀리초라 시험도
|
|
24
|
-
* 초록이다. 규모에서만 드러나고, 그때는 「서버가 느리다」로만 보인다.
|
|
25
|
-
*
|
|
26
|
-
* 그래서 **소스를 훑어** 잡는다: 저널을 시간으로 거르는 질의가 시간으로 정렬하지 않으면 붉어진다.
|
|
27
|
-
* 어휘를 세지 않고 **짝**을 본다 — 한 질의 안에서 시간 조건과 정렬 축이 함께 있는지.
|
|
28
|
-
*/
|
|
29
|
-
import { test } from 'node:test'
|
|
30
|
-
import assert from 'node:assert/strict'
|
|
31
|
-
import { readFileSync, readdirSync, statSync } from 'node:fs'
|
|
32
|
-
import { join } from 'node:path'
|
|
33
|
-
|
|
34
|
-
/** 저널을 시간으로 거르는 표현 — TypeORM 의 두 방식(객체 조건·쿼리빌더). */
|
|
35
|
-
const TIME_FILTER = /\beventTime\s*[:=]\s*(Between|LessThan|LessThanOrEqual|MoreThan|MoreThanOrEqual)\(|['"`][^'"`]*\beventTime\b[^'"`]*(BETWEEN|<=|>=|<|>)/
|
|
36
|
-
|
|
37
|
-
/** 정렬 축 — `order: { … }` 와 `orderBy('e.…')` 양쪽. */
|
|
38
|
-
const ORDER_AXIS = /order(?:By)?\s*[:(]\s*[{'"`]?\s*(?:'?e\.)?(\w+)/g
|
|
39
|
-
|
|
40
|
-
function walk(dir: string, out: string[] = []): string[] {
|
|
41
|
-
for (const name of readdirSync(dir)) {
|
|
42
|
-
if (name === 'node_modules' || name.startsWith('.')) continue
|
|
43
|
-
const full = join(dir, name)
|
|
44
|
-
if (statSync(full).isDirectory()) walk(full, out)
|
|
45
|
-
else if (name.endsWith('.ts') && !name.endsWith('.d.ts')) out.push(full)
|
|
46
|
-
}
|
|
47
|
-
return out
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** 한 질의로 보이는 덩어리 — 저널 저장소를 부른 뒤 그 문장이 끝날 때까지. */
|
|
51
|
-
function journalQueries(src: string): string[] {
|
|
52
|
-
const out: string[] = []
|
|
53
|
-
const re = /(?:getRepository|getReadRepository)\(\s*TwinEvent\s*\)/g
|
|
54
|
-
let m: RegExpExecArray | null
|
|
55
|
-
while ((m = re.exec(src))) {
|
|
56
|
-
/* 그 호출부터 다음 빈 줄까지를 한 질의로 본다 — 이 저장소의 질의는 그 안에서 끝난다. */
|
|
57
|
-
const rest = src.slice(m.index)
|
|
58
|
-
const end = rest.search(/\n\s*\n/)
|
|
59
|
-
out.push(rest.slice(0, end === -1 ? 1200 : Math.min(end, 1200)))
|
|
60
|
-
}
|
|
61
|
-
return out
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
test('저널을 시간으로 거르는 질의는 시간으로 정렬한다 — 상한이 살아 있게', () => {
|
|
65
|
-
const offenders: string[] = []
|
|
66
|
-
|
|
67
|
-
for (const file of walk(join(import.meta.dirname, '..', 'server'))) {
|
|
68
|
-
const src = readFileSync(file, 'utf8')
|
|
69
|
-
if (!src.includes('TwinEvent')) continue
|
|
70
|
-
|
|
71
|
-
for (const q of journalQueries(src)) {
|
|
72
|
-
if (!TIME_FILTER.test(q)) continue
|
|
73
|
-
|
|
74
|
-
/*
|
|
75
|
-
* **상한이 있는 질의만 본다.** 이 결함은 「상한이 정렬 뒤에 걸려 무력해지는 것」이다. 상한이 없는
|
|
76
|
-
* 질의(재개 시 체크포인트 뒤를 전부 계산하는 경로)는 어차피 전부 읽어야 하므로 정렬 축을 바꿔도
|
|
77
|
-
* 얻는 것이 없다 — 그 자리를 붉히면 **거짓 빨강**이고, 그런 가드는 없는 것보다 나쁘다.
|
|
78
|
-
*/
|
|
79
|
-
if (!/\b(take|limit)\s*[:(]/.test(q)) continue
|
|
80
|
-
|
|
81
|
-
const axes: string[] = []
|
|
82
|
-
let a: RegExpExecArray | null
|
|
83
|
-
const re = new RegExp(ORDER_AXIS.source, 'g')
|
|
84
|
-
while ((a = re.exec(q))) axes.push(a[1])
|
|
85
|
-
if (!axes.length) continue /* 정렬이 없으면 이 결함이 아니다 */
|
|
86
|
-
|
|
87
|
-
/* 첫 정렬 축이 시간이어야 한다 — 뒤에 `revision` 이 오는 것은 동시각 가름이라 정상이다. */
|
|
88
|
-
if (axes[0] !== 'eventTime') {
|
|
89
|
-
offenders.push(`${file.replace(/^.*\/server\//, 'server/')} — 시간으로 거르면서 '${axes[0]}' 로 정렬`)
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
assert.deepEqual(
|
|
95
|
-
offenders,
|
|
96
|
-
[],
|
|
97
|
-
'시간창으로 거르면서 다른 축으로 정렬하면 sqlite 가 전량 정렬한 뒤 상한을 적용한다 ' +
|
|
98
|
-
'(실측 32.2초 대 0.41초). 정렬을 `eventTime` 먼저로 두고 동시각 가름을 위해 `revision` 을 뒤에 두라.\n' +
|
|
99
|
-
offenders.join('\n')
|
|
100
|
-
)
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
test('가드가 실제로 잡는다 — 거짓 초록을 내지 않는지 확인한다', () => {
|
|
104
|
-
/*
|
|
105
|
-
* 가드는 스스로를 증명해야 한다. 이 저장소에서 실제로 고친 두 모양을 그대로 넣어 **잡히는지** 본다.
|
|
106
|
-
* 잡지 못하는 가드는 없는 것보다 나쁘다(있다는 사실이 안심을 준다).
|
|
107
|
-
*/
|
|
108
|
-
const bad = `
|
|
109
|
-
const rows = await getReadRepository(TwinEvent)
|
|
110
|
-
.createQueryBuilder('e')
|
|
111
|
-
.andWhere('e.eventTime BETWEEN :from AND :to', { from, to })
|
|
112
|
-
.orderBy('e.revision', 'ASC')
|
|
113
|
-
.limit(MAX_EVENTS)
|
|
114
|
-
`
|
|
115
|
-
const good = `
|
|
116
|
-
const rows = await getReadRepository(TwinEvent)
|
|
117
|
-
.createQueryBuilder('e')
|
|
118
|
-
.andWhere('e.eventTime BETWEEN :from AND :to', { from, to })
|
|
119
|
-
.orderBy('e.eventTime', 'ASC')
|
|
120
|
-
.addOrderBy('e.revision', 'ASC')
|
|
121
|
-
.limit(MAX_EVENTS)
|
|
122
|
-
`
|
|
123
|
-
const firstAxis = (q: string): string | undefined => {
|
|
124
|
-
const re = new RegExp(ORDER_AXIS.source, 'g')
|
|
125
|
-
const m = re.exec(q)
|
|
126
|
-
return m?.[1]
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/* 상한이 없는 질의는 이 결함이 아니다 — 가드가 그것을 지나쳐야 한다. */
|
|
130
|
-
const noLimit = `
|
|
131
|
-
const rows = await getRepository(TwinEvent).find({
|
|
132
|
-
where: { ...baseWhere, eventTime: LessThanOrEqual(new Date(cutoffMs)) },
|
|
133
|
-
order: { revision: 'ASC' }
|
|
134
|
-
})
|
|
135
|
-
`
|
|
136
|
-
assert.equal(/\b(take|limit)\s*[:(]/.test(noLimit), false, '상한이 없는 질의를 알아본다(지나쳐야 한다)')
|
|
137
|
-
assert.equal(/\b(take|limit)\s*[:(]/.test(bad), true, '상한이 있는 질의를 알아본다')
|
|
138
|
-
|
|
139
|
-
assert.equal(TIME_FILTER.test(bad), true, '시간 조건을 알아본다')
|
|
140
|
-
assert.equal(firstAxis(bad), 'revision', '어긋난 정렬을 알아본다')
|
|
141
|
-
assert.equal(firstAxis(good), 'eventTime', '맞는 정렬을 알아본다')
|
|
142
|
-
})
|