@things-factory/headless-twin 10.0.11 → 10.0.13
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/canonical-ingest.d.ts +81 -3
- package/dist-server/engine/canonical-ingest.js +89 -10
- 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.d.ts +43 -0
- package/dist-server/engine/declared-stimulus.js +57 -0
- package/dist-server/engine/declared-stimulus.js.map +1 -0
- package/dist-server/engine/index.d.ts +5 -0
- package/dist-server/engine/index.js +6 -0
- package/dist-server/engine/index.js.map +1 -1
- package/dist-server/engine/ingest-health.d.ts +335 -0
- package/dist-server/engine/ingest-health.js +434 -0
- package/dist-server/engine/ingest-health.js.map +1 -0
- package/dist-server/engine/integration-coverage.d.ts +76 -0
- package/dist-server/engine/integration-coverage.js +73 -0
- package/dist-server/engine/integration-coverage.js.map +1 -0
- package/dist-server/engine/integration-probes.d.ts +65 -0
- package/dist-server/engine/integration-probes.js +100 -0
- package/dist-server/engine/integration-probes.js.map +1 -0
- package/dist-server/engine/integration-runner.d.ts +45 -0
- package/dist-server/engine/integration-runner.js +59 -0
- package/dist-server/engine/integration-runner.js.map +1 -0
- package/dist-server/engine/integration-target-profile.d.ts +57 -0
- package/dist-server/engine/integration-target-profile.js +79 -0
- package/dist-server/engine/integration-target-profile.js.map +1 -0
- package/dist-server/engine/kpi-fold.d.ts +39 -0
- package/dist-server/engine/kpi-fold.js +68 -5
- package/dist-server/engine/kpi-fold.js.map +1 -1
- package/dist-server/engine/kpi-query.d.ts +3 -3
- package/dist-server/engine/kpi-query.js +132 -18
- 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 +3 -17
- 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 +12 -3
- package/dist-server/engine/load-meter.js.map +1 -1
- package/dist-server/engine/local-declarations.d.ts +3 -6
- package/dist-server/engine/local-declarations.js +97 -16
- package/dist-server/engine/local-declarations.js.map +1 -1
- package/dist-server/engine/log.d.ts +18 -0
- package/dist-server/engine/log.js +80 -0
- package/dist-server/engine/log.js.map +1 -0
- package/dist-server/engine/loop-lag.d.ts +54 -0
- package/dist-server/engine/loop-lag.js +87 -0
- package/dist-server/engine/loop-lag.js.map +1 -0
- package/dist-server/engine/measured-yield.d.ts +42 -0
- package/dist-server/engine/measured-yield.js +75 -0
- package/dist-server/engine/measured-yield.js.map +1 -0
- package/dist-server/engine/model-gap.d.ts +108 -0
- package/dist-server/engine/model-gap.js +95 -0
- package/dist-server/engine/model-gap.js.map +1 -0
- package/dist-server/engine/operation-basis.d.ts +16 -0
- package/dist-server/engine/operation-basis.js +20 -2
- package/dist-server/engine/operation-basis.js.map +1 -1
- package/dist-server/engine/property-effects.js +17 -0
- package/dist-server/engine/property-effects.js.map +1 -1
- package/dist-server/engine/restart-policy.d.ts +13 -0
- package/dist-server/engine/restart-policy.js +52 -0
- package/dist-server/engine/restart-policy.js.map +1 -0
- 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 +8 -0
- package/dist-server/engine/spec-coverage.js +3 -1
- package/dist-server/engine/spec-coverage.js.map +1 -1
- package/dist-server/engine/stage-path.d.ts +83 -0
- package/dist-server/engine/stage-path.js +118 -0
- package/dist-server/engine/stage-path.js.map +1 -0
- package/dist-server/engine/twin-engine.d.ts +400 -31
- package/dist-server/engine/twin-engine.js +1198 -187
- package/dist-server/engine/twin-engine.js.map +1 -1
- package/dist-server/index.js +35 -3
- package/dist-server/index.js.map +1 -1
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.d.ts +5 -0
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js +58 -0
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +1 -0
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.d.ts +5 -0
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js +55 -0
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +1 -0
- package/dist-server/migrations/index.js +7 -1
- package/dist-server/migrations/index.js.map +1 -1
- package/dist-server/service/index.d.ts +5 -2
- package/dist-server/service/index.js +18 -7
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/reference/control-routing.d.ts +14 -0
- package/dist-server/service/reference/control-routing.js +64 -0
- package/dist-server/service/reference/control-routing.js.map +1 -0
- package/dist-server/service/reference/discovery-result.d.ts +1 -1
- package/dist-server/service/reference/discovery-result.js +1 -1
- package/dist-server/service/reference/discovery-result.js.map +1 -1
- package/dist-server/service/reference/index.d.ts +5 -1
- package/dist-server/service/reference/index.js +8 -1
- package/dist-server/service/reference/index.js.map +1 -1
- package/dist-server/service/reference/reference-adapter.d.ts +251 -3
- package/dist-server/service/reference/reference-adapter.js +43 -1
- package/dist-server/service/reference/reference-adapter.js.map +1 -1
- package/dist-server/service/reference/reference-assessment.d.ts +68 -0
- package/dist-server/service/reference/reference-assessment.js +136 -0
- package/dist-server/service/reference/reference-assessment.js.map +1 -0
- package/dist-server/service/reference/reference-live.d.ts +12 -1
- package/dist-server/service/reference/reference-live.js +119 -14
- package/dist-server/service/reference/reference-live.js.map +1 -1
- package/dist-server/service/reference/reference-master.d.ts +47 -1
- package/dist-server/service/reference/reference-master.js +34 -4
- package/dist-server/service/reference/reference-master.js.map +1 -1
- package/dist-server/service/reference/reference-probe.d.ts +92 -0
- package/dist-server/service/reference/reference-probe.js +186 -0
- package/dist-server/service/reference/reference-probe.js.map +1 -0
- package/dist-server/service/reference/reference-progress-subscription.d.ts +17 -0
- package/dist-server/service/reference/reference-progress-subscription.js +94 -0
- package/dist-server/service/reference/reference-progress-subscription.js.map +1 -0
- package/dist-server/service/reference/reference-progress.d.ts +38 -0
- package/dist-server/service/reference/reference-progress.js +71 -0
- package/dist-server/service/reference/reference-progress.js.map +1 -0
- package/dist-server/service/reference/reference-resolver.d.ts +3 -3
- package/dist-server/service/reference/reference-resolver.js +242 -58
- package/dist-server/service/reference/reference-resolver.js.map +1 -1
- package/dist-server/service/reference/twin-reference.d.ts +32 -0
- package/dist-server/service/reference/twin-reference.js +10 -0
- package/dist-server/service/reference/twin-reference.js.map +1 -1
- package/dist-server/service/twin-audit/command-audit.d.ts +34 -0
- package/dist-server/service/twin-audit/command-audit.js +15 -1
- package/dist-server/service/twin-audit/command-audit.js.map +1 -1
- package/dist-server/service/twin-audit/twin-audit-event.d.ts +3 -0
- package/dist-server/service/twin-audit/twin-audit-event.js +10 -0
- package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -1
- package/dist-server/service/twin-control/twin-control-mutation.d.ts +15 -2
- package/dist-server/service/twin-control/twin-control-mutation.js +88 -36
- package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
- package/dist-server/service/twin-event/journal-count.d.ts +21 -0
- package/dist-server/service/twin-event/journal-count.js +26 -0
- package/dist-server/service/twin-event/journal-count.js.map +1 -0
- package/dist-server/service/twin-event/twin-event-keys.d.ts +9 -0
- package/dist-server/service/twin-event/twin-event-keys.js +15 -18
- package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
- package/dist-server/service/twin-event/twin-event-type.d.ts +3 -1
- package/dist-server/service/twin-event/twin-event-type.js +18 -1
- package/dist-server/service/twin-event/twin-event-type.js.map +1 -1
- package/dist-server/service/twin-event/twin-event.d.ts +1 -0
- package/dist-server/service/twin-event/twin-event.js +9 -1
- package/dist-server/service/twin-event/twin-event.js.map +1 -1
- package/dist-server/service/twin-forecast/twin-forecast-query.js +2 -1
- package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
- package/dist-server/service/twin-ingest-window/index.d.ts +5 -0
- package/dist-server/service/twin-ingest-window/index.js +10 -0
- package/dist-server/service/twin-ingest-window/index.js.map +1 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.d.ts +12 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js +123 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +1 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.d.ts +1 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js +51 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +1 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window.d.ts +16 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window.js +108 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window.js.map +1 -0
- package/dist-server/service/twin-instance/twin-instance.d.ts +1 -1
- package/dist-server/service/twin-instance/twin-instance.js +2 -2
- package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
- package/dist-server/service/twin-journal/twin-journal-query.js +58 -5
- package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +14 -0
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +74 -3
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
- package/dist-server/service/twin-metrics/twin-metrics-query.d.ts +1 -0
- package/dist-server/service/twin-metrics/twin-metrics-query.js +78 -3
- package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
- package/dist-server/service/twin-model/axis-journal-evidence.d.ts +2 -0
- package/dist-server/service/twin-model/axis-journal-evidence.js +89 -0
- package/dist-server/service/twin-model/axis-journal-evidence.js.map +1 -0
- package/dist-server/service/twin-model/epcis-coverage.d.ts +1 -1
- package/dist-server/service/twin-model/epcis-coverage.js +19 -7
- package/dist-server/service/twin-model/epcis-coverage.js.map +1 -1
- package/dist-server/service/twin-model/iec61850-coverage.d.ts +1 -1
- package/dist-server/service/twin-model/iec61850-coverage.js +18 -7
- package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -1
- package/dist-server/service/twin-model/isa95-coverage.d.ts +14 -0
- package/dist-server/service/twin-model/isa95-coverage.js +23 -9
- package/dist-server/service/twin-model/isa95-coverage.js.map +1 -1
- package/dist-server/service/twin-model/item-ref.d.ts +24 -0
- package/dist-server/service/twin-model/item-ref.js +88 -0
- package/dist-server/service/twin-model/item-ref.js.map +1 -0
- package/dist-server/service/twin-model/name-index.d.ts +36 -0
- package/dist-server/service/twin-model/name-index.js +116 -0
- package/dist-server/service/twin-model/name-index.js.map +1 -0
- package/dist-server/service/twin-model/project-structure.js +1 -1
- package/dist-server/service/twin-model/project-structure.js.map +1 -1
- package/dist-server/service/twin-model/status-tally.d.ts +9 -0
- package/dist-server/service/twin-model/status-tally.js +37 -0
- package/dist-server/service/twin-model/status-tally.js.map +1 -0
- package/dist-server/service/twin-model/twin-lineage-query.js +40 -12
- package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-item-query.js +38 -39
- package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-query.js +157 -5
- package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-tree-query.js +7 -0
- package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
- package/dist-server/service/twin-readiness/index.d.ts +2 -0
- package/dist-server/service/twin-readiness/index.js +6 -0
- package/dist-server/service/twin-readiness/index.js.map +1 -0
- package/dist-server/service/twin-readiness/twin-readiness-query.d.ts +3 -0
- package/dist-server/service/twin-readiness/twin-readiness-query.js +103 -0
- package/dist-server/service/twin-readiness/twin-readiness-query.js.map +1 -0
- package/dist-server/service/twin-space/twin-space-resolver.d.ts +2 -2
- package/dist-server/service/twin-space/twin-space-resolver.js +5 -4
- package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
- package/dist-shared/entity-delta.d.ts +23 -3
- package/dist-shared/entity-delta.js +12 -8
- package/dist-shared/entity-delta.js.map +1 -1
- package/dist-shared/kpi-broadcast.js +1 -1
- package/dist-shared/kpi-broadcast.js.map +1 -1
- package/dist-shared/touched-items.d.ts +7 -0
- package/dist-shared/touched-items.js +80 -0
- package/dist-shared/touched-items.js.map +1 -0
- package/package.json +7 -7
- package/server/engine/canonical-ingest.ts +164 -15
- package/server/engine/command-routing.ts +1 -1
- package/server/engine/declared-stimulus.ts +66 -0
- package/server/engine/index.ts +6 -0
- package/server/engine/ingest-health.ts +704 -0
- package/server/engine/integration-coverage.ts +147 -0
- package/server/engine/integration-probes.ts +144 -0
- package/server/engine/integration-runner.ts +95 -0
- package/server/engine/integration-target-profile.ts +103 -0
- package/server/engine/kpi-fold.ts +101 -5
- package/server/engine/kpi-query.ts +137 -23
- package/server/engine/live-feed-registry.ts +4 -3
- package/server/engine/load-meter.ts +12 -3
- package/server/engine/local-declarations.ts +97 -19
- package/server/engine/log.ts +72 -0
- package/server/engine/loop-lag.ts +120 -0
- package/server/engine/measured-yield.ts +89 -0
- package/server/engine/model-gap.ts +168 -0
- package/server/engine/operation-basis.ts +33 -2
- package/server/engine/property-effects.ts +17 -0
- package/server/engine/restart-policy.ts +55 -0
- package/server/engine/runtime-key.ts +1 -1
- package/server/engine/spec-coverage.ts +23 -3
- package/server/engine/stage-path.ts +172 -0
- package/server/engine/twin-engine.ts +1318 -186
- package/server/index.ts +36 -3
- package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +54 -0
- package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +53 -0
- package/server/migrations/index.ts +7 -1
- package/server/service/index.ts +11 -0
- package/server/service/reference/control-routing.ts +62 -0
- package/server/service/reference/discovery-result.ts +1 -1
- package/server/service/reference/index.ts +7 -1
- package/server/service/reference/reference-adapter.ts +275 -5
- package/server/service/reference/reference-assessment.ts +215 -0
- package/server/service/reference/reference-live.ts +126 -14
- package/server/service/reference/reference-master.ts +64 -4
- package/server/service/reference/reference-probe.ts +264 -0
- package/server/service/reference/reference-progress-subscription.ts +73 -0
- package/server/service/reference/reference-progress.ts +95 -0
- package/server/service/reference/reference-resolver.ts +246 -19
- package/server/service/reference/twin-reference.ts +39 -1
- package/server/service/twin-audit/command-audit.ts +34 -1
- package/server/service/twin-audit/twin-audit-event.ts +20 -0
- package/server/service/twin-control/twin-control-mutation.ts +78 -30
- package/server/service/twin-event/journal-count.ts +53 -0
- package/server/service/twin-event/twin-event-keys.ts +16 -1
- package/server/service/twin-event/twin-event-type.ts +26 -2
- package/server/service/twin-event/twin-event.ts +7 -0
- package/server/service/twin-forecast/twin-forecast-query.ts +2 -1
- package/server/service/twin-ingest-window/index.ts +7 -0
- package/server/service/twin-ingest-window/twin-ingest-window-query.ts +125 -0
- package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +57 -0
- package/server/service/twin-ingest-window/twin-ingest-window.ts +113 -0
- package/server/service/twin-instance/twin-instance.ts +16 -10
- package/server/service/twin-journal/twin-journal-query.ts +59 -5
- package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +79 -4
- package/server/service/twin-metrics/twin-metrics-query.ts +81 -3
- package/server/service/twin-model/axis-journal-evidence.ts +57 -0
- package/server/service/twin-model/epcis-coverage.ts +2 -8
- package/server/service/twin-model/iec61850-coverage.ts +2 -8
- package/server/service/twin-model/isa95-coverage.ts +22 -9
- package/server/service/twin-model/item-ref.ts +80 -0
- package/server/service/twin-model/name-index.ts +94 -0
- package/server/service/twin-model/project-structure.ts +1 -1
- package/server/service/twin-model/status-tally.ts +37 -0
- package/server/service/twin-model/twin-lineage-query.ts +38 -9
- package/server/service/twin-model/twin-model-item-query.ts +26 -27
- package/server/service/twin-model/twin-model-query.ts +156 -5
- package/server/service/twin-model/twin-model-tree-query.ts +7 -0
- package/server/service/twin-readiness/index.ts +3 -0
- package/server/service/twin-readiness/twin-readiness-query.ts +96 -0
- package/server/service/twin-space/twin-space-resolver.ts +5 -4
- package/shared/entity-delta.ts +31 -7
- package/shared/kpi-broadcast.ts +1 -1
- package/shared/touched-items.ts +73 -0
- package/test/adopt-structure-live.test.ts +7 -7
- package/test/axis-journal-evidence.test.ts +71 -0
- package/test/axis-read.test.ts +101 -1
- package/test/boot-resume.test.ts +93 -27
- package/test/broadcast-cost-baseline.test.ts +200 -0
- package/test/broadcast-period.test.ts +58 -0
- package/test/canonical-ingest-vocabularies.test.ts +36 -1
- package/test/canonical-quantity-door.test.ts +61 -0
- package/test/command-routing.test.ts +1 -1
- package/test/control-capability.test.ts +103 -0
- package/test/declared-location-types.test.ts +89 -0
- package/test/declared-stimulus.test.ts +88 -0
- package/test/discovery-result.test.ts +1 -1
- package/test/duration-estimators.test.ts +1 -1
- package/test/entity-delta.test.ts +2 -2
- package/test/ingest-bench.test.ts +3 -3
- package/test/ingest-health-engine.test.ts +248 -0
- package/test/ingest-health-wiring.test.ts +119 -0
- package/test/ingest-health.test.ts +306 -0
- package/test/ingest-history.test.ts +247 -0
- package/test/ingest-running-guard.test.ts +5 -5
- package/test/instance-cache-lifecycle.test.ts +1 -1
- package/test/integration-probes.test.ts +103 -0
- package/test/integration-runner.test.ts +95 -0
- package/test/item-ref.test.ts +78 -0
- package/test/journal-read-discipline.test.ts +177 -0
- package/test/journal-retention.test.ts +133 -0
- package/test/journal-sort-axis.test.ts +142 -0
- package/test/journal-write-door.test.ts +110 -0
- package/test/journal-write-trend.test.ts +115 -0
- package/test/kernel-kind-guard.test.ts +4 -4
- package/test/kpi-baseline-db.test.ts +1 -1
- package/test/kpi-fold.test.ts +90 -3
- package/test/kpi-query-bench.test.ts +3 -3
- package/test/lineage-survives-restart.test.ts +20 -4
- package/test/live-cursor-wiring.test.ts +87 -0
- package/test/live-feed-registry.test.ts +15 -7
- package/test/live-kernel-facts.test.ts +7 -7
- package/test/live-mirror-parity.test.ts +6 -0
- package/test/load-meter.test.ts +29 -15
- package/test/local-declarations.test.ts +109 -2
- package/test/log-stamp.test.ts +59 -0
- package/test/loop-lag.test.ts +82 -0
- package/test/measured-yield.test.ts +90 -0
- package/test/model-gap.test.ts +153 -0
- package/test/oee-accumulator.test.ts +4 -0
- package/test/operation-basis.test.ts +28 -1
- package/test/operational-vocabulary.test.ts +108 -0
- package/test/operations-capability-db.test.ts +4 -4
- package/test/project-structure-db.test.ts +1 -1
- package/test/projection-reaches-screen.test.ts +1 -1
- package/test/property-effects.test.ts +28 -0
- package/test/read-failure-visible.test.ts +145 -0
- package/test/reference-grounding.test.ts +70 -0
- package/test/resolve-ts-siblings.mjs +52 -0
- package/test/restart-policy.test.ts +111 -0
- package/test/resync-origin-site.test.ts +7 -1
- package/test/revision-axis.test.ts +93 -0
- package/test/runtime-key.test.ts +2 -2
- package/test/scale-twin-bench.test.ts +2 -2
- package/test/source-outcome-audit.test.ts +104 -0
- package/test/spec-coverage.test.ts +1 -1
- package/test/stage-path.test.ts +95 -0
- package/test/standard-coverage.test.ts +15 -3
- package/test/status-tally.test.ts +55 -0
- package/test/structure-revision-db.test.ts +31 -27
- package/test/tenant-registry-db.test.ts +3 -3
- package/test/time-range.test.ts +152 -0
- package/test/touched-items.test.ts +61 -0
- package/test/twin-event-keys.test.ts +10 -2
- package/test/twin-model-item-db.test.ts +5 -3
- package/test/twin-model-tree-db.test.ts +7 -7
- package/test/twin-origin-resync.test.ts +4 -4
- package/test/warm-start-seam.test.ts +4 -0
- package/test/yield-loop.test.ts +199 -0
- package/tsconfig.shared.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import { pubsub } from '@things-factory/shell'
|
|
2
|
+
import { HOST_STEPS, type ReferenceProgress } from './reference-progress.js'
|
|
3
|
+
import { assessMaster } from './reference-assessment.js'
|
|
1
4
|
import { Arg, Ctx, Mutation, Query, Resolver, Directive} from 'type-graphql'
|
|
2
5
|
|
|
3
6
|
import { ScalarObject, getRepository } from '@things-factory/shell'
|
|
4
7
|
|
|
5
8
|
import { TwinEngine } from '../../engine/index.js'
|
|
9
|
+
import { readRestartPolicy } from '../../engine/restart-policy.js'
|
|
6
10
|
import { readBoardEquipment } from '@operato/twin-kernel'
|
|
7
11
|
|
|
8
12
|
import { applyKnobDefaults, filledKnobKeys } from './knob-defaults.js'
|
|
@@ -140,9 +144,13 @@ export class TwinReferenceResolver {
|
|
|
140
144
|
|
|
141
145
|
/** 소스를 인제스트 → 공간 + 인스턴스 생성(provision). start 면 기동까지. */
|
|
142
146
|
@Directive('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
143
|
-
@Mutation(returns => ScalarObject, {
|
|
147
|
+
@Mutation(returns => ScalarObject, {
|
|
148
|
+
description:
|
|
149
|
+
"Ingest a registered reference source into a twin (space + instance). Optionally start it. restartPolicy declares how the new twin treats its own past on reboot: 'resync' (read the source again) | 'resume' (continue its journal) | 'reset' (wipe and replay from the seed). It has no default — a twin cannot exist without the declaration."
|
|
150
|
+
})
|
|
144
151
|
async importFromReference(
|
|
145
152
|
@Arg('source') source: string,
|
|
153
|
+
@Arg('restartPolicy') restartPolicy: string,
|
|
146
154
|
@Ctx() context: ResolverContext,
|
|
147
155
|
@Arg('start', { nullable: true }) start?: boolean
|
|
148
156
|
): Promise<any> {
|
|
@@ -153,7 +161,13 @@ export class TwinReferenceResolver {
|
|
|
153
161
|
/* 어디서 왔는지 남긴다 — 이것이 없으면 이 트윈은 두 번 다시 읽을 수 없다.
|
|
154
162
|
이 경로는 **메모리에 등록된 마스터 하나**를 그대로 쓰므로 사이트 서술자가 없다(사이트 정체를
|
|
155
163
|
적을 것이 없다). 다시 읽기도 같은 등록본을 집으므로 사이트를 찾을 필요가 없다. */
|
|
156
|
-
const res = await TwinEngine.ingestMaster(
|
|
164
|
+
const res = await TwinEngine.ingestMaster(
|
|
165
|
+
domainId,
|
|
166
|
+
{ ...master, origin: { kind: 'reference', source } },
|
|
167
|
+
readRestartPolicy(restartPolicy, 'importFromReference'),
|
|
168
|
+
undefined,
|
|
169
|
+
context.state.user
|
|
170
|
+
)
|
|
157
171
|
if (start) await TwinEngine.startFromRegistry(domainId, res.instanceId)
|
|
158
172
|
return { ok: true, instanceId: res.instanceId, spaceId: res.spaceId, warnings: res.warnings }
|
|
159
173
|
} catch (e: any) {
|
|
@@ -196,23 +210,96 @@ export class TwinReferenceResolver {
|
|
|
196
210
|
error: `twin "${instanceId}" does not remember where it came from — it cannot be re-read. Re-create it from its template or reference so the origin is recorded.`
|
|
197
211
|
}
|
|
198
212
|
|
|
213
|
+
/*
|
|
214
|
+
* 진행을 알리는 통로 — 만들 때와 **같은 것**이다. 레퍼런스에서 온 트윈만 알린다(템플릿에서 온
|
|
215
|
+
* 트윈은 원본을 읽지 않으므로 알릴 단계가 없다).
|
|
216
|
+
*
|
|
217
|
+
* 분모는 그 어댑터가 선언한 단계 수다(`masterSteps`) + 호스트 단계. 선언하지 않은 어댑터는
|
|
218
|
+
* `total` 이 없고, 그때 화면은 퍼센트를 만들지 않는다 — 모르는 진행률을 지어내지 않는다.
|
|
219
|
+
*/
|
|
220
|
+
const progressSource: string | undefined = origin.kind === 'reference' ? origin.source : undefined
|
|
221
|
+
const refRow = progressSource
|
|
222
|
+
? await getRepository(TwinReference).findOne({ where: { domain: { id: domainId }, source: progressSource } })
|
|
223
|
+
: null
|
|
224
|
+
const adapterForSteps = refRow ? getAdapter(refRow.adapterType) : undefined
|
|
225
|
+
const declaredSteps = adapterForSteps?.masterSteps?.length
|
|
226
|
+
const stepTotal = declaredSteps === undefined ? undefined : declaredSteps + HOST_STEPS.length
|
|
227
|
+
const siteId = origin.siteId ?? instanceId
|
|
228
|
+
let lastDone = 0
|
|
229
|
+
let lastStep: ReferenceProgress['step'] = (adapterForSteps?.masterSteps?.[0] ?? 'provision') as ReferenceProgress['step']
|
|
230
|
+
const emit = (p: Omit<ReferenceProgress, 'siteCount' | 'siteId' | 'siteIndex'>) => {
|
|
231
|
+
if (!progressSource) return
|
|
232
|
+
lastDone = p.done
|
|
233
|
+
lastStep = p.step
|
|
234
|
+
pubsub.publish('twin-reference-progress', {
|
|
235
|
+
twinReferenceProgress: { ...p, siteId, siteIndex: 0, siteCount: 1, total: p.total ?? stepTotal, source: progressSource }
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
|
|
199
239
|
try {
|
|
200
|
-
const master = await this.masterFromOrigin(domainId, origin, instanceId
|
|
240
|
+
const master = await this.masterFromOrigin(domainId, origin, instanceId, st =>
|
|
241
|
+
emit({ step: st.key, done: st.done, total: stepTotal, detail: st.detail })
|
|
242
|
+
)
|
|
243
|
+
emit({ step: 'provision', done: declaredSteps ?? 0, total: stepTotal })
|
|
201
244
|
/*
|
|
202
245
|
* **어느 현장의 트윈이었나를 지킨다** — 다시 읽기는 구조만 새로 그리는 일이고, 현장 소속은 그때
|
|
203
246
|
* 사람이 고른 것이다. 기억이 없는 옛 트윈은 **지금 있는 현장**을 그대로 쓴다(빈손으로 두면 템플릿
|
|
204
247
|
* 기본 공간으로 옮겨 간다 — 조용히 현장을 떠나고 그 현장의 값도 잃는다).
|
|
205
248
|
*/
|
|
206
249
|
const keepSpace = origin.spaceId || inst.spaceId
|
|
207
|
-
|
|
250
|
+
/* 다시 읽기는 **구조만** 새로 그린다 — 재기동 정책은 그 트윈의 선언이므로 그대로 지킨다. */
|
|
251
|
+
const res = await TwinEngine.ingestMaster(
|
|
252
|
+
domainId,
|
|
253
|
+
master,
|
|
254
|
+
readRestartPolicy(inst.restartPolicy, `resync of twin "${instanceId}"`),
|
|
255
|
+
keepSpace ? { spaceId: keepSpace } : undefined,
|
|
256
|
+
context.state.user
|
|
257
|
+
)
|
|
258
|
+
emit({ step: 'start', done: (declaredSteps ?? 0) + 1, total: stepTotal })
|
|
259
|
+
/*
|
|
260
|
+
* **총평도 새로 남긴다.** 다시 읽었는데 총평이 옛것이면 화면이 지난 읽기를 현재로 보여 준다 —
|
|
261
|
+
* 그 표를 보고 사람이 원본을 고치러 간다.
|
|
262
|
+
*/
|
|
263
|
+
if (refRow && progressSource) {
|
|
264
|
+
const prevSpec = (refRow.scopeSpec as any) ?? {}
|
|
265
|
+
await getRepository(TwinReference).update(
|
|
266
|
+
{ id: refRow.id },
|
|
267
|
+
{
|
|
268
|
+
scopeSpec: { ...prevSpec, assessments: { ...(prevSpec.assessments ?? {}), [siteId]: assessMaster(master, siteId) } }
|
|
269
|
+
} as any
|
|
270
|
+
)
|
|
271
|
+
}
|
|
208
272
|
return { ok: true, instanceId: res.instanceId, spaceId: res.spaceId, warnings: res.warnings }
|
|
209
273
|
} catch (e: any) {
|
|
274
|
+
/* 막힌 자리를 그대로 알린다 — 「끝낸 수」의 다음 단계다(만들 때와 같은 규칙). */
|
|
275
|
+
emit({
|
|
276
|
+
step: (adapterForSteps?.masterSteps?.[lastDone] ?? lastStep) as ReferenceProgress['step'],
|
|
277
|
+
done: lastDone,
|
|
278
|
+
total: stepTotal,
|
|
279
|
+
failed: true,
|
|
280
|
+
error: e?.message ?? String(e)
|
|
281
|
+
})
|
|
210
282
|
return refusal(context, e, 'resync failed')
|
|
211
283
|
}
|
|
212
284
|
}
|
|
213
285
|
|
|
214
286
|
/** 기억해 둔 원천에서 마스터를 다시 얻는다 — 어댑터든 템플릿이든 이 한 곳에서 갈린다. */
|
|
215
|
-
private async masterFromOrigin(
|
|
287
|
+
private async masterFromOrigin(
|
|
288
|
+
domainId: string,
|
|
289
|
+
origin: any,
|
|
290
|
+
instanceId: string,
|
|
291
|
+
/*
|
|
292
|
+
* **다시 읽기도 진행을 보여야 한다** (2026-08-22).
|
|
293
|
+
*
|
|
294
|
+
* 만들 때는 8단계가 화면에 흐르는데 다시 읽을 때는 아무것도 없었다 — 사용자가 버튼을 누르고
|
|
295
|
+
* 「진행되고 있는지 확인할 수가 없다」고 했다. 실제로 chef 는 원본 읽기가 14~21초이고 그 사이
|
|
296
|
+
* 화면에 아무 변화가 없다.
|
|
297
|
+
*
|
|
298
|
+
* 통로는 만들 때와 **같은 것**이다(`twin-reference-progress`). 화면도 같은 표시를 쓴다 —
|
|
299
|
+
* 두 벌을 만들면 한쪽만 고쳐지고 사람은 같은 일이 다르게 보이는 것을 결함으로 읽는다.
|
|
300
|
+
*/
|
|
301
|
+
onStep?: (s: { key: any; done: number; total?: number; detail?: string }) => void
|
|
302
|
+
): Promise<any> {
|
|
216
303
|
if (origin.kind === 'template') {
|
|
217
304
|
const tpl = getTemplate(origin.templateId)
|
|
218
305
|
if (!tpl) throw new Error(`unknown template: ${origin.templateId} — this twin cannot be rebuilt`)
|
|
@@ -249,7 +336,7 @@ export class TwinReferenceResolver {
|
|
|
249
336
|
* 다른 현장의 구조를 이 트윈에 덮는 것은 오류가 아니라 조용히 틀린 공장으로 나타난다.
|
|
250
337
|
*/
|
|
251
338
|
const picked = pickOriginSite(sites, origin)
|
|
252
|
-
if (picked.site) return { ...(await adapter.fetchMaster(cfg, picked.site)), origin }
|
|
339
|
+
if (picked.site) return { ...(await adapter.fetchMaster(cfg, picked.site, onStep)), origin }
|
|
253
340
|
if (picked.reason === 'site-gone')
|
|
254
341
|
throw new Error(
|
|
255
342
|
`reference "${origin.source}" no longer reports site "${origin.siteId}" — refusing to overwrite this twin with a different site`
|
|
@@ -273,10 +360,14 @@ export class TwinReferenceResolver {
|
|
|
273
360
|
|
|
274
361
|
/** 템플릿 + 노브 → 합성 마스터 → 인제스트(+기동+시나리오). 레퍼런스 import 와 같은 경로, 원천만 합성. */
|
|
275
362
|
@Directive('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
276
|
-
@Mutation(returns => ScalarObject, {
|
|
363
|
+
@Mutation(returns => ScalarObject, {
|
|
364
|
+
description:
|
|
365
|
+
"Instantiate a twin template with params → synthetic master → ingest (+start+scenario). restartPolicy declares how the new twin treats its own past on reboot ('resync' | 'resume' | 'reset') — there is no default. Returns {ok, instanceId, spaceId}."
|
|
366
|
+
})
|
|
277
367
|
async createFromTemplate(
|
|
278
368
|
@Arg('templateId') templateId: string,
|
|
279
369
|
@Arg('params', type => ScalarObject) params: any,
|
|
370
|
+
@Arg('restartPolicy') restartPolicy: string,
|
|
280
371
|
@Ctx() context: ResolverContext,
|
|
281
372
|
@Arg('start', { nullable: true }) start?: boolean,
|
|
282
373
|
@Arg('spaceId', { nullable: true, description: 'Existing space to co-locate this twin into (add a lens to a site). Omit to create its own space.' }) spaceId?: string
|
|
@@ -310,7 +401,13 @@ export class TwinReferenceResolver {
|
|
|
310
401
|
master.origin = { kind: 'template', templateId, params: effective, ...(spaceId ? { spaceId } : {}) }
|
|
311
402
|
/* 공간 선택은 템플릿을 거치지 않는다 — 템플릿마다 이 노브를 알고 있어야 한다면 새 템플릿이
|
|
312
403
|
조용히 무시하게 된다. 합성된 마스터에 한 곳에서 얹는다(레퍼런스 인제스트와 같은 경로). */
|
|
313
|
-
const res = await TwinEngine.ingestMaster(
|
|
404
|
+
const res = await TwinEngine.ingestMaster(
|
|
405
|
+
domainId,
|
|
406
|
+
master,
|
|
407
|
+
readRestartPolicy(restartPolicy, `createFromTemplate("${templateId}")`),
|
|
408
|
+
{ spaceId },
|
|
409
|
+
context.state.user
|
|
410
|
+
)
|
|
314
411
|
if (start !== false) {
|
|
315
412
|
await TwinEngine.startFromRegistry(domainId, res.instanceId)
|
|
316
413
|
if (scenario) {
|
|
@@ -340,8 +437,19 @@ export class TwinReferenceResolver {
|
|
|
340
437
|
async twinReferences(@Ctx() context: ResolverContext): Promise<any[]> {
|
|
341
438
|
const domainId = context.state.domain.id
|
|
342
439
|
const rows = await getRepository(TwinReference).find({ where: { domain: { id: domainId } } })
|
|
343
|
-
|
|
344
|
-
|
|
440
|
+
/*
|
|
441
|
+
* provenance 라이브 대조 — 지워진 트윈은 producedCount 에서 자동 제외(상태 동기의 lazy 방식).
|
|
442
|
+
*
|
|
443
|
+
* **id 만 읽는다** (2026-08-21). 예전에는 인스턴스 행 전체를 읽었고, 그 행에는 **모델 JSON** 이
|
|
444
|
+
* 들어 있다(자리 수백 개 + 발자국 좌표). 그래서 이 목록을 여는 데 수 메가바이트를 읽고 파싱했고,
|
|
445
|
+
* 화면은 그동안 「아직 연결된 시스템이 없습니다」를 보이고 있었다 — 사용자에게는 「없다」는 답으로
|
|
446
|
+
* 읽힌다. 여기서 필요한 것은 살아 있는 id 집합 하나다.
|
|
447
|
+
*/
|
|
448
|
+
const liveIds = new Set(
|
|
449
|
+
(await getRepository(TwinInstance).find({ where: { domain: { id: domainId } }, select: ['instanceId'] })).map(
|
|
450
|
+
i => i.instanceId
|
|
451
|
+
)
|
|
452
|
+
)
|
|
345
453
|
return rows.map(r => {
|
|
346
454
|
const produced = (((r.scopeSpec as any)?.produced ?? []) as any[]).filter(p => liveIds.has(p.instanceId))
|
|
347
455
|
return {
|
|
@@ -417,15 +525,37 @@ export class TwinReferenceResolver {
|
|
|
417
525
|
const secretKeys = new Set((getAdapter(r.adapterType)?.meta?.configSchema ?? []).filter(f => f.secret).map(f => f.key))
|
|
418
526
|
const cfg: any = {}
|
|
419
527
|
for (const [k, v] of Object.entries((r.connectionConfig as any) ?? {})) cfg[k] = secretKeys.has(k) ? (v ? '••••••' : '') : v
|
|
528
|
+
/*
|
|
529
|
+
* **이 연결이 만든 트윈 id 를 함께 낸다.** 상세 화면이 「이 연결이 잘 동기화되고 있나」를 물으려면
|
|
530
|
+
* 어느 트윈을 물어야 하는지 알아야 한다. 예전에는 개수(`producedCount`)만 있어서 화면이 id 를
|
|
531
|
+
* 알 방법이 없었다.
|
|
532
|
+
*/
|
|
533
|
+
const producedInstanceIds = [
|
|
534
|
+
...new Set(
|
|
535
|
+
((r.scopeSpec as any)?.produced ?? [])
|
|
536
|
+
.map((p: any) => p?.instanceId)
|
|
537
|
+
.filter((x: any): x is string => typeof x === 'string' && !!x)
|
|
538
|
+
)
|
|
539
|
+
]
|
|
420
540
|
return {
|
|
421
541
|
source: r.source, system: r.system, siteName: r.siteName, description: r.description,
|
|
422
|
-
adapterType: r.adapterType, connectionConfig: cfg, status: r.status, lastSyncedAt: r.lastSyncedAt, lastError: r.lastError
|
|
542
|
+
adapterType: r.adapterType, connectionConfig: cfg, status: r.status, lastSyncedAt: r.lastSyncedAt, lastError: r.lastError,
|
|
543
|
+
producedInstanceIds
|
|
423
544
|
}
|
|
424
545
|
}
|
|
425
546
|
|
|
426
|
-
@Query(returns => [ScalarObject], { description: 'List reference connector metadata (label, domain, config schema, availability) for the connect UI.' })
|
|
547
|
+
@Query(returns => [ScalarObject], { description: 'List reference connector metadata (label, domain, config schema, availability, capabilities) for the connect UI.' })
|
|
427
548
|
referenceAdapters(): any[] {
|
|
428
|
-
|
|
549
|
+
/*
|
|
550
|
+
* **능력을 함께 낸다** (2026-08-21).
|
|
551
|
+
*
|
|
552
|
+
* `capabilities` 는 어댑터 뿌리에 있고 `meta` 에는 없었다. 그래서 연결 화면이 「이 원본이 라이브를
|
|
553
|
+
* 낼 수 있나」를 알 수 없었고, 트윈을 만들 때 `live` 를 말하지 못해 **재기동 정책이 없다고 거절**됐다
|
|
554
|
+
* (실 연동에서 그것에 막혔다 — 미러는 정의상 `resync` 이므로 `live` 하나로 정해진다).
|
|
555
|
+
*
|
|
556
|
+
* 화면이 그 사실을 알아야 정책을 사람에게 물을지 스스로 정할지 가를 수 있다.
|
|
557
|
+
*/
|
|
558
|
+
return listAdapters().map(a => ({ type: a.type, ...(a.meta ?? {}), capabilities: a.capabilities ?? [] }))
|
|
429
559
|
}
|
|
430
560
|
|
|
431
561
|
@Directive('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
@@ -545,14 +675,18 @@ export class TwinReferenceResolver {
|
|
|
545
675
|
* 넣는 것도 거절(`refuseImportSpace`) — 서로 다른 현장이 조용히 합쳐지는 것을 막는다.
|
|
546
676
|
*/
|
|
547
677
|
@Directive('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
548
|
-
@Mutation(returns => ScalarObject, {
|
|
678
|
+
@Mutation(returns => ScalarObject, {
|
|
679
|
+
description:
|
|
680
|
+
"Create twins from a reference: discover sites → filter by siteIds (if given) → fetch master(s) → provision. spaceId co-locates the twin into an existing space (one site only). start=false to only provision; live=true to run as a mirror (adapter openLiveFeed → live projector) instead of sim. restartPolicy declares how each new twin treats its own past on reboot ('resync' | 'resume' | 'reset'); omit it and live=true implies 'resync' — a mirror reads the source again — while a simulation must declare 'resume' or 'reset' for itself."
|
|
681
|
+
})
|
|
549
682
|
async importTwinReference(
|
|
550
683
|
@Arg('source') source: string,
|
|
551
684
|
@Ctx() context: ResolverContext,
|
|
552
685
|
@Arg('siteIds', type => [String], { nullable: true }) siteIds?: string[],
|
|
553
686
|
@Arg('start', { nullable: true }) start?: boolean,
|
|
554
687
|
@Arg('live', { nullable: true }) live?: boolean,
|
|
555
|
-
@Arg('spaceId', { nullable: true, description: 'Existing space to co-locate this twin into (add a lens to a site). Only when importing a single site. Omit to give each site its own space.' }) spaceId?: string
|
|
688
|
+
@Arg('spaceId', { nullable: true, description: 'Existing space to co-locate this twin into (add a lens to a site). Only when importing a single site. Omit to give each site its own space.' }) spaceId?: string,
|
|
689
|
+
@Arg('restartPolicy', { nullable: true, description: "How each new twin treats its own past on reboot: 'resync' | 'resume' | 'reset'. Required unless live=true (a mirror is 'resync' by definition)." }) restartPolicy?: string
|
|
556
690
|
): Promise<any> {
|
|
557
691
|
const domainId = context.state.domain.id
|
|
558
692
|
const repo = getRepository(TwinReference)
|
|
@@ -569,26 +703,98 @@ export class TwinReferenceResolver {
|
|
|
569
703
|
if (refusal) return { ok: false, error: refusal.message, errorCode: refusal.code, errorParams: refusal.params }
|
|
570
704
|
const instances: any[] = []
|
|
571
705
|
const failed: any[] = []
|
|
572
|
-
|
|
706
|
+
/*
|
|
707
|
+
* **진행을 구독으로 흘린다** (2026-08-21).
|
|
708
|
+
*
|
|
709
|
+
* 마스터 읽기는 원본에 여러 질의를 보내는 긴 작업이고, 화면은 그 사이 아무것도 알 수 없었다.
|
|
710
|
+
* 단계를 아는 것은 어댑터뿐이므로 어댑터가 말하고(표준 낱말), 호스트가 자기 두 단계(생성·기동)를
|
|
711
|
+
* 더해 흘린다.
|
|
712
|
+
*
|
|
713
|
+
* **분모는 어댑터가 선언한 것**이다(`masterSteps`). 선언하지 않은 어댑터는 `total` 이 없고, 그때
|
|
714
|
+
* 화면은 퍼센트를 만들지 않는다 — 모르는 진행률을 지어내지 않는다.
|
|
715
|
+
*/
|
|
716
|
+
const declared = adapter.masterSteps?.length
|
|
717
|
+
const stepTotal = declared === undefined ? undefined : declared + HOST_STEPS.length
|
|
718
|
+
const emit = (p: Omit<ReferenceProgress, 'siteCount'>) =>
|
|
719
|
+
pubsub.publish('twin-reference-progress', {
|
|
720
|
+
twinReferenceProgress: { ...p, siteCount: sites.length, total: p.total ?? stepTotal, source }
|
|
721
|
+
})
|
|
722
|
+
|
|
723
|
+
/*
|
|
724
|
+
* **읽은 뒤의 총평을 사이트마다 남긴다** (2026-08-22).
|
|
725
|
+
*
|
|
726
|
+
* 총평은 읽는 순간에만 있었다 — 대화상자를 닫으면 사라지고, 다시 보려면 같은 읽기를 또 해야 했다.
|
|
727
|
+
* 그런데 사람이 그것을 묻는 자리는 「모델 살펴보기」다(트윈을 보다가 「이건 왜 비었나」를 묻는다).
|
|
728
|
+
* 그래서 레퍼런스 행에 남겨 그 화면이 언제든 읽게 한다.
|
|
729
|
+
*
|
|
730
|
+
* 사실만 담는다(`assessMaster`) — 문장은 화면이 만들고, 더 알고 싶을 때 AI 가 이 사실만 근거로
|
|
731
|
+
* 서술한다. 다시 읽으면 그 사이트의 총평이 새것으로 바뀐다(지난 읽기의 총평을 현재로 보이게
|
|
732
|
+
* 두지 않는다).
|
|
733
|
+
*/
|
|
734
|
+
const assessments: Record<string, unknown> = {}
|
|
735
|
+
|
|
736
|
+
for (const [siteIndex, site] of sites.entries()) {
|
|
737
|
+
/*
|
|
738
|
+
* **실패를 실제로 막힌 단계에 적는다** (2026-08-22).
|
|
739
|
+
*
|
|
740
|
+
* 예전에는 실패를 무조건 `provision` 으로 적었다. 그래서 원본을 읽다 끊긴 것도 화면에는
|
|
741
|
+
* 「트윈 생성에서 실패」로 떴다 — 사람이 원인을 엉뚱한 자리에서 찾는다(실제로 그렇게 됐다).
|
|
742
|
+
*
|
|
743
|
+
* 규약이 답을 준다: `done` 은 **끝낸 단계 수**다(`ReferenceProgress.done`). 그러면 지금 막힌 것은
|
|
744
|
+
* 그 다음 단계, 즉 `masterSteps[done]` 이다. 그 표를 넘어서면(호스트 단계) 마지막으로 알린 것을 쓴다.
|
|
745
|
+
*
|
|
746
|
+
* 이 규칙은 두 가지 보고 방식에 함께 맞는다 — 단계 **시작**에 알리는 어댑터(`done` 이 아직 0)와
|
|
747
|
+
* 단계 **완료**에 알리는 어댑터(`done` 이 1 늘어난다) 모두 「끝낸 수」만 지키면 된다.
|
|
748
|
+
*/
|
|
749
|
+
let lastDone = 0
|
|
750
|
+
let lastStep: ReferenceProgress['step'] = (adapter.masterSteps?.[0] ?? 'provision') as ReferenceProgress['step']
|
|
751
|
+
const track = (p: Omit<ReferenceProgress, 'siteCount'>) => {
|
|
752
|
+
lastDone = p.done
|
|
753
|
+
lastStep = p.step
|
|
754
|
+
emit(p)
|
|
755
|
+
}
|
|
573
756
|
try {
|
|
574
|
-
const master = await adapter.fetchMaster(cfg, site
|
|
757
|
+
const master = await adapter.fetchMaster(cfg, site, st =>
|
|
758
|
+
track({ siteId: site.siteId, siteIndex, step: st.key, done: st.done, total: stepTotal, detail: st.detail })
|
|
759
|
+
)
|
|
575
760
|
/* 어느 레퍼런스의 **어느 사이트**에서 왔는지 남긴다 — 다시 읽기가 같은 어댑터·같은 사이트로 돌아오려면
|
|
576
761
|
사이트 정체가 있어야 한다(`source` 는 참조 이름이라 사이트 전부에 같다). */
|
|
762
|
+
/*
|
|
763
|
+
* 정책은 **선언이거나 의도에서 곧바로 따라오는 것**이다: `live=true` 는 미러를 띄우겠다는 뜻이고
|
|
764
|
+
* 미러는 정의상 `resync` 다(외부가 진실이므로 우리 과거를 이어 붙이지 않는다). 시뮬로 띄우려면
|
|
765
|
+
* 스스로 선언해야 한다 — 그 자리에서 우리가 고르면 재기동마다 이력을 지우는 트윈이 조용히 생긴다.
|
|
766
|
+
*/
|
|
767
|
+
assessments[site.siteId] = assessMaster(master, site.siteId)
|
|
768
|
+
const policy = readRestartPolicy(restartPolicy ?? (live ? 'resync' : undefined), `importTwinReference("${source}")`)
|
|
769
|
+
track({ siteId: site.siteId, siteIndex, step: 'provision', done: declared ?? 0, total: stepTotal })
|
|
577
770
|
const res = await TwinEngine.ingestMaster(
|
|
578
771
|
domainId,
|
|
579
772
|
{ ...master, origin: { kind: 'reference', source, siteId: site.siteId, ...(spaceId ? { spaceId } : {}) } },
|
|
773
|
+
policy,
|
|
580
774
|
{ spaceId },
|
|
581
775
|
context.state.user
|
|
582
776
|
)
|
|
777
|
+
track({ siteId: site.siteId, siteIndex, step: 'start', done: (declared ?? 0) + 1, total: stepTotal })
|
|
583
778
|
if (start !== false) {
|
|
584
779
|
// live=true + 어댑터가 openLiveFeed 지원 → mirror(실 이벤트 미러). 아니면 sim 기동(fallback).
|
|
585
|
-
|
|
780
|
+
/* 읽기 커서를 저장할 자리를 함께 넘긴다 — 레퍼런스의 이름은 호스트가 붙이는 것이고
|
|
781
|
+
커넥터는 그것을 알 수 없다(§`startReferenceLiveFeed`). */
|
|
782
|
+
const wentLive = live
|
|
783
|
+
? await startReferenceLiveFeed(domainId, ref.adapterType, cfg, site, res.instanceId, ref.source)
|
|
784
|
+
: false
|
|
586
785
|
if (!wentLive) await TwinEngine.startFromRegistry(domainId, res.instanceId)
|
|
587
786
|
instances.push({ siteId: site.siteId, instanceId: res.instanceId, spaceId: res.spaceId, mode: wentLive ? 'live' : 'sim', warnings: res.warnings })
|
|
588
787
|
} else {
|
|
589
788
|
instances.push({ siteId: site.siteId, instanceId: res.instanceId, spaceId: res.spaceId, mode: 'provisioned', warnings: res.warnings })
|
|
590
789
|
}
|
|
591
790
|
} catch (e: any) {
|
|
791
|
+
/*
|
|
792
|
+
* **실패한 단계를 그대로 알린다.** 화면이 진행 표시를 지우지 않고 그 자리를 붉게 두어야
|
|
793
|
+
* 사람이 「어디까지 갔는지」를 안다. 막힌 자리는 「끝낸 수」의 다음 단계다(위 주석).
|
|
794
|
+
*/
|
|
795
|
+
const at = (adapter.masterSteps?.[lastDone] ?? lastStep) as ReferenceProgress['step']
|
|
796
|
+
emit({ siteId: site.siteId, siteIndex, step: at, done: lastDone, total: stepTotal, failed: true, error: e?.message ?? String(e) })
|
|
797
|
+
|
|
592
798
|
failed.push({ siteId: site.siteId, error: e?.message ?? 'failed' })
|
|
593
799
|
}
|
|
594
800
|
}
|
|
@@ -600,6 +806,10 @@ export class TwinReferenceResolver {
|
|
|
600
806
|
{
|
|
601
807
|
status: 'connected',
|
|
602
808
|
lastSyncedAt: new Date().toISOString(),
|
|
809
|
+
/* 못 옮긴 값을 **세어 남긴다**. 아무 인스턴스도 보고하지 않았으면 `null` — 「없었다」로 위장하지 않는다. */
|
|
810
|
+
lastWarningCount: instances.some((i: any) => Array.isArray(i.warnings))
|
|
811
|
+
? instances.reduce((n: number, i: any) => n + (Array.isArray(i.warnings) ? i.warnings.length : 0), 0)
|
|
812
|
+
: null,
|
|
603
813
|
/*
|
|
604
814
|
* **영어 canonical 로 남긴다.** 이 값은 DB 에 저장되고 연결 화면이 그대로 뿌린다 — 예전에는
|
|
605
815
|
* 한글 산문(`N개 사이트 실패`)이라 다섯 언어 화면에 한글이 새어 나갔다. 어댑터가 주는 사유도
|
|
@@ -607,7 +817,24 @@ export class TwinReferenceResolver {
|
|
|
607
817
|
* 어느 사이트가 왜 실패했는지는 `failed[]` 가 답한다 — 화면이 그것을 보여 준다.
|
|
608
818
|
*/
|
|
609
819
|
lastError: failed.length ? `${failed.length} of ${sites.length} site(s) failed: ${failed.map((f: any) => f.siteId).join(', ')}` : null,
|
|
610
|
-
|
|
820
|
+
/*
|
|
821
|
+
* ── 실패 사유를 **남긴다** (2026-08-21) ────────────────────────────────
|
|
822
|
+
*
|
|
823
|
+
* 사유는 대화상자에만 있었다. 닫으면 사라지고, 다시 보려면 같은 실패를 다시 만들어야 했다 —
|
|
824
|
+
* 실제로 「승화푸드」 연결이 그 상태였다(요약 한 줄만 남고 원인은 어디에도 없었다).
|
|
825
|
+
*
|
|
826
|
+
* `scopeSpec` 은 이미 이 레퍼런스의 부속 사실을 담는 자리이므로(`produced`) 스키마를 늘리지 않고
|
|
827
|
+
* 여기 적는다. 성공한 회차에는 **비운다** — 지나간 실패를 현재 상태처럼 보이게 두지 않는다.
|
|
828
|
+
*/
|
|
829
|
+
scopeSpec: {
|
|
830
|
+
...((ref.scopeSpec as any) ?? {}),
|
|
831
|
+
produced: [...prev, ...producedNew],
|
|
832
|
+
/* 이번에 읽은 사이트의 총평만 갈아 끼운다 — 읽지 않은 사이트의 것은 그대로 둔다. */
|
|
833
|
+
assessments: { ...(((ref.scopeSpec as any) ?? {}).assessments ?? {}), ...assessments },
|
|
834
|
+
...(failed.length
|
|
835
|
+
? { lastFailures: failed.map((f: any) => ({ siteId: f.siteId, error: f.error, at: new Date().toISOString() })) }
|
|
836
|
+
: { lastFailures: [] })
|
|
837
|
+
}
|
|
611
838
|
} as any
|
|
612
839
|
)
|
|
613
840
|
return { ok: instances.length > 0, instances, failed }
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CreateDateColumn, Entity, Index, Column, RelationId, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'
|
|
2
|
-
import { ObjectType, Field, ID } from 'type-graphql'
|
|
2
|
+
import { ObjectType, Field, ID, Int } from 'type-graphql'
|
|
3
3
|
|
|
4
4
|
import { Domain, ScalarObject } from '@things-factory/shell'
|
|
5
5
|
|
|
@@ -70,6 +70,44 @@ export class TwinReference {
|
|
|
70
70
|
@Field({ nullable: true, description: 'Last connection/sync error message, if any.' })
|
|
71
71
|
lastError?: string
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* 마지막 동기에서 **못 옮긴 값의 수**. `null` = 세지 않았다(0 과 다르다).
|
|
75
|
+
*
|
|
76
|
+
* 세지 않은 구조 인제스트는 도크를 랙으로 바꿔 놓고도 조용히 성공한다. 그 사실을 사후에 물을 수
|
|
77
|
+
* 있어야 단계 판정이 「구조는 됐다」를 말할 수 있다 — 없는 동안 판정은 늘 `unmeasured` 였다.
|
|
78
|
+
* 기본값을 두지 않는다: 0 을 기본으로 깔면 「경고 없었다」와 「세지 않았다」가 같아진다.
|
|
79
|
+
*/
|
|
80
|
+
@Column({ type: 'int', nullable: true })
|
|
81
|
+
@Field(type => Int, { nullable: true, description: 'Number of values the last sync could not carry over (mapping fallbacks). Null means they were not counted at all, which is different from zero.' })
|
|
82
|
+
lastWarningCount?: number
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* **어디까지 읽었나** — 라이브 피드의 읽기 커서(§`LiveFeedContinuity`).
|
|
86
|
+
*
|
|
87
|
+
* ── 왜 저장하나 (2026-08-23 실측) ──────────────────────────────────────────
|
|
88
|
+
* 어댑터가 붙을 때마다 `Date.now() − 되돌아볼 날수` 로 창을 새로 만들고 있었다. 그래서 **재기동마다
|
|
89
|
+
* 미러의 과거가 잘렸다** — 작업 2,855 → 2,820(8시간 흐른 만큼). 미러가 아는 것이 「원본의 사실」이
|
|
90
|
+
* 아니라 「창의 함수」였고, 그 사실을 아무도 말하지 않았다.
|
|
91
|
+
*
|
|
92
|
+
* 커널이 그 갈림을 이미 적어 두었다(§`hydrateContinuity`): 「원천이 애초에 다시 말해 주지 않는 축」은
|
|
93
|
+
* 재기동 연속성으로 이어받는다. 「우리가 어디까지 읽었나」가 정확히 그 성질이므로 **어댑터의 사물함이
|
|
94
|
+
* 아니라 이 층**에 있다 — 원본이 늘 때마다 저장 기제가 늘고 그중 하나가 조용히 다르게 동작하지 않게.
|
|
95
|
+
*
|
|
96
|
+
* ── 왜 캐시가 아니라 표인가 ────────────────────────────────────────────────
|
|
97
|
+
* 만료되면 창이 다시 미끄러지고, 그 손실은 오류 없이 조용하다. 스냅샷 체크포인트와 성질이 다르다
|
|
98
|
+
* (그쪽은 잃어도 원천이 정정해 준다 — 이 값은 **잃으면 원천에 묻지 않게 된다**).
|
|
99
|
+
*
|
|
100
|
+
* 모양은 `{ streams: { [흐름]: { since?, seen[] } }, firstAttachedAt? }` 다. **흐름 이름은 어댑터가
|
|
101
|
+
* 정한다** — 원본마다 흐름 수와 뜻이 다르므로 이 층은 열쇠로만 다룬다. `simple-json` 이라 드라이버
|
|
102
|
+
* 다섯을 그대로 지난다.
|
|
103
|
+
*
|
|
104
|
+
* `firstAttachedAt` 은 「언제부터 아는가」다. 커널은 상한만 안다(`nowTime` = 마지막으로 들은 시각).
|
|
105
|
+
* 둘이 함께 「이 트윈이 아는 구간」이고, 화면이 수를 보일 때 그 구간을 말해야 한다.
|
|
106
|
+
*/
|
|
107
|
+
@Column({ type: 'simple-json', nullable: true })
|
|
108
|
+
@Field(type => ScalarObject, { nullable: true, description: 'Live feed read cursor carried across restarts, keyed by adapter-defined stream. Null means the feed has never attached, and the next attach decides its first window.' })
|
|
109
|
+
liveCursor?: any
|
|
110
|
+
|
|
73
111
|
@CreateDateColumn()
|
|
74
112
|
@Field({ nullable: true, description: 'Timestamp when the reference was first created.' })
|
|
75
113
|
createdAt?: Date
|
|
@@ -17,6 +17,19 @@ export type CommandAuditInput = {
|
|
|
17
17
|
command: any
|
|
18
18
|
/** 커널 판정. */
|
|
19
19
|
ack: { accepted?: boolean; errorCode?: string; error?: string }
|
|
20
|
+
/**
|
|
21
|
+
* **원본(어댑터)이 준 답** — 허락과 일어남 사이에 끼어 있는 것을 같은 기록에 닫는다 (ADR-0029 §1 결정 C).
|
|
22
|
+
*
|
|
23
|
+
* ── 왜 이 칸이 필요한가 ──────────────────────────────────────────────────
|
|
24
|
+
* 명령이 원본으로 가면 감사는 「허락했다」까지만 증명한다 — 어댑터가 조용히 실패하면 원장에는 승인만
|
|
25
|
+
* 남는다. 그래서 어댑터의 답을 **같은 행**에 되적는다.
|
|
26
|
+
*
|
|
27
|
+
* 셋을 구별한다:
|
|
28
|
+
* · 없음(`undefined`) — **원본에 묻지 않았다**(커널이 받은 명령이다). 「모른다」와 다르다.
|
|
29
|
+
* · `{ answered: false }` — 물었는데 **답을 못 받았다** → `unknown` 으로 남긴다. 성공으로 적지 않는다.
|
|
30
|
+
* · `{ answered: true, ok, code? }` — 원본이 답했다(적용·거절 모두 사실이다).
|
|
31
|
+
*/
|
|
32
|
+
source?: { answered: boolean; ok?: boolean; code?: string }
|
|
20
33
|
/** 세션이 아는 사용자 — **서버만 채운다.** */
|
|
21
34
|
actor?: { id?: string; email?: string; name?: string }
|
|
22
35
|
/** 서버 시각. 호출부가 주입해 테스트가 시간을 고정할 수 있게 한다. */
|
|
@@ -35,6 +48,13 @@ export type CommandAuditRow = {
|
|
|
35
48
|
actorName?: string
|
|
36
49
|
accepted: boolean
|
|
37
50
|
errorCode?: string
|
|
51
|
+
/**
|
|
52
|
+
* 원본이 그래서 어떻게 했나 — `applied` · `refused` · `unknown`(물었지만 답이 없다).
|
|
53
|
+
* **없으면 묻지 않은 것이다**(커널이 받은 명령). 0 이나 성공으로 채우지 않는다.
|
|
54
|
+
*/
|
|
55
|
+
sourceOutcome?: 'applied' | 'refused' | 'unknown'
|
|
56
|
+
/** 원본이 준 사유 코드 — 있으면 그대로. 우리가 지어내지 않는다. */
|
|
57
|
+
sourceCode?: string
|
|
38
58
|
args?: any
|
|
39
59
|
argsTruncated: boolean
|
|
40
60
|
}
|
|
@@ -48,7 +68,7 @@ export function normalizeOrigin(v: unknown): CommandOrigin {
|
|
|
48
68
|
const ARGS_MAX = 4000
|
|
49
69
|
|
|
50
70
|
export function buildCommandAudit(input: CommandAuditInput): CommandAuditRow {
|
|
51
|
-
const { command, ack, actor, at } = input
|
|
71
|
+
const { command, ack, actor, at, source } = input
|
|
52
72
|
const argsText = safeJson(command?.args)
|
|
53
73
|
const truncated = argsText !== null && argsText.length > ARGS_MAX
|
|
54
74
|
return {
|
|
@@ -57,6 +77,8 @@ export function buildCommandAudit(input: CommandAuditInput): CommandAuditRow {
|
|
|
57
77
|
commandType: str(command?.type),
|
|
58
78
|
origin: normalizeOrigin(command?.origin),
|
|
59
79
|
reason: str(command?.reason),
|
|
80
|
+
/* 원본의 답 — 묻지 않았으면 칸을 만들지 않는다(모름과 구별한다). */
|
|
81
|
+
...(source ? { sourceOutcome: sourceOutcomeOf(source), ...(source.code ? { sourceCode: source.code } : {}) } : {}),
|
|
60
82
|
/* 신원 — 세션 값만. 커맨드에 실려 온 신원 주장은 쳐다보지 않는다. */
|
|
61
83
|
actorId: str(actor?.id),
|
|
62
84
|
actorEmail: str(actor?.email),
|
|
@@ -79,3 +101,14 @@ function safeJson(v: unknown): string | null {
|
|
|
79
101
|
return null /* 순환 참조 등 — 못 재면 자르지 않는다(원본을 그대로 둔다) */
|
|
80
102
|
}
|
|
81
103
|
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 원본의 답을 세 값으로 — **답이 없으면 `unknown`**(성공으로 적지 않는다).
|
|
107
|
+
*
|
|
108
|
+
* 이 함수가 있는 이유: 「물었지만 답이 없다」를 실패로도 성공으로도 적을 수 없다. 실패로 적으면 일어난 일을
|
|
109
|
+
* 없던 일로 만들고, 성공으로 적으면 없던 일을 일어난 일로 만든다. 셋째 값이 사실이다.
|
|
110
|
+
*/
|
|
111
|
+
export function sourceOutcomeOf(source: { answered: boolean; ok?: boolean }): 'applied' | 'refused' | 'unknown' {
|
|
112
|
+
if (!source.answered || typeof source.ok !== 'boolean') return 'unknown'
|
|
113
|
+
return source.ok ? 'applied' : 'refused'
|
|
114
|
+
}
|
|
@@ -99,6 +99,26 @@ export class TwinAuditEvent {
|
|
|
99
99
|
reason?: string
|
|
100
100
|
|
|
101
101
|
/* ── 신원: 세션에서만 온다 ── */
|
|
102
|
+
/*
|
|
103
|
+
* **원본이 그래서 어떻게 했나** — 허락과 일어남 사이에 끼어 있는 것을 같은 행에 닫는다
|
|
104
|
+
* (ADR-0029 §1 결정 C, 2026-08-19).
|
|
105
|
+
*
|
|
106
|
+
* 명령이 원본으로 가면 감사는 「허락했다」까지만 증명한다 — 어댑터가 조용히 실패하면 원장에는 승인만
|
|
107
|
+
* 남는다. 그래서 어댑터의 답을 되적는다.
|
|
108
|
+
*
|
|
109
|
+
* · 비어 있음 — **원본에 묻지 않았다**(커널이 받은 명령이다). 「모른다」와 다르다.
|
|
110
|
+
* · `unknown` — 물었는데 **답을 못 받았다**. 성공으로 적지 않는다.
|
|
111
|
+
* · `applied` · `refused` — 원본이 답했다(둘 다 사실이다).
|
|
112
|
+
*/
|
|
113
|
+
@Column({ nullable: true })
|
|
114
|
+
@Field({ nullable: true, description: "What the source did with it: 'applied' | 'refused' | 'unknown' (asked but no answer). Empty means the source was not asked — the kernel took the command." })
|
|
115
|
+
sourceOutcome?: string
|
|
116
|
+
|
|
117
|
+
/** 원본이 준 사유 코드 — 있으면 그대로 적는다(우리가 지어내지 않는다). */
|
|
118
|
+
@Column({ nullable: true })
|
|
119
|
+
@Field({ nullable: true, description: 'Reason code the source returned, as it gave it.' })
|
|
120
|
+
sourceCode?: string
|
|
121
|
+
|
|
102
122
|
@Column({ nullable: true })
|
|
103
123
|
@Field({ nullable: true, description: 'Approving user id, stamped by the server from the session.' })
|
|
104
124
|
actorId?: string
|