@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
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* `${domainId}:${instanceId}` — 이 저장소가 이미 쓰는 모양이다(`structureRevCache`·`measuredCache`).
|
|
27
27
|
* 새 모양을 만들지 않는다. 파싱은 **첫 구분자**에서 자르므로 `instanceId` 에 `:` 가 들어 있어도
|
|
28
28
|
* 안전하다(레퍼런스 소스 이름에 들어갈 수 있다). 대신 `domainId` 에 구분자가 있으면 키가 모호해지므로
|
|
29
|
-
* **그때는
|
|
29
|
+
* **그때는 오류를 낸다** — 조용히 잘못된 키를 만들면 그게 곧 이 결함의 재발이다.
|
|
30
30
|
*/
|
|
31
31
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
32
|
exports.RUNTIME_KEY_SEP = void 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-key.js","sourceRoot":"","sources":["../../server/engine/runtime-key.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;;;AAUH,gCAOC;AAGD,0CAIC;AAGD,gCAEC;AA3BD,kDAAkD;AACrC,QAAA,eAAe,GAAG,GAAG,CAAA;AAElC;;;;GAIG;AACH,SAAgB,UAAU,CAAC,QAAgB,EAAE,UAAkB;IAC7D,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,8EAA8E,CAAC,CAAA;IAC9G,IAAI,CAAC,UAAU;QAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAA;IACtE,IAAI,QAAQ,CAAC,QAAQ,CAAC,uBAAe,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CAAC,0CAA0C,uBAAe,kCAAkC,CAAC,CAAA;IAC9G,CAAC;IACD,OAAO,GAAG,QAAQ,GAAG,uBAAe,GAAG,UAAU,EAAE,CAAA;AACrD,CAAC;AAED,iDAAiD;AACjD,SAAgB,eAAe,CAAC,GAAW;IACzC,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,uBAAe,CAAC,CAAA;IACvC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,GAAG,wBAAwB,CAAC,CAAA;IACvG,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAA;AACtE,CAAC;AAED,iDAAiD;AACjD,SAAgB,UAAU,CAAC,GAAW,EAAE,QAAgB;IACtD,OAAO,CAAC,CAAC,QAAQ,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,QAAQ,GAAG,uBAAe,EAAE,CAAC,CAAA;AACtE,CAAC","sourcesContent":["/*\n * 런타임 레지스트리 키 — **트윈의 정체성은 `(도메인, 인스턴스 id)` 다.** 순수.\n *\n * ── 왜 있나 (2026-08-06 테넌트 격리 전수 확인) ───────────────────────────────\n * `TwinEngine.instances` 와 `recovered` 가 **`instanceId` 하나로** 키를 잡고 있었다. 그런데 DB 유일성은\n * `[domain, instanceId]` 이고(`TwinInstance` 인덱스) 트윈의 정체성도 그쪽이다. 즉 런타임 맵만 **전역\n * 이름 공간**이었다.\n *\n * **충돌은 이론이 아니다.** 레퍼런스 경로가 `instanceId` 로 **레퍼런스 소스 이름**을 쓴다\n * (`sap-ewm-1710`). 벤더 시스템 이름은 테넌트가 **공유하는 어휘**라, 두 테넌트가 같은 시스템에\n * 연결하면 같은 id 가 된다 — 드문 사고가 아니라 예상되는 경우다.\n *\n * 그래서 무엇이 어긋났나:\n * · `startLive` 의 조기 반환이 A 의 런타임을 B 에게 돌려주고 `ingestLive` 는 도메인을 확인하지 않아\n * **B 의 라이브 이벤트가 A 의 커널에 적용**될 수 있었다.\n * · `recovered` 가 겹치면 웜스타트 씨앗이 **남의 스냅샷**이 되어 재고가 섞인다.\n * · `start`/`stop` 이 남의 런타임을 집으므로 B 는 \"기동됨\" 을 받고도 이후 전부 거부됐다.\n *\n * 이 부류는 **이미 한 번 물었다** — `twin-lifecycle-mutation.ts` 주석이 증언한다: \"과거엔\n * `Object.keys(TwinEngine.instances)` 로 전역 반환 → 다른 도메인 트윈이 새던 버그.\" 그 자리만 고쳤다.\n * 그래서 이번에는 가드를 더 붙이지 않고 **키를 고친다** — 저수준 메서드가 `domainId` 를 받게 되는 것이\n * 요점이다. \"누구의 트윈인가\" 를 말하지 않고는 만질 수 없다.\n *\n * ── 구분자 규약 ─────────────────────────────────────────────────────────────\n * `${domainId}:${instanceId}` — 이 저장소가 이미 쓰는 모양이다(`structureRevCache`·`measuredCache`).\n * 새 모양을 만들지 않는다. 파싱은 **첫 구분자**에서 자르므로 `instanceId` 에 `:` 가 들어 있어도\n * 안전하다(레퍼런스 소스 이름에 들어갈 수 있다). 대신 `domainId` 에 구분자가 있으면 키가 모호해지므로\n * **그때는
|
|
1
|
+
{"version":3,"file":"runtime-key.js","sourceRoot":"","sources":["../../server/engine/runtime-key.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;;;AAUH,gCAOC;AAGD,0CAIC;AAGD,gCAEC;AA3BD,kDAAkD;AACrC,QAAA,eAAe,GAAG,GAAG,CAAA;AAElC;;;;GAIG;AACH,SAAgB,UAAU,CAAC,QAAgB,EAAE,UAAkB;IAC7D,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,8EAA8E,CAAC,CAAA;IAC9G,IAAI,CAAC,UAAU;QAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAA;IACtE,IAAI,QAAQ,CAAC,QAAQ,CAAC,uBAAe,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CAAC,0CAA0C,uBAAe,kCAAkC,CAAC,CAAA;IAC9G,CAAC;IACD,OAAO,GAAG,QAAQ,GAAG,uBAAe,GAAG,UAAU,EAAE,CAAA;AACrD,CAAC;AAED,iDAAiD;AACjD,SAAgB,eAAe,CAAC,GAAW;IACzC,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,uBAAe,CAAC,CAAA;IACvC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,GAAG,wBAAwB,CAAC,CAAA;IACvG,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAA;AACtE,CAAC;AAED,iDAAiD;AACjD,SAAgB,UAAU,CAAC,GAAW,EAAE,QAAgB;IACtD,OAAO,CAAC,CAAC,QAAQ,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,QAAQ,GAAG,uBAAe,EAAE,CAAC,CAAA;AACtE,CAAC","sourcesContent":["/*\n * 런타임 레지스트리 키 — **트윈의 정체성은 `(도메인, 인스턴스 id)` 다.** 순수.\n *\n * ── 왜 있나 (2026-08-06 테넌트 격리 전수 확인) ───────────────────────────────\n * `TwinEngine.instances` 와 `recovered` 가 **`instanceId` 하나로** 키를 잡고 있었다. 그런데 DB 유일성은\n * `[domain, instanceId]` 이고(`TwinInstance` 인덱스) 트윈의 정체성도 그쪽이다. 즉 런타임 맵만 **전역\n * 이름 공간**이었다.\n *\n * **충돌은 이론이 아니다.** 레퍼런스 경로가 `instanceId` 로 **레퍼런스 소스 이름**을 쓴다\n * (`sap-ewm-1710`). 벤더 시스템 이름은 테넌트가 **공유하는 어휘**라, 두 테넌트가 같은 시스템에\n * 연결하면 같은 id 가 된다 — 드문 사고가 아니라 예상되는 경우다.\n *\n * 그래서 무엇이 어긋났나:\n * · `startLive` 의 조기 반환이 A 의 런타임을 B 에게 돌려주고 `ingestLive` 는 도메인을 확인하지 않아\n * **B 의 라이브 이벤트가 A 의 커널에 적용**될 수 있었다.\n * · `recovered` 가 겹치면 웜스타트 씨앗이 **남의 스냅샷**이 되어 재고가 섞인다.\n * · `start`/`stop` 이 남의 런타임을 집으므로 B 는 \"기동됨\" 을 받고도 이후 전부 거부됐다.\n *\n * 이 부류는 **이미 한 번 물었다** — `twin-lifecycle-mutation.ts` 주석이 증언한다: \"과거엔\n * `Object.keys(TwinEngine.instances)` 로 전역 반환 → 다른 도메인 트윈이 새던 버그.\" 그 자리만 고쳤다.\n * 그래서 이번에는 가드를 더 붙이지 않고 **키를 고친다** — 저수준 메서드가 `domainId` 를 받게 되는 것이\n * 요점이다. \"누구의 트윈인가\" 를 말하지 않고는 만질 수 없다.\n *\n * ── 구분자 규약 ─────────────────────────────────────────────────────────────\n * `${domainId}:${instanceId}` — 이 저장소가 이미 쓰는 모양이다(`structureRevCache`·`measuredCache`).\n * 새 모양을 만들지 않는다. 파싱은 **첫 구분자**에서 자르므로 `instanceId` 에 `:` 가 들어 있어도\n * 안전하다(레퍼런스 소스 이름에 들어갈 수 있다). 대신 `domainId` 에 구분자가 있으면 키가 모호해지므로\n * **그때는 오류를 낸다** — 조용히 잘못된 키를 만들면 그게 곧 이 결함의 재발이다.\n */\n\n/** 도메인과 인스턴스 id 를 잇는 문자 — 저장소의 기존 캐시 키와 같은 모양. */\nexport const RUNTIME_KEY_SEP = ':'\n\n/**\n * 런타임 레지스트리 키.\n *\n * @throws 도메인 id 가 비었거나 구분자를 담고 있으면(키가 모호해진다). 인스턴스 id 가 비었으면.\n */\nexport function runtimeKey(domainId: string, instanceId: string): string {\n if (!domainId) throw new Error('runtimeKey: domainId is required — a twin runtime always belongs to a tenant')\n if (!instanceId) throw new Error('runtimeKey: instanceId is required')\n if (domainId.includes(RUNTIME_KEY_SEP)) {\n throw new Error(`runtimeKey: domainId must not contain \"${RUNTIME_KEY_SEP}\" (would make the key ambiguous)`)\n }\n return `${domainId}${RUNTIME_KEY_SEP}${instanceId}`\n}\n\n/** 키를 되돌린다 — 첫 구분자에서 자른다(인스턴스 id 안의 구분자는 보존). */\nexport function parseRuntimeKey(key: string): { domainId: string; instanceId: string } {\n const at = key.indexOf(RUNTIME_KEY_SEP)\n if (at <= 0 || at === key.length - 1) throw new Error(`parseRuntimeKey: \"${key}\" is not a runtime key`)\n return { domainId: key.slice(0, at), instanceId: key.slice(at + 1) }\n}\n\n/** 이 키가 이 테넌트의 것인가 — 레지스트리를 조회할 때 도메인으로 걸러낸다. */\nexport function isOfDomain(key: string, domainId: string): boolean {\n return !!domainId && key.startsWith(`${domainId}${RUNTIME_KEY_SEP}`)\n}\n"]}
|
|
@@ -17,6 +17,13 @@ export interface SpecCoverageReport {
|
|
|
17
17
|
duration: 'measured' | 'declared' | 'default';
|
|
18
18
|
variability?: string;
|
|
19
19
|
parameters: string[];
|
|
20
|
+
/**
|
|
21
|
+
* 그 모수를 **무엇으로** 읽었나(`id → measured|declared`) — 없는 id 는 코드 상수를 썼다는 뜻이다.
|
|
22
|
+
*
|
|
23
|
+
* 소요시간이 근거를 밝히듯 모수도 밝힌다(2026-08-19): 수율이 이력에서 온 값인지 사람이 적은 값인지
|
|
24
|
+
* 상수인지에 따라 「불량이 늘었다」는 화면 문장의 무게가 다르다.
|
|
25
|
+
*/
|
|
26
|
+
parameterBasis?: Record<string, 'measured' | 'declared'>;
|
|
20
27
|
}[];
|
|
21
28
|
/** 이력·계산에서 온 소요(추정기) — 선언값보다 강한 근거. */
|
|
22
29
|
measuredDurations: number;
|
|
@@ -36,6 +43,7 @@ export declare function classifySpecCoverage(cov: {
|
|
|
36
43
|
duration: 'measured' | 'declared' | 'default';
|
|
37
44
|
variability?: string;
|
|
38
45
|
parameters?: string[];
|
|
46
|
+
parameterBasis?: Record<string, 'measured' | 'declared'>;
|
|
39
47
|
}[];
|
|
40
48
|
measuredDurations?: number;
|
|
41
49
|
declaredDurations?: number;
|
|
@@ -25,7 +25,9 @@ function classifySpecCoverage(cov) {
|
|
|
25
25
|
kind: o.kind,
|
|
26
26
|
duration: o.duration,
|
|
27
27
|
...(o.variability ? { variability: o.variability } : {}),
|
|
28
|
-
parameters: o.parameters ?? []
|
|
28
|
+
parameters: o.parameters ?? [],
|
|
29
|
+
/* 근거는 커널이 말한 것만 나른다 — 없으면 필드를 만들지 않는다(상수를 썼다는 뜻이 빈칸이다). */
|
|
30
|
+
...(o.parameterBasis && Object.keys(o.parameterBasis).length ? { parameterBasis: o.parameterBasis } : {})
|
|
29
31
|
}));
|
|
30
32
|
const measuredDurations = cov.measuredDurations ?? operations.filter(o => o.duration === 'measured').length;
|
|
31
33
|
const declaredDurations = cov.declaredDurations ?? operations.filter(o => o.duration === 'declared').length;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spec-coverage.js","sourceRoot":"","sources":["../../server/engine/spec-coverage.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;
|
|
1
|
+
{"version":3,"file":"spec-coverage.js","sourceRoot":"","sources":["../../server/engine/spec-coverage.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;AA2CH,oDAqCC;AAOD,4CAOC;AAvDD;;;GAGG;AACH,SAAgB,oBAAoB,CAAC,GAWpC;IACC,MAAM,GAAG,GAAG,GAAG,EAAE,UAAU,CAAA;IAC3B,IAAI,CAAC,GAAG,EAAE,MAAM;QAAE,OAAO,SAAS,CAAA;IAClC,MAAM,UAAU,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/B,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,UAAU,EAAE,CAAC,CAAC,UAAU,IAAI,EAAE;QAC9B,2DAA2D;QAC3D,GAAG,CAAC,CAAC,CAAC,cAAc,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1G,CAAC,CAAC,CAAA;IACH,MAAM,iBAAiB,GAAG,GAAG,CAAC,iBAAiB,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,MAAM,CAAA;IAC3G,MAAM,iBAAiB,GAAG,GAAG,CAAC,iBAAiB,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,MAAM,CAAA;IAC3G,MAAM,gBAAgB,GAAG,GAAG,CAAC,gBAAgB,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,MAAM,CAAA;IACxG,MAAM,gBAAgB,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,WAAW,KAAK,UAAU,CAAC,CAAC,MAAM,CAAA;IACrG,iGAAiG;IACjG,MAAM,QAAQ,GAAG,iBAAiB,GAAG,iBAAiB,CAAA;IACtD,MAAM,aAAa,GACjB,QAAQ,KAAK,CAAC;QACZ,CAAC,CAAC,UAAU;QACZ,CAAC,CAAC,gBAAgB,GAAG,CAAC;YACpB,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,iBAAiB,KAAK,CAAC;gBACvB,CAAC,CAAC,YAAY;gBACd,CAAC,CAAC,kBAAkB,CAAA;IAC5B,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAE,CAAA;AAChH,CAAC;AAED;;;;GAIG;AACH,SAAgB,gBAAgB,CAAC,MAAW;IAC1C,IAAI,OAAO,MAAM,EAAE,YAAY,KAAK,UAAU;QAAE,OAAO,SAAS,CAAA;IAChE,IAAI,CAAC;QACH,OAAO,oBAAoB,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC,CAAA;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC","sourcesContent":["/*\n * 시뮬 명세 자기보고 — **\"이 예측으로 무엇을 말할 수 있나\" 를 예측과 함께 내보낸다.**\n *\n * 배경(operato-twin design/plans/simulation-spec.md): 공정 소요·수율이 커널 소스의 상수였다. 이제\n * 마스터가 명세를 실어 오면 커널이 그것으로 실행하고, `specCoverage()` 가 무엇을 선언값으로 무엇을\n * 기본값으로 썼는지 보고한다. 그 보고를 **예측 응답에 붙이지 않으면** 받는 쪽은 상수로 계산한 숫자와\n * 현장 명세로 계산한 숫자를 구별할 수 없다 — 똑같이 \"p50 168\" 로 보인다.\n *\n * 언어 중립: 여기서는 **코드와 수치만** 낸다(`qualification` + 카운트). 사람이 읽는 문장은 표현\n * 계층(클라 i18next / AI 답변)이 만든다 — attention 과 같은 규율.\n */\n\n/** 예측 자격 — 선언된 명세가 얼마나 덮는지에 따라 이 예측으로 정당하게 말할 수 있는 범위가 달라진다. */\nexport type ForecastQualification =\n /** 모든 소요가 커널 기본 상수 → **상대 비교만**(\"자원 2대 더 넣으면 나아지나\"). 절대 시각 주장 불가. */\n | 'relative'\n /** 일부만 선언·실측 → 그 구간은 접지, 나머지는 상수. 혼합. */\n | 'partial'\n /** 소요 전부 선언 → 절대 예측 시도 가능. 단 **정확도는 이력이 확인한다**(선언 != 검증). */\n | 'absolute-capable'\n /**\n * 소요 전부가 **이 현장의 이력에서 온 값**(추정기) → 가장 강한 근거. 선언은 사람이 적은 값이고\n * 이것은 실제로 일어난 값이다. 그래도 \"정확하다\" 가 아니라 \"관측에 접지됐다\" 는 뜻이다.\n */\n | 'calibrated'\n\nexport interface SpecCoverageReport {\n operations: {\n kind: string\n duration: 'measured' | 'declared' | 'default'\n variability?: string\n parameters: string[]\n /**\n * 그 모수를 **무엇으로** 읽었나(`id → measured|declared`) — 없는 id 는 코드 상수를 썼다는 뜻이다.\n *\n * 소요시간이 근거를 밝히듯 모수도 밝힌다(2026-08-19): 수율이 이력에서 온 값인지 사람이 적은 값인지\n * 상수인지에 따라 「불량이 늘었다」는 화면 문장의 무게가 다르다.\n */\n parameterBasis?: Record<string, 'measured' | 'declared'>\n }[]\n /** 이력·계산에서 온 소요(추정기) — 선언값보다 강한 근거. */\n measuredDurations: number\n declaredDurations: number\n defaultDurations: number\n /** 변동(분포)을 가진 작업 종류 수. **0 이면 모든 소요가 고정값**이라 대기·병목이 과소평가된다. */\n varyingDurations: number\n qualification: ForecastQualification\n}\n\n/**\n * 명세 자격 판정(순수) — 커널 보고를 받아 분류한다. 커널을 모르므로 테스트가 쉽다.\n * 관측된 작업이 하나도 없으면(`operations` 빈 배열) 판정할 근거가 없으므로 **undefined**(모른다).\n */\nexport function classifySpecCoverage(cov: {\n operations?: {\n kind: string\n duration: 'measured' | 'declared' | 'default'\n variability?: string\n parameters?: string[]\n parameterBasis?: Record<string, 'measured' | 'declared'>\n }[]\n measuredDurations?: number\n declaredDurations?: number\n defaultDurations?: number\n}): SpecCoverageReport | undefined {\n const ops = cov?.operations\n if (!ops?.length) return undefined\n const operations = ops.map(o => ({\n kind: o.kind,\n duration: o.duration,\n ...(o.variability ? { variability: o.variability } : {}),\n parameters: o.parameters ?? [],\n /* 근거는 커널이 말한 것만 나른다 — 없으면 필드를 만들지 않는다(상수를 썼다는 뜻이 빈칸이다). */\n ...(o.parameterBasis && Object.keys(o.parameterBasis).length ? { parameterBasis: o.parameterBasis } : {})\n }))\n const measuredDurations = cov.measuredDurations ?? operations.filter(o => o.duration === 'measured').length\n const declaredDurations = cov.declaredDurations ?? operations.filter(o => o.duration === 'declared').length\n const defaultDurations = cov.defaultDurations ?? operations.filter(o => o.duration === 'default').length\n const varyingDurations = operations.filter(o => o.variability && o.variability !== 'constant').length\n /* 사다리: 전부 이력 → calibrated · 상수 없음 → absolute-capable · 접지가 하나도 없음 → relative · 나머지 → partial. */\n const grounded = measuredDurations + declaredDurations\n const qualification: ForecastQualification =\n grounded === 0\n ? 'relative'\n : defaultDurations > 0\n ? 'partial'\n : declaredDurations === 0\n ? 'calibrated'\n : 'absolute-capable'\n return { operations, measuredDurations, declaredDurations, defaultDurations, varyingDurations, qualification }\n}\n\n/**\n * 커널에서 읽는다 — **아직 이 API 가 없는 커널(발행 이전)이면 undefined.**\n * 그때 `undefined` 는 \"명세가 없다\" 가 아니라 \"모른다\" 다. 소비처가 그 둘을 섞지 않도록 주석을 남긴다:\n * 없는 것을 \"전부 기본값\" 이라고 단정하지 않는다(단정하려면 커널이 말해 줘야 한다).\n */\nexport function readSpecCoverage(kernel: any): SpecCoverageReport | undefined {\n if (typeof kernel?.specCoverage !== 'function') return undefined\n try {\n return classifySpecCoverage(kernel.specCoverage())\n } catch {\n return undefined\n }\n}\n"]}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export type StageId = 'structure' | 'live' | 'forecast' | 'closed-loop';
|
|
2
|
+
/** 판정에 쓰는 사실. `null` = **계측되지 않았다**(0 이 아니다). */
|
|
3
|
+
export interface StageEvidence {
|
|
4
|
+
/** 이 트윈이 든 자리 수. */
|
|
5
|
+
locationCount: number;
|
|
6
|
+
/** 자리에 놓인 물품 수(비직렬 수량 물품 포함 — 식품·화학은 개수가 아니라 총량으로 온다). */
|
|
7
|
+
itemCount: number;
|
|
8
|
+
/**
|
|
9
|
+
* 마스터를 옮기며 **못 옮긴 값의 수**. `null` = 세지 않았다.
|
|
10
|
+
*
|
|
11
|
+
* 0 이면 통과이고 n 이어도 통과다 — 이 판정이 요구하는 것은 「없다」가 아니라 **「말할 수 있다」**다.
|
|
12
|
+
* 못 옮긴 값을 세지 않은 구조 인제스트는 도크를 랙으로 바꿔 놓고도 조용히 성공한다.
|
|
13
|
+
*/
|
|
14
|
+
masterWarnings: number | null;
|
|
15
|
+
/**
|
|
16
|
+
* 이 축이 이 트윈에 **해당하나**. 레퍼런스 없이 템플릿으로 세운 트윈에는 옮겨 온 마스터가 없으므로
|
|
17
|
+
* 「못 옮긴 값」을 물을 대상이 아니다 — 그걸 미측정으로 접으면 영원히 배선을 기다린다.
|
|
18
|
+
*/
|
|
19
|
+
masterWarningsApplicable?: boolean;
|
|
20
|
+
/** 인제스트 누적 건수. */
|
|
21
|
+
liveIngestTotal: number;
|
|
22
|
+
/** **반출(remove)을 접은 누적 건수.** `null` = 계측되지 않음. 0 = 세었으나 없었다(→ 스냅샷 의심). */
|
|
23
|
+
removalsObserved: number | null;
|
|
24
|
+
/** 이 트윈에서 예측이 한 번이라도 돌았나. */
|
|
25
|
+
forecastRan: boolean;
|
|
26
|
+
/** 선언 적재량 — `declared`/`total`. `null` = 알 수 없음. */
|
|
27
|
+
specCoverage: {
|
|
28
|
+
declared: number;
|
|
29
|
+
total: number;
|
|
30
|
+
} | null;
|
|
31
|
+
/**
|
|
32
|
+
* 이 트윈이 **무엇인가** — 시뮬레이션은 원천에서 받는 것이 없다.
|
|
33
|
+
*
|
|
34
|
+
* 시뮬에 라이브를 요구하면 영원히 빨갛고, 그 빨강은 결함이 아니다. 도입 단계는 **연결된 트윈**의
|
|
35
|
+
* 축이므로 시뮬은 라이브 이후를 「해당 없음」으로 말한다(달성으로도, 미측정으로도 접지 않는다).
|
|
36
|
+
*/
|
|
37
|
+
subject?: 'live' | 'sim';
|
|
38
|
+
/** 피드 성격 — `snapshot-poll` 이면 반출 0 은 「아직」이 아니라 **구조적으로 불가**다. */
|
|
39
|
+
feed?: 'push' | 'delta-poll' | 'snapshot-poll' | 'none' | 'unknown';
|
|
40
|
+
/** 원본이 **선언∩구현**으로 액추에이션 능력을 갖나(`capabilitiesOf`). */
|
|
41
|
+
actuateCapability: boolean;
|
|
42
|
+
/** 내 명령의 효과가 인바운드로 **상관되어** 돌아온 누적 건수. `null` = 계측되지 않음. */
|
|
43
|
+
correlatedEffectObserved: number | null;
|
|
44
|
+
}
|
|
45
|
+
export interface StageVerdict {
|
|
46
|
+
id: StageId;
|
|
47
|
+
achieved: boolean;
|
|
48
|
+
/** 아직 아닌 이유 — 언어중립 코드(화면이 옮긴다). 달성이면 빈 배열. */
|
|
49
|
+
blockedBy: string[];
|
|
50
|
+
/** 판정할 수 없었던 신호 — **배선되지 않은 계측**. 있으면 달성이 아니다. */
|
|
51
|
+
unmeasured: string[];
|
|
52
|
+
}
|
|
53
|
+
export interface StagePath {
|
|
54
|
+
stages: StageVerdict[];
|
|
55
|
+
/** 지금 서 있는 칸 — 연속으로 달성된 마지막 단계. 하나도 없으면 `null`. */
|
|
56
|
+
reached: StageId | null;
|
|
57
|
+
/**
|
|
58
|
+
* 다음 한 걸음 — 사람이 오늘 할 수 있는 일 하나. 없으면(전부 달성) `null`.
|
|
59
|
+
* 「무엇이 부족한지」의 목록이 아니라 **다음 하나**다. 목록은 사람을 멈추게 한다.
|
|
60
|
+
*/
|
|
61
|
+
next: {
|
|
62
|
+
stage: StageId;
|
|
63
|
+
blockedBy: string[];
|
|
64
|
+
unmeasured: string[];
|
|
65
|
+
} | null;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 증거로 단계를 판정한다.
|
|
69
|
+
*
|
|
70
|
+
* **단계는 앞선 단계를 전제한다**(구조 없이 라이브를 달성했다고 말할 수 없다). 앞이 막히면 뒤는
|
|
71
|
+
* 자기 조건을 따지지 않고 `prerequisite` 으로 막는다 — 뒤 단계의 조건을 나열하면 사람이 순서를
|
|
72
|
+
* 건너뛴다.
|
|
73
|
+
*/
|
|
74
|
+
export declare function assessStagePath(e: StageEvidence): StagePath;
|
|
75
|
+
/**
|
|
76
|
+
* 이 단계에 이르지 못했다면 **쓰지 않기로 한 말**.
|
|
77
|
+
*
|
|
78
|
+
* 근거 수준 규율(`connector-developer-guide` §8)을 판정과 같은 자리에 둔다. 문장이 문서에만 있으면
|
|
79
|
+
* 제안서를 쓰는 사람이 그 문서를 읽지 않는다. 값이 되면 화면이 그 자리에서 말할 수 있다.
|
|
80
|
+
*/
|
|
81
|
+
export declare const CLAIM_GATE: Record<StageId, string>;
|
|
82
|
+
/** 아직 쓸 수 없는 주장들 — 도달하지 못한 단계의 게이트 전부. */
|
|
83
|
+
export declare function forbiddenClaims(path: StagePath): string[];
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* 단계 판정 — **가벼운 성공을 최종 성공으로 잇는 길을, 사람의 체크가 아니라 증거로 센다.**
|
|
4
|
+
*
|
|
5
|
+
* ── 왜 이 모듈이 있나 ───────────────────────────────────────────────────────
|
|
6
|
+
* 트윈 도입은 「구조 → 라이브 → 예측 → 폐루프」로 간다. 그런데 이 진행을 **체크리스트**로 관리하면
|
|
7
|
+
* 사람이 체크하는 순간 참이 된다. 실제로 가장 자주 건너뛰어지는 자리가 구조 다음이다 — 구조가 서면
|
|
8
|
+
* 화면이 그럴듯해지므로 「실시간」이라고 말해 버리고, 되풀어 읽는 스냅샷이 그 말을 뒷받침하지 못한다.
|
|
9
|
+
*
|
|
10
|
+
* 그래서 단계를 **판정식**으로 둔다. 각 단계는 시스템이 이미 내놓는 사실로만 판정되고, 선언으로
|
|
11
|
+
* 위조할 수 없다. 특히:
|
|
12
|
+
*
|
|
13
|
+
* · **반출 관측**이 라이브의 판별식이다. 스냅샷을 되풀어 읽는 방식은 **원리적으로** 반출을 낼 수 없다
|
|
14
|
+
* (사라진 것은 그냥 없어질 뿐이라 `OBSERVE` 만 남는다). 그러므로 「반출을 한 번이라도 접었는가」는
|
|
15
|
+
* 진짜 delta 의 증거이고, 폴링을 실시간이라 부르는 것을 막는다.
|
|
16
|
+
* · **선언 적재량**이 예측의 판별식이다. 커널 기본값으로 돌아간 예측도 숫자를 내므로, 화면만 보면
|
|
17
|
+
* 구별되지 않는다. 그 숫자가 무엇 위에 섰는지는 `specCoverage` 만 안다.
|
|
18
|
+
*
|
|
19
|
+
* ── 이 모듈의 정수: 「모른다」를 「아니다」로도 「그렇다」로도 바꾸지 않는다 ──────────────
|
|
20
|
+
* 계측되지 않은 신호는 `null` 로 온다. `null` 은 0 이 아니다. 계측되지 않았으면 그 단계는 **달성이
|
|
21
|
+
* 아니고**, 그 사실을 `unmeasured` 로 말한다 — 신호를 배선하지 않은 채 단계를 주장할 길을 남기지
|
|
22
|
+
* 않는다. (0 으로 대체하면 「반출 0건」과 「반출을 세지 않았다」가 같아진다.)
|
|
23
|
+
*
|
|
24
|
+
* 순수 함수다(프레임워크·DB 무의존) — 증거 수집은 호출자 몫이다.
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.CLAIM_GATE = void 0;
|
|
28
|
+
exports.assessStagePath = assessStagePath;
|
|
29
|
+
exports.forbiddenClaims = forbiddenClaims;
|
|
30
|
+
const ORDER = ['structure', 'live', 'forecast', 'closed-loop'];
|
|
31
|
+
/**
|
|
32
|
+
* 증거로 단계를 판정한다.
|
|
33
|
+
*
|
|
34
|
+
* **단계는 앞선 단계를 전제한다**(구조 없이 라이브를 달성했다고 말할 수 없다). 앞이 막히면 뒤는
|
|
35
|
+
* 자기 조건을 따지지 않고 `prerequisite` 으로 막는다 — 뒤 단계의 조건을 나열하면 사람이 순서를
|
|
36
|
+
* 건너뛴다.
|
|
37
|
+
*/
|
|
38
|
+
function assessStagePath(e) {
|
|
39
|
+
const stages = [];
|
|
40
|
+
let prereqOk = true;
|
|
41
|
+
for (const id of ORDER) {
|
|
42
|
+
const blockedBy = [];
|
|
43
|
+
const unmeasured = [];
|
|
44
|
+
if (!prereqOk) {
|
|
45
|
+
blockedBy.push('prerequisite');
|
|
46
|
+
}
|
|
47
|
+
else if (id === 'structure') {
|
|
48
|
+
if (!(e.locationCount > 0))
|
|
49
|
+
blockedBy.push('no-locations');
|
|
50
|
+
if (!(e.itemCount > 0))
|
|
51
|
+
blockedBy.push('no-items');
|
|
52
|
+
if (e.masterWarningsApplicable !== false && e.masterWarnings === null)
|
|
53
|
+
unmeasured.push('master-warnings');
|
|
54
|
+
}
|
|
55
|
+
else if (id === 'live') {
|
|
56
|
+
if (e.subject === 'sim')
|
|
57
|
+
blockedBy.push('subject-is-simulation');
|
|
58
|
+
else {
|
|
59
|
+
if (!(e.liveIngestTotal > 0))
|
|
60
|
+
blockedBy.push('no-ingest');
|
|
61
|
+
if (e.removalsObserved === null)
|
|
62
|
+
unmeasured.push('removals');
|
|
63
|
+
/*
|
|
64
|
+
* 세었는데 없다. 이유가 둘이고 **처방이 정반대**다: 스냅샷을 되풀어 읽는 방식이면 더 기다려도
|
|
65
|
+
* 오지 않으므로 방식을 바꿔야 하고, 그 밖이면 아직 반출이 일어나지 않은 것일 수 있다.
|
|
66
|
+
*/
|
|
67
|
+
else if (e.removalsObserved === 0)
|
|
68
|
+
blockedBy.push(e.feed === 'snapshot-poll' ? 'snapshot-only-feed' : 'no-removal-observed');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else if (id === 'forecast') {
|
|
72
|
+
if (!e.forecastRan)
|
|
73
|
+
blockedBy.push('no-forecast-run');
|
|
74
|
+
if (e.specCoverage === null)
|
|
75
|
+
unmeasured.push('spec-coverage');
|
|
76
|
+
/* 선언이 0 이면 예측은 커널 기본값 위에 섰다 — 숫자는 나오지만 이 현장의 숫자가 아니다. */
|
|
77
|
+
else if (!(e.specCoverage.declared > 0))
|
|
78
|
+
blockedBy.push('declarations-empty');
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
if (!e.actuateCapability)
|
|
82
|
+
blockedBy.push('no-actuate-capability');
|
|
83
|
+
if (e.correlatedEffectObserved === null)
|
|
84
|
+
unmeasured.push('correlated-effect');
|
|
85
|
+
/* 접수만으로는 폐루프가 아니다 — 효과가 관측으로 돌아와야 닫힌다. */
|
|
86
|
+
else if (!(e.correlatedEffectObserved > 0))
|
|
87
|
+
blockedBy.push('effect-not-observed');
|
|
88
|
+
}
|
|
89
|
+
const achieved = blockedBy.length === 0 && unmeasured.length === 0;
|
|
90
|
+
stages.push({ id, achieved, blockedBy, unmeasured });
|
|
91
|
+
if (!achieved)
|
|
92
|
+
prereqOk = false;
|
|
93
|
+
}
|
|
94
|
+
const achievedIds = stages.filter(s => s.achieved).map(s => s.id);
|
|
95
|
+
const firstUnmet = stages.find(s => !s.achieved);
|
|
96
|
+
return {
|
|
97
|
+
stages,
|
|
98
|
+
reached: achievedIds.length ? achievedIds[achievedIds.length - 1] : null,
|
|
99
|
+
next: firstUnmet ? { stage: firstUnmet.id, blockedBy: firstUnmet.blockedBy, unmeasured: firstUnmet.unmeasured } : null
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* 이 단계에 이르지 못했다면 **쓰지 않기로 한 말**.
|
|
104
|
+
*
|
|
105
|
+
* 근거 수준 규율(`connector-developer-guide` §8)을 판정과 같은 자리에 둔다. 문장이 문서에만 있으면
|
|
106
|
+
* 제안서를 쓰는 사람이 그 문서를 읽지 않는다. 값이 되면 화면이 그 자리에서 말할 수 있다.
|
|
107
|
+
*/
|
|
108
|
+
exports.CLAIM_GATE = {
|
|
109
|
+
structure: 'claim.structure-synced',
|
|
110
|
+
live: 'claim.realtime',
|
|
111
|
+
forecast: 'claim.prediction',
|
|
112
|
+
'closed-loop': 'claim.automatic-control'
|
|
113
|
+
};
|
|
114
|
+
/** 아직 쓸 수 없는 주장들 — 도달하지 못한 단계의 게이트 전부. */
|
|
115
|
+
function forbiddenClaims(path) {
|
|
116
|
+
return path.stages.filter(s => !s.achieved).map(s => exports.CLAIM_GATE[s.id]);
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=stage-path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stage-path.js","sourceRoot":"","sources":["../../server/engine/stage-path.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 단계 판정 — **가벼운 성공을 최종 성공으로 잇는 길을, 사람의 체크가 아니라 증거로 센다.**\n *\n * ── 왜 이 모듈이 있나 ───────────────────────────────────────────────────────\n * 트윈 도입은 「구조 → 라이브 → 예측 → 폐루프」로 간다. 그런데 이 진행을 **체크리스트**로 관리하면\n * 사람이 체크하는 순간 참이 된다. 실제로 가장 자주 건너뛰어지는 자리가 구조 다음이다 — 구조가 서면\n * 화면이 그럴듯해지므로 「실시간」이라고 말해 버리고, 되풀어 읽는 스냅샷이 그 말을 뒷받침하지 못한다.\n *\n * 그래서 단계를 **판정식**으로 둔다. 각 단계는 시스템이 이미 내놓는 사실로만 판정되고, 선언으로\n * 위조할 수 없다. 특히:\n *\n * · **반출 관측**이 라이브의 판별식이다. 스냅샷을 되풀어 읽는 방식은 **원리적으로** 반출을 낼 수 없다\n * (사라진 것은 그냥 없어질 뿐이라 `OBSERVE` 만 남는다). 그러므로 「반출을 한 번이라도 접었는가」는\n * 진짜 delta 의 증거이고, 폴링을 실시간이라 부르는 것을 막는다.\n * · **선언 적재량**이 예측의 판별식이다. 커널 기본값으로 돌아간 예측도 숫자를 내므로, 화면만 보면\n * 구별되지 않는다. 그 숫자가 무엇 위에 섰는지는 `specCoverage` 만 안다.\n *\n * ── 이 모듈의 정수: 「모른다」를 「아니다」로도 「그렇다」로도 바꾸지 않는다 ──────────────\n * 계측되지 않은 신호는 `null` 로 온다. `null` 은 0 이 아니다. 계측되지 않았으면 그 단계는 **달성이\n * 아니고**, 그 사실을 `unmeasured` 로 말한다 — 신호를 배선하지 않은 채 단계를 주장할 길을 남기지\n * 않는다. (0 으로 대체하면 「반출 0건」과 「반출을 세지 않았다」가 같아진다.)\n *\n * 순수 함수다(프레임워크·DB 무의존) — 증거 수집은 호출자 몫이다.\n */\n\nexport type StageId = 'structure' | 'live' | 'forecast' | 'closed-loop'\n\n/** 판정에 쓰는 사실. `null` = **계측되지 않았다**(0 이 아니다). */\nexport interface StageEvidence {\n /* ── S1 구조 ── */\n /** 이 트윈이 든 자리 수. */\n locationCount: number\n /** 자리에 놓인 물품 수(비직렬 수량 물품 포함 — 식품·화학은 개수가 아니라 총량으로 온다). */\n itemCount: number\n /**\n * 마스터를 옮기며 **못 옮긴 값의 수**. `null` = 세지 않았다.\n *\n * 0 이면 통과이고 n 이어도 통과다 — 이 판정이 요구하는 것은 「없다」가 아니라 **「말할 수 있다」**다.\n * 못 옮긴 값을 세지 않은 구조 인제스트는 도크를 랙으로 바꿔 놓고도 조용히 성공한다.\n */\n masterWarnings: number | null\n /**\n * 이 축이 이 트윈에 **해당하나**. 레퍼런스 없이 템플릿으로 세운 트윈에는 옮겨 온 마스터가 없으므로\n * 「못 옮긴 값」을 물을 대상이 아니다 — 그걸 미측정으로 접으면 영원히 배선을 기다린다.\n */\n masterWarningsApplicable?: boolean\n\n /* ── S2 라이브 ── */\n /** 인제스트 누적 건수. */\n liveIngestTotal: number\n /** **반출(remove)을 접은 누적 건수.** `null` = 계측되지 않음. 0 = 세었으나 없었다(→ 스냅샷 의심). */\n removalsObserved: number | null\n\n /* ── S3 예측 ── */\n /** 이 트윈에서 예측이 한 번이라도 돌았나. */\n forecastRan: boolean\n /** 선언 적재량 — `declared`/`total`. `null` = 알 수 없음. */\n specCoverage: { declared: number; total: number } | null\n\n /* ── S4 폐루프 ── */\n /**\n * 이 트윈이 **무엇인가** — 시뮬레이션은 원천에서 받는 것이 없다.\n *\n * 시뮬에 라이브를 요구하면 영원히 빨갛고, 그 빨강은 결함이 아니다. 도입 단계는 **연결된 트윈**의\n * 축이므로 시뮬은 라이브 이후를 「해당 없음」으로 말한다(달성으로도, 미측정으로도 접지 않는다).\n */\n subject?: 'live' | 'sim'\n /** 피드 성격 — `snapshot-poll` 이면 반출 0 은 「아직」이 아니라 **구조적으로 불가**다. */\n feed?: 'push' | 'delta-poll' | 'snapshot-poll' | 'none' | 'unknown'\n /** 원본이 **선언∩구현**으로 액추에이션 능력을 갖나(`capabilitiesOf`). */\n actuateCapability: boolean\n /** 내 명령의 효과가 인바운드로 **상관되어** 돌아온 누적 건수. `null` = 계측되지 않음. */\n correlatedEffectObserved: number | null\n}\n\nexport interface StageVerdict {\n id: StageId\n achieved: boolean\n /** 아직 아닌 이유 — 언어중립 코드(화면이 옮긴다). 달성이면 빈 배열. */\n blockedBy: string[]\n /** 판정할 수 없었던 신호 — **배선되지 않은 계측**. 있으면 달성이 아니다. */\n unmeasured: string[]\n}\n\nexport interface StagePath {\n stages: StageVerdict[]\n /** 지금 서 있는 칸 — 연속으로 달성된 마지막 단계. 하나도 없으면 `null`. */\n reached: StageId | null\n /**\n * 다음 한 걸음 — 사람이 오늘 할 수 있는 일 하나. 없으면(전부 달성) `null`.\n * 「무엇이 부족한지」의 목록이 아니라 **다음 하나**다. 목록은 사람을 멈추게 한다.\n */\n next: { stage: StageId; blockedBy: string[]; unmeasured: string[] } | null\n}\n\nconst ORDER: StageId[] = ['structure', 'live', 'forecast', 'closed-loop']\n\n/**\n * 증거로 단계를 판정한다.\n *\n * **단계는 앞선 단계를 전제한다**(구조 없이 라이브를 달성했다고 말할 수 없다). 앞이 막히면 뒤는\n * 자기 조건을 따지지 않고 `prerequisite` 으로 막는다 — 뒤 단계의 조건을 나열하면 사람이 순서를\n * 건너뛴다.\n */\nexport function assessStagePath(e: StageEvidence): StagePath {\n const stages: StageVerdict[] = []\n let prereqOk = true\n\n for (const id of ORDER) {\n const blockedBy: string[] = []\n const unmeasured: string[] = []\n\n if (!prereqOk) {\n blockedBy.push('prerequisite')\n } else if (id === 'structure') {\n if (!(e.locationCount > 0)) blockedBy.push('no-locations')\n if (!(e.itemCount > 0)) blockedBy.push('no-items')\n if (e.masterWarningsApplicable !== false && e.masterWarnings === null) unmeasured.push('master-warnings')\n } else if (id === 'live') {\n if (e.subject === 'sim') blockedBy.push('subject-is-simulation')\n else {\n if (!(e.liveIngestTotal > 0)) blockedBy.push('no-ingest')\n if (e.removalsObserved === null) unmeasured.push('removals')\n /*\n * 세었는데 없다. 이유가 둘이고 **처방이 정반대**다: 스냅샷을 되풀어 읽는 방식이면 더 기다려도\n * 오지 않으므로 방식을 바꿔야 하고, 그 밖이면 아직 반출이 일어나지 않은 것일 수 있다.\n */\n else if (e.removalsObserved === 0) blockedBy.push(e.feed === 'snapshot-poll' ? 'snapshot-only-feed' : 'no-removal-observed')\n }\n } else if (id === 'forecast') {\n if (!e.forecastRan) blockedBy.push('no-forecast-run')\n if (e.specCoverage === null) unmeasured.push('spec-coverage')\n /* 선언이 0 이면 예측은 커널 기본값 위에 섰다 — 숫자는 나오지만 이 현장의 숫자가 아니다. */\n else if (!(e.specCoverage.declared > 0)) blockedBy.push('declarations-empty')\n } else {\n if (!e.actuateCapability) blockedBy.push('no-actuate-capability')\n if (e.correlatedEffectObserved === null) unmeasured.push('correlated-effect')\n /* 접수만으로는 폐루프가 아니다 — 효과가 관측으로 돌아와야 닫힌다. */\n else if (!(e.correlatedEffectObserved > 0)) blockedBy.push('effect-not-observed')\n }\n\n const achieved = blockedBy.length === 0 && unmeasured.length === 0\n stages.push({ id, achieved, blockedBy, unmeasured })\n if (!achieved) prereqOk = false\n }\n\n const achievedIds = stages.filter(s => s.achieved).map(s => s.id)\n const firstUnmet = stages.find(s => !s.achieved)\n return {\n stages,\n reached: achievedIds.length ? achievedIds[achievedIds.length - 1] : null,\n next: firstUnmet ? { stage: firstUnmet.id, blockedBy: firstUnmet.blockedBy, unmeasured: firstUnmet.unmeasured } : null\n }\n}\n\n/**\n * 이 단계에 이르지 못했다면 **쓰지 않기로 한 말**.\n *\n * 근거 수준 규율(`connector-developer-guide` §8)을 판정과 같은 자리에 둔다. 문장이 문서에만 있으면\n * 제안서를 쓰는 사람이 그 문서를 읽지 않는다. 값이 되면 화면이 그 자리에서 말할 수 있다.\n */\nexport const CLAIM_GATE: Record<StageId, string> = {\n structure: 'claim.structure-synced',\n live: 'claim.realtime',\n forecast: 'claim.prediction',\n 'closed-loop': 'claim.automatic-control'\n}\n\n/** 아직 쓸 수 없는 주장들 — 도달하지 못한 단계의 게이트 전부. */\nexport function forbiddenClaims(path: StagePath): string[] {\n return path.stages.filter(s => !s.achieved).map(s => CLAIM_GATE[s.id])\n}\n"]}
|