@things-factory/headless-twin 10.0.19 → 10.1.0
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 +10 -1
- package/dist-server/engine/canonical-ingest.js +110 -33
- package/dist-server/engine/canonical-ingest.js.map +1 -1
- package/dist-server/engine/event-time.d.ts +25 -0
- package/dist-server/engine/event-time.js +98 -0
- package/dist-server/engine/event-time.js.map +1 -0
- package/dist-server/engine/index.d.ts +2 -0
- package/dist-server/engine/index.js +23 -0
- package/dist-server/engine/index.js.map +1 -1
- package/dist-server/engine/ingest-dedupe.d.ts +18 -6
- package/dist-server/engine/ingest-dedupe.js +201 -15
- package/dist-server/engine/ingest-dedupe.js.map +1 -1
- package/dist-server/engine/ingest-health.d.ts +60 -1
- package/dist-server/engine/ingest-health.js +43 -8
- package/dist-server/engine/ingest-health.js.map +1 -1
- package/dist-server/engine/kpi-fold.d.ts +19 -0
- package/dist-server/engine/kpi-fold.js +4 -1
- package/dist-server/engine/kpi-fold.js.map +1 -1
- package/dist-server/engine/kpi-query.js +23 -3
- package/dist-server/engine/kpi-query.js.map +1 -1
- package/dist-server/engine/oee-accumulator.d.ts +35 -2
- package/dist-server/engine/oee-accumulator.js +56 -4
- package/dist-server/engine/oee-accumulator.js.map +1 -1
- package/dist-server/engine/twin-engine.d.ts +72 -3
- package/dist-server/engine/twin-engine.js +118 -5
- package/dist-server/engine/twin-engine.js.map +1 -1
- package/dist-server/index.js +21 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/migrations/1786600000000-RenameOperatoMesAdapterType.d.ts +5 -0
- package/dist-server/migrations/1786600000000-RenameOperatoMesAdapterType.js +66 -0
- package/dist-server/migrations/1786600000000-RenameOperatoMesAdapterType.js.map +1 -0
- package/dist-server/migrations/1786700000000-AddDispatchRetryColumns.d.ts +5 -0
- package/dist-server/migrations/1786700000000-AddDispatchRetryColumns.js +79 -0
- package/dist-server/migrations/1786700000000-AddDispatchRetryColumns.js.map +1 -0
- package/dist-server/migrations/index.js +5 -1
- package/dist-server/migrations/index.js.map +1 -1
- package/dist-server/routes.js +12 -20
- package/dist-server/routes.js.map +1 -1
- package/dist-server/service/actuation/actuation-advisor.d.ts +60 -0
- package/dist-server/service/actuation/actuation-advisor.js +53 -0
- package/dist-server/service/actuation/actuation-advisor.js.map +1 -0
- package/dist-server/service/actuation/actuation-outcome.d.ts +52 -0
- package/dist-server/service/actuation/actuation-outcome.js +34 -0
- package/dist-server/service/actuation/actuation-outcome.js.map +1 -0
- package/dist-server/service/actuation/actuation-rule-resolver.d.ts +19 -0
- package/dist-server/service/actuation/actuation-rule-resolver.js +161 -0
- package/dist-server/service/actuation/actuation-rule-resolver.js.map +1 -0
- package/dist-server/service/actuation/actuation-rule.d.ts +55 -0
- package/dist-server/service/actuation/actuation-rule.js +130 -0
- package/dist-server/service/actuation/actuation-rule.js.map +1 -0
- package/dist-server/service/actuation/approval-gateway.d.ts +20 -0
- package/dist-server/service/actuation/approval-gateway.js +14 -0
- package/dist-server/service/actuation/approval-gateway.js.map +1 -0
- package/dist-server/service/actuation/catch-up-effects.d.ts +16 -0
- package/dist-server/service/actuation/catch-up-effects.js +120 -0
- package/dist-server/service/actuation/catch-up-effects.js.map +1 -0
- package/dist-server/service/actuation/command-dispatcher.d.ts +206 -8
- package/dist-server/service/actuation/command-dispatcher.js +191 -14
- package/dist-server/service/actuation/command-dispatcher.js.map +1 -1
- package/dist-server/service/actuation/command-store.d.ts +10 -2
- package/dist-server/service/actuation/command-store.js +168 -5
- package/dist-server/service/actuation/command-store.js.map +1 -1
- package/dist-server/service/actuation/dispatch-after-approval.d.ts +11 -0
- package/dist-server/service/actuation/dispatch-after-approval.js +73 -0
- package/dist-server/service/actuation/dispatch-after-approval.js.map +1 -0
- package/dist-server/service/actuation/effect-match.d.ts +53 -0
- package/dist-server/service/actuation/effect-match.js +65 -0
- package/dist-server/service/actuation/effect-match.js.map +1 -0
- package/dist-server/service/actuation/index.d.ts +21 -2
- package/dist-server/service/actuation/index.js +21 -2
- package/dist-server/service/actuation/index.js.map +1 -1
- package/dist-server/service/actuation/retry-plan.d.ts +63 -0
- package/dist-server/service/actuation/retry-plan.js +99 -0
- package/dist-server/service/actuation/retry-plan.js.map +1 -0
- package/dist-server/service/actuation/rule-evaluate.d.ts +127 -0
- package/dist-server/service/actuation/rule-evaluate.js +205 -0
- package/dist-server/service/actuation/rule-evaluate.js.map +1 -0
- package/dist-server/service/actuation/rule-loop.d.ts +5 -0
- package/dist-server/service/actuation/rule-loop.js +156 -0
- package/dist-server/service/actuation/rule-loop.js.map +1 -0
- package/dist-server/service/actuation/rule-runner.d.ts +52 -0
- package/dist-server/service/actuation/rule-runner.js +135 -0
- package/dist-server/service/actuation/rule-runner.js.map +1 -0
- package/dist-server/service/actuation/rule-wiring.d.ts +24 -0
- package/dist-server/service/actuation/rule-wiring.js +80 -0
- package/dist-server/service/actuation/rule-wiring.js.map +1 -0
- package/dist-server/service/actuation/settle-effects.d.ts +21 -0
- package/dist-server/service/actuation/settle-effects.js +101 -0
- package/dist-server/service/actuation/settle-effects.js.map +1 -0
- package/dist-server/service/actuation/sweep-dispatch.d.ts +37 -0
- package/dist-server/service/actuation/sweep-dispatch.js +125 -0
- package/dist-server/service/actuation/sweep-dispatch.js.map +1 -0
- package/dist-server/service/actuation/twin-command-resolver.d.ts +120 -0
- package/dist-server/service/actuation/twin-command-resolver.js +406 -0
- package/dist-server/service/actuation/twin-command-resolver.js.map +1 -0
- package/dist-server/service/actuation/twin-command.d.ts +81 -0
- package/dist-server/service/actuation/twin-command.js +60 -0
- package/dist-server/service/actuation/twin-command.js.map +1 -1
- package/dist-server/service/actuation/twin-now.d.ts +41 -0
- package/dist-server/service/actuation/twin-now.js +65 -0
- package/dist-server/service/actuation/twin-now.js.map +1 -0
- package/dist-server/service/index.d.ts +5 -2
- package/dist-server/service/index.js +8 -0
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/reference/actuation-routing.d.ts +60 -0
- package/dist-server/service/reference/actuation-routing.js +126 -0
- package/dist-server/service/reference/actuation-routing.js.map +1 -0
- package/dist-server/service/reference/actuation-target.d.ts +65 -0
- package/dist-server/service/reference/actuation-target.js +94 -0
- package/dist-server/service/reference/actuation-target.js.map +1 -0
- package/dist-server/service/reference/connection-portability-resolver.d.ts +29 -0
- package/dist-server/service/reference/connection-portability-resolver.js +181 -0
- package/dist-server/service/reference/connection-portability-resolver.js.map +1 -0
- package/dist-server/service/reference/connection-portability.d.ts +138 -0
- package/dist-server/service/reference/connection-portability.js +230 -0
- package/dist-server/service/reference/connection-portability.js.map +1 -0
- package/dist-server/service/reference/fill-cadence.d.ts +17 -0
- package/dist-server/service/reference/fill-cadence.js +31 -0
- package/dist-server/service/reference/fill-cadence.js.map +1 -0
- package/dist-server/service/reference/fill-dedupe.d.ts +16 -0
- package/dist-server/service/reference/fill-dedupe.js +63 -0
- package/dist-server/service/reference/fill-dedupe.js.map +1 -0
- package/dist-server/service/reference/fill-loop.d.ts +35 -0
- package/dist-server/service/reference/fill-loop.js +187 -0
- package/dist-server/service/reference/fill-loop.js.map +1 -0
- package/dist-server/service/reference/fill-span.d.ts +10 -0
- package/dist-server/service/reference/fill-span.js +37 -0
- package/dist-server/service/reference/fill-span.js.map +1 -0
- package/dist-server/service/reference/hook-contract.d.ts +88 -4
- package/dist-server/service/reference/hook-contract.js +105 -4
- package/dist-server/service/reference/hook-contract.js.map +1 -1
- package/dist-server/service/reference/hook-rejected.d.ts +25 -0
- package/dist-server/service/reference/hook-rejected.js +48 -0
- package/dist-server/service/reference/hook-rejected.js.map +1 -0
- package/dist-server/service/reference/index.d.ts +4 -1
- package/dist-server/service/reference/index.js +5 -1
- package/dist-server/service/reference/index.js.map +1 -1
- package/dist-server/service/reference/live-cadence.d.ts +68 -0
- package/dist-server/service/reference/live-cadence.js +44 -0
- package/dist-server/service/reference/live-cadence.js.map +1 -0
- package/dist-server/service/reference/live-feed-lease.d.ts +15 -0
- package/dist-server/service/reference/live-feed-lease.js +56 -0
- package/dist-server/service/reference/live-feed-lease.js.map +1 -0
- package/dist-server/service/reference/live-ingest.d.ts +21 -0
- package/dist-server/service/reference/live-ingest.js +66 -0
- package/dist-server/service/reference/live-ingest.js.map +1 -0
- package/dist-server/service/reference/reference-adapter.d.ts +291 -2
- package/dist-server/service/reference/reference-adapter.js +19 -1
- package/dist-server/service/reference/reference-adapter.js.map +1 -1
- package/dist-server/service/reference/reference-fill-run.d.ts +50 -0
- package/dist-server/service/reference/reference-fill-run.js +142 -0
- package/dist-server/service/reference/reference-fill-run.js.map +1 -0
- package/dist-server/service/reference/reference-fill.d.ts +90 -0
- package/dist-server/service/reference/reference-fill.js +141 -0
- package/dist-server/service/reference/reference-fill.js.map +1 -0
- package/dist-server/service/reference/reference-hook.d.ts +22 -0
- package/dist-server/service/reference/reference-hook.js +135 -55
- package/dist-server/service/reference/reference-hook.js.map +1 -1
- package/dist-server/service/reference/reference-link-groups.d.ts +10 -0
- package/dist-server/service/reference/reference-link-groups.js +15 -0
- package/dist-server/service/reference/reference-link-groups.js.map +1 -0
- package/dist-server/service/reference/reference-links.d.ts +14 -0
- package/dist-server/service/reference/reference-links.js +73 -0
- package/dist-server/service/reference/reference-links.js.map +1 -0
- package/dist-server/service/reference/reference-live.d.ts +1 -0
- package/dist-server/service/reference/reference-live.js +32 -6
- package/dist-server/service/reference/reference-live.js.map +1 -1
- package/dist-server/service/reference/reference-master.d.ts +56 -2
- package/dist-server/service/reference/reference-master.js +33 -3
- package/dist-server/service/reference/reference-master.js.map +1 -1
- package/dist-server/service/reference/reference-resolver.d.ts +27 -0
- package/dist-server/service/reference/reference-resolver.js +79 -1
- package/dist-server/service/reference/reference-resolver.js.map +1 -1
- package/dist-server/service/twin-event/twin-event-keys.d.ts +31 -3
- package/dist-server/service/twin-event/twin-event-keys.js +36 -4
- package/dist-server/service/twin-event/twin-event-keys.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 +11 -2
- package/dist-server/service/twin-event/twin-event.js.map +1 -1
- package/dist-server/service/twin-model/choose-recipe.d.ts +15 -0
- package/dist-server/service/twin-model/choose-recipe.js +33 -0
- package/dist-server/service/twin-model/choose-recipe.js.map +1 -0
- package/dist-server/service/twin-model/external-resolver.d.ts +76 -0
- package/dist-server/service/twin-model/external-resolver.js +109 -0
- package/dist-server/service/twin-model/external-resolver.js.map +1 -0
- package/dist-server/service/twin-model/project-structure.js +16 -3
- package/dist-server/service/twin-model/project-structure.js.map +1 -1
- package/dist-server/service/twin-model/twin-equipment.d.ts +17 -0
- package/dist-server/service/twin-model/twin-equipment.js +10 -0
- package/dist-server/service/twin-model/twin-equipment.js.map +1 -1
- package/dist-server/service/twin-model/twin-lineage-query.js +75 -1
- package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-item-query.js +94 -5
- package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
- package/dist-server/service/twin-space/twin-space.d.ts +29 -0
- package/dist-server/service/twin-space/twin-space.js +13 -0
- package/dist-server/service/twin-space/twin-space.js.map +1 -1
- package/dist-server/service/twin-subject/event-subjects.d.ts +3 -1
- package/dist-server/service/twin-subject/event-subjects.js +8 -1
- package/dist-server/service/twin-subject/event-subjects.js.map +1 -1
- package/dist-shared/entity-delta.js +1 -1
- package/dist-shared/entity-delta.js.map +1 -1
- package/package.json +7 -7
- package/server/engine/canonical-ingest.ts +119 -33
- package/server/engine/event-time.ts +96 -0
- package/server/engine/index.ts +20 -0
- package/server/engine/ingest-dedupe.ts +203 -27
- package/server/engine/ingest-health.ts +91 -6
- package/server/engine/kpi-fold.ts +24 -1
- package/server/engine/kpi-query.ts +23 -4
- package/server/engine/oee-accumulator.ts +80 -8
- package/server/engine/twin-engine.ts +119 -5
- package/server/index.ts +23 -0
- package/server/migrations/1786600000000-RenameOperatoMesAdapterType.ts +66 -0
- package/server/migrations/1786700000000-AddDispatchRetryColumns.ts +82 -0
- package/server/migrations/index.ts +5 -1
- package/server/routes.ts +12 -24
- package/server/service/actuation/actuation-advisor.ts +124 -0
- package/server/service/actuation/actuation-outcome.ts +97 -0
- package/server/service/actuation/actuation-rule-resolver.ts +147 -0
- package/server/service/actuation/actuation-rule.ts +139 -0
- package/server/service/actuation/approval-gateway.ts +44 -0
- package/server/service/actuation/catch-up-effects.ts +152 -0
- package/server/service/actuation/command-dispatcher.ts +401 -20
- package/server/service/actuation/command-store.ts +182 -8
- package/server/service/actuation/dispatch-after-approval.ts +71 -0
- package/server/service/actuation/effect-match.ts +124 -0
- package/server/service/actuation/index.ts +21 -2
- package/server/service/actuation/retry-plan.ts +145 -0
- package/server/service/actuation/rule-evaluate.ts +352 -0
- package/server/service/actuation/rule-loop.ts +160 -0
- package/server/service/actuation/rule-runner.ts +216 -0
- package/server/service/actuation/rule-wiring.ts +84 -0
- package/server/service/actuation/settle-effects.ts +115 -0
- package/server/service/actuation/sweep-dispatch.ts +179 -0
- package/server/service/actuation/twin-command-resolver.ts +387 -0
- package/server/service/actuation/twin-command.ts +128 -1
- package/server/service/actuation/twin-now.ts +97 -0
- package/server/service/index.ts +9 -1
- package/server/service/reference/actuation-routing.ts +165 -0
- package/server/service/reference/actuation-target.ts +163 -0
- package/server/service/reference/connection-portability-resolver.ts +171 -0
- package/server/service/reference/connection-portability.ts +285 -0
- package/server/service/reference/fill-cadence.ts +29 -0
- package/server/service/reference/fill-dedupe.ts +66 -0
- package/server/service/reference/fill-loop.ts +153 -0
- package/server/service/reference/fill-span.ts +31 -0
- package/server/service/reference/hook-contract.ts +185 -5
- package/server/service/reference/hook-rejected.ts +53 -0
- package/server/service/reference/index.ts +5 -1
- package/server/service/reference/live-cadence.ts +129 -0
- package/server/service/reference/live-feed-lease.ts +56 -0
- package/server/service/reference/live-ingest.ts +94 -0
- package/server/service/reference/reference-adapter.ts +327 -5
- package/server/service/reference/reference-fill-run.ts +165 -0
- package/server/service/reference/reference-fill.ts +223 -0
- package/server/service/reference/reference-hook.ts +162 -56
- package/server/service/reference/reference-link-groups.ts +21 -0
- package/server/service/reference/reference-links.ts +77 -0
- package/server/service/reference/reference-live.ts +30 -5
- package/server/service/reference/reference-master.ts +90 -5
- package/server/service/reference/reference-resolver.ts +77 -2
- package/server/service/twin-event/twin-event-keys.ts +37 -4
- package/server/service/twin-event/twin-event.ts +18 -1
- package/server/service/twin-model/choose-recipe.ts +31 -0
- package/server/service/twin-model/external-resolver.ts +161 -0
- package/server/service/twin-model/project-structure.ts +16 -3
- package/server/service/twin-model/twin-equipment.ts +22 -0
- package/server/service/twin-model/twin-lineage-query.ts +82 -1
- package/server/service/twin-model/twin-model-item-query.ts +95 -4
- package/server/service/twin-model/twin-model-tree-query.ts +1 -1
- package/server/service/twin-space/twin-space.ts +39 -0
- package/server/service/twin-subject/event-subjects.ts +8 -1
- package/shared/entity-delta.ts +1 -1
- package/test/ack-shape-alignment.test.ts +97 -0
- package/test/actuation-approval-door.test.ts +421 -0
- package/test/actuation-dispatch.test.ts +16 -3
- package/test/actuation-effect.test.ts +162 -0
- package/test/actuation-outcome.test.ts +152 -0
- package/test/actuation-rule-runner.test.ts +281 -0
- package/test/actuation-rule.test.ts +266 -0
- package/test/actuation-seam.test.ts +249 -0
- package/test/actuation-settle.test.ts +164 -0
- package/test/actuation-target.test.ts +145 -0
- package/test/canonical-ingest-vocabularies.test.ts +49 -2
- package/test/capability-mapping.test.ts +1 -1
- package/test/choose-recipe.test.ts +117 -0
- package/test/command-store-writes-columns.test.ts +126 -0
- package/test/connection-portability-doors.test.ts +87 -0
- package/test/connector-capability-declaration.test.ts +54 -4
- package/test/declaration-reaches-model.test.ts +43 -0
- package/test/dedupe-transformation.test.ts +112 -0
- package/test/entity-delta.test.ts +1 -1
- package/test/event-subjects.test.ts +2 -1
- package/test/external-resolver.test.ts +171 -0
- package/test/fill-dedupe.test.ts +59 -0
- package/test/fill-loop.test.ts +35 -0
- package/test/first-envelope.test.ts +153 -0
- package/test/fold-shift-dst.test.ts +110 -0
- package/test/home-in-area.test.ts +90 -0
- package/test/hook-rejected-shape.test.ts +86 -0
- package/test/hook-response-sum.test.ts +193 -0
- package/test/hook-sequence.test.ts +1 -1
- package/test/ingest-dedupe.test.ts +56 -0
- package/test/ingest-expected-quiet.test.ts +111 -0
- package/test/ingest-idempotent.test.ts +79 -0
- package/test/ingest-reconcile-four-ways.test.ts +97 -0
- package/test/ingest-rules-header.test.ts +100 -0
- package/test/ingest-window-rolls-unobserved.test.ts +72 -0
- package/test/intake-mapping-parity.test.ts +175 -0
- package/test/live-cadence.test.ts +170 -0
- package/test/live-feed-lease.test.ts +63 -0
- package/test/live-mirror-parity.test.ts +1 -1
- package/test/oee-accumulator.test.ts +3 -3
- package/test/oee-site-calendar.test.ts +146 -0
- package/test/order-and-transaction-split.test.ts +107 -0
- package/test/reference-fill.test.ts +235 -0
- package/test/reference-hook.test.ts +1 -1
- package/test/reference-links.test.ts +56 -0
- package/test/retry-plan.test.ts +139 -0
- package/test/sweep-dispatch.test.ts +191 -0
- package/test/twin-event-keys.test.ts +20 -6
- package/test/twin-model-item-db.test.ts +6 -6
- package/test/twin-model-tree-db.test.ts +6 -6
- package/tsconfig.shared.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { ReferenceCapability } from './reference-adapter.js';
|
|
2
|
+
/** 판단에 필요한 것만 — 엔티티 전체를 받지 않는다. */
|
|
3
|
+
export interface ActuationTargetInput {
|
|
4
|
+
/** 어느 트윈인가 — **거절 문장이 이것을 말한다.** 「모르는 트윈입니다」만으로는 찾을 수 없다. */
|
|
5
|
+
instanceId: string;
|
|
6
|
+
/** 트윈이 있나. 없으면 `undefined`. */
|
|
7
|
+
twinExists: boolean;
|
|
8
|
+
/** 이 트윈이 어느 사이트에서 왔나 — `TwinInstance.origin.siteId`. */
|
|
9
|
+
siteId?: string;
|
|
10
|
+
/** 연결된 원본. 없으면 `undefined`(시뮬레이션으로만 세운 트윈). */
|
|
11
|
+
ref?: {
|
|
12
|
+
source: string;
|
|
13
|
+
adapterType: string;
|
|
14
|
+
};
|
|
15
|
+
/** 커넥터가 등록되어 있나. */
|
|
16
|
+
adapterRegistered: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* 그 커넥터가 실제로 낼 수 있는 능력 — `capabilitiesOf` 가 판정한 값.
|
|
19
|
+
*
|
|
20
|
+
* **낼 수 있나는 이 값만 본다.** 아래 둘은 못 낼 때 무엇을 고쳐야 하는지 말하기 위한 것이고,
|
|
21
|
+
* 판정을 다시 만들지 않는다.
|
|
22
|
+
*/
|
|
23
|
+
capabilities: readonly ReferenceCapability[];
|
|
24
|
+
/** 커넥터가 `capabilities` 에 조치를 적었나. */
|
|
25
|
+
declaresActuate: boolean;
|
|
26
|
+
/** 커넥터에 `actuate` 함수가 있나. */
|
|
27
|
+
hasActuateImpl: boolean;
|
|
28
|
+
}
|
|
29
|
+
/** 보낼 수 있다 — 어디로까지 정해진 상태. */
|
|
30
|
+
export interface ActuationAllowed {
|
|
31
|
+
ok: true;
|
|
32
|
+
siteId: string;
|
|
33
|
+
adapterType: string;
|
|
34
|
+
source: string;
|
|
35
|
+
}
|
|
36
|
+
/** 보낼 수 없다 — 왜인지 코드와 문장으로. */
|
|
37
|
+
export interface ActuationRefused {
|
|
38
|
+
ok: false;
|
|
39
|
+
code: ActuationRefusal;
|
|
40
|
+
detail: string;
|
|
41
|
+
}
|
|
42
|
+
export type ActuationTarget = ActuationAllowed | ActuationRefused;
|
|
43
|
+
/**
|
|
44
|
+
* 보낼 수 있나 — **타입 가드로 둔다.**
|
|
45
|
+
*
|
|
46
|
+
* 이 저장소는 기본 설정이 `strict: false` 이고(`packages/tsconfig-base.json`), 그 상태에서는 `ok` 가
|
|
47
|
+
* `true`·`false` 로 갈린 유니온이 `if (!t.ok)` 로 좁혀지지 않는다. 부르는 쪽마다 캐스팅을 넣으면 그
|
|
48
|
+
* 캐스팅이 언젠가 틀린 쪽을 가리킨다. 판정을 여기 한 자리에 둔다.
|
|
49
|
+
*/
|
|
50
|
+
export declare function isActuationAllowed(target: ActuationTarget): target is ActuationAllowed;
|
|
51
|
+
/**
|
|
52
|
+
* 왜 못 보내나 — **코드로 답한다.**
|
|
53
|
+
*
|
|
54
|
+
* 화면이 이 코드로 다음 할 일을 가른다. 문장만 주면 화면은 그것을 그대로 띄우는 것밖에 못 하고,
|
|
55
|
+
* 사람은 연결 설정을 열어야 하는지 커넥터를 고쳐야 하는지 모른다.
|
|
56
|
+
*/
|
|
57
|
+
export type ActuationRefusal = 'unknown-twin' | 'no-source' | 'no-adapter' | 'not-declared' | 'not-implemented' | 'not-supported' | 'no-site';
|
|
58
|
+
/**
|
|
59
|
+
* 보낼 수 있나 — 보낼 수 있으면 **어디로**까지 답한다.
|
|
60
|
+
*
|
|
61
|
+
* 순서가 뜻을 갖는다. 앞의 것이 없으면 뒤를 물을 수 없다 — 원본을 모르면 능력을 물을 데가 없다.
|
|
62
|
+
*/
|
|
63
|
+
export declare function actuationTarget(input: ActuationTargetInput): ActuationTarget;
|
|
64
|
+
/** 승인은 남는다는 것을 함께 말한다 — 원인을 고친 뒤 다시 넘길 수 있다. */
|
|
65
|
+
export declare const APPROVAL_STANDS = "\uC2B9\uC778\uC740 \uADF8\uB300\uB85C \uB0A8\uC544 \uC788\uC2B5\uB2C8\uB2E4 \u2014 \uACE0\uCE5C \uB4A4 \uB2E4\uC2DC \uB118\uAE38 \uC218 \uC788\uC2B5\uB2C8\uB2E4";
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.APPROVAL_STANDS = void 0;
|
|
4
|
+
exports.isActuationAllowed = isActuationAllowed;
|
|
5
|
+
exports.actuationTarget = actuationTarget;
|
|
6
|
+
/**
|
|
7
|
+
* 보낼 수 있나 — **타입 가드로 둔다.**
|
|
8
|
+
*
|
|
9
|
+
* 이 저장소는 기본 설정이 `strict: false` 이고(`packages/tsconfig-base.json`), 그 상태에서는 `ok` 가
|
|
10
|
+
* `true`·`false` 로 갈린 유니온이 `if (!t.ok)` 로 좁혀지지 않는다. 부르는 쪽마다 캐스팅을 넣으면 그
|
|
11
|
+
* 캐스팅이 언젠가 틀린 쪽을 가리킨다. 판정을 여기 한 자리에 둔다.
|
|
12
|
+
*/
|
|
13
|
+
function isActuationAllowed(target) {
|
|
14
|
+
return target.ok === true;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 보낼 수 있나 — 보낼 수 있으면 **어디로**까지 답한다.
|
|
18
|
+
*
|
|
19
|
+
* 순서가 뜻을 갖는다. 앞의 것이 없으면 뒤를 물을 수 없다 — 원본을 모르면 능력을 물을 데가 없다.
|
|
20
|
+
*/
|
|
21
|
+
function actuationTarget(input) {
|
|
22
|
+
if (!input.twinExists) {
|
|
23
|
+
return { ok: false, code: 'unknown-twin', detail: `모르는 트윈입니다 (${input.instanceId})` };
|
|
24
|
+
}
|
|
25
|
+
if (!input.ref) {
|
|
26
|
+
/*
|
|
27
|
+
* 시뮬레이션으로만 세운 트윈이 이 경우다. 규칙 경로가 시뮬 트윈을 이미 막지만
|
|
28
|
+
* (§`rule-runner.ts`), 사람이 손으로 낸 조치는 그 문을 지나지 않는다. 막는 지점이 두 경로에
|
|
29
|
+
* 각각 있는 것이고, 같은 판정을 두 벌 만든 것이 아니다.
|
|
30
|
+
*/
|
|
31
|
+
return {
|
|
32
|
+
ok: false,
|
|
33
|
+
code: 'no-source',
|
|
34
|
+
detail: `트윈 "${input.instanceId}" 에는 연결된 원본이 없습니다 — 보낼 곳이 없습니다`
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (!input.adapterRegistered) {
|
|
38
|
+
return { ok: false, code: 'no-adapter', detail: `등록되지 않은 커넥터입니다 (${input.ref.adapterType})` };
|
|
39
|
+
}
|
|
40
|
+
if (!input.capabilities.includes('actuate')) {
|
|
41
|
+
/*
|
|
42
|
+
* **왜 못 내는지를 셋으로 가른다** — 고칠 것이 셋 다 다르다.
|
|
43
|
+
*
|
|
44
|
+
* 선언 없음 · 구현 있음 한 줄 넣으면 된다
|
|
45
|
+
* 선언 있음 · 구현 없음 만들다 만 커넥터다. 등록 검사는 이쪽을 말하지 않는다
|
|
46
|
+
* 선언도 구현도 없음 이 커넥터는 조치를 받지 않는다
|
|
47
|
+
*
|
|
48
|
+
* 뭉개면 한 줄로 끝날 일에 커넥터를 다시 만들려 들거나, 반대로 「관측만 냅니다」를 믿고 조치를
|
|
49
|
+
* 포기한다. 가운데 것을 「관측만 냅니다」로 답하는 것은 **사실이 아니다** — 그 사람은 조치를
|
|
50
|
+
* 하겠다고 적어 두었다.
|
|
51
|
+
*/
|
|
52
|
+
const { adapterType, source } = input.ref;
|
|
53
|
+
if (input.hasActuateImpl && !input.declaresActuate) {
|
|
54
|
+
return {
|
|
55
|
+
ok: false,
|
|
56
|
+
code: 'not-declared',
|
|
57
|
+
detail: `커넥터 "${adapterType}" 가 조치를 보내는 구현은 있는데 능력으로 선언하지 않았습니다 — ` +
|
|
58
|
+
"connector 의 capabilities 에 'actuate' 를 넣어야 합니다"
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (input.declaresActuate && !input.hasActuateImpl) {
|
|
62
|
+
return {
|
|
63
|
+
ok: false,
|
|
64
|
+
code: 'not-implemented',
|
|
65
|
+
detail: `커넥터 "${adapterType}" 가 조치를 보낸다고 선언했는데 그 구현이 없습니다 — ` +
|
|
66
|
+
'actuate 를 만들거나 선언을 지워야 합니다'
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
code: 'not-supported',
|
|
72
|
+
detail: `원본 "${source}" (${adapterType}) 는 조치를 받지 않습니다 — 이 커넥터는 관측만 냅니다`
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/*
|
|
76
|
+
* 사이트를 **지어내지 않는다.** 원본 하나가 여러 사이트를 낼 수 있다(`discoverSites`).
|
|
77
|
+
*
|
|
78
|
+
* 구동(§`control-routing.ts`)은 사이트를 몰라도 넘긴다 — 시늉을 멈추는 일에는 대상이 하나다.
|
|
79
|
+
* 조치는 안 된다. 어느 공장에 낼지 모르는 작업지시를 첫 번째 사이트로 보내면 **잘못된 공장이
|
|
80
|
+
* 실제로 만든다.**
|
|
81
|
+
*/
|
|
82
|
+
const siteId = String(input.siteId ?? '').trim();
|
|
83
|
+
if (!siteId) {
|
|
84
|
+
return {
|
|
85
|
+
ok: false,
|
|
86
|
+
code: 'no-site',
|
|
87
|
+
detail: `트윈 "${input.instanceId}" 이 어느 사이트에서 왔는지 알 수 없습니다 — 조치를 보낼 대상을 정할 수 없습니다`
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return { ok: true, siteId, adapterType: input.ref.adapterType, source: input.ref.source };
|
|
91
|
+
}
|
|
92
|
+
/** 승인은 남는다는 것을 함께 말한다 — 원인을 고친 뒤 다시 넘길 수 있다. */
|
|
93
|
+
exports.APPROVAL_STANDS = '승인은 그대로 남아 있습니다 — 고친 뒤 다시 넘길 수 있습니다';
|
|
94
|
+
//# sourceMappingURL=actuation-target.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actuation-target.js","sourceRoot":"","sources":["../../../server/service/reference/actuation-target.ts"],"names":[],"mappings":";;;AA0DA,gDAEC;AAsBD,0CA6EC;AA5GD;;;;;;GAMG;AACH,SAAgB,kBAAkB,CAAC,MAAuB;IACxD,OAAO,MAAM,CAAC,EAAE,KAAK,IAAI,CAAA;AAC3B,CAAC;AAiBD;;;;GAIG;AACH,SAAgB,eAAe,CAAC,KAA2B;IACzD,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;QACtB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,cAAc,KAAK,CAAC,UAAU,GAAG,EAAE,CAAA;IACvF,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QACf;;;;WAIG;QACH,OAAO;YACL,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,WAAW;YACjB,MAAM,EAAE,OAAO,KAAK,CAAC,UAAU,gCAAgC;SAChE,CAAA;IACH,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAC7B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,mBAAmB,KAAK,CAAC,GAAG,CAAC,WAAW,GAAG,EAAE,CAAA;IAC/F,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5C;;;;;;;;;;WAUG;QACH,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC,GAAG,CAAA;QACzC,IAAI,KAAK,CAAC,cAAc,IAAI,CAAC,KAAK,CAAC,eAAe,EAAE,CAAC;YACnD,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,IAAI,EAAE,cAAc;gBACpB,MAAM,EACJ,QAAQ,WAAW,wCAAwC;oBAC3D,gDAAgD;aACnD,CAAA;QACH,CAAC;QACD,IAAI,KAAK,CAAC,eAAe,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;YACnD,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,IAAI,EAAE,iBAAiB;gBACvB,MAAM,EACJ,QAAQ,WAAW,kCAAkC;oBACrD,4BAA4B;aAC/B,CAAA;QACH,CAAC;QACD,OAAO;YACL,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,eAAe;YACrB,MAAM,EAAE,OAAO,MAAM,MAAM,WAAW,kCAAkC;SACzE,CAAA;IACH,CAAC;IAED;;;;;;OAMG;IACH,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IAChD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO;YACL,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,OAAO,KAAK,CAAC,UAAU,kDAAkD;SAClF,CAAA;IACH,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,CAAA;AAC3F,CAAC;AAED,gDAAgD;AACnC,QAAA,eAAe,GAAG,qCAAqC,CAAA","sourcesContent":["/*\n * **조치를 어디로 보낼 수 있나** — 판단만. 저장소도 어댑터 호출도 여기 없다.\n *\n * 배선(§`actuation-routing.ts`)에서 갈라 두는 이유는 규칙 쪽과 같다(§`rule-evaluate.ts`). 이 파일을\n * 부르는 시험이 TypeORM 데코레이터를 지나면 시험이 서지 않고, 그러면 **거절 판정을 아무 시험도 걷지\n * 않는다.** 조치 경로에서 그것은 승인이 쌓이는데 현장에 아무것도 안 가는 상태로 나타난다.\n */\nimport type { ReferenceCapability } from './reference-adapter.js'\n\n/** 판단에 필요한 것만 — 엔티티 전체를 받지 않는다. */\nexport interface ActuationTargetInput {\n /** 어느 트윈인가 — **거절 문장이 이것을 말한다.** 「모르는 트윈입니다」만으로는 찾을 수 없다. */\n instanceId: string\n /** 트윈이 있나. 없으면 `undefined`. */\n twinExists: boolean\n /** 이 트윈이 어느 사이트에서 왔나 — `TwinInstance.origin.siteId`. */\n siteId?: string\n /** 연결된 원본. 없으면 `undefined`(시뮬레이션으로만 세운 트윈). */\n ref?: { source: string; adapterType: string }\n /** 커넥터가 등록되어 있나. */\n adapterRegistered: boolean\n /**\n * 그 커넥터가 실제로 낼 수 있는 능력 — `capabilitiesOf` 가 판정한 값.\n *\n * **낼 수 있나는 이 값만 본다.** 아래 둘은 못 낼 때 무엇을 고쳐야 하는지 말하기 위한 것이고,\n * 판정을 다시 만들지 않는다.\n */\n capabilities: readonly ReferenceCapability[]\n /** 커넥터가 `capabilities` 에 조치를 적었나. */\n declaresActuate: boolean\n /** 커넥터에 `actuate` 함수가 있나. */\n hasActuateImpl: boolean\n}\n\n/** 보낼 수 있다 — 어디로까지 정해진 상태. */\nexport interface ActuationAllowed {\n ok: true\n siteId: string\n adapterType: string\n source: string\n}\n\n/** 보낼 수 없다 — 왜인지 코드와 문장으로. */\nexport interface ActuationRefused {\n ok: false\n code: ActuationRefusal\n detail: string\n}\n\nexport type ActuationTarget = ActuationAllowed | ActuationRefused\n\n/**\n * 보낼 수 있나 — **타입 가드로 둔다.**\n *\n * 이 저장소는 기본 설정이 `strict: false` 이고(`packages/tsconfig-base.json`), 그 상태에서는 `ok` 가\n * `true`·`false` 로 갈린 유니온이 `if (!t.ok)` 로 좁혀지지 않는다. 부르는 쪽마다 캐스팅을 넣으면 그\n * 캐스팅이 언젠가 틀린 쪽을 가리킨다. 판정을 여기 한 자리에 둔다.\n */\nexport function isActuationAllowed(target: ActuationTarget): target is ActuationAllowed {\n return target.ok === true\n}\n\n/**\n * 왜 못 보내나 — **코드로 답한다.**\n *\n * 화면이 이 코드로 다음 할 일을 가른다. 문장만 주면 화면은 그것을 그대로 띄우는 것밖에 못 하고,\n * 사람은 연결 설정을 열어야 하는지 커넥터를 고쳐야 하는지 모른다.\n */\nexport type ActuationRefusal =\n | 'unknown-twin'\n | 'no-source' /* 원본이 없는 트윈 — 보낼 곳이 없다 */\n | 'no-adapter' /* 커넥터가 등록되지 않았다 */\n | 'not-declared' /* 구현은 있는데 선언이 없다 — 한 줄 넣으면 된다 */\n | 'not-implemented' /* 선언은 있는데 구현이 없다 — 만들다 만 커넥터다 */\n | 'not-supported' /* 선언도 구현도 없다 — 이 커넥터는 관측만 낸다 */\n | 'no-site' /* 어느 사이트로 낼지 모른다 */\n\n/**\n * 보낼 수 있나 — 보낼 수 있으면 **어디로**까지 답한다.\n *\n * 순서가 뜻을 갖는다. 앞의 것이 없으면 뒤를 물을 수 없다 — 원본을 모르면 능력을 물을 데가 없다.\n */\nexport function actuationTarget(input: ActuationTargetInput): ActuationTarget {\n if (!input.twinExists) {\n return { ok: false, code: 'unknown-twin', detail: `모르는 트윈입니다 (${input.instanceId})` }\n }\n\n if (!input.ref) {\n /*\n * 시뮬레이션으로만 세운 트윈이 이 경우다. 규칙 경로가 시뮬 트윈을 이미 막지만\n * (§`rule-runner.ts`), 사람이 손으로 낸 조치는 그 문을 지나지 않는다. 막는 지점이 두 경로에\n * 각각 있는 것이고, 같은 판정을 두 벌 만든 것이 아니다.\n */\n return {\n ok: false,\n code: 'no-source',\n detail: `트윈 \"${input.instanceId}\" 에는 연결된 원본이 없습니다 — 보낼 곳이 없습니다`\n }\n }\n\n if (!input.adapterRegistered) {\n return { ok: false, code: 'no-adapter', detail: `등록되지 않은 커넥터입니다 (${input.ref.adapterType})` }\n }\n\n if (!input.capabilities.includes('actuate')) {\n /*\n * **왜 못 내는지를 셋으로 가른다** — 고칠 것이 셋 다 다르다.\n *\n * 선언 없음 · 구현 있음 한 줄 넣으면 된다\n * 선언 있음 · 구현 없음 만들다 만 커넥터다. 등록 검사는 이쪽을 말하지 않는다\n * 선언도 구현도 없음 이 커넥터는 조치를 받지 않는다\n *\n * 뭉개면 한 줄로 끝날 일에 커넥터를 다시 만들려 들거나, 반대로 「관측만 냅니다」를 믿고 조치를\n * 포기한다. 가운데 것을 「관측만 냅니다」로 답하는 것은 **사실이 아니다** — 그 사람은 조치를\n * 하겠다고 적어 두었다.\n */\n const { adapterType, source } = input.ref\n if (input.hasActuateImpl && !input.declaresActuate) {\n return {\n ok: false,\n code: 'not-declared',\n detail:\n `커넥터 \"${adapterType}\" 가 조치를 보내는 구현은 있는데 능력으로 선언하지 않았습니다 — ` +\n \"connector 의 capabilities 에 'actuate' 를 넣어야 합니다\"\n }\n }\n if (input.declaresActuate && !input.hasActuateImpl) {\n return {\n ok: false,\n code: 'not-implemented',\n detail:\n `커넥터 \"${adapterType}\" 가 조치를 보낸다고 선언했는데 그 구현이 없습니다 — ` +\n 'actuate 를 만들거나 선언을 지워야 합니다'\n }\n }\n return {\n ok: false,\n code: 'not-supported',\n detail: `원본 \"${source}\" (${adapterType}) 는 조치를 받지 않습니다 — 이 커넥터는 관측만 냅니다`\n }\n }\n\n /*\n * 사이트를 **지어내지 않는다.** 원본 하나가 여러 사이트를 낼 수 있다(`discoverSites`).\n *\n * 구동(§`control-routing.ts`)은 사이트를 몰라도 넘긴다 — 시늉을 멈추는 일에는 대상이 하나다.\n * 조치는 안 된다. 어느 공장에 낼지 모르는 작업지시를 첫 번째 사이트로 보내면 **잘못된 공장이\n * 실제로 만든다.**\n */\n const siteId = String(input.siteId ?? '').trim()\n if (!siteId) {\n return {\n ok: false,\n code: 'no-site',\n detail: `트윈 \"${input.instanceId}\" 이 어느 사이트에서 왔는지 알 수 없습니다 — 조치를 보낼 대상을 정할 수 없습니다`\n }\n }\n\n return { ok: true, siteId, adapterType: input.ref.adapterType, source: input.ref.source }\n}\n\n/** 승인은 남는다는 것을 함께 말한다 — 원인을 고친 뒤 다시 넘길 수 있다. */\nexport const APPROVAL_STANDS = '승인은 그대로 남아 있습니다 — 고친 뒤 다시 넘길 수 있습니다'\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { intakeCollisions } from './connection-portability.js';
|
|
2
|
+
export declare class ConnectionPortabilityResolver {
|
|
3
|
+
/**
|
|
4
|
+
* 지금 연결을 꾸러미로 낸다 — **비밀값은 안 나간다.**
|
|
5
|
+
*
|
|
6
|
+
* 이름 규칙으로 걸러서 **새 비밀 칸도 기본으로 안 나간다**(`SECRET_KEYS`). 나가지 않은 것은
|
|
7
|
+
* 꾸러미가 이름으로 말하고, 받는 쪽이 그 이름을 사람에게 보인다.
|
|
8
|
+
*/
|
|
9
|
+
twinConnectionBundle(context: ResolverContext, note?: string): Promise<any>;
|
|
10
|
+
/**
|
|
11
|
+
* 꾸러미를 받아 적용한다 — **`dryRun` 이 기본이다.**
|
|
12
|
+
*
|
|
13
|
+
* 쓰기가 기본이면 「무엇이 바뀌나」를 보기 위해 쓰게 된다. 계획을 먼저 보고 사람이 정하는 것이
|
|
14
|
+
* 이 문의 요점이고, 그래서 적용은 명시해야 일어난다.
|
|
15
|
+
*/
|
|
16
|
+
importTwinConnections(bundle: any, context: ResolverContext, apply?: boolean): Promise<any>;
|
|
17
|
+
/**
|
|
18
|
+
* 저쪽 배포에서 당겨와 적용한다 — **본래 원한 문.**
|
|
19
|
+
*
|
|
20
|
+
* 받는 쪽이 주체다. 저쪽의 `twinConnectionBundle` 을 불러 이 배포에 적용한다 — 위 두 문을 잇는
|
|
21
|
+
* 얇은 층이고, 판정과 쓰기는 `importTwinConnections` 와 같은 규칙을 쓴다.
|
|
22
|
+
*
|
|
23
|
+
* **자격은 부르는 사람이 준다.** 이 서버가 저쪽 자격을 들고 있으면 그 통로가 상시로 열려 있게 되고,
|
|
24
|
+
* 그것은 옮기는 일에 필요한 것보다 넓다.
|
|
25
|
+
*/
|
|
26
|
+
pullTwinConnectionsFrom(endpoint: string, context: ResolverContext, token?: string, apply?: boolean): Promise<any>;
|
|
27
|
+
}
|
|
28
|
+
/** 이 리졸버가 다루는 부딪힘 판정 — 화면이 같은 함수를 쓸 수 있게 다시 내보낸다. */
|
|
29
|
+
export { intakeCollisions };
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.intakeCollisions = exports.ConnectionPortabilityResolver = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
/*
|
|
6
|
+
* **연결을 배포 사이로 옮기는 문 셋** — 몸통은 `connection-portability.ts` 의 순수 함수들이다.
|
|
7
|
+
*
|
|
8
|
+
* ── 무엇을 옮기나 (사용자와 맞춘 개념, 2026-09-05) ─────────────────────────
|
|
9
|
+
* **옮기는 것은 「붙는 법」이지 「붙은 결과」가 아니다.**
|
|
10
|
+
*
|
|
11
|
+
* 옮긴다 연결 선언 — 주소 · 도메인 · 라인 크기 · 자재 이름공간
|
|
12
|
+
* 안 옮긴다 마스터 원본에서 다시 읽으면 된다
|
|
13
|
+
* 안 옮긴다 사실(저널) 원본이 다시 보내면 된다
|
|
14
|
+
* 안 옮긴다 잰 값 그 서버에서 다시 세면 된다
|
|
15
|
+
* 안 옮긴다 비밀값 그 환경의 것이지 셋업이 아니다
|
|
16
|
+
*
|
|
17
|
+
* **꾸러미는 정본이 아니라 운반 수단이다.** 정본은 언제나 그 배포의 DB 이고, 꾸러미는 쓰고 버린다.
|
|
18
|
+
*
|
|
19
|
+
* ── 주체는 받는 쪽이다 ─────────────────────────────────────────────────────
|
|
20
|
+
* 운영이 개발에서 **당겨온다.** 개발이 운영으로 밀면 개발 환경에 운영 자격이 살고, 그 통로는 실수로도
|
|
21
|
+
* 열린다. 그래서 `pullTwinConnectionsFrom` 이 본래 원하던 문이고, 나머지 둘은 저쪽에 못 닿을 때의
|
|
22
|
+
* 길이다.
|
|
23
|
+
*
|
|
24
|
+
* ── 쓰기 전에 계획을 보인다 ────────────────────────────────────────────────
|
|
25
|
+
* `planImport` 가 새로/갱신/건너뜀과 **사람이 채울 비밀값**을 먼저 낸다. 부딪힘(두 연결이 같은 공장에
|
|
26
|
+
* 조치를 내는 것)이 하나라도 있으면 **아무것도 쓰지 않는다** — 반만 적용된 연결 설정은 어느 쪽이
|
|
27
|
+
* 참인지 아무도 모른다.
|
|
28
|
+
*/
|
|
29
|
+
const type_graphql_1 = require("type-graphql");
|
|
30
|
+
const shell_1 = require("@things-factory/shell");
|
|
31
|
+
const twin_reference_js_1 = require("./twin-reference.js");
|
|
32
|
+
const connection_portability_js_1 = require("./connection-portability.js");
|
|
33
|
+
Object.defineProperty(exports, "intakeCollisions", { enumerable: true, get: function () { return connection_portability_js_1.intakeCollisions; } });
|
|
34
|
+
/** 그 환경이 이 비밀값을 줄 수 있나 — **값은 읽지 않는다.** 이름의 존재만 본다. */
|
|
35
|
+
function hasSecret(source, key) {
|
|
36
|
+
const name = (0, connection_portability_js_1.secretEnvName)(source, key);
|
|
37
|
+
const v = process.env[name];
|
|
38
|
+
return typeof v === 'string' && v.trim().length > 0;
|
|
39
|
+
}
|
|
40
|
+
let ConnectionPortabilityResolver = class ConnectionPortabilityResolver {
|
|
41
|
+
/**
|
|
42
|
+
* 지금 연결을 꾸러미로 낸다 — **비밀값은 안 나간다.**
|
|
43
|
+
*
|
|
44
|
+
* 이름 규칙으로 걸러서 **새 비밀 칸도 기본으로 안 나간다**(`SECRET_KEYS`). 나가지 않은 것은
|
|
45
|
+
* 꾸러미가 이름으로 말하고, 받는 쪽이 그 이름을 사람에게 보인다.
|
|
46
|
+
*/
|
|
47
|
+
/*
|
|
48
|
+
* **읽기인데 쓰기와 같은 문을 쓴다.** 이 꾸러미는 연결 주소·도메인과 **비밀 칸의 이름**까지 낸다 —
|
|
49
|
+
* 그 이름만으로도 그 배포가 무엇에 붙어 있는지 드러난다. 권한 가드가 「쓰기는 GATE 하나」로
|
|
50
|
+
* 통일해 두었고, 이 문은 읽기지만 그 무게가 쓰기 쪽이다(ADR-0027).
|
|
51
|
+
*/
|
|
52
|
+
async twinConnectionBundle(context, note) {
|
|
53
|
+
const domain = context.state.domain;
|
|
54
|
+
const rows = await (0, shell_1.getRepository)(twin_reference_js_1.TwinReference).find({ where: { domain: { id: domain.id } } });
|
|
55
|
+
return (0, connection_portability_js_1.exportConnections)(rows, { at: new Date().toISOString(), domain: domain.subdomain ?? domain.name, ...(note ? { note } : {}) });
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 꾸러미를 받아 적용한다 — **`dryRun` 이 기본이다.**
|
|
59
|
+
*
|
|
60
|
+
* 쓰기가 기본이면 「무엇이 바뀌나」를 보기 위해 쓰게 된다. 계획을 먼저 보고 사람이 정하는 것이
|
|
61
|
+
* 이 문의 요점이고, 그래서 적용은 명시해야 일어난다.
|
|
62
|
+
*/
|
|
63
|
+
async importTwinConnections(bundle, context, apply) {
|
|
64
|
+
const domainId = context.state.domain.id;
|
|
65
|
+
const repo = (0, shell_1.getRepository)(twin_reference_js_1.TwinReference);
|
|
66
|
+
const existing = await repo.find({ where: { domain: { id: domainId } } });
|
|
67
|
+
const plan = (0, connection_portability_js_1.planImport)(bundle, existing, hasSecret);
|
|
68
|
+
const said = (0, connection_portability_js_1.sayPlan)(plan);
|
|
69
|
+
/*
|
|
70
|
+
* 부딪힘이 있으면 **아무것도 쓰지 않는다.** 두 연결이 같은 공장에 조치를 내면 어느 쪽이 그 라인의
|
|
71
|
+
* 주인인지 정할 수 없고, 반만 적용하면 그 물음이 조용히 남는다.
|
|
72
|
+
*/
|
|
73
|
+
if (plan.collisions.length || plan.errors.length) {
|
|
74
|
+
return { ok: false, applied: 0, plan, said, refused: plan.collisions.length ? 'intake-collision' : 'bundle-error' };
|
|
75
|
+
}
|
|
76
|
+
if (!apply)
|
|
77
|
+
return { ok: true, applied: 0, plan, said, dryRun: true };
|
|
78
|
+
let applied = 0;
|
|
79
|
+
for (const a of plan.actions) {
|
|
80
|
+
/* 건너뛴 것은 이유가 계획에 남아 있다 — 여기서 다시 판정하지 않는다. */
|
|
81
|
+
if (a.action === 'skip')
|
|
82
|
+
continue;
|
|
83
|
+
const c = a.connection;
|
|
84
|
+
/*
|
|
85
|
+
* **비밀값을 빈 값으로 안 채운다.** 꾸러미에 비밀값이 없고, `planImport` 가 그 이름을
|
|
86
|
+
* `missingSecrets`·`needsHuman` 으로 이미 냈다. 여기서 그 칸을 `undefined` 로 쓰면 사람이
|
|
87
|
+
* 화면에서 넣어 둔 값을 파일 하나가 지운다.
|
|
88
|
+
*
|
|
89
|
+
* 그래서 갱신은 **선언 칸만** 건드린다. 이미 있는 연결의 `connectionConfig` 는 꾸러미가 든
|
|
90
|
+
* 것만 덮어쓰고 나머지 키는 그대로 남긴다.
|
|
91
|
+
*/
|
|
92
|
+
const found = await repo.findOne({ where: { domain: { id: domainId }, source: c.source } });
|
|
93
|
+
const declared = {
|
|
94
|
+
...(c.system ? { system: c.system } : {}),
|
|
95
|
+
...(c.siteName ? { siteName: c.siteName } : {}),
|
|
96
|
+
adapterType: c.adapterType,
|
|
97
|
+
...(c.scope ? { scopeSpec: c.scope } : {})
|
|
98
|
+
};
|
|
99
|
+
if (found) {
|
|
100
|
+
const merged = { ...(found.connectionConfig ?? {}), ...(c.config ?? {}) };
|
|
101
|
+
await repo.update({ id: found.id }, { ...declared, connectionConfig: merged });
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
await repo.save(repo.create({ source: c.source, ...declared, connectionConfig: c.config ?? {}, domain: { id: domainId } }));
|
|
105
|
+
}
|
|
106
|
+
applied++;
|
|
107
|
+
}
|
|
108
|
+
return { ok: true, applied, plan, said };
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* 저쪽 배포에서 당겨와 적용한다 — **본래 원한 문.**
|
|
112
|
+
*
|
|
113
|
+
* 받는 쪽이 주체다. 저쪽의 `twinConnectionBundle` 을 불러 이 배포에 적용한다 — 위 두 문을 잇는
|
|
114
|
+
* 얇은 층이고, 판정과 쓰기는 `importTwinConnections` 와 같은 규칙을 쓴다.
|
|
115
|
+
*
|
|
116
|
+
* **자격은 부르는 사람이 준다.** 이 서버가 저쪽 자격을 들고 있으면 그 통로가 상시로 열려 있게 되고,
|
|
117
|
+
* 그것은 옮기는 일에 필요한 것보다 넓다.
|
|
118
|
+
*/
|
|
119
|
+
async pullTwinConnectionsFrom(endpoint, context, token, apply) {
|
|
120
|
+
let bundle;
|
|
121
|
+
try {
|
|
122
|
+
const res = await fetch(endpoint, {
|
|
123
|
+
method: 'POST',
|
|
124
|
+
headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) },
|
|
125
|
+
body: JSON.stringify({ query: '{ twinConnectionBundle }' })
|
|
126
|
+
});
|
|
127
|
+
const json = await res.json();
|
|
128
|
+
/* 저쪽이 오류를 냈으면 그 말을 그대로 올린다 — 여기서 다시 지으면 원인이 흐려진다. */
|
|
129
|
+
if (json?.errors?.length)
|
|
130
|
+
return { ok: false, applied: 0, refused: 'remote-error', remote: json.errors.map((e) => e?.message) };
|
|
131
|
+
bundle = json?.data?.twinConnectionBundle;
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
return { ok: false, applied: 0, refused: 'unreachable', remote: [String(e?.message ?? e)] };
|
|
135
|
+
}
|
|
136
|
+
if (!bundle)
|
|
137
|
+
return { ok: false, applied: 0, refused: 'empty-bundle' };
|
|
138
|
+
return this.importTwinConnections(bundle, context, apply);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
exports.ConnectionPortabilityResolver = ConnectionPortabilityResolver;
|
|
142
|
+
tslib_1.__decorate([
|
|
143
|
+
(0, type_graphql_1.Directive)('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)'),
|
|
144
|
+
(0, type_graphql_1.Query)(returns => shell_1.ScalarObject, {
|
|
145
|
+
description: 'Export this deployment’s twin connection declarations as a portable bundle. Secrets are never included — the bundle names them so the receiving side can ask a human.'
|
|
146
|
+
}),
|
|
147
|
+
tslib_1.__param(0, (0, type_graphql_1.Ctx)()),
|
|
148
|
+
tslib_1.__param(1, (0, type_graphql_1.Arg)('note', { nullable: true, description: 'Free note carried in the bundle (why this export was made).' })),
|
|
149
|
+
tslib_1.__metadata("design:type", Function),
|
|
150
|
+
tslib_1.__metadata("design:paramtypes", [Object, String]),
|
|
151
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
152
|
+
], ConnectionPortabilityResolver.prototype, "twinConnectionBundle", null);
|
|
153
|
+
tslib_1.__decorate([
|
|
154
|
+
(0, type_graphql_1.Directive)('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)'),
|
|
155
|
+
(0, type_graphql_1.Mutation)(returns => shell_1.ScalarObject, {
|
|
156
|
+
description: 'Apply a connection bundle. Returns the plan (new / updated / skipped, secrets a human must supply, collisions). Defaults to dryRun — pass apply:true to write.'
|
|
157
|
+
}),
|
|
158
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('bundle', type => shell_1.ScalarObject)),
|
|
159
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
160
|
+
tslib_1.__param(2, (0, type_graphql_1.Arg)('apply', { nullable: true, description: 'Write the plan. Omit or false to only see what would happen.' })),
|
|
161
|
+
tslib_1.__metadata("design:type", Function),
|
|
162
|
+
tslib_1.__metadata("design:paramtypes", [Object, Object, Boolean]),
|
|
163
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
164
|
+
], ConnectionPortabilityResolver.prototype, "importTwinConnections", null);
|
|
165
|
+
tslib_1.__decorate([
|
|
166
|
+
(0, type_graphql_1.Directive)('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)'),
|
|
167
|
+
(0, type_graphql_1.Mutation)(returns => shell_1.ScalarObject, {
|
|
168
|
+
description: 'Pull connection declarations from another deployment and apply them here. The caller supplies that deployment’s URL and token — this server does not keep them.'
|
|
169
|
+
}),
|
|
170
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('endpoint', { description: 'That deployment’s GraphQL endpoint.' })),
|
|
171
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
172
|
+
tslib_1.__param(2, (0, type_graphql_1.Arg)('token', { nullable: true, description: 'Bearer token for that deployment. Not stored.' })),
|
|
173
|
+
tslib_1.__param(3, (0, type_graphql_1.Arg)('apply', { nullable: true })),
|
|
174
|
+
tslib_1.__metadata("design:type", Function),
|
|
175
|
+
tslib_1.__metadata("design:paramtypes", [String, Object, String, Boolean]),
|
|
176
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
177
|
+
], ConnectionPortabilityResolver.prototype, "pullTwinConnectionsFrom", null);
|
|
178
|
+
exports.ConnectionPortabilityResolver = ConnectionPortabilityResolver = tslib_1.__decorate([
|
|
179
|
+
(0, type_graphql_1.Resolver)()
|
|
180
|
+
], ConnectionPortabilityResolver);
|
|
181
|
+
//# sourceMappingURL=connection-portability-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection-portability-resolver.js","sourceRoot":"","sources":["../../../server/service/reference/connection-portability-resolver.ts"],"names":[],"mappings":";;;;AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,+CAA6E;AAC7E,iDAAmE;AAEnE,2DAAmD;AACnD,2EAAqH;AA8I5G,iGA9ImB,4CAAgB,OA8InB;AA5IzB,uDAAuD;AACvD,SAAS,SAAS,CAAC,MAAc,EAAE,GAAW;IAC5C,MAAM,IAAI,GAAG,IAAA,yCAAa,EAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACvC,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAC3B,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAA;AACrD,CAAC;AAGM,IAAM,6BAA6B,GAAnC,MAAM,6BAA6B;IACxC;;;;;OAKG;IACH;;;;OAIG;IAMG,AAAN,KAAK,CAAC,oBAAoB,CACjB,OAAwB,EAC8E,IAAa;QAE1H,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAA;QACnC,MAAM,IAAI,GAAG,MAAM,IAAA,qBAAa,EAAC,iCAAa,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;QAC9F,OAAO,IAAA,6CAAiB,EAAC,IAAW,EAAE,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;IAC7I,CAAC;IAED;;;;;OAKG;IAMG,AAAN,KAAK,CAAC,qBAAqB,CACY,MAAW,EACzC,OAAwB,EACgF,KAAe;QAE9H,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAA;QACxC,MAAM,IAAI,GAAG,IAAA,qBAAa,EAAC,iCAAa,CAAC,CAAA;QACzC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAA;QAEzE,MAAM,IAAI,GAAG,IAAA,sCAAU,EAAC,MAAM,EAAE,QAAe,EAAE,SAAS,CAAC,CAAA;QAC3D,MAAM,IAAI,GAAG,IAAA,mCAAO,EAAC,IAAI,CAAC,CAAA;QAE1B;;;WAGG;QACH,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACjD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,cAAc,EAAE,CAAA;QACrH,CAAC;QACD,IAAI,CAAC,KAAK;YAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;QAErE,IAAI,OAAO,GAAG,CAAC,CAAA;QACf,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC7B,6CAA6C;YAC7C,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM;gBAAE,SAAQ;YAEjC,MAAM,CAAC,GAAG,CAAC,CAAC,UAAU,CAAA;YACtB;;;;;;;eAOG;YACH,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;YAC3F,MAAM,QAAQ,GAAG;gBACf,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACzC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC/C,WAAW,EAAE,CAAC,CAAC,WAAW;gBAC1B,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACpC,CAAA;YAER,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,MAAM,GAAG,EAAE,GAAG,CAAE,KAAa,CAAC,gBAAgB,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAA;gBAClF,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,EAAG,KAAa,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,QAAQ,EAAE,gBAAgB,EAAE,MAAM,EAAS,CAAC,CAAA;YAChG,CAAC;iBAAM,CAAC;gBACN,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,GAAG,QAAQ,EAAE,gBAAgB,EAAE,CAAC,CAAC,MAAM,IAAI,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAS,EAAE,CAAC,CAAC,CAAA;YACpI,CAAC;YACD,OAAO,EAAE,CAAA;QACX,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;IAC1C,CAAC;IAED;;;;;;;;OAQG;IAMG,AAAN,KAAK,CAAC,uBAAuB,CAC8C,QAAgB,EAClF,OAAwB,EACiE,KAAc,EAC5E,KAAe;QAEjD,IAAI,MAAW,CAAA;QACf,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE;gBAChC,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE;gBACvG,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC;aAC5D,CAAC,CAAA;YACF,MAAM,IAAI,GAAQ,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;YAClC,qDAAqD;YACrD,IAAI,IAAI,EAAE,MAAM,EAAE,MAAM;gBAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,CAAA;YACpI,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,oBAAoB,CAAA;QAC3C,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,CAAA;QAC7F,CAAC;QACD,IAAI,CAAC,MAAM;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,CAAA;QAEtE,OAAO,IAAI,CAAC,qBAAqB,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;IAC3D,CAAC;CACF,CAAA;AAjIY,sEAA6B;AAiBlC;IALL,IAAA,wBAAS,EAAC,uGAAuG,CAAC;IAClH,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,oBAAY,EAAE;QAC9B,WAAW,EACT,uKAAuK;KAC1K,CAAC;IAEC,mBAAA,IAAA,kBAAG,GAAE,CAAA;IACL,mBAAA,IAAA,kBAAG,EAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,6DAA6D,EAAE,CAAC,CAAA;;;;yEAK7G;AAaK;IALL,IAAA,wBAAS,EAAC,uGAAuG,CAAC;IAClH,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,oBAAY,EAAE;QACjC,WAAW,EACT,gKAAgK;KACnK,CAAC;IAEC,mBAAA,IAAA,kBAAG,EAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC,oBAAY,CAAC,CAAA;IACnC,mBAAA,IAAA,kBAAG,GAAE,CAAA;IACL,mBAAA,IAAA,kBAAG,EAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,8DAA8D,EAAE,CAAC,CAAA;;;;0EAiD/G;AAgBK;IALL,IAAA,wBAAS,EAAC,uGAAuG,CAAC;IAClH,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,oBAAY,EAAE;QACjC,WAAW,EACT,iKAAiK;KACpK,CAAC;IAEC,mBAAA,IAAA,kBAAG,EAAC,UAAU,EAAE,EAAE,WAAW,EAAE,qCAAqC,EAAE,CAAC,CAAA;IACvE,mBAAA,IAAA,kBAAG,GAAE,CAAA;IACL,mBAAA,IAAA,kBAAG,EAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,+CAA+C,EAAE,CAAC,CAAA;IAC9F,mBAAA,IAAA,kBAAG,EAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;;;;4EAmBlC;wCAhIU,6BAA6B;IADzC,IAAA,uBAAQ,GAAE;GACE,6BAA6B,CAiIzC","sourcesContent":["/*\n * **연결을 배포 사이로 옮기는 문 셋** — 몸통은 `connection-portability.ts` 의 순수 함수들이다.\n *\n * ── 무엇을 옮기나 (사용자와 맞춘 개념, 2026-09-05) ─────────────────────────\n * **옮기는 것은 「붙는 법」이지 「붙은 결과」가 아니다.**\n *\n * 옮긴다 연결 선언 — 주소 · 도메인 · 라인 크기 · 자재 이름공간\n * 안 옮긴다 마스터 원본에서 다시 읽으면 된다\n * 안 옮긴다 사실(저널) 원본이 다시 보내면 된다\n * 안 옮긴다 잰 값 그 서버에서 다시 세면 된다\n * 안 옮긴다 비밀값 그 환경의 것이지 셋업이 아니다\n *\n * **꾸러미는 정본이 아니라 운반 수단이다.** 정본은 언제나 그 배포의 DB 이고, 꾸러미는 쓰고 버린다.\n *\n * ── 주체는 받는 쪽이다 ─────────────────────────────────────────────────────\n * 운영이 개발에서 **당겨온다.** 개발이 운영으로 밀면 개발 환경에 운영 자격이 살고, 그 통로는 실수로도\n * 열린다. 그래서 `pullTwinConnectionsFrom` 이 본래 원하던 문이고, 나머지 둘은 저쪽에 못 닿을 때의\n * 길이다.\n *\n * ── 쓰기 전에 계획을 보인다 ────────────────────────────────────────────────\n * `planImport` 가 새로/갱신/건너뜀과 **사람이 채울 비밀값**을 먼저 낸다. 부딪힘(두 연결이 같은 공장에\n * 조치를 내는 것)이 하나라도 있으면 **아무것도 쓰지 않는다** — 반만 적용된 연결 설정은 어느 쪽이\n * 참인지 아무도 모른다.\n */\nimport { Arg, Ctx, Directive, Mutation, Query, Resolver } from 'type-graphql'\nimport { ScalarObject, getRepository } from '@things-factory/shell'\n\nimport { TwinReference } from './twin-reference.js'\nimport { exportConnections, intakeCollisions, planImport, sayPlan, secretEnvName } from './connection-portability.js'\n\n/** 그 환경이 이 비밀값을 줄 수 있나 — **값은 읽지 않는다.** 이름의 존재만 본다. */\nfunction hasSecret(source: string, key: string): boolean {\n const name = secretEnvName(source, key)\n const v = process.env[name]\n return typeof v === 'string' && v.trim().length > 0\n}\n\n@Resolver()\nexport class ConnectionPortabilityResolver {\n /**\n * 지금 연결을 꾸러미로 낸다 — **비밀값은 안 나간다.**\n *\n * 이름 규칙으로 걸러서 **새 비밀 칸도 기본으로 안 나간다**(`SECRET_KEYS`). 나가지 않은 것은\n * 꾸러미가 이름으로 말하고, 받는 쪽이 그 이름을 사람에게 보인다.\n */\n /*\n * **읽기인데 쓰기와 같은 문을 쓴다.** 이 꾸러미는 연결 주소·도메인과 **비밀 칸의 이름**까지 낸다 —\n * 그 이름만으로도 그 배포가 무엇에 붙어 있는지 드러난다. 권한 가드가 「쓰기는 GATE 하나」로\n * 통일해 두었고, 이 문은 읽기지만 그 무게가 쓰기 쪽이다(ADR-0027).\n */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Query(returns => ScalarObject, {\n description:\n 'Export this deployment’s twin connection declarations as a portable bundle. Secrets are never included — the bundle names them so the receiving side can ask a human.'\n })\n async twinConnectionBundle(\n @Ctx() context: ResolverContext,\n @Arg('note', { nullable: true, description: 'Free note carried in the bundle (why this export was made).' }) note?: string\n ): Promise<any> {\n const domain = context.state.domain\n const rows = await getRepository(TwinReference).find({ where: { domain: { id: domain.id } } })\n return exportConnections(rows as any, { at: new Date().toISOString(), domain: domain.subdomain ?? domain.name, ...(note ? { note } : {}) })\n }\n\n /**\n * 꾸러미를 받아 적용한다 — **`dryRun` 이 기본이다.**\n *\n * 쓰기가 기본이면 「무엇이 바뀌나」를 보기 위해 쓰게 된다. 계획을 먼저 보고 사람이 정하는 것이\n * 이 문의 요점이고, 그래서 적용은 명시해야 일어난다.\n */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => ScalarObject, {\n description:\n 'Apply a connection bundle. Returns the plan (new / updated / skipped, secrets a human must supply, collisions). Defaults to dryRun — pass apply:true to write.'\n })\n async importTwinConnections(\n @Arg('bundle', type => ScalarObject) bundle: any,\n @Ctx() context: ResolverContext,\n @Arg('apply', { nullable: true, description: 'Write the plan. Omit or false to only see what would happen.' }) apply?: boolean\n ): Promise<any> {\n const domainId = context.state.domain.id\n const repo = getRepository(TwinReference)\n const existing = await repo.find({ where: { domain: { id: domainId } } })\n\n const plan = planImport(bundle, existing as any, hasSecret)\n const said = sayPlan(plan)\n\n /*\n * 부딪힘이 있으면 **아무것도 쓰지 않는다.** 두 연결이 같은 공장에 조치를 내면 어느 쪽이 그 라인의\n * 주인인지 정할 수 없고, 반만 적용하면 그 물음이 조용히 남는다.\n */\n if (plan.collisions.length || plan.errors.length) {\n return { ok: false, applied: 0, plan, said, refused: plan.collisions.length ? 'intake-collision' : 'bundle-error' }\n }\n if (!apply) return { ok: true, applied: 0, plan, said, dryRun: true }\n\n let applied = 0\n for (const a of plan.actions) {\n /* 건너뛴 것은 이유가 계획에 남아 있다 — 여기서 다시 판정하지 않는다. */\n if (a.action === 'skip') continue\n\n const c = a.connection\n /*\n * **비밀값을 빈 값으로 안 채운다.** 꾸러미에 비밀값이 없고, `planImport` 가 그 이름을\n * `missingSecrets`·`needsHuman` 으로 이미 냈다. 여기서 그 칸을 `undefined` 로 쓰면 사람이\n * 화면에서 넣어 둔 값을 파일 하나가 지운다.\n *\n * 그래서 갱신은 **선언 칸만** 건드린다. 이미 있는 연결의 `connectionConfig` 는 꾸러미가 든\n * 것만 덮어쓰고 나머지 키는 그대로 남긴다.\n */\n const found = await repo.findOne({ where: { domain: { id: domainId }, source: c.source } })\n const declared = {\n ...(c.system ? { system: c.system } : {}),\n ...(c.siteName ? { siteName: c.siteName } : {}),\n adapterType: c.adapterType,\n ...(c.scope ? { scopeSpec: c.scope } : {})\n } as any\n\n if (found) {\n const merged = { ...((found as any).connectionConfig ?? {}), ...(c.config ?? {}) }\n await repo.update({ id: (found as any).id }, { ...declared, connectionConfig: merged } as any)\n } else {\n await repo.save(repo.create({ source: c.source, ...declared, connectionConfig: c.config ?? {}, domain: { id: domainId } as any }))\n }\n applied++\n }\n return { ok: true, applied, plan, said }\n }\n\n /**\n * 저쪽 배포에서 당겨와 적용한다 — **본래 원한 문.**\n *\n * 받는 쪽이 주체다. 저쪽의 `twinConnectionBundle` 을 불러 이 배포에 적용한다 — 위 두 문을 잇는\n * 얇은 층이고, 판정과 쓰기는 `importTwinConnections` 와 같은 규칙을 쓴다.\n *\n * **자격은 부르는 사람이 준다.** 이 서버가 저쪽 자격을 들고 있으면 그 통로가 상시로 열려 있게 되고,\n * 그것은 옮기는 일에 필요한 것보다 넓다.\n */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => ScalarObject, {\n description:\n 'Pull connection declarations from another deployment and apply them here. The caller supplies that deployment’s URL and token — this server does not keep them.'\n })\n async pullTwinConnectionsFrom(\n @Arg('endpoint', { description: 'That deployment’s GraphQL endpoint.' }) endpoint: string,\n @Ctx() context: ResolverContext,\n @Arg('token', { nullable: true, description: 'Bearer token for that deployment. Not stored.' }) token?: string,\n @Arg('apply', { nullable: true }) apply?: boolean\n ): Promise<any> {\n let bundle: any\n try {\n const res = await fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) },\n body: JSON.stringify({ query: '{ twinConnectionBundle }' })\n })\n const json: any = await res.json()\n /* 저쪽이 오류를 냈으면 그 말을 그대로 올린다 — 여기서 다시 지으면 원인이 흐려진다. */\n if (json?.errors?.length) return { ok: false, applied: 0, refused: 'remote-error', remote: json.errors.map((e: any) => e?.message) }\n bundle = json?.data?.twinConnectionBundle\n } catch (e: any) {\n return { ok: false, applied: 0, refused: 'unreachable', remote: [String(e?.message ?? e)] }\n }\n if (!bundle) return { ok: false, applied: 0, refused: 'empty-bundle' }\n\n return this.importTwinConnections(bundle, context, apply)\n }\n}\n\n/** 이 리졸버가 다루는 부딪힘 판정 — 화면이 같은 함수를 쓸 수 있게 다시 내보낸다. */\nexport { intakeCollisions }\n"]}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/** 값이 아니라 **채워져 있었다는 사실**만 나가는 칸들. */
|
|
2
|
+
export declare const SECRET_KEYS: readonly ["token", "hookSecret", "intakeSecret", "password", "apiKey"];
|
|
3
|
+
export interface PortableConnection {
|
|
4
|
+
source: string;
|
|
5
|
+
system?: string;
|
|
6
|
+
siteName?: string;
|
|
7
|
+
adapterType: string;
|
|
8
|
+
/** 비밀값을 뺀 연결 설정. */
|
|
9
|
+
config: Record<string, unknown>;
|
|
10
|
+
/** 내보낼 때 채워져 있던 비밀 칸 이름 — 값은 없습니다. */
|
|
11
|
+
secretsRequired: string[];
|
|
12
|
+
/** 이 연결이 무엇을 세우나. */
|
|
13
|
+
scope?: unknown;
|
|
14
|
+
}
|
|
15
|
+
export interface ConnectionBundle {
|
|
16
|
+
kind: 'operato-twin/connections';
|
|
17
|
+
version: 1;
|
|
18
|
+
exportedAt: string;
|
|
19
|
+
/** 어디서 내보냈나 — 사람이 나중에 「이게 어느 배포 것인가」를 물을 때 답합니다. */
|
|
20
|
+
exportedFrom?: {
|
|
21
|
+
domain?: string;
|
|
22
|
+
note?: string;
|
|
23
|
+
};
|
|
24
|
+
connections: PortableConnection[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 연결 행들을 옮길 수 있는 모양으로 — **비밀값을 빼고**.
|
|
28
|
+
*
|
|
29
|
+
* 빼는 판정을 이름으로 합니다. 목록으로만 하면 커넥터가 새 비밀 칸을 더할 때 그 값이 조용히
|
|
30
|
+
* 파일로 나갑니다 — 이름 규칙을 함께 두어 **새 칸이 기본으로 안 나가게** 합니다.
|
|
31
|
+
*/
|
|
32
|
+
export declare function exportConnections(rows: readonly {
|
|
33
|
+
source: string;
|
|
34
|
+
system?: string;
|
|
35
|
+
siteName?: string;
|
|
36
|
+
adapterType: string;
|
|
37
|
+
connectionConfig?: any;
|
|
38
|
+
scopeSpec?: any;
|
|
39
|
+
}[], meta: {
|
|
40
|
+
at: string;
|
|
41
|
+
domain?: string;
|
|
42
|
+
note?: string;
|
|
43
|
+
}): ConnectionBundle;
|
|
44
|
+
export type ImportAction = {
|
|
45
|
+
action: 'create';
|
|
46
|
+
connection: PortableConnection;
|
|
47
|
+
missingSecrets: string[];
|
|
48
|
+
} | {
|
|
49
|
+
action: 'update';
|
|
50
|
+
connection: PortableConnection;
|
|
51
|
+
missingSecrets: string[];
|
|
52
|
+
keptSecrets: string[];
|
|
53
|
+
} | {
|
|
54
|
+
action: 'skip';
|
|
55
|
+
source: string;
|
|
56
|
+
why: string;
|
|
57
|
+
};
|
|
58
|
+
export interface ImportPlan {
|
|
59
|
+
actions: ImportAction[];
|
|
60
|
+
/** 사람이 연결 화면에서 채워야 하는 것 — 이것이 비어야 「시드만으로 끝」입니다. */
|
|
61
|
+
/**
|
|
62
|
+
* 사람이 환경에 넣어야 하는 비밀값.
|
|
63
|
+
*
|
|
64
|
+
* `env` 를 함께 냅니다 — **그 이름을 사람이 손으로 적기 때문**입니다. 화면이 이름을 따로 만들면
|
|
65
|
+
* 규칙이 두 곳에 살고, 갈리는 날 「넣었는데 안 읽힌다」가 됩니다. 실제로 났습니다(2026-09-06):
|
|
66
|
+
* 화면이 `TWIN_SECRET__MES-MIRATEK__HOOKSECRET` 을 알려 줬는데 서버가 읽는 이름은
|
|
67
|
+
* `TWIN_SECRET__MES_MIRATEK__HOOK_SECRET` 이었습니다. 하이픈과 낙타 경계 둘 다 달랐습니다.
|
|
68
|
+
*
|
|
69
|
+
* `keys` 와 순서가 같습니다 — `keys[i]` 의 이름이 `env[i]` 입니다.
|
|
70
|
+
*/
|
|
71
|
+
needsHuman: {
|
|
72
|
+
source: string;
|
|
73
|
+
keys: string[];
|
|
74
|
+
env: string[];
|
|
75
|
+
}[];
|
|
76
|
+
/** 들여온 **뒤에** 두 연결이 같은 곳에 조치를 내게 되나 — 있으면 들여오면 안 됩니다. */
|
|
77
|
+
collisions: {
|
|
78
|
+
intake: string;
|
|
79
|
+
sources: string[];
|
|
80
|
+
}[];
|
|
81
|
+
errors: string[];
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* 들여오기 **계획**을 먼저 냅니다 — 쓰기 전에 무엇이 일어날지 사람이 봅니다.
|
|
85
|
+
*
|
|
86
|
+
* ── 이미 있는 연결을 덮지 않습니다 ──────────────────────────────────────────
|
|
87
|
+
* 같은 이름이 이미 있으면 **선언만 갱신하고 비밀값은 그대로 둡니다.** 사람이 화면에서 넣은 값을
|
|
88
|
+
* 파일이 지우면, 파일 하나 들여왔다가 도는 연결이 멈춥니다. 데모 시드도 같은 규율을 적어 두었습니다
|
|
89
|
+
* (「옆 설정을 지우지 않는다 — 시드는 자기가 아는 것만 채운다」).
|
|
90
|
+
*
|
|
91
|
+
* ── 없는 비밀값을 지어내지 않습니다 ─────────────────────────────────────────
|
|
92
|
+
* 빈 값으로 채워 두면 연결이 서고 **동기화할 때 처음 실패합니다.** 그때는 무엇이 없어서인지 화면에서
|
|
93
|
+
* 멉니다. 여기서 이름을 대고 멈추는 것이 낫습니다.
|
|
94
|
+
*/
|
|
95
|
+
export declare function planImport(bundle: unknown, existing: readonly {
|
|
96
|
+
source: string;
|
|
97
|
+
connectionConfig?: any;
|
|
98
|
+
}[],
|
|
99
|
+
/** 지금 환경이 줄 수 있는 비밀값 이름들 — 값은 안 받습니다(이 함수는 값을 만지지 않습니다). */
|
|
100
|
+
availableSecrets?: (source: string, key: string) => boolean): ImportPlan;
|
|
101
|
+
/** 사람이 읽는 한 줄 — 화면과 로그가 같은 문장을 쓰게. */
|
|
102
|
+
export declare function sayPlan(plan: ImportPlan): string;
|
|
103
|
+
/**
|
|
104
|
+
* 비밀값이 사는 **환경 변수 이름** — `mes-miratek` · `hookSecret` → `TWIN_SECRET__MES_MIRATEK__HOOK_SECRET`.
|
|
105
|
+
*
|
|
106
|
+
* 꾸러미에는 비밀 칸의 이름만 있고 값은 환경에서 옵니다. 그 이름을 **사람이 손으로 적으므로** 규칙이
|
|
107
|
+
* 흔들리면 「넣었는데 안 읽힌다」가 됩니다 — 오늘 종일 잡은 「양쪽 다 초록인데 안 간다」의 설정 쪽
|
|
108
|
+
* 판본입니다. 그래서 이 규칙을 한 곳에 두고 시험으로 못 박습니다.
|
|
109
|
+
*
|
|
110
|
+
* 낙타 이름의 경계를 밑줄로 가릅니다 — 안 그러면 `HOOKSECRET` 과 `HOOK_SECRET` 이 갈려 한쪽만 읽힙니다.
|
|
111
|
+
*/
|
|
112
|
+
export declare function secretEnvName(source: string, key: string): string;
|
|
113
|
+
/**
|
|
114
|
+
* **두 연결이 같은 곳에 조치를 내지 않나** — 같으면 다른 공장의 작업지시가 한 공장에 섭니다.
|
|
115
|
+
*
|
|
116
|
+
* ── 왜 이 검사가 생겼나 (2026-09-05) ────────────────────────────────────────
|
|
117
|
+
* `mes-miratek` 이 서면서 한 커넥터가 처음으로 **공장 둘**을 달았습니다. 조치가 나가는 주소는
|
|
118
|
+
* `intakeSiteId` 가 정하는데(`operato-plant.ts:389`), 그 값이 트윈의 사이트 이름을 **이깁니다** —
|
|
119
|
+
* 트윈에서는 `mes-line-a` 인데 MES 의 도메인은 `system` 이라 그 override 가 필요합니다.
|
|
120
|
+
*
|
|
121
|
+
* 그래서 **연결을 베껴 만들고 그 칸만 안 고치면** 두 트윈의 조치가 같은 공장으로 갑니다. 오류가
|
|
122
|
+
* 안 납니다 — 저쪽은 정상적인 작업지시로 받고 **실제로 만듭니다.**
|
|
123
|
+
*
|
|
124
|
+
* 게이트에 이미 「사이트를 모르면 보내지 않는다」가 있는데(`no-site`), 그 이유가 적혀 있는 문장이
|
|
125
|
+
* **「잘못된 공장이 실제로 만든다」** 입니다. 이것은 그 둘째 길입니다 — 사이트를 아는데 **틀린
|
|
126
|
+
* 사이트를 압니다.**
|
|
127
|
+
*
|
|
128
|
+
* 파일로 연결을 만들 수 있게 되면서 더 쉬워졌습니다: 꾸러미를 베껴 이름만 바꾸면 받는 자리가
|
|
129
|
+
* 그대로 따라옵니다.
|
|
130
|
+
*/
|
|
131
|
+
export declare function intakeCollisions(connections: readonly {
|
|
132
|
+
source: string;
|
|
133
|
+
adapterType?: string;
|
|
134
|
+
config?: Record<string, unknown>;
|
|
135
|
+
}[]): {
|
|
136
|
+
intake: string;
|
|
137
|
+
sources: string[];
|
|
138
|
+
}[];
|