@things-factory/headless-twin 10.0.14 → 10.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-server/engine/attention-digest.d.ts +4 -4
- package/dist-server/engine/attention-digest.js +7 -7
- package/dist-server/engine/attention-digest.js.map +1 -1
- package/dist-server/engine/canonical-ingest.d.ts +11 -2
- package/dist-server/engine/canonical-ingest.js +137 -8
- package/dist-server/engine/canonical-ingest.js.map +1 -1
- package/dist-server/engine/command-routing.js +1 -1
- package/dist-server/engine/command-routing.js.map +1 -1
- package/dist-server/engine/declared-stimulus.js +1 -1
- package/dist-server/engine/declared-stimulus.js.map +1 -1
- package/dist-server/engine/energy-topology.js +1 -1
- package/dist-server/engine/energy-topology.js.map +1 -1
- package/dist-server/engine/ingest-dedupe.d.ts +38 -0
- package/dist-server/engine/ingest-dedupe.js +145 -0
- package/dist-server/engine/ingest-dedupe.js.map +1 -0
- package/dist-server/engine/ingest-health.d.ts +124 -9
- package/dist-server/engine/ingest-health.js +169 -15
- package/dist-server/engine/ingest-health.js.map +1 -1
- package/dist-server/engine/integration-probes.d.ts +3 -3
- package/dist-server/engine/integration-probes.js +6 -6
- package/dist-server/engine/integration-probes.js.map +1 -1
- package/dist-server/engine/kpi-baseline.js +4 -4
- package/dist-server/engine/kpi-baseline.js.map +1 -1
- package/dist-server/engine/kpi-fold.d.ts +122 -12
- package/dist-server/engine/kpi-fold.js +247 -18
- package/dist-server/engine/kpi-fold.js.map +1 -1
- package/dist-server/engine/kpi-query.d.ts +11 -11
- package/dist-server/engine/kpi-query.js +185 -28
- package/dist-server/engine/kpi-query.js.map +1 -1
- package/dist-server/engine/live-feed-registry.d.ts +1 -1
- package/dist-server/engine/live-feed-registry.js +1 -1
- package/dist-server/engine/live-feed-registry.js.map +1 -1
- package/dist-server/engine/load-meter.d.ts +1 -1
- package/dist-server/engine/load-meter.js.map +1 -1
- package/dist-server/engine/local-declarations.d.ts +7 -7
- package/dist-server/engine/local-declarations.js +9 -9
- package/dist-server/engine/local-declarations.js.map +1 -1
- package/dist-server/engine/log.d.ts +1 -1
- package/dist-server/engine/log.js +1 -1
- package/dist-server/engine/log.js.map +1 -1
- package/dist-server/engine/measured-estimator.js +1 -1
- package/dist-server/engine/measured-estimator.js.map +1 -1
- package/dist-server/engine/measured-yield.js +1 -1
- package/dist-server/engine/measured-yield.js.map +1 -1
- package/dist-server/engine/model-basis.d.ts +1 -1
- package/dist-server/engine/model-basis.js +1 -1
- package/dist-server/engine/model-basis.js.map +1 -1
- package/dist-server/engine/model-gap.d.ts +3 -3
- package/dist-server/engine/model-gap.js +1 -1
- package/dist-server/engine/model-gap.js.map +1 -1
- package/dist-server/engine/model-vocabulary.js +1 -1
- package/dist-server/engine/model-vocabulary.js.map +1 -1
- package/dist-server/engine/oee-accumulator.d.ts +4 -4
- package/dist-server/engine/oee-accumulator.js +3 -3
- package/dist-server/engine/oee-accumulator.js.map +1 -1
- package/dist-server/engine/property-effects.js +2 -2
- package/dist-server/engine/property-effects.js.map +1 -1
- package/dist-server/engine/read-time.d.ts +22 -0
- package/dist-server/engine/read-time.js +81 -0
- package/dist-server/engine/read-time.js.map +1 -0
- package/dist-server/engine/restart-policy.js +1 -1
- package/dist-server/engine/restart-policy.js.map +1 -1
- package/dist-server/engine/runtime-key.js +1 -1
- package/dist-server/engine/runtime-key.js.map +1 -1
- package/dist-server/engine/spec-coverage.d.ts +2 -2
- package/dist-server/engine/spec-coverage.js +1 -1
- package/dist-server/engine/spec-coverage.js.map +1 -1
- package/dist-server/engine/stage-path.d.ts +4 -4
- package/dist-server/engine/stage-path.js +1 -1
- package/dist-server/engine/stage-path.js.map +1 -1
- package/dist-server/engine/state-axes.d.ts +1 -1
- package/dist-server/engine/state-axes.js +2 -2
- package/dist-server/engine/state-axes.js.map +1 -1
- package/dist-server/engine/travel-estimator.d.ts +2 -2
- package/dist-server/engine/travel-estimator.js +1 -1
- package/dist-server/engine/travel-estimator.js.map +1 -1
- package/dist-server/engine/twin-engine.d.ts +243 -53
- package/dist-server/engine/twin-engine.js +690 -191
- package/dist-server/engine/twin-engine.js.map +1 -1
- package/dist-server/engine/warm-start.d.ts +2 -2
- package/dist-server/engine/warm-start.js +5 -5
- package/dist-server/engine/warm-start.js.map +1 -1
- package/dist-server/index.js +1 -1
- package/dist-server/index.js.map +1 -1
- package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js +1 -1
- package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +1 -1
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js +3 -3
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +1 -1
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js +2 -2
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +1 -1
- package/dist-server/migrations/1786300000000-IndexEventTypeByTime.d.ts +5 -0
- package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js +54 -0
- package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js.map +1 -0
- package/dist-server/migrations/1786400000000-IndexEventCreatedAt.d.ts +5 -0
- package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js +44 -0
- package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js.map +1 -0
- package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.d.ts +6 -0
- package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js +84 -0
- package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js.map +1 -0
- package/dist-server/migrations/index.js +7 -1
- package/dist-server/migrations/index.js.map +1 -1
- package/dist-server/routes.d.ts +1 -0
- package/dist-server/routes.js +69 -5
- package/dist-server/routes.js.map +1 -1
- package/dist-server/service/index.d.ts +2 -2
- package/dist-server/service/index.js +27 -24
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/reference/control-routing.d.ts +1 -1
- package/dist-server/service/reference/control-routing.js +1 -1
- package/dist-server/service/reference/control-routing.js.map +1 -1
- package/dist-server/service/reference/discovery-result.d.ts +2 -2
- package/dist-server/service/reference/discovery-result.js +3 -3
- package/dist-server/service/reference/discovery-result.js.map +1 -1
- package/dist-server/service/reference/hook-contract.d.ts +43 -0
- package/dist-server/service/reference/hook-contract.js +59 -0
- package/dist-server/service/reference/hook-contract.js.map +1 -0
- package/dist-server/service/reference/ingest-space.d.ts +1 -1
- package/dist-server/service/reference/ingest-space.js +4 -4
- package/dist-server/service/reference/ingest-space.js.map +1 -1
- package/dist-server/service/reference/knob-defaults.d.ts +1 -1
- package/dist-server/service/reference/knob-defaults.js +4 -4
- package/dist-server/service/reference/knob-defaults.js.map +1 -1
- package/dist-server/service/reference/reference-adapter.d.ts +140 -8
- package/dist-server/service/reference/reference-adapter.js +27 -0
- package/dist-server/service/reference/reference-adapter.js.map +1 -1
- package/dist-server/service/reference/reference-hook.d.ts +20 -0
- package/dist-server/service/reference/reference-hook.js +133 -0
- package/dist-server/service/reference/reference-hook.js.map +1 -0
- package/dist-server/service/reference/reference-live.d.ts +2 -2
- package/dist-server/service/reference/reference-live.js +88 -13
- package/dist-server/service/reference/reference-live.js.map +1 -1
- package/dist-server/service/reference/reference-master.d.ts +14 -14
- package/dist-server/service/reference/reference-master.js +34 -12
- package/dist-server/service/reference/reference-master.js.map +1 -1
- package/dist-server/service/reference/reference-resolver.d.ts +1 -1
- package/dist-server/service/reference/reference-resolver.js +10 -10
- package/dist-server/service/reference/reference-resolver.js.map +1 -1
- package/dist-server/service/reference/twin-reference.d.ts +3 -3
- package/dist-server/service/reference/twin-reference.js.map +1 -1
- package/dist-server/service/twin-attention/twin-attention-query.d.ts +1 -1
- package/dist-server/service/twin-attention/twin-attention-query.js +1 -1
- package/dist-server/service/twin-attention/twin-attention-query.js.map +1 -1
- package/dist-server/service/twin-audit/command-audit.d.ts +1 -1
- package/dist-server/service/twin-audit/command-audit.js.map +1 -1
- package/dist-server/service/twin-audit/twin-audit-event.d.ts +1 -1
- package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -1
- package/dist-server/service/twin-audit/twin-audit-query.js +1 -1
- package/dist-server/service/twin-audit/twin-audit-query.js.map +1 -1
- package/dist-server/service/twin-control/twin-control-mutation.js +1 -1
- package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
- package/dist-server/service/twin-event/twin-event-keys.d.ts +22 -2
- package/dist-server/service/twin-event/twin-event-keys.js +37 -5
- package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
- package/dist-server/service/twin-event/twin-event-type.d.ts +1 -0
- package/dist-server/service/twin-event/twin-event-type.js +8 -1
- package/dist-server/service/twin-event/twin-event-type.js.map +1 -1
- package/dist-server/service/twin-event/twin-event.d.ts +2 -2
- package/dist-server/service/twin-event/twin-event.js +32 -4
- package/dist-server/service/twin-event/twin-event.js.map +1 -1
- package/dist-server/service/twin-forecast/forecast-metrics.js +1 -1
- package/dist-server/service/twin-forecast/forecast-metrics.js.map +1 -1
- package/dist-server/service/twin-forecast/gap-analytics.d.ts +1 -1
- package/dist-server/service/twin-forecast/gap-analytics.js.map +1 -1
- package/dist-server/service/twin-forecast/twin-forecast-query.js +29 -11
- package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.d.ts +1 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js +1 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +1 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js +1 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +1 -1
- package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
- package/dist-server/service/twin-journal/event-type-split-shape.d.ts +50 -0
- package/dist-server/service/twin-journal/event-type-split-shape.js +111 -0
- package/dist-server/service/twin-journal/event-type-split-shape.js.map +1 -0
- package/dist-server/service/twin-journal/event-type-split.d.ts +15 -0
- package/dist-server/service/twin-journal/event-type-split.js +102 -0
- package/dist-server/service/twin-journal/event-type-split.js.map +1 -0
- package/dist-server/service/twin-journal/journal-order.d.ts +15 -0
- package/dist-server/service/twin-journal/journal-order.js +17 -0
- package/dist-server/service/twin-journal/journal-order.js.map +1 -0
- package/dist-server/service/twin-journal/twin-journal-query.d.ts +5 -5
- package/dist-server/service/twin-journal/twin-journal-query.js +232 -21
- package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +27 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +59 -3
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
- package/dist-server/service/twin-metrics/twin-metrics-query.js +14 -1
- package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
- package/dist-server/service/twin-model/axis-journal-evidence.js +1 -1
- package/dist-server/service/twin-model/axis-journal-evidence.js.map +1 -1
- package/dist-server/service/twin-model/epcis-coverage.js +36 -1
- package/dist-server/service/twin-model/epcis-coverage.js.map +1 -1
- package/dist-server/service/twin-model/iec61850-coverage.js +1 -1
- package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -1
- package/dist-server/service/twin-model/isa95-coverage.d.ts +15 -2
- package/dist-server/service/twin-model/isa95-coverage.js +32 -8
- package/dist-server/service/twin-model/isa95-coverage.js.map +1 -1
- package/dist-server/service/twin-model/item-ref.js +2 -2
- package/dist-server/service/twin-model/item-ref.js.map +1 -1
- package/dist-server/service/twin-model/name-index.d.ts +1 -1
- package/dist-server/service/twin-model/name-index.js +1 -1
- package/dist-server/service/twin-model/name-index.js.map +1 -1
- package/dist-server/service/twin-model/project-structure.d.ts +1 -1
- package/dist-server/service/twin-model/project-structure.js +3 -3
- package/dist-server/service/twin-model/project-structure.js.map +1 -1
- package/dist-server/service/twin-model/twin-equipment.d.ts +1 -1
- package/dist-server/service/twin-model/twin-equipment.js +1 -1
- package/dist-server/service/twin-model/twin-equipment.js.map +1 -1
- package/dist-server/service/twin-model/twin-lineage-query.js +16 -5
- package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-location.d.ts +1 -1
- package/dist-server/service/twin-model/twin-location.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-item-query.d.ts +3 -3
- package/dist-server/service/twin-model/twin-model-item-query.js +14 -14
- package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-mutation.d.ts +2 -2
- package/dist-server/service/twin-model/twin-model-mutation.js +4 -4
- package/dist-server/service/twin-model/twin-model-mutation.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-query.js +117 -28
- package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-tree-query.js +22 -0
- package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-operation.d.ts +1 -1
- package/dist-server/service/twin-model/twin-operation.js +1 -1
- package/dist-server/service/twin-model/twin-operation.js.map +1 -1
- package/dist-server/service/twin-space/move-space.js +2 -2
- package/dist-server/service/twin-space/move-space.js.map +1 -1
- package/dist-server/service/twin-space/representation-role.d.ts +2 -0
- package/dist-server/service/twin-space/representation-role.js +18 -0
- package/dist-server/service/twin-space/representation-role.js.map +1 -0
- package/dist-server/service/twin-space/space-integrity.d.ts +1 -1
- package/dist-server/service/twin-space/space-integrity.js.map +1 -1
- package/dist-server/service/twin-space/twin-space-representation.d.ts +2 -0
- package/dist-server/service/twin-space/twin-space-representation.js +16 -0
- package/dist-server/service/twin-space/twin-space-representation.js.map +1 -1
- package/dist-server/service/twin-space/twin-space-resolver.d.ts +2 -2
- package/dist-server/service/twin-space/twin-space-resolver.js +36 -7
- package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
- package/dist-server/service/twin-structure/twin-structure.js +3 -3
- package/dist-server/service/twin-structure/twin-structure.js.map +1 -1
- package/dist-server/service/twin-subject/event-subjects.d.ts +43 -0
- package/dist-server/service/twin-subject/event-subjects.js +137 -0
- package/dist-server/service/twin-subject/event-subjects.js.map +1 -0
- package/dist-server/service/twin-subject/index.d.ts +9 -0
- package/dist-server/service/twin-subject/index.js +16 -0
- package/dist-server/service/twin-subject/index.js.map +1 -0
- package/dist-server/service/twin-subject/item-resolver.d.ts +10 -0
- package/dist-server/service/twin-subject/item-resolver.js +93 -0
- package/dist-server/service/twin-subject/item-resolver.js.map +1 -0
- package/dist-server/service/twin-subject/subject-query-shape.d.ts +22 -0
- package/dist-server/service/twin-subject/subject-query-shape.js +88 -0
- package/dist-server/service/twin-subject/subject-query-shape.js.map +1 -0
- package/dist-server/service/twin-subject/twin-subject-backfill.d.ts +23 -0
- package/dist-server/service/twin-subject/twin-subject-backfill.js +159 -0
- package/dist-server/service/twin-subject/twin-subject-backfill.js.map +1 -0
- package/dist-server/service/twin-subject/twin-subject-event.d.ts +14 -0
- package/dist-server/service/twin-subject/twin-subject-event.js +111 -0
- package/dist-server/service/twin-subject/twin-subject-event.js.map +1 -0
- package/dist-server/service/twin-subject/twin-subject-fast-path.d.ts +39 -0
- package/dist-server/service/twin-subject/twin-subject-fast-path.js +295 -0
- package/dist-server/service/twin-subject/twin-subject-fast-path.js.map +1 -0
- package/dist-server/service/twin-subject/twin-subject-query.d.ts +27 -0
- package/dist-server/service/twin-subject/twin-subject-query.js +232 -0
- package/dist-server/service/twin-subject/twin-subject-query.js.map +1 -0
- package/dist-server/service/twin-subject/twin-subject-writer.d.ts +27 -0
- package/dist-server/service/twin-subject/twin-subject-writer.js +166 -0
- package/dist-server/service/twin-subject/twin-subject-writer.js.map +1 -0
- package/dist-server/service/twin-target/twin-target-resolver.js +1 -1
- package/dist-server/service/twin-target/twin-target-resolver.js.map +1 -1
- package/dist-shared/entity-delta.d.ts +1 -1
- package/dist-shared/entity-delta.js +3 -3
- package/dist-shared/entity-delta.js.map +1 -1
- package/dist-shared/touched-items.js +2 -2
- package/dist-shared/touched-items.js.map +1 -1
- package/dist-shared/twin-level.js +1 -1
- package/dist-shared/twin-level.js.map +1 -1
- package/package.json +3 -3
- package/server/engine/attention-digest.ts +9 -9
- package/server/engine/canonical-ingest.ts +170 -13
- package/server/engine/command-routing.ts +1 -1
- package/server/engine/declared-stimulus.ts +1 -1
- package/server/engine/energy-topology.ts +1 -1
- package/server/engine/ingest-dedupe.ts +149 -0
- package/server/engine/ingest-health.ts +234 -20
- package/server/engine/integration-probes.ts +7 -7
- package/server/engine/kpi-baseline.ts +4 -4
- package/server/engine/kpi-fold.ts +395 -27
- package/server/engine/kpi-query.ts +212 -46
- package/server/engine/live-feed-registry.ts +1 -1
- package/server/engine/load-meter.ts +1 -1
- package/server/engine/local-declarations.ts +13 -13
- package/server/engine/log.ts +1 -1
- package/server/engine/measured-estimator.ts +1 -1
- package/server/engine/measured-yield.ts +1 -1
- package/server/engine/model-basis.ts +2 -2
- package/server/engine/model-gap.ts +3 -3
- package/server/engine/model-vocabulary.ts +1 -1
- package/server/engine/oee-accumulator.ts +4 -4
- package/server/engine/property-effects.ts +2 -2
- package/server/engine/read-time.ts +77 -0
- package/server/engine/restart-policy.ts +1 -1
- package/server/engine/runtime-key.ts +1 -1
- package/server/engine/spec-coverage.ts +3 -3
- package/server/engine/stage-path.ts +5 -5
- package/server/engine/state-axes.ts +2 -2
- package/server/engine/travel-estimator.ts +2 -2
- package/server/engine/twin-engine.ts +764 -201
- package/server/engine/warm-start.ts +6 -6
- package/server/index.ts +1 -1
- package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +1 -1
- package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +3 -3
- package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +2 -2
- package/server/migrations/1786300000000-IndexEventTypeByTime.ts +48 -0
- package/server/migrations/1786400000000-IndexEventCreatedAt.ts +38 -0
- package/server/migrations/1786500000000-CreateTwinSubjectEvents.ts +93 -0
- package/server/migrations/index.ts +7 -1
- package/server/routes.ts +68 -5
- package/server/service/index.ts +4 -1
- package/server/service/reference/control-routing.ts +1 -1
- package/server/service/reference/discovery-result.ts +3 -3
- package/server/service/reference/hook-contract.ts +103 -0
- package/server/service/reference/ingest-space.ts +4 -4
- package/server/service/reference/knob-defaults.ts +4 -4
- package/server/service/reference/reference-adapter.ts +166 -9
- package/server/service/reference/reference-hook.ts +145 -0
- package/server/service/reference/reference-live.ts +92 -14
- package/server/service/reference/reference-master.ts +39 -17
- package/server/service/reference/reference-resolver.ts +10 -10
- package/server/service/reference/twin-reference.ts +3 -3
- package/server/service/twin-attention/twin-attention-query.ts +1 -1
- package/server/service/twin-audit/command-audit.ts +1 -1
- package/server/service/twin-audit/twin-audit-event.ts +2 -2
- package/server/service/twin-audit/twin-audit-query.ts +1 -1
- package/server/service/twin-control/twin-control-mutation.ts +1 -1
- package/server/service/twin-event/twin-event-keys.ts +37 -5
- package/server/service/twin-event/twin-event-type.ts +18 -1
- package/server/service/twin-event/twin-event.ts +32 -6
- package/server/service/twin-forecast/forecast-metrics.ts +1 -1
- package/server/service/twin-forecast/gap-analytics.ts +1 -1
- package/server/service/twin-forecast/twin-forecast-query.ts +30 -11
- package/server/service/twin-ingest-window/twin-ingest-window-query.ts +1 -1
- package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +1 -1
- package/server/service/twin-instance/twin-instance.ts +4 -4
- package/server/service/twin-journal/event-type-split-shape.ts +112 -0
- package/server/service/twin-journal/event-type-split.ts +144 -0
- package/server/service/twin-journal/journal-order.ts +20 -0
- package/server/service/twin-journal/twin-journal-query.ts +224 -20
- package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +57 -3
- package/server/service/twin-metrics/twin-metrics-query.ts +14 -1
- package/server/service/twin-model/axis-journal-evidence.ts +1 -1
- package/server/service/twin-model/epcis-coverage.ts +36 -1
- package/server/service/twin-model/iec61850-coverage.ts +1 -1
- package/server/service/twin-model/isa95-coverage.ts +45 -8
- package/server/service/twin-model/item-ref.ts +2 -2
- package/server/service/twin-model/name-index.ts +1 -1
- package/server/service/twin-model/project-structure.ts +4 -4
- package/server/service/twin-model/twin-equipment.ts +2 -2
- package/server/service/twin-model/twin-lineage-query.ts +16 -5
- package/server/service/twin-model/twin-location.ts +1 -1
- package/server/service/twin-model/twin-model-item-query.ts +14 -14
- package/server/service/twin-model/twin-model-mutation.ts +4 -4
- package/server/service/twin-model/twin-model-query.ts +117 -28
- package/server/service/twin-model/twin-model-tree-query.ts +25 -1
- package/server/service/twin-model/twin-operation.ts +2 -2
- package/server/service/twin-space/move-space.ts +3 -3
- package/server/service/twin-space/representation-role.ts +15 -0
- package/server/service/twin-space/space-integrity.ts +1 -1
- package/server/service/twin-space/twin-space-representation.ts +37 -0
- package/server/service/twin-space/twin-space-resolver.ts +39 -7
- package/server/service/twin-structure/twin-structure.ts +3 -3
- package/server/service/twin-subject/event-subjects.ts +155 -0
- package/server/service/twin-subject/index.ts +10 -0
- package/server/service/twin-subject/item-resolver.ts +92 -0
- package/server/service/twin-subject/subject-query-shape.ts +84 -0
- package/server/service/twin-subject/twin-subject-backfill.ts +196 -0
- package/server/service/twin-subject/twin-subject-event.ts +101 -0
- package/server/service/twin-subject/twin-subject-fast-path.ts +346 -0
- package/server/service/twin-subject/twin-subject-query.ts +210 -0
- package/server/service/twin-subject/twin-subject-writer.ts +189 -0
- package/server/service/twin-target/twin-target-resolver.ts +1 -1
- package/shared/entity-delta.ts +3 -3
- package/shared/touched-items.ts +2 -2
- package/shared/twin-level.ts +1 -1
- package/test/adopt-structure-live.test.ts +3 -3
- package/test/aggregate-not-findone.test.ts +112 -0
- package/test/attention-digest.test.ts +6 -6
- package/test/axis-read.test.ts +2 -2
- package/test/boot-resume.test.ts +20 -7
- package/test/broadcast-period.test.ts +2 -2
- package/test/broadcast-queue-overflow.test.ts +244 -0
- package/test/canonical-ingest-vocabularies.test.ts +179 -7
- package/test/canonical-quantity-door.test.ts +2 -2
- package/test/checkpoint-refuses-empty.test.ts +2 -2
- package/test/connector-capability-declaration.test.ts +88 -0
- package/test/control-capability.test.ts +1 -1
- package/test/cursor-stall-not-read-failure.test.ts +2 -2
- package/test/declaration-reaches-model.test.ts +5 -5
- package/test/declared-stimulus.test.ts +4 -4
- package/test/discovery-result.test.ts +2 -2
- package/test/entity-delta.test.ts +4 -4
- package/test/event-subjects.test.ts +147 -0
- package/test/event-time-column.test.ts +3 -3
- package/test/event-type-split.test.ts +155 -0
- package/test/fold-gap-speaks.test.ts +53 -0
- package/test/forecast-metrics.test.ts +1 -1
- package/test/forecast-tuning.test.ts +1 -1
- package/test/ingest-health-engine.test.ts +2 -2
- package/test/ingest-health-wiring.test.ts +7 -5
- package/test/ingest-health.test.ts +6 -6
- package/test/ingest-history.test.ts +2 -2
- package/test/ingest-idempotent.test.ts +132 -0
- package/test/ingest-space.test.ts +3 -3
- package/test/integration-probes.test.ts +3 -3
- package/test/integration-runner.test.ts +1 -1
- package/test/item-ref.test.ts +1 -1
- package/test/journal-order-axis.test.ts +80 -0
- package/test/journal-read-discipline.test.ts +16 -6
- package/test/journal-retention.test.ts +171 -29
- package/test/journal-sort-axis.test.ts +1 -1
- package/test/journal-write-door.test.ts +3 -3
- package/test/journal-write-trend.test.ts +1 -1
- package/test/kernel-kind-guard.test.ts +3 -3
- package/test/knob-defaults.test.ts +1 -1
- package/test/kpi-baseline-db.test.ts +2 -2
- package/test/kpi-fold.test.ts +185 -14
- package/test/kpi-query-bench.test.ts +3 -3
- package/test/live-cursor-wiring.test.ts +6 -6
- package/test/live-kernel-facts.test.ts +11 -11
- package/test/live-mirror-parity.test.ts +3 -3
- package/test/load-meter.test.ts +1 -1
- package/test/local-declarations.test.ts +10 -10
- package/test/log-stamp.test.ts +1 -1
- package/test/measured-yield.test.ts +2 -2
- package/test/merge-streams.test.ts +70 -0
- package/test/mirror-resumes-from-checkpoint.test.ts +13 -13
- package/test/model-basis.test.ts +2 -2
- package/test/model-gap.test.ts +4 -4
- package/test/model-vocabulary.test.ts +2 -2
- package/test/move-space.test.ts +1 -1
- package/test/oee-accumulator.test.ts +2 -2
- package/test/operational-vocabulary.test.ts +2 -2
- package/test/project-structure-db.test.ts +2 -2
- package/test/projection-reaches-screen.test.ts +2 -2
- package/test/property-effects.test.ts +3 -3
- package/test/read-failure-ledger.test.ts +106 -0
- package/test/read-failure-visible.test.ts +7 -7
- package/test/read-time.test.ts +118 -0
- package/test/reference-grounding.test.ts +1 -1
- package/test/reference-hook.test.ts +86 -0
- package/test/registry-key-guard.test.ts +1 -1
- package/test/rename-twin.test.ts +81 -0
- package/test/restart-policy.test.ts +3 -3
- package/test/resync-origin-site.test.ts +3 -3
- package/test/revision-axis.test.ts +14 -8
- package/test/runtime-key.test.ts +3 -3
- package/test/snapshot-freshness.test.ts +4 -4
- package/test/source-outcome-audit.test.ts +1 -1
- package/test/space-integrity.test.ts +1 -1
- package/test/space-representation.test.ts +66 -0
- package/test/space-scope-includes-stopped.test.ts +55 -0
- package/test/spec-coverage.test.ts +1 -1
- package/test/standard-coverage.test.ts +43 -1
- package/test/status-tally.test.ts +1 -1
- package/test/structure-revision-db.test.ts +7 -7
- package/test/subject-fast-path.test.ts +148 -0
- package/test/subject-read-model.test.ts +175 -0
- package/test/touched-items.test.ts +2 -2
- package/test/twin-audit.test.ts +1 -1
- package/test/twin-event-keys.test.ts +49 -3
- package/test/twin-model-item-db.test.ts +5 -5
- package/test/twin-model-tree-db.test.ts +35 -2
- package/test/twin-origin-resync.test.ts +1 -1
- package/test/warm-start-seam.test.ts +1 -1
- package/test/warm-start.test.ts +6 -6
- package/test/withheld-door.test.ts +95 -0
- package/test/yield-loop.test.ts +4 -4
- package/tsconfig.shared.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 같은 사실을 두 번 받으면 한 번만 반영한다 — **유입 경계 한 곳에서.**
|
|
3
|
+
*
|
|
4
|
+
* ── 왜 필요한가 (2026-08-26) ────────────────────────────────────────────────
|
|
5
|
+
* 지금 중복을 막는 것은 커넥터마다 각자 하고 있다. 그래서 커넥터가 그것을 잊으면 트윈이 그대로 두 번
|
|
6
|
+
* 센다. 실제로 그랬다: 태양광 커넥터가 10초마다 물으면서 10분마다 바뀌는 값을 매번 다시 보냈고,
|
|
7
|
+
* 한 주기에 76건이 되풀이됐다(커넥터에서 고쳐서 2건이 됐다).
|
|
8
|
+
*
|
|
9
|
+
* 그리고 이것은 웹훅으로 받을 때 더 자주 난다. 밀어 주는 방식은 재전송이 정상이다 — 그쪽이 응답을
|
|
10
|
+
* 못 받으면 같은 것을 다시 보낸다.
|
|
11
|
+
*
|
|
12
|
+
* ── 무엇이 두 번 세어지나 ──────────────────────────────────────────────────
|
|
13
|
+
* 절대값으로 말하는 사실은 여러 번 반영해도 결과가 같다(잔량 · 상태 · 적산 · 순간 kW). 그런데 세는
|
|
14
|
+
* 값은 그렇지 않다.
|
|
15
|
+
*
|
|
16
|
+
* 구간의 표본 수 · 평균 부하(표본 수로 나눈다) · OEE 카운터 · 유입 계수
|
|
17
|
+
* 그리고 지난 기록에 같은 사실이 두 줄로 남는다 — 나중에 그 기록으로 다시 계산하면 또 두 번 센다
|
|
18
|
+
*
|
|
19
|
+
* ── 정체를 무엇으로 보나 ────────────────────────────────────────────────────
|
|
20
|
+
* 봉투의 `eventId` 로는 판단할 수 없다. 보낸 쪽이 매번 새로 만드는 순번이고(`…-energy-1` 처럼 호출마다
|
|
21
|
+
* 1부터 다시 시작한다), 같은 사실을 두 번 보내면 다른 번호가 붙는다. 다른 사실에 같은 번호가 붙는 일도
|
|
22
|
+
* 생긴다.
|
|
23
|
+
*
|
|
24
|
+
* 그래서 **사실 자체로** 판단한다.
|
|
25
|
+
*
|
|
26
|
+
* 무엇에 대한 것인가 설비 · 계량 지점 · 물품 · 작업 · 오더 · 자리
|
|
27
|
+
* 어떤 종류인가 사건 종류(eventType)
|
|
28
|
+
* 언제 잰 것인가 연결된 시스템이 준 시각
|
|
29
|
+
*
|
|
30
|
+
* 이 셋이 같으면 같은 사실이다. 태양광 커넥터가 `collectedAt` 으로 판단한 것과 같은 방식이고, 그것을
|
|
31
|
+
* 커넥터마다 하지 않도록 여기로 올린다.
|
|
32
|
+
*
|
|
33
|
+
* ── 정확히 같은 것만 버린다 ─────────────────────────────────────────────────
|
|
34
|
+
* 늦게 도착한 옛 사실은 버리지 않는다. 그것이 왔다는 것 자체가 사실이고, 상태에 반영되지 않는 것은
|
|
35
|
+
* 커널의 순서 판단이 이미 맡는다. 여기서 버리는 것은 **정체가 완전히 같은 것**뿐이다.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/** 시각을 밀리초로 — 연결된 시스템이 준 시각이 먼저, 없으면 봉투의 시각. */
|
|
39
|
+
function timeOf(envelope: any): number | undefined {
|
|
40
|
+
for (const v of [envelope?.data?.at, envelope?.data?.eventTime, envelope?.eventTime]) {
|
|
41
|
+
const t = Date.parse(String(v ?? ''))
|
|
42
|
+
if (Number.isFinite(t)) return t
|
|
43
|
+
}
|
|
44
|
+
return undefined
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 이 봉투가 말하는 대상 — 어휘마다 이름이 다르므로 아는 이름을 모두 본다.
|
|
49
|
+
*
|
|
50
|
+
* 순서가 뜻을 갖는다: 주체를 먼저 본다. 설비 사실은 「지금 붙어 있는 작업」을 함께 싣고, 작업 사실은
|
|
51
|
+
* 「소속 오더」를 함께 싣는다. 아무 이름이나 먼저 보면 참조를 주체로 읽는다(승격 키에서 겪은 것과 같다).
|
|
52
|
+
*/
|
|
53
|
+
function targetOf(envelope: any): string | undefined {
|
|
54
|
+
const d = envelope?.data ?? {}
|
|
55
|
+
const first = (...vals: unknown[]) => {
|
|
56
|
+
for (const v of vals) {
|
|
57
|
+
const s = String(v ?? '').trim()
|
|
58
|
+
if (s) return s
|
|
59
|
+
}
|
|
60
|
+
return undefined
|
|
61
|
+
}
|
|
62
|
+
return first(
|
|
63
|
+
d.meterId,
|
|
64
|
+
d.equipmentId,
|
|
65
|
+
d.moverId,
|
|
66
|
+
d.taskId,
|
|
67
|
+
d.orderId,
|
|
68
|
+
d.personId,
|
|
69
|
+
d.assetId,
|
|
70
|
+
d.locationId,
|
|
71
|
+
d.epcList?.[0],
|
|
72
|
+
d.parentID,
|
|
73
|
+
d.quantityList?.[0]?.epcClass,
|
|
74
|
+
d.transformationID
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 이 봉투가 말하는 사실의 정체 — 같으면 같은 사실이다.
|
|
80
|
+
*
|
|
81
|
+
* 대상이나 시각을 알 수 없으면 **정체를 만들지 않는다**(`undefined`). 그때는 거르지 않고 통과시킨다 —
|
|
82
|
+
* 모르는 것을 같다고 판단하면 서로 다른 사실이 하나로 합쳐진다. 알리지 않고 잃는 것보다 두 번 세는 것이 낫다.
|
|
83
|
+
*/
|
|
84
|
+
export function factIdentity(envelope: any): string | undefined {
|
|
85
|
+
const type = String(envelope?.eventType ?? '').trim()
|
|
86
|
+
if (!type) return undefined
|
|
87
|
+
const target = targetOf(envelope)
|
|
88
|
+
if (!target) return undefined
|
|
89
|
+
const at = timeOf(envelope)
|
|
90
|
+
if (at === undefined) return undefined
|
|
91
|
+
return `${type}|${target}|${at}`
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 최근에 본 정체를 기억한다 — **크기에 상한을 둔다.**
|
|
96
|
+
*
|
|
97
|
+
* 전부 기억할 수는 없다(트윈 하나가 수천만 사건을 갖는다). 창을 넘으면 오래된 것부터 잊는다. 창 밖의
|
|
98
|
+
* 재전송은 다시 통과하는데, 그것은 절대값 사실이라 상태에 해가 없고 지난 기록에 한 줄 더 남는다.
|
|
99
|
+
* 창을 크게 잡으면 그만큼 메모리를 쓴다 — 그 값을 여기서 이름으로 못 박는다.
|
|
100
|
+
*
|
|
101
|
+
* **재기동을 건너지 못한다.** 프로세스가 새로 뜨면 기억이 비어 있으므로, 재기동 직후 첫 주기가 같은
|
|
102
|
+
* 값을 다시 보내면 걸러지지 않는다. 그것을 막으려면 이 기억이 저장돼야 하고, 그것은 다음 걸음이다.
|
|
103
|
+
* 지금은 그 사실을 여기 적어 둔다 — 적어 두지 않으면 「중복은 이제 없다」로 읽힌다.
|
|
104
|
+
*/
|
|
105
|
+
export class FactDeduper {
|
|
106
|
+
/** 기억하는 정체의 수 — 창. */
|
|
107
|
+
static WINDOW = 20_000
|
|
108
|
+
|
|
109
|
+
private seen = new Set<string>()
|
|
110
|
+
private order: string[] = []
|
|
111
|
+
|
|
112
|
+
/** 이 사실을 본 적이 있나 — 없으면 기억하고 `false`. */
|
|
113
|
+
private known(identity: string): boolean {
|
|
114
|
+
if (this.seen.has(identity)) return true
|
|
115
|
+
this.seen.add(identity)
|
|
116
|
+
this.order.push(identity)
|
|
117
|
+
if (this.order.length > FactDeduper.WINDOW) {
|
|
118
|
+
const gone = this.order.splice(0, this.order.length - FactDeduper.WINDOW)
|
|
119
|
+
for (const g of gone) this.seen.delete(g)
|
|
120
|
+
}
|
|
121
|
+
return false
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 처음 보는 것만 돌려준다 — 그리고 몇 개를 버렸는지 함께 낸다.
|
|
126
|
+
*
|
|
127
|
+
* 버린 수를 함께 내는 이유: 알리지 않고 버리면 「보낸 수」와 「반영된 수」가 어긋나는데 아무도 그
|
|
128
|
+
* 이유를 모른다. 그 수가 크면 보내는 쪽이 되풀이 보내고 있다는 뜻이고, 그것은 고칠 일이다.
|
|
129
|
+
*/
|
|
130
|
+
filter<T>(envelopes: readonly T[]): { fresh: T[]; duplicates: number } {
|
|
131
|
+
const fresh: T[] = []
|
|
132
|
+
let duplicates = 0
|
|
133
|
+
for (const e of envelopes) {
|
|
134
|
+
const id = factIdentity(e)
|
|
135
|
+
if (id === undefined) {
|
|
136
|
+
fresh.push(e)
|
|
137
|
+
continue
|
|
138
|
+
}
|
|
139
|
+
if (this.known(id)) duplicates++
|
|
140
|
+
else fresh.push(e)
|
|
141
|
+
}
|
|
142
|
+
return { fresh, duplicates }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** 기억하고 있는 정체의 수 — 계기가 읽는다. */
|
|
146
|
+
size(): number {
|
|
147
|
+
return this.seen.size
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
* ── 한 질문 ─────────────────────────────────────────────────────────────────
|
|
5
5
|
* 화면에서 값이 변하지 않을 때 원인이 넷인데, 지금까지 **넷이 똑같이 보였다.**
|
|
6
6
|
* ① 피드가 붙지 않았다 ② 붙었는데 아무것도 오지 않는다
|
|
7
|
-
* ③ 오는데 전부 거부된다 ④ 정상인데 현장이
|
|
7
|
+
* ③ 오는데 전부 거부된다 ④ 정상인데 현장이 들어온 것이 없다
|
|
8
8
|
* ③은 특히 나쁘다 — 커넥터의 매핑이 한 글자 틀리면 트윈은 「빈 공장」이 되고, 그 사실이 서버 로그
|
|
9
9
|
* 한 줄에만 남았다. 현장에 나간 사람은 로그를 뒤져야 했다.
|
|
10
10
|
*
|
|
11
11
|
* ── 왜 저널로는 답할 수 없나 ────────────────────────────────────────────────
|
|
12
|
-
* **거부된 것은 저널에 없다** — 거부의 뜻이 그것이다. 저널을
|
|
12
|
+
* **거부된 것은 저널에 없다** — 거부의 뜻이 그것이다. 저널을 계산하면 통과한 쪽만 나온다. 거부의 추이는
|
|
13
13
|
* 세는 자리를 따로 두어야 나온다. 이 장부가 그 자리다.
|
|
14
14
|
*
|
|
15
15
|
* ── 왜 건마다 저장하지 않나 ─────────────────────────────────────────────────
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* ── 지키는 규율 넷 ──────────────────────────────────────────────────────────
|
|
24
24
|
* ① **닫힌 창만 「최근」이다** — 채우는 중인 창은 과소 집계다(부하 계기판에서 겪었다).
|
|
25
25
|
* ② **0 과 「없음」을 가른다** — 안 받은 것은 `null`, 받았지만 거부 0 은 `0`.
|
|
26
|
-
* ③
|
|
26
|
+
* ③ **알리지 않고 자르지 않는다** — 표본·추이 상한에서 밀려난 수를 함께 낸다.
|
|
27
27
|
* ④ **통과율에 1 을 함부로 쓰지 않는다** — 1,000건 중 1건 거부는 0.999 다.
|
|
28
28
|
*/
|
|
29
29
|
|
|
@@ -41,7 +41,7 @@ export interface IngestWindow {
|
|
|
41
41
|
* 매핑은 통과했지만 **트윈에 닿지 못한** 수 — 처방은 트윈을 세우는 것이다.
|
|
42
42
|
*
|
|
43
43
|
* ── 왜 거부와 따로 세나 (2026-08-20에 겪었다) ─────────────────────────────
|
|
44
|
-
* 트윈이 라이브로 돌지 않으면 커널이 레코드를
|
|
44
|
+
* 트윈이 라이브로 돌지 않으면 커널이 레코드를 **알리지 않고 버린다**. 그런데 장부는 그것을 「통과」로 셌다.
|
|
45
45
|
* 화면이 「150 들어옴 · 150 통과 · 100%」라고 말하는 동안 150건은 바닥에 버려지고 있었다. 이 기능이
|
|
46
46
|
* 막으려던 바로 그것을 이 기능이 저지른 셈이다.
|
|
47
47
|
*
|
|
@@ -103,12 +103,29 @@ export interface IngestLedger {
|
|
|
103
103
|
*/
|
|
104
104
|
lastUndeliveredAtMs: number
|
|
105
105
|
/** 사유별 누적 건수와 **마지막으로 그 사유가 난 시각** — 고친 사유는 시각이 멈춘다. */
|
|
106
|
+
/**
|
|
107
|
+
* **어떤 길로 들어왔나** — 경로마다 건수와 마지막 시각.
|
|
108
|
+
*
|
|
109
|
+
* ── 왜 필요한가 (2026-08-26) ──────────────────────────────────────────────
|
|
110
|
+
* 트윈이 값을 받고 있다는 것은 계기가 말해 주는데, **무엇을 통해 받는지는 아무도 말하지 않았다.**
|
|
111
|
+
* 커넥터가 물어서 받는지, 연결된 시스템이 밀어 주는지, 시나리오가 넣는지, 부하 도구가 넣는지 —
|
|
112
|
+
* 화면에서 구별할 수 없었다. 그 구별이 없으면 「폴링을 웹훅으로 바꿨다」를 확인할 방법도 없다.
|
|
113
|
+
*/
|
|
114
|
+
bySource: Record<string, { count: number; lastAtMs: number }>
|
|
115
|
+
/**
|
|
116
|
+
* **같은 사실이 두 번 와서 버린 수** — 알리지 않고 버리지 않는다.
|
|
117
|
+
*
|
|
118
|
+
* 이 수가 크면 보내는 쪽이 되풀이 보내고 있다는 뜻이고, 그것은 고칠 일이다(태양광 커넥터가 한 주기에
|
|
119
|
+
* 76건을 되풀이 보내던 것을 이 수로 볼 수 있었어야 했다).
|
|
120
|
+
*/
|
|
121
|
+
duplicates: number
|
|
122
|
+
lastDuplicateAtMs: number
|
|
106
123
|
byReason: Record<string, { count: number; lastAtMs: number }>
|
|
107
124
|
/**
|
|
108
125
|
* 사유 종류가 상한에 닿았나.
|
|
109
126
|
*
|
|
110
|
-
*
|
|
111
|
-
* 바로 이 상한으로 막으려던 무한한 집합이다.
|
|
127
|
+
* **모은 종류 수는 세지 않는다** — 그것을 세려면 이미 본 사유를 모두 기억해야 하고, 그 집합이
|
|
128
|
+
* 바로 이 상한으로 막으려던 무한한 집합이다. 모은 **건수**는 `byReason[OTHER_REASON].count` 가 답한다.
|
|
112
129
|
*/
|
|
113
130
|
reasonsCapped: boolean
|
|
114
131
|
/** 표본을 이미 뜬 사유들 — 같은 사유를 다시 직렬화하지 않기 위한 색인(O(1) 조회). */
|
|
@@ -122,12 +139,12 @@ export interface IngestLedger {
|
|
|
122
139
|
/**
|
|
123
140
|
* **원본에 닿지 못한 사실** (2026-08-23) — 「받은 것이 없다」와 **다른 사실**이다.
|
|
124
141
|
*
|
|
125
|
-
* ── 무엇이
|
|
142
|
+
* ── 무엇이 아무 신호도 내지 않았나 ─────────────────────────────────────────────────────────
|
|
126
143
|
* 실 원본이 끊겼을 때(접속 시간 초과) 트윈의 **조회 가능한 상태 어디에도** 그 사실이 없었다.
|
|
127
144
|
* `recordIngest` 는 `onRecords` 가 불릴 때만 도달하고, 읽기가 실패하면 그 문에 오지 않는다.
|
|
128
145
|
* `TwinReference.lastError` 는 마스터 동기·접속 시험에만 적힌다.
|
|
129
146
|
*
|
|
130
|
-
* 그래서 화면이 볼 수 있는 것은 「새 사실이 없다」뿐이었고, 그것은
|
|
147
|
+
* 그래서 화면이 볼 수 있는 것은 「새 사실이 없다」뿐이었고, 그것은 **「연결된 시스템에서 들어온 것이 없다」와 구별되지
|
|
131
148
|
* 않는다.** 실증 데모 중에 원본이 끊기면 사용자가 원인을 찾을 수 없다.
|
|
132
149
|
*
|
|
133
150
|
* ── 왜 「몇 번」과 「언제부터」를 함께 두나 ──────────────────────────────────
|
|
@@ -136,6 +153,13 @@ export interface IngestLedger {
|
|
|
136
153
|
*
|
|
137
154
|
* 성공하면 지운다 — 고쳐진 단절이 화면에 남아 있으면 그것도 거짓이다.
|
|
138
155
|
*/
|
|
156
|
+
/**
|
|
157
|
+
* 마지막으로 끊겼다가 돌아온 것 — 없으면 끊긴 적이 없거나 아직 돌아오지 않은 것이다.
|
|
158
|
+
*
|
|
159
|
+
* 실패 기록(`readFailure`)은 돌아오면 지워진다. 그것만 지우면 「끊긴 적이 있었다」가 화면에서
|
|
160
|
+
* 사라지므로, 돌아온 사실을 따로 남긴다.
|
|
161
|
+
*/
|
|
162
|
+
lastRecovery?: { atMs: number; afterFailures: number; downMs: number }
|
|
139
163
|
readFailure?: {
|
|
140
164
|
/** 연속 실패 수 — 성공 한 번에 0 으로 돌아간다. */
|
|
141
165
|
consecutive: number
|
|
@@ -147,6 +171,8 @@ export interface IngestLedger {
|
|
|
147
171
|
reason: string
|
|
148
172
|
/** 어느 흐름에서 났나(어댑터가 말해 주면). 원본마다 흐름이 다르므로 열려 있다. */
|
|
149
173
|
stream?: string
|
|
174
|
+
/** 다음에 다시 물을 때까지 남은 시간(ms) — 커넥터가 물러섰을 때만 있다. */
|
|
175
|
+
nextRetryMs?: number
|
|
150
176
|
}
|
|
151
177
|
/**
|
|
152
178
|
* **읽었는데 창을 넘길 수 없다** (2026-08-24) — 위와 **다른 사실**이다.
|
|
@@ -165,6 +191,27 @@ export interface IngestLedger {
|
|
|
165
191
|
* 모양은 위와 같게 둔다 — 「몇 번」과 「언제부터」가 여기서도 같은 값을 한다(한 번의 밀림과 사흘째
|
|
166
192
|
* 못 넘어가는 것은 다르다). 넘어가면 지운다.
|
|
167
193
|
*/
|
|
194
|
+
/**
|
|
195
|
+
* **원본이 말했는데 트윈에 세우지 않은 것** — 이유별로 센다.
|
|
196
|
+
*
|
|
197
|
+
* ── 왜 이 자리가 필요한가 (2026-08-24) ─────────────────────────────────────
|
|
198
|
+
* 커넥터가 원본의 사실 일부를 **일부러 받지 않는 일이 정상이다**: 입자가 맞지 않거나, 받으면 상태가
|
|
199
|
+
* 거짓이 되거나, 커널 어휘로 옮길 수 없다. 그때 커넥터는 경고를 낸다 — **그런데 그 경고가 프로비저닝
|
|
200
|
+
* 화면에서 한 번 스쳐 지나갈 뿐이었다.** 그 뒤로는 어디에서도 볼 수 없었다.
|
|
201
|
+
*
|
|
202
|
+
* 그래서 사용자가 「원본보다 계획이 적다」를 물으면 **답이 어디에도 없다.** 그 사실은 한 번의
|
|
203
|
+
* 사건이 아니라 **상태의 성질**이다 — 세우지 않은 것은 지금도 트윈에 없다.
|
|
204
|
+
*
|
|
205
|
+
* ── `unhandled` 의 형제다 ──────────────────────────────────────────────────
|
|
206
|
+
* `unhandled` 리듀서가 **받았는데** 담을 자리를 몰랐다
|
|
207
|
+
* 여기 커넥터가 **보지만 일부러 보내지 않았다** — 이유와 함께
|
|
208
|
+
*
|
|
209
|
+
* 둘 다 「사실이 오류 없이 사라지지 않게」 세는 것이고, 이유가 다르므로 자리도 다르다.
|
|
210
|
+
*
|
|
211
|
+
* 이유를 **문자열로 받는다**: 무엇을 왜 안 받는지는 원본마다 다르고, 계약이 그 목록을 닫으면 새 원본을
|
|
212
|
+
* 붙일 때마다 계약을 고치게 된다(흐름 열쇠와 같은 규율).
|
|
213
|
+
*/
|
|
214
|
+
withheld?: { reason: string; count: number; lastAtMs: number }[]
|
|
168
215
|
cursorStall?: {
|
|
169
216
|
/** 연속 정체 수 — 넘어가면 0 으로 돌아간다. */
|
|
170
217
|
consecutive: number
|
|
@@ -189,7 +236,17 @@ export function recordReadFailure(
|
|
|
189
236
|
ledger: IngestLedger,
|
|
190
237
|
reason: string,
|
|
191
238
|
nowMs: number,
|
|
192
|
-
stream?: string
|
|
239
|
+
stream?: string,
|
|
240
|
+
/**
|
|
241
|
+
* 다음에 다시 물을 때까지 남은 시간(ms) — 커넥터가 물러섰으면 그 값을 준다 (2026-08-27).
|
|
242
|
+
*
|
|
243
|
+
* 이것이 없으면 화면이 「닿지 못하고 있다」까지만 말하고, 사람은 **언제 다시 시도하는지** 알 수
|
|
244
|
+
* 없다. 그러면 기다려야 하는지 손을 써야 하는지 판단하지 못한다.
|
|
245
|
+
*
|
|
246
|
+
* **연달아 횟수와 시작 시각은 커넥터가 보내지 않는다** — 이 함수가 이미 센다. 두 곳에서 세면
|
|
247
|
+
* 갈라지고, 갈라지면 어느 쪽이 사실인지 알 수 없다.
|
|
248
|
+
*/
|
|
249
|
+
nextRetryMs?: number
|
|
193
250
|
): void {
|
|
194
251
|
const prev = ledger.readFailure
|
|
195
252
|
ledger.readFailure = {
|
|
@@ -197,15 +254,28 @@ export function recordReadFailure(
|
|
|
197
254
|
sinceMs: prev?.sinceMs ?? nowMs,
|
|
198
255
|
lastAtMs: nowMs,
|
|
199
256
|
reason,
|
|
200
|
-
...(stream ? { stream } : {})
|
|
257
|
+
...(stream ? { stream } : {}),
|
|
258
|
+
...(Number.isFinite(nextRetryMs) && (nextRetryMs as number) > 0 ? { nextRetryMs: nextRetryMs as number } : {})
|
|
201
259
|
}
|
|
202
260
|
}
|
|
203
261
|
|
|
262
|
+
/**
|
|
263
|
+
* **끊겼다가 돌아왔다** — 그 사실을 남긴다 (2026-08-27).
|
|
264
|
+
*
|
|
265
|
+
* 실패 기록만 지우면 「끊긴 적이 있었다」가 화면에서 사라진다. 그러면 밤새 두 시간 끊겼던 연결과 한
|
|
266
|
+
* 번도 끊기지 않은 연결이 아침에 똑같이 보인다. 몇 번 실패한 뒤에 돌아왔는지, 얼마나 끊겼는지를 함께
|
|
267
|
+
* 남긴다.
|
|
268
|
+
*/
|
|
269
|
+
export function recordRecovered(ledger: IngestLedger, afterFailures: number, downMs: number, nowMs: number): void {
|
|
270
|
+
if (ledger.readFailure) delete ledger.readFailure
|
|
271
|
+
ledger.lastRecovery = { atMs: nowMs, afterFailures, downMs }
|
|
272
|
+
}
|
|
273
|
+
|
|
204
274
|
/**
|
|
205
275
|
* 읽기가 성공했다 — 단절 기록을 지운다.
|
|
206
276
|
*
|
|
207
277
|
* **빈 읽기도 성공이다**: 원본이 「새 것이 없다」고 답한 것은 닿았다는 뜻이다. 그 둘을 같게 두면
|
|
208
|
-
*
|
|
278
|
+
* 들어온 것이 없는 연결이 끊긴 원본으로 보인다(고치려던 것의 반대 방향으로 틀린다).
|
|
209
279
|
*/
|
|
210
280
|
export function clearReadFailure(ledger: IngestLedger): void {
|
|
211
281
|
if (ledger.readFailure) delete ledger.readFailure
|
|
@@ -229,6 +299,48 @@ export function recordCursorStall(ledger: IngestLedger, reason: string, nowMs: n
|
|
|
229
299
|
}
|
|
230
300
|
}
|
|
231
301
|
|
|
302
|
+
/**
|
|
303
|
+
* **세우지 않은 것을 적는다** — 이유별 누적.
|
|
304
|
+
*
|
|
305
|
+
* 주기마다 다시 알려도 시끄러워지지 않게 **이유로 묶어 센다**(줄이 늘지 않는다). 그리고 마지막 시각을
|
|
306
|
+
* 함께 든다 — 「지금도 그런가」와 「한때 그랬나」는 다른 사실이고, 시각 없이는 구별할 수 없다.
|
|
307
|
+
*
|
|
308
|
+
* 이유가 같고 수가 달라지면 **마지막 수로 덮는다**: 이것은 누적 사건이 아니라 **지금 세우지 않은 것의
|
|
309
|
+
* 수**다. 주기마다 8건이면 8이고, 더해서 800이 되면 거짓이다.
|
|
310
|
+
*/
|
|
311
|
+
/**
|
|
312
|
+
* 어떤 길로 몇 건이 들어왔나 — 경로 이름은 부르는 쪽이 정한다.
|
|
313
|
+
*
|
|
314
|
+
* 이름을 자유 문자열로 두는 이유: 길이 늘어난다(지금은 물어보기·시나리오·부하 도구, 다음은 웹훅).
|
|
315
|
+
* 열거로 못 박으면 새 길이 생길 때 이 파일을 함께 고쳐야 하고, 잊으면 그 길이 통계에서 사라진다.
|
|
316
|
+
*/
|
|
317
|
+
export function recordIngestSource(ledger: IngestLedger, source: string, count: number, nowMs: number): void {
|
|
318
|
+
const key = String(source ?? '').trim() || 'unknown'
|
|
319
|
+
if (count <= 0) return
|
|
320
|
+
const cur = (ledger.bySource[key] ??= { count: 0, lastAtMs: -1 })
|
|
321
|
+
cur.count += count
|
|
322
|
+
cur.lastAtMs = nowMs
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** 같은 사실이 두 번 와서 버린 수 — 알리지 않고 버리지 않는다. */
|
|
326
|
+
export function recordDuplicates(ledger: IngestLedger, count: number, nowMs: number): void {
|
|
327
|
+
if (count <= 0) return
|
|
328
|
+
ledger.duplicates += count
|
|
329
|
+
ledger.lastDuplicateAtMs = nowMs
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export function recordWithheld(ledger: IngestLedger, reason: string, count: number, nowMs: number): void {
|
|
333
|
+
if (!reason || !(count > 0)) return
|
|
334
|
+
const list = (ledger.withheld ??= [])
|
|
335
|
+
const at = list.find(w => w.reason === reason)
|
|
336
|
+
if (at) {
|
|
337
|
+
at.count = count
|
|
338
|
+
at.lastAtMs = nowMs
|
|
339
|
+
return
|
|
340
|
+
}
|
|
341
|
+
list.push({ reason, count, lastAtMs: nowMs })
|
|
342
|
+
}
|
|
343
|
+
|
|
232
344
|
/** 창을 넘겼으면 지운다 — 풀린 정체가 화면에 남아 있으면 그것도 거짓이다. */
|
|
233
345
|
export function clearCursorStall(ledger: IngestLedger): void {
|
|
234
346
|
if (ledger.cursorStall) delete ledger.cursorStall
|
|
@@ -255,7 +367,7 @@ export const TREND_LIMIT = 36
|
|
|
255
367
|
* (`unknown bizStep "PUTAWAY_X17"`, `…X18`, …) 키가 무한히 늘어난다. 초당 1만 건 규모에서 이것은
|
|
256
368
|
* 미미한 오버헤드가 아니라 누수다.
|
|
257
369
|
*
|
|
258
|
-
* 넘치면 `OTHER_REASON` 한 칸에 모으고 **몇 종을
|
|
370
|
+
* 넘치면 `OTHER_REASON` 한 칸에 모으고 **몇 종을 모았는지 함께 낸다**(알리지 않고 자르지 않는다).
|
|
259
371
|
* 30종이면 고칠 곳을 찾기에 충분하다 — 목록을 훑는 일이 아니라 매핑 몇 줄을 고치는 일이다.
|
|
260
372
|
*/
|
|
261
373
|
export const REASON_LIMIT = 30
|
|
@@ -296,6 +408,9 @@ export function newIngestLedger(): IngestLedger {
|
|
|
296
408
|
flushes: 0,
|
|
297
409
|
lastWriteAtMs: -1,
|
|
298
410
|
lastUndeliveredAtMs: -1,
|
|
411
|
+
bySource: {},
|
|
412
|
+
duplicates: 0,
|
|
413
|
+
lastDuplicateAtMs: -1,
|
|
299
414
|
byReason: {},
|
|
300
415
|
reasonsCapped: false,
|
|
301
416
|
sampledReasons: new Set<string>(),
|
|
@@ -404,7 +519,7 @@ export function recordIngest(
|
|
|
404
519
|
const raw = reasonKey(it?.errors)
|
|
405
520
|
const known = raw in ledger.byReason
|
|
406
521
|
|
|
407
|
-
/* 사유 종류 상한 — 넘치면 한 칸으로
|
|
522
|
+
/* 사유 종류 상한 — 넘치면 한 칸으로 모으고, **닿았다는 사실**을 표시한다. */
|
|
408
523
|
let key = raw
|
|
409
524
|
if (!known && Object.keys(ledger.byReason).length >= REASON_LIMIT) {
|
|
410
525
|
key = OTHER_REASON
|
|
@@ -469,7 +584,7 @@ export function recordJournalWrite(
|
|
|
469
584
|
/*
|
|
470
585
|
* **`lastAtMs` 는 건드리지 않는다.** 그 값은 「마지막 유입」이고, 끊긴 피드를 가려내는 근거다. 쓰기로
|
|
471
586
|
* 갱신하면 라이브 트윈은 유입이 끊겨도 틱이 만든 델타를 계속 적으므로 **영원히 「방금 받았다」**가 되고,
|
|
472
|
-
* 「오래 끊긴 피드에 옛 창으로 초록을 내지 않는다」가
|
|
587
|
+
* 「오래 끊긴 피드에 옛 창으로 초록을 내지 않는다」가 오류 하나 없이 무력해진다. 축을 따로 든다.
|
|
473
588
|
*/
|
|
474
589
|
ledger.lastWriteAtMs = nowMs
|
|
475
590
|
}
|
|
@@ -512,11 +627,18 @@ export function syncVerdictOf(args: {
|
|
|
512
627
|
nowMs?: number
|
|
513
628
|
/** 마지막으로 **버려진** 시각. 한 번도 없으면 `-1`. */
|
|
514
629
|
lastUndeliveredAtMs?: number
|
|
630
|
+
/**
|
|
631
|
+
* 지금까지 제시된 누적 수 — **「받고 있는 중」과 「오는 것이 없다」를 가르는 사실**이다.
|
|
632
|
+
*
|
|
633
|
+
* 닿은 시각(`lastAtMs`)으로는 가를 수 없다: 빈 읽기도 닿은 것이므로 그 시각이 선다. 창이 아직 닫히지
|
|
634
|
+
* 않았을 때 이 수가 0 이면 오는 것이 없는 것이고, 0 이 아니면 받고 있는 중이다.
|
|
635
|
+
*/
|
|
636
|
+
offeredTotal?: number
|
|
515
637
|
threshold?: number
|
|
516
638
|
}): SyncVerdict {
|
|
517
639
|
/*
|
|
518
640
|
* ── 버려지고 있는 것이 가장 먼저다 ────────────────────────────────────────
|
|
519
|
-
* 트윈이 멈추면 피드는 남아 계속 레코드를 나르고, 커널은 그것을
|
|
641
|
+
* 트윈이 멈추면 피드는 남아 계속 레코드를 나르고, 커널은 그것을 알리지 않고 버린다. 그 상태를 「해당 없음」
|
|
520
642
|
* 이라 부르면 **사실이 사라지는 동안 화면이 안심시킨다** — 실제로 그렇게 떴다(멈춘 트윈 옆에 「150
|
|
521
643
|
* 통과 · 100%」). 그래서 시뮬·멈춤 판정보다 먼저 본다.
|
|
522
644
|
*
|
|
@@ -555,7 +677,29 @@ export function syncVerdictOf(args: {
|
|
|
555
677
|
if (args.nowMs - args.lastAtMs > STALE_AFTER_MS) return 'no-traffic'
|
|
556
678
|
}
|
|
557
679
|
|
|
558
|
-
|
|
680
|
+
/*
|
|
681
|
+
* ── **닿았는데 오는 것이 없다** 를 「모른다」로 답하지 않는다 (2026-08-28) ────
|
|
682
|
+
*
|
|
683
|
+
* 창은 **다음 유입이 있을 때** 닫힌다. 그래서 오는 것이 하나도 없는 트윈은 창이 영원히 닫히지 않고,
|
|
684
|
+
* 이 자리가 `unknown` 을 냈다 — 붙어 있고 37초마다 성공적으로 읽는 트윈이 화면에서 「모른다」였다.
|
|
685
|
+
*
|
|
686
|
+
* 그것이 드러난 계기: 커넥터가 재기동마다 재고 2,665건을 다시 말하던 것을 없앴다. 그러자 정상 상태의
|
|
687
|
+
* 유입이 0 이 되었고, **건강 신호가 그 소음을 타고 있었다**는 것이 보였다.
|
|
688
|
+
*
|
|
689
|
+
* ── 가르는 사실은 **받은 것이 있나**다 (첫 판을 고침) ─────────────────────
|
|
690
|
+
* 처음에는 닿은 시각으로 갈랐다. 그것이 틀렸다 — **빈 읽기도 닿은 것**이라 그 시각은 언제나 선다.
|
|
691
|
+
* 그래서 창을 채우는 중인 트윈(제시 200건)까지 「오는 것이 없다」로 답하게 됐고, 시험이 그것을 잡았다.
|
|
692
|
+
* 받고 있는 중인데 오는 것이 없다고 말하는 것은 **없는 것과 못 본 것을 뒤집는 것**이다.
|
|
693
|
+
*
|
|
694
|
+
* 닿았고 · 제시가 0 오는 것이 없다 `no-traffic`
|
|
695
|
+
* 닿았고 · 제시가 있다 받는 중이고 판정할 창이 아직 없다 `unknown`
|
|
696
|
+
* 한 번도 닿지 않았다 `unknown`(위 `lastAtMs < 0` 갈래)
|
|
697
|
+
*/
|
|
698
|
+
if (!args.recent) {
|
|
699
|
+
const touched = typeof args.lastAtMs === 'number' && args.lastAtMs >= 0
|
|
700
|
+
const nothingOffered = (args.offeredTotal ?? 0) <= 0
|
|
701
|
+
return touched && nothingOffered ? 'no-traffic' : 'unknown'
|
|
702
|
+
}
|
|
559
703
|
if (args.recent.offered <= 0) return 'no-traffic'
|
|
560
704
|
|
|
561
705
|
const ratio = (args.recent.offered - args.recent.rejected) / args.recent.offered
|
|
@@ -616,7 +760,7 @@ export interface IngestHealthView {
|
|
|
616
760
|
*
|
|
617
761
|
* 커서 정체(`cursorStall`)와 **섞이지 않는다**: 이쪽은 기다리면 풀리고 그쪽은 기다려도 안 풀린다.
|
|
618
762
|
*
|
|
619
|
-
* 「유입 없음」과 **다른 판정**이다: 유입 없음은
|
|
763
|
+
* 「유입 없음」과 **다른 판정**이다: 유입 없음은 연결된 시스템에서 들어온 것이 없는 것일 수 있고, 이것은 우리가 묻지도
|
|
620
764
|
* 못한 것이다. 화면은 두 문장을 다르게 말해야 한다 — 실 원본이 끊겼을 때 사용자가 원인을 찾을 수
|
|
621
765
|
* 있는지가 그 차이다.
|
|
622
766
|
*/
|
|
@@ -627,7 +771,16 @@ export interface IngestHealthView {
|
|
|
627
771
|
lastAt: string
|
|
628
772
|
reason: string
|
|
629
773
|
stream?: string
|
|
774
|
+
/** 다음에 다시 물을 때까지 남은 시간(ms) — 커넥터가 물러섰을 때만 있다. */
|
|
775
|
+
nextRetryMs?: number
|
|
630
776
|
}
|
|
777
|
+
/**
|
|
778
|
+
* 마지막으로 끊겼다가 돌아온 것 — 없으면 끊긴 적이 없거나 아직 돌아오지 않은 것이다 (2026-08-27).
|
|
779
|
+
*
|
|
780
|
+
* `readFailure` 는 돌아오면 사라진다. 이 칸이 없으면 밤새 끊겼던 연결과 한 번도 끊기지 않은 연결이
|
|
781
|
+
* 아침에 똑같이 보인다.
|
|
782
|
+
*/
|
|
783
|
+
lastRecovery?: { at: string; afterFailures: number; downMs: number }
|
|
631
784
|
}
|
|
632
785
|
|
|
633
786
|
function toWindowView(w: IngestWindow, fallbackEndMs: number): IngestWindowView {
|
|
@@ -670,6 +823,8 @@ export function ingestHealth(
|
|
|
670
823
|
recent: recentRaw,
|
|
671
824
|
lastAtMs: ledger?.lastAtMs ?? -1,
|
|
672
825
|
lastUndeliveredAtMs: ledger?.lastUndeliveredAtMs ?? -1,
|
|
826
|
+
/* 창이 닫히지 않았을 때 「받는 중」과 「오는 것이 없다」를 가르는 사실. */
|
|
827
|
+
offeredTotal: ledger?.offered ?? 0,
|
|
673
828
|
nowMs,
|
|
674
829
|
threshold
|
|
675
830
|
})
|
|
@@ -706,12 +861,22 @@ export function ingestHealth(
|
|
|
706
861
|
since: new Date(ledger.readFailure.sinceMs).toISOString(),
|
|
707
862
|
lastAt: new Date(ledger.readFailure.lastAtMs).toISOString(),
|
|
708
863
|
reason: ledger.readFailure.reason,
|
|
709
|
-
...(ledger.readFailure.stream ? { stream: ledger.readFailure.stream } : {})
|
|
864
|
+
...(ledger.readFailure.stream ? { stream: ledger.readFailure.stream } : {}),
|
|
865
|
+
...(ledger.readFailure.nextRetryMs ? { nextRetryMs: ledger.readFailure.nextRetryMs } : {})
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
: {}),
|
|
869
|
+
...(ledger?.lastRecovery
|
|
870
|
+
? {
|
|
871
|
+
lastRecovery: {
|
|
872
|
+
at: new Date(ledger.lastRecovery.atMs).toISOString(),
|
|
873
|
+
afterFailures: ledger.lastRecovery.afterFailures,
|
|
874
|
+
downMs: ledger.lastRecovery.downMs
|
|
710
875
|
}
|
|
711
876
|
}
|
|
712
877
|
: {}),
|
|
713
878
|
/* 커서 정체도 **이 갈래에서** 내보낸다 — 유입이 없는 장부에서도 정체는 실재한다(읽었으나 못
|
|
714
|
-
넘겼으면 유입이 0이다). 여기서 떨어뜨리면 바로 그 상황에서
|
|
879
|
+
넘겼으면 유입이 0이다). 여기서 떨어뜨리면 바로 그 상황에서 들어온 것이 없어진다. */
|
|
715
880
|
...(ledger?.cursorStall
|
|
716
881
|
? {
|
|
717
882
|
cursorStall: {
|
|
@@ -722,6 +887,19 @@ export function ingestHealth(
|
|
|
722
887
|
...(ledger.cursorStall.stream ? { stream: ledger.cursorStall.stream } : {})
|
|
723
888
|
}
|
|
724
889
|
}
|
|
890
|
+
: {}),
|
|
891
|
+
/*
|
|
892
|
+
* **세우지 않은 것도 낸다 — 양쪽 갈래에서.** 유입이 한 건도 없는 장부에서도 이 사실은 실재한다
|
|
893
|
+
* (원본이 말하는데 전부 안 받는 경우가 있다). 여기서 떨어뜨리면 바로 그 상황에서 들어온 것이 없어진다.
|
|
894
|
+
*/
|
|
895
|
+
...(ledger?.withheld?.length
|
|
896
|
+
? {
|
|
897
|
+
withheld: ledger.withheld.map(w => ({
|
|
898
|
+
reason: w.reason,
|
|
899
|
+
count: w.count,
|
|
900
|
+
lastAt: new Date(w.lastAtMs).toISOString()
|
|
901
|
+
}))
|
|
902
|
+
}
|
|
725
903
|
: {})
|
|
726
904
|
}
|
|
727
905
|
}
|
|
@@ -734,6 +912,31 @@ export function ingestHealth(
|
|
|
734
912
|
recent: recentRaw ? toWindowView(recentRaw, lastAnyMs) : null,
|
|
735
913
|
trend: ledger.trend.map(w => toWindowView(w, lastAnyMs)),
|
|
736
914
|
trendDropped: ledger.trendDropped,
|
|
915
|
+
/* 세우지 않은 것 — 양쪽 갈래에서 같은 모양으로 낸다(§`IngestLedger.withheld`). */
|
|
916
|
+
...(ledger.withheld?.length
|
|
917
|
+
? {
|
|
918
|
+
withheld: ledger.withheld.map(w => ({
|
|
919
|
+
reason: w.reason,
|
|
920
|
+
count: w.count,
|
|
921
|
+
lastAt: new Date(w.lastAtMs).toISOString()
|
|
922
|
+
}))
|
|
923
|
+
}
|
|
924
|
+
: {}),
|
|
925
|
+
/*
|
|
926
|
+
* **어떤 길로 들어오고 있나** — 화면이 「이 트윈은 무엇을 통해 값을 받나」를 말할 수 있게.
|
|
927
|
+
* 비어 있으면 칸을 만들지 않는다: 아직 아무것도 안 받은 트윈과 「길이 없다」를 구별한다.
|
|
928
|
+
*/
|
|
929
|
+
...(Object.keys(ledger.bySource).length
|
|
930
|
+
? {
|
|
931
|
+
sources: Object.entries(ledger.bySource)
|
|
932
|
+
.map(([source, v]) => ({ source, count: v.count, lastAt: new Date(v.lastAtMs).toISOString() }))
|
|
933
|
+
.sort((a, b) => b.count - a.count)
|
|
934
|
+
}
|
|
935
|
+
: {}),
|
|
936
|
+
/* 같은 사실이 두 번 와서 버린 수 — 0 이면 칸을 만들지 않는다(버린 적이 없다는 뜻이다). */
|
|
937
|
+
...(ledger.duplicates
|
|
938
|
+
? { duplicates: { count: ledger.duplicates, lastAt: new Date(ledger.lastDuplicateAtMs).toISOString() } }
|
|
939
|
+
: {}),
|
|
737
940
|
total: {
|
|
738
941
|
offered: ledger.offered,
|
|
739
942
|
accepted,
|
|
@@ -760,7 +963,18 @@ export function ingestHealth(
|
|
|
760
963
|
since: new Date(ledger.readFailure.sinceMs).toISOString(),
|
|
761
964
|
lastAt: new Date(ledger.readFailure.lastAtMs).toISOString(),
|
|
762
965
|
reason: ledger.readFailure.reason,
|
|
763
|
-
...(ledger.readFailure.stream ? { stream: ledger.readFailure.stream } : {})
|
|
966
|
+
...(ledger.readFailure.stream ? { stream: ledger.readFailure.stream } : {}),
|
|
967
|
+
...(ledger.readFailure.nextRetryMs ? { nextRetryMs: ledger.readFailure.nextRetryMs } : {})
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
: {}),
|
|
971
|
+
/* 끊겼다가 돌아온 것 — 지금 닿고 있어도 남긴다(그것이 없으면 밤새의 단절이 사라진다). */
|
|
972
|
+
...(ledger.lastRecovery
|
|
973
|
+
? {
|
|
974
|
+
lastRecovery: {
|
|
975
|
+
at: new Date(ledger.lastRecovery.atMs).toISOString(),
|
|
976
|
+
afterFailures: ledger.lastRecovery.afterFailures,
|
|
977
|
+
downMs: ledger.lastRecovery.downMs
|
|
764
978
|
}
|
|
765
979
|
}
|
|
766
980
|
: {}),
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* 연결 적합성 하네스 — **통합을 시험 스위트처럼 돌린다.**
|
|
3
3
|
*
|
|
4
|
-
* 선언 적합성 하네스 세 벌과 같은 규율을 **연결**에 적용한다: 사실이
|
|
4
|
+
* 선언 적합성 하네스 세 벌과 같은 규율을 **연결**에 적용한다: 사실이 오류 없이 사라지는 것을 막는다.
|
|
5
5
|
* 다른 점은 대상이 우리 선언이 아니라 **남의 시스템**이라는 것이고, 그래서 두 규칙이 더 붙는다.
|
|
6
6
|
*
|
|
7
|
-
* ① **돌리지 않은 프로브는 「실패」가 아니라 「모른다」다.** 실패로
|
|
8
|
-
* 없애려고 무엇이든 하게 되고, 모른다로
|
|
7
|
+
* ① **돌리지 않은 프로브는 「실패」가 아니라 「모른다」다.** 실패로 처리하면 사람이 그 빨간 칸을
|
|
8
|
+
* 없애려고 무엇이든 하게 되고, 모른다로 처리하면 **무엇을 아직 모르는지**가 남는다. 판정
|
|
9
9
|
* (`assessStagePath`)이 「모른다」를 달성으로 바꾸지 않으므로 이 구분이 끝까지 살아 있다.
|
|
10
10
|
* ② **쓰기 프로브는 명시 동의 없이 돌지 않는다.** 이 스위트는 고객의 운영 시스템을 두드린다.
|
|
11
11
|
* 읽기는 기본으로 돌리고, 남의 공장에 명령을 넣는 프로브는 사람이 켜야 돈다 — 켜지 않으면
|
|
@@ -71,7 +71,7 @@ export interface ProbeResult {
|
|
|
71
71
|
*
|
|
72
72
|
* 시뮬레이션 트윈에 라이브 프로브를 들이대면 빨간 칸이 생기는데 그건 결함이 아니다 — 시뮬은 원천에서
|
|
73
73
|
* 받는 것이 없다. 레퍼런스 없이 템플릿으로 세운 트윈에는 옮겨 온 마스터가 없으므로 「못 옮긴 값」도 없다.
|
|
74
|
-
* 이걸 통과로
|
|
74
|
+
* 이걸 통과로 처리하면 없는 능력을 주장하고, 미측정으로 처리하면 영원히 배선을 기다린다.
|
|
75
75
|
*/
|
|
76
76
|
applicable?: boolean
|
|
77
77
|
/** 돌렸을 때의 판정. 돌리지 않았으면 무의미하다. */
|
|
@@ -87,7 +87,7 @@ export interface ProbeResult {
|
|
|
87
87
|
const by = (rs: readonly ProbeResult[], id: ProbeId) => rs.find(r => r.id === id)
|
|
88
88
|
/** 돌렸고 셌으면 그 수, 아니면 `null`(모른다). 실패한 프로브의 수를 0 으로 읽지 않는다. */
|
|
89
89
|
/**
|
|
90
|
-
* 센 값은 **판정과 무관한 사실**이다 — `passed` 를 요구하면 「세어서 0 을 얻었다」가
|
|
90
|
+
* 센 값은 **판정과 무관한 사실**이다 — `passed` 를 요구하면 「세어서 0 을 얻었다」가 「모른다」와 구별되지 않는다.
|
|
91
91
|
* 그 구분이 이 하네스의 전부이므로 여기서 잃으면 나머지가 무의미하다. 해당 없음만 `null` 이다.
|
|
92
92
|
*/
|
|
93
93
|
const counted = (r: ProbeResult | undefined): number | null =>
|
|
@@ -96,9 +96,9 @@ const counted = (r: ProbeResult | undefined): number | null =>
|
|
|
96
96
|
const countedOrZero = (r: ProbeResult | undefined): number => counted(r) ?? 0
|
|
97
97
|
|
|
98
98
|
/**
|
|
99
|
-
* 프로브 결과를 **증거**로
|
|
99
|
+
* 프로브 결과를 **증거**로 처리한다.
|
|
100
100
|
*
|
|
101
|
-
*
|
|
101
|
+
* 계산 규칙의 정수: **돌리지 않은 프로브는 `null`** 로 간다. 그러면 판정이 그 단계를 달성으로
|
|
102
102
|
* 만들지 않고 `unmeasured` 로 말한다 — 스위트를 절반만 돌리고 초록을 주장할 길이 없다.
|
|
103
103
|
*/
|
|
104
104
|
export function foldProbeResults(results: readonly ProbeResult[]): StageEvidence {
|