@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
package/dist-server/index.js
CHANGED
|
@@ -12,6 +12,9 @@ require("./routes.js");
|
|
|
12
12
|
const index_js_2 = require("./engine/index.js");
|
|
13
13
|
const reference_live_js_1 = require("./service/reference/reference-live.js");
|
|
14
14
|
const index_js_3 = require("./service/twin-ingest-window/index.js");
|
|
15
|
+
const reference_links_js_1 = require("./service/reference/reference-links.js");
|
|
16
|
+
const rule_loop_js_1 = require("./service/actuation/rule-loop.js");
|
|
17
|
+
const fill_loop_js_1 = require("./service/reference/fill-loop.js");
|
|
15
18
|
/*
|
|
16
19
|
* 모듈 부팅 — **도는 트윈을 되살린다.**
|
|
17
20
|
*
|
|
@@ -28,6 +31,11 @@ process.on('bootstrap-module-start', async ({ app, config, client }) => {
|
|
|
28
31
|
* 남는다. 커널 층은 저장 계층을 모르므로 이 등록이 그 둘을 잇는 유일한 자리다.
|
|
29
32
|
*/
|
|
30
33
|
(0, index_js_3.installIngestWindowWriter)();
|
|
34
|
+
/*
|
|
35
|
+
* 원본에만 있는 관계를 조회하는 문을 연다 — MES 의 생산 실적·정비 계획처럼 트윈이 담지 않는
|
|
36
|
+
* 것들. 조회용 참조는 트윈으로 복제하지 않고 그때 물어본다.
|
|
37
|
+
*/
|
|
38
|
+
(0, reference_links_js_1.installReferenceLinkResolver)();
|
|
31
39
|
/*
|
|
32
40
|
* **저널 보존 기간은 선언에서 온다** — 코드에 박아 두지 않는다(2026-08-22).
|
|
33
41
|
*
|
|
@@ -55,6 +63,19 @@ process.on('bootstrap-module-start', async ({ app, config, client }) => {
|
|
|
55
63
|
}
|
|
56
64
|
await index_js_2.TwinEngine.bootstrap();
|
|
57
65
|
await (0, reference_live_js_1.resumeReferenceLiveFeeds)();
|
|
66
|
+
/*
|
|
67
|
+
* 빠진 구간을 다시 받는 주기 — 기동에서 한 번, 그 뒤 10분마다(§`fill-loop.ts`).
|
|
68
|
+
* 피드를 붙인 **뒤에** 세운다: 커널이 되살아나기 전에 부르면 도는 트윈이 0개다.
|
|
69
|
+
*/
|
|
70
|
+
(0, fill_loop_js_1.startFillLoop)();
|
|
71
|
+
/*
|
|
72
|
+
* 조치 규칙 주기 — **트윈이 다 선 뒤에** 세운다. 앞에 세우면 첫 바퀴가 아직 안 선 트윈들을 돌고
|
|
73
|
+
* 「상태를 읽을 수 없다」를 규칙 행에 적는다. 그것은 사실이지만 사람에게는 오해다.
|
|
74
|
+
*
|
|
75
|
+
* 커널 틱과 따로 돈다 — 틱은 예외가 나면 그 트윈을 멈추고, 규칙 하나가 잘못됐다고 공장이 멈추면
|
|
76
|
+
* 안 된다(§`rule-loop.ts`).
|
|
77
|
+
*/
|
|
78
|
+
(0, rule_loop_js_1.startActuationRuleLoop)();
|
|
58
79
|
(0, log_js_1.twinLog)('Headless Twin host has started.');
|
|
59
80
|
}
|
|
60
81
|
catch (ex) {
|
package/dist-server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;;AAAA,4DAAiC;AACjC,6DAAkC;AAClC,qEAAqE;AACrE,kDAAkD;AAAzC,sGAAA,UAAU,OAAA;AAEnB,4CAAoD;AACpD,uBAAoB;AAEpB,gDAA8C;AAC9C,6EAAgF;AAChF,oEAAiF;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;;AAAA,4DAAiC;AACjC,6DAAkC;AAClC,qEAAqE;AACrE,kDAAkD;AAAzC,sGAAA,UAAU,OAAA;AAEnB,4CAAoD;AACpD,uBAAoB;AAEpB,gDAA8C;AAC9C,6EAAgF;AAChF,oEAAiF;AACjF,+EAAqF;AACrF,mEAAyE;AACzE,mEAAgE;AAEhE;;;;;;;;GAQG;AACH,OAAO,CAAC,EAAE,CAAC,wBAA+B,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAO,EAAE,EAAE;IACjF,IAAI,CAAC;QACH;;;WAGG;QACH,IAAA,oCAAyB,GAAE,CAAA;QAC3B;;;WAGG;QACH,IAAA,iDAA4B,GAAE,CAAA;QAC9B;;;;;;;;WAQG;QACH;;;;;;;;WAQG;QACH,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,2BAA2B,IAAI,CAAC,CAAC,CAAA;QACpE,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,oBAAoB,IAAI,CAAC,CAAC,CAAA;QAC3E,MAAM,aAAa,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAA;QACxD,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;YACtB,qBAAU,CAAC,sBAAsB,GAAG,aAAa,CAAA;YACjD,IAAA,gBAAO,EAAC,4BAA4B,aAAa,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAA;QACxF,CAAC;QAED,MAAM,qBAAU,CAAC,SAAS,EAAE,CAAA;QAC5B,MAAM,IAAA,4CAAwB,GAAE,CAAA;QAChC;;;WAGG;QACH,IAAA,4BAAa,GAAE,CAAA;QAEf;;;;;;WAMG;QACH,IAAA,qCAAsB,GAAE,CAAA;QAExB,IAAA,gBAAO,EAAC,iCAAiC,CAAC,CAAA;IAC5C,CAAC;IAAC,OAAO,EAAE,EAAE,CAAC;QACZ,IAAA,kBAAS,EAAC,qCAAqC,EAAE,EAAE,CAAC,CAAA;IACtD,CAAC;AACH,CAAC,CAAC,CAAA","sourcesContent":["export * from './engine/index.js'\nexport * from './service/index.js'\n/* 스키마 이력 — 프레임워크가 `migrations` export 를 모아 실행한다(`run migration`). */\nexport { migrations } from './migrations/index.js'\n\nimport { twinLog, twinError } from './engine/log.js'\nimport './routes.js'\n\nimport { TwinEngine } from './engine/index.js'\nimport { resumeReferenceLiveFeeds } from './service/reference/reference-live.js'\nimport { installIngestWindowWriter } from './service/twin-ingest-window/index.js'\nimport { installReferenceLinkResolver } from './service/reference/reference-links.js'\nimport { startActuationRuleLoop } from './service/actuation/rule-loop.js'\nimport { startFillLoop } from './service/reference/fill-loop.js'\n\n/*\n * 모듈 부팅 — **도는 트윈을 되살린다.**\n *\n * 두 층이 순서대로다: ① 엔진이 등록부의 `running` 행을 선언된 `restartPolicy` 그대로 되살리고\n * (`bootstrap`), ② 레퍼런스 계층이 미러 트윈의 커넥터 피드를 다시 붙인다. 순서가 뒤집히면 피드가\n * 아직 없는 커널에 계측을 붓게 된다.\n *\n * 예전에는 ①도 ②도 없었다 — 재기동 한 번에 등록부만 「도는 중」이라 말하는 트윈이 남았다.\n */\nprocess.on('bootstrap-module-start' as any, async ({ app, config, client }: any) => {\n try {\n /*\n * 닫힌 인제스트 창을 표로 옮기는 문을 먼저 연다 — 트윈을 되살리기 **전에** 등록해야 첫 창부터\n * 남는다. 커널 층은 저장 계층을 모르므로 이 등록이 그 둘을 잇는 유일한 자리다.\n */\n installIngestWindowWriter()\n /*\n * 원본에만 있는 관계를 조회하는 문을 연다 — MES 의 생산 실적·정비 계획처럼 트윈이 담지 않는\n * 것들. 조회용 참조는 트윈으로 복제하지 않고 그때 물어본다.\n */\n installReferenceLinkResolver()\n /*\n * **저널 보존 기간은 선언에서 온다** — 코드에 박아 두지 않는다(2026-08-22).\n *\n * 실측: 개발 저널이 시간당 581,794행 · 0.85GB(하루 20GB) 로 자랐고 지우는 것이 없었다. 그런데\n * 「며칠까지 남기나」는 **배포마다 갈리는 사실**이다 — 개발은 짧아도 되고 실증은 길어야 한다.\n *\n * 선언이 없으면 **아무것도 지우지 않는다.** 알리지 않고 지우는 편이 알리지 않고 쌓는 것보다 나쁘다.\n * 값은 스냅샷 TTL(7일)보다 짧을 수 없다 — 엔진이 그것을 올려 주고 그 사실을 말한다.\n */\n /*\n * 읽는 순서: **환경변수 → 설정 → 없음.**\n *\n * 설정(`config.twin.journalRetentionDays`)이 배포의 선언이고, 환경변수\n * (`TWIN_JOURNAL_RETENTION_DAYS`)는 그것을 **그 자리에서** 덮는다. 컨테이너로 도는 배포에서는\n * 파일을 고치지 않고 값을 바꿀 수 있어야 하고, 개발에서도 그 편이 빠르다.\n *\n * 둘 다 없으면 **아무것도 지우지 않는다.**\n */\n const fromEnv = Number(process.env.TWIN_JOURNAL_RETENTION_DAYS ?? 0)\n const fromConfig = Number(config?.get?.('twin')?.journalRetentionDays ?? 0)\n const retentionDays = fromEnv > 0 ? fromEnv : fromConfig\n if (retentionDays > 0) {\n TwinEngine.JOURNAL_RETENTION_DAYS = retentionDays\n twinLog(`[twin] journal retention ${retentionDays}일 (${fromEnv > 0 ? '환경변수' : '설정'})`)\n }\n\n await TwinEngine.bootstrap()\n await resumeReferenceLiveFeeds()\n /*\n * 빠진 구간을 다시 받는 주기 — 기동에서 한 번, 그 뒤 10분마다(§`fill-loop.ts`).\n * 피드를 붙인 **뒤에** 세운다: 커널이 되살아나기 전에 부르면 도는 트윈이 0개다.\n */\n startFillLoop()\n\n /*\n * 조치 규칙 주기 — **트윈이 다 선 뒤에** 세운다. 앞에 세우면 첫 바퀴가 아직 안 선 트윈들을 돌고\n * 「상태를 읽을 수 없다」를 규칙 행에 적는다. 그것은 사실이지만 사람에게는 오해다.\n *\n * 커널 틱과 따로 돈다 — 틱은 예외가 나면 그 트윈을 멈추고, 규칙 하나가 잘못됐다고 공장이 멈추면\n * 안 된다(§`rule-loop.ts`).\n */\n startActuationRuleLoop()\n\n twinLog('Headless Twin host has started.')\n } catch (ex) {\n twinError('Headless Twin host failed to start.', ex)\n }\n})\n"]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RenameOperatoMesAdapterType1786600000000 = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* `twin_references.adapter_type` — `operato-mes` → `operato-plant`.
|
|
6
|
+
*
|
|
7
|
+
* ── 왜 마이그레이션이 필요한가 ────────────────────────────────────────────────
|
|
8
|
+
* 커넥터의 `type` 은 **저장되는 식별자**다. 트윈을 원본에 붙일 때 그 문자열이 이 컬럼에 적히고,
|
|
9
|
+
* 넘길 때·라이브를 열 때 그 값으로 커넥터를 찾는다(§`getAdapter`).
|
|
10
|
+
*
|
|
11
|
+
* 그래서 제품 개명(`operato-mes` → `operato-plant`)이 코드만 바뀌면 **이미 붙어 있는 원본이 커넥터를
|
|
12
|
+
* 못 찾는다.** 그때 나오는 것은 이렇다.
|
|
13
|
+
*
|
|
14
|
+
* 조치 넘김 `no-adapter` 로 거절
|
|
15
|
+
* 라이브 커넥터가 없어 관측이 끊긴다
|
|
16
|
+
* 화면 「등록되지 않은 커넥터입니다 (operato-mes)」 한 줄
|
|
17
|
+
*
|
|
18
|
+
* **오류는 안 나고, 원인이 개명이라는 것이 그 문장에 없다.** 트윈은 그대로 서 있고 값만 안 온다.
|
|
19
|
+
* 이 저장소에서 개명이 저장된 식별자에 닿았을 때 늘 이 모양으로 났다(보드 키·씬 컴포넌트 타입).
|
|
20
|
+
*
|
|
21
|
+
* ── 왜 이 패키지인가 ─────────────────────────────────────────────────────────
|
|
22
|
+
* 표를 든 쪽이 이 패키지다(`twin_references`). 커넥터는 `operato-twin` 이 등록하지만, 그 값이 사는
|
|
23
|
+
* 자리는 여기다. 마이그레이션은 표를 든 패키지에 둔다.
|
|
24
|
+
*
|
|
25
|
+
* ── 되돌리기 ─────────────────────────────────────────────────────────────────
|
|
26
|
+
* `down` 이 값을 되돌린다. 옛 코드로 되돌아가는 경우가 실제로 있고(배포 롤백), 그때 값이 새 이름으로
|
|
27
|
+
* 남아 있으면 같은 결함이 반대 방향으로 난다.
|
|
28
|
+
*
|
|
29
|
+
* 멱등: 해당하는 행이 없으면 아무것도 하지 않는다. TypeORM 질의 빌더만 쓴다 — 드라이버마다 파라미터
|
|
30
|
+
* 표기가 다르므로 raw SQL 을 쓰지 않는다.
|
|
31
|
+
*/
|
|
32
|
+
const REFS = 'twin_references';
|
|
33
|
+
const COLUMN = 'adapter_type';
|
|
34
|
+
/**
|
|
35
|
+
* 질의 빌더에는 **속성 이름**을 준다.
|
|
36
|
+
*
|
|
37
|
+
* 처음에 컬럼 이름(`adapter_type`)으로 썼더니 TypeORM 이 표 이름을 엔티티(`TwinReference`)로 풀고
|
|
38
|
+
* 「그런 속성이 없다」로 거절했다 — 표 이름을 주더라도 매핑은 엔티티 기준이다. 컬럼 이름은 표가
|
|
39
|
+
* 있는지 볼 때만 쓴다.
|
|
40
|
+
*/
|
|
41
|
+
const PROPERTY = 'adapterType';
|
|
42
|
+
const OLD = 'operato-mes';
|
|
43
|
+
const NEW = 'operato-plant';
|
|
44
|
+
async function rename(queryRunner, from, to) {
|
|
45
|
+
/* 표가 아직 없는 배포(첫 기동)에서는 할 일이 없다. */
|
|
46
|
+
const table = await queryRunner.getTable(REFS);
|
|
47
|
+
if (!table?.columns.some(c => c.name === COLUMN))
|
|
48
|
+
return;
|
|
49
|
+
/* 객체 형태로 준다 — 문자열 조건은 드라이버마다 식별자 인용이 달라 그대로 나간다. */
|
|
50
|
+
await queryRunner.manager
|
|
51
|
+
.createQueryBuilder()
|
|
52
|
+
.update(REFS)
|
|
53
|
+
.set({ [PROPERTY]: to })
|
|
54
|
+
.where({ [PROPERTY]: from })
|
|
55
|
+
.execute();
|
|
56
|
+
}
|
|
57
|
+
class RenameOperatoMesAdapterType1786600000000 {
|
|
58
|
+
async up(queryRunner) {
|
|
59
|
+
await rename(queryRunner, OLD, NEW);
|
|
60
|
+
}
|
|
61
|
+
async down(queryRunner) {
|
|
62
|
+
await rename(queryRunner, NEW, OLD);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.RenameOperatoMesAdapterType1786600000000 = RenameOperatoMesAdapterType1786600000000;
|
|
66
|
+
//# sourceMappingURL=1786600000000-RenameOperatoMesAdapterType.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"1786600000000-RenameOperatoMesAdapterType.js","sourceRoot":"","sources":["../../server/migrations/1786600000000-RenameOperatoMesAdapterType.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,IAAI,GAAG,iBAAiB,CAAA;AAC9B,MAAM,MAAM,GAAG,cAAc,CAAA;AAC7B;;;;;;GAMG;AACH,MAAM,QAAQ,GAAG,aAAa,CAAA;AAC9B,MAAM,GAAG,GAAG,aAAa,CAAA;AACzB,MAAM,GAAG,GAAG,eAAe,CAAA;AAE3B,KAAK,UAAU,MAAM,CAAC,WAAwB,EAAE,IAAY,EAAE,EAAU;IACtE,mCAAmC;IACnC,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC9C,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;QAAE,OAAM;IAExD,oDAAoD;IACpD,MAAM,WAAW,CAAC,OAAO;SACtB,kBAAkB,EAAE;SACpB,MAAM,CAAC,IAAI,CAAC;SACZ,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC;SACvB,KAAK,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC;SAC3B,OAAO,EAAE,CAAA;AACd,CAAC;AAED,MAAa,wCAAwC;IAC5C,KAAK,CAAC,EAAE,CAAC,WAAwB;QACtC,MAAM,MAAM,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;IACrC,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,WAAwB;QACxC,MAAM,MAAM,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;IACrC,CAAC;CACF;AARD,4FAQC","sourcesContent":["import { MigrationInterface, QueryRunner } from 'typeorm'\n\n/**\n * `twin_references.adapter_type` — `operato-mes` → `operato-plant`.\n *\n * ── 왜 마이그레이션이 필요한가 ────────────────────────────────────────────────\n * 커넥터의 `type` 은 **저장되는 식별자**다. 트윈을 원본에 붙일 때 그 문자열이 이 컬럼에 적히고,\n * 넘길 때·라이브를 열 때 그 값으로 커넥터를 찾는다(§`getAdapter`).\n *\n * 그래서 제품 개명(`operato-mes` → `operato-plant`)이 코드만 바뀌면 **이미 붙어 있는 원본이 커넥터를\n * 못 찾는다.** 그때 나오는 것은 이렇다.\n *\n * 조치 넘김 `no-adapter` 로 거절\n * 라이브 커넥터가 없어 관측이 끊긴다\n * 화면 「등록되지 않은 커넥터입니다 (operato-mes)」 한 줄\n *\n * **오류는 안 나고, 원인이 개명이라는 것이 그 문장에 없다.** 트윈은 그대로 서 있고 값만 안 온다.\n * 이 저장소에서 개명이 저장된 식별자에 닿았을 때 늘 이 모양으로 났다(보드 키·씬 컴포넌트 타입).\n *\n * ── 왜 이 패키지인가 ─────────────────────────────────────────────────────────\n * 표를 든 쪽이 이 패키지다(`twin_references`). 커넥터는 `operato-twin` 이 등록하지만, 그 값이 사는\n * 자리는 여기다. 마이그레이션은 표를 든 패키지에 둔다.\n *\n * ── 되돌리기 ─────────────────────────────────────────────────────────────────\n * `down` 이 값을 되돌린다. 옛 코드로 되돌아가는 경우가 실제로 있고(배포 롤백), 그때 값이 새 이름으로\n * 남아 있으면 같은 결함이 반대 방향으로 난다.\n *\n * 멱등: 해당하는 행이 없으면 아무것도 하지 않는다. TypeORM 질의 빌더만 쓴다 — 드라이버마다 파라미터\n * 표기가 다르므로 raw SQL 을 쓰지 않는다.\n */\nconst REFS = 'twin_references'\nconst COLUMN = 'adapter_type'\n/**\n * 질의 빌더에는 **속성 이름**을 준다.\n *\n * 처음에 컬럼 이름(`adapter_type`)으로 썼더니 TypeORM 이 표 이름을 엔티티(`TwinReference`)로 풀고\n * 「그런 속성이 없다」로 거절했다 — 표 이름을 주더라도 매핑은 엔티티 기준이다. 컬럼 이름은 표가\n * 있는지 볼 때만 쓴다.\n */\nconst PROPERTY = 'adapterType'\nconst OLD = 'operato-mes'\nconst NEW = 'operato-plant'\n\nasync function rename(queryRunner: QueryRunner, from: string, to: string): Promise<void> {\n /* 표가 아직 없는 배포(첫 기동)에서는 할 일이 없다. */\n const table = await queryRunner.getTable(REFS)\n if (!table?.columns.some(c => c.name === COLUMN)) return\n\n /* 객체 형태로 준다 — 문자열 조건은 드라이버마다 식별자 인용이 달라 그대로 나간다. */\n await queryRunner.manager\n .createQueryBuilder()\n .update(REFS)\n .set({ [PROPERTY]: to })\n .where({ [PROPERTY]: from })\n .execute()\n}\n\nexport class RenameOperatoMesAdapterType1786600000000 implements MigrationInterface {\n public async up(queryRunner: QueryRunner): Promise<void> {\n await rename(queryRunner, OLD, NEW)\n }\n\n public async down(queryRunner: QueryRunner): Promise<void> {\n await rename(queryRunner, NEW, OLD)\n }\n}\n"]}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AddDispatchRetryColumns1786700000000 = void 0;
|
|
4
|
+
/*
|
|
5
|
+
* 조치 표에 **일꾼이 쓸 네 칸**을 더한다.
|
|
6
|
+
*
|
|
7
|
+
* ── 왜 표에 두나 ────────────────────────────────────────────────────────────
|
|
8
|
+
* 물러섬을 메모리에 두면 재기동에 사라지고, 그러면 물러서던 것이 전부 즉시 재시도가 된다. 그리고
|
|
9
|
+
* 일꾼이 여러 대가 되면 같은 답을 봐야 한다.
|
|
10
|
+
*
|
|
11
|
+
* dispatch_note_next 저쪽 말 중 「다음에 할 일」 한 줄 — 원인은 dispatch_note
|
|
12
|
+
* retry 다시 해서 될 일인가 (again · after-fix · never)
|
|
13
|
+
* attempts 몇 번 넘겨 봤나 — 목록의 「몇 번 해 봤나」
|
|
14
|
+
* next_attempt_at 다음에 넘겨 볼 시각(ISO)
|
|
15
|
+
*
|
|
16
|
+
* ── 타입을 짐작하지 않는다 ──────────────────────────────────────────────────
|
|
17
|
+
* 엔티티가 `@Column({ nullable: true })` 로 두면 그것이 어느 컬럼 타입이 되는지는 **드라이버마다
|
|
18
|
+
* 다르다.** `'varchar'` 라고 적으면 sqlite·postgres·mysql 에서는 맞고 MSSQL·Oracle 에서 어긋난다.
|
|
19
|
+
*
|
|
20
|
+
* 그래서 같은 성질로 **이미 서 있는 형제 칸**의 타입을 읽어 그대로 쓴다 — 드라이버가 스스로 고른
|
|
21
|
+
* 답이라 다섯 곳에서 다 맞는다. 형제가 없으면 짐작하지 않고 멈춘다.
|
|
22
|
+
*
|
|
23
|
+
* 글자 셋 dispatch_note 에서
|
|
24
|
+
* 수 하나 effect_seen 에서
|
|
25
|
+
*
|
|
26
|
+
* plant 레인이 오늘 `equipmentClassIds` 마이그레이션에서 같은 방법을 썼다.
|
|
27
|
+
*
|
|
28
|
+
* ── 멱등 ────────────────────────────────────────────────────────────────────
|
|
29
|
+
* 칸이 이미 있으면 아무것도 하지 않는다 — 개발 sqlite 는 엔티티 선언만으로 이미 만들어 둔다.
|
|
30
|
+
* 표가 아직 없는 첫 기동에서도 할 일이 없다.
|
|
31
|
+
*/
|
|
32
|
+
const typeorm_1 = require("typeorm");
|
|
33
|
+
const TABLE = 'twin_command_records';
|
|
34
|
+
/** 글자 칸의 형제 — 같은 성질(선택·자유 문자열)로 이미 서 있다. */
|
|
35
|
+
const TEXT_SIBLING = 'dispatch_note';
|
|
36
|
+
/** 수 칸의 형제 — 선택·정수. */
|
|
37
|
+
const INT_SIBLING = 'effect_seen';
|
|
38
|
+
const TEXT_COLUMNS = ['dispatch_note_next', 'retry', 'next_attempt_at'];
|
|
39
|
+
const INT_COLUMNS = ['attempts'];
|
|
40
|
+
class AddDispatchRetryColumns1786700000000 {
|
|
41
|
+
async up(queryRunner) {
|
|
42
|
+
const table = await queryRunner.getTable(TABLE);
|
|
43
|
+
if (!table)
|
|
44
|
+
return; /* 첫 기동 — 엔티티가 만든다 */
|
|
45
|
+
const typeOf = (sibling) => table.columns.find(c => c.name === sibling)?.type;
|
|
46
|
+
const textType = typeOf(TEXT_SIBLING);
|
|
47
|
+
const intType = typeOf(INT_SIBLING);
|
|
48
|
+
/*
|
|
49
|
+
* 형제가 없으면 **멈춘다.** 짐작한 타입으로 칸을 세우면 어느 드라이버에서 어긋나는지 개발에서
|
|
50
|
+
* 안 잡히고, 그 배포에서만 난다.
|
|
51
|
+
*/
|
|
52
|
+
if (!textType || !intType) {
|
|
53
|
+
throw new Error(`${TABLE} 에서 형제 칸을 못 찾았습니다 — ${TEXT_SIBLING}(${textType ?? '없음'}) · ` +
|
|
54
|
+
`${INT_SIBLING}(${intType ?? '없음'}). 타입을 짐작하지 않고 멈춥니다`);
|
|
55
|
+
}
|
|
56
|
+
for (const name of TEXT_COLUMNS) {
|
|
57
|
+
if (table.columns.some(c => c.name === name))
|
|
58
|
+
continue;
|
|
59
|
+
await queryRunner.addColumn(TABLE, new typeorm_1.TableColumn({ name, type: textType, isNullable: true }));
|
|
60
|
+
}
|
|
61
|
+
for (const name of INT_COLUMNS) {
|
|
62
|
+
if (table.columns.some(c => c.name === name))
|
|
63
|
+
continue;
|
|
64
|
+
await queryRunner.addColumn(TABLE, new typeorm_1.TableColumn({ name, type: intType, isNullable: true }));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async down(queryRunner) {
|
|
68
|
+
const table = await queryRunner.getTable(TABLE);
|
|
69
|
+
if (!table)
|
|
70
|
+
return;
|
|
71
|
+
for (const name of [...TEXT_COLUMNS, ...INT_COLUMNS]) {
|
|
72
|
+
if (!table.columns.some(c => c.name === name))
|
|
73
|
+
continue;
|
|
74
|
+
await queryRunner.dropColumn(TABLE, name);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.AddDispatchRetryColumns1786700000000 = AddDispatchRetryColumns1786700000000;
|
|
79
|
+
//# sourceMappingURL=1786700000000-AddDispatchRetryColumns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"1786700000000-AddDispatchRetryColumns.js","sourceRoot":"","sources":["../../server/migrations/1786700000000-AddDispatchRetryColumns.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qCAAsE;AAEtE,MAAM,KAAK,GAAG,sBAAsB,CAAA;AAEpC,4CAA4C;AAC5C,MAAM,YAAY,GAAG,eAAe,CAAA;AACpC,uBAAuB;AACvB,MAAM,WAAW,GAAG,aAAa,CAAA;AAEjC,MAAM,YAAY,GAAG,CAAC,oBAAoB,EAAE,OAAO,EAAE,iBAAiB,CAAU,CAAA;AAChF,MAAM,WAAW,GAAG,CAAC,UAAU,CAAU,CAAA;AAEzC,MAAa,oCAAoC;IACxC,KAAK,CAAC,EAAE,CAAC,WAAwB;QACtC,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;QAC/C,IAAI,CAAC,KAAK;YAAE,OAAM,CAAC,qBAAqB;QAExC,MAAM,MAAM,GAAG,CAAC,OAAe,EAAsB,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,EAAE,IAAI,CAAA;QAEzG,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAC,CAAA;QACrC,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,CAAC,CAAA;QAEnC;;;WAGG;QACH,IAAI,CAAC,QAAQ,IAAI,CAAC,OAAO,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CACb,GAAG,KAAK,uBAAuB,YAAY,IAAI,QAAQ,IAAI,IAAI,MAAM;gBACnE,GAAG,WAAW,IAAI,OAAO,IAAI,IAAI,qBAAqB,CACzD,CAAA;QACH,CAAC;QAED,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;YAChC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC;gBAAE,SAAQ;YACtD,MAAM,WAAW,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,qBAAW,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QACjG,CAAC;QAED,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;YAC/B,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC;gBAAE,SAAQ;YACtD,MAAM,WAAW,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,qBAAW,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QAChG,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,WAAwB;QACxC,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;QAC/C,IAAI,CAAC,KAAK;YAAE,OAAM;QAElB,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,YAAY,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC;YACrD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC;gBAAE,SAAQ;YACvD,MAAM,WAAW,CAAC,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;QAC3C,CAAC;IACH,CAAC;CACF;AAzCD,oFAyCC","sourcesContent":["/*\n * 조치 표에 **일꾼이 쓸 네 칸**을 더한다.\n *\n * ── 왜 표에 두나 ────────────────────────────────────────────────────────────\n * 물러섬을 메모리에 두면 재기동에 사라지고, 그러면 물러서던 것이 전부 즉시 재시도가 된다. 그리고\n * 일꾼이 여러 대가 되면 같은 답을 봐야 한다.\n *\n * dispatch_note_next 저쪽 말 중 「다음에 할 일」 한 줄 — 원인은 dispatch_note\n * retry 다시 해서 될 일인가 (again · after-fix · never)\n * attempts 몇 번 넘겨 봤나 — 목록의 「몇 번 해 봤나」\n * next_attempt_at 다음에 넘겨 볼 시각(ISO)\n *\n * ── 타입을 짐작하지 않는다 ──────────────────────────────────────────────────\n * 엔티티가 `@Column({ nullable: true })` 로 두면 그것이 어느 컬럼 타입이 되는지는 **드라이버마다\n * 다르다.** `'varchar'` 라고 적으면 sqlite·postgres·mysql 에서는 맞고 MSSQL·Oracle 에서 어긋난다.\n *\n * 그래서 같은 성질로 **이미 서 있는 형제 칸**의 타입을 읽어 그대로 쓴다 — 드라이버가 스스로 고른\n * 답이라 다섯 곳에서 다 맞는다. 형제가 없으면 짐작하지 않고 멈춘다.\n *\n * 글자 셋 dispatch_note 에서\n * 수 하나 effect_seen 에서\n *\n * plant 레인이 오늘 `equipmentClassIds` 마이그레이션에서 같은 방법을 썼다.\n *\n * ── 멱등 ────────────────────────────────────────────────────────────────────\n * 칸이 이미 있으면 아무것도 하지 않는다 — 개발 sqlite 는 엔티티 선언만으로 이미 만들어 둔다.\n * 표가 아직 없는 첫 기동에서도 할 일이 없다.\n */\nimport { MigrationInterface, QueryRunner, TableColumn } from 'typeorm'\n\nconst TABLE = 'twin_command_records'\n\n/** 글자 칸의 형제 — 같은 성질(선택·자유 문자열)로 이미 서 있다. */\nconst TEXT_SIBLING = 'dispatch_note'\n/** 수 칸의 형제 — 선택·정수. */\nconst INT_SIBLING = 'effect_seen'\n\nconst TEXT_COLUMNS = ['dispatch_note_next', 'retry', 'next_attempt_at'] as const\nconst INT_COLUMNS = ['attempts'] as const\n\nexport class AddDispatchRetryColumns1786700000000 implements MigrationInterface {\n public async up(queryRunner: QueryRunner): Promise<void> {\n const table = await queryRunner.getTable(TABLE)\n if (!table) return /* 첫 기동 — 엔티티가 만든다 */\n\n const typeOf = (sibling: string): string | undefined => table.columns.find(c => c.name === sibling)?.type\n\n const textType = typeOf(TEXT_SIBLING)\n const intType = typeOf(INT_SIBLING)\n\n /*\n * 형제가 없으면 **멈춘다.** 짐작한 타입으로 칸을 세우면 어느 드라이버에서 어긋나는지 개발에서\n * 안 잡히고, 그 배포에서만 난다.\n */\n if (!textType || !intType) {\n throw new Error(\n `${TABLE} 에서 형제 칸을 못 찾았습니다 — ${TEXT_SIBLING}(${textType ?? '없음'}) · ` +\n `${INT_SIBLING}(${intType ?? '없음'}). 타입을 짐작하지 않고 멈춥니다`\n )\n }\n\n for (const name of TEXT_COLUMNS) {\n if (table.columns.some(c => c.name === name)) continue\n await queryRunner.addColumn(TABLE, new TableColumn({ name, type: textType, isNullable: true }))\n }\n\n for (const name of INT_COLUMNS) {\n if (table.columns.some(c => c.name === name)) continue\n await queryRunner.addColumn(TABLE, new TableColumn({ name, type: intType, isNullable: true }))\n }\n }\n\n public async down(queryRunner: QueryRunner): Promise<void> {\n const table = await queryRunner.getTable(TABLE)\n if (!table) return\n\n for (const name of [...TEXT_COLUMNS, ...INT_COLUMNS]) {\n if (!table.columns.some(c => c.name === name)) continue\n await queryRunner.dropColumn(TABLE, name)\n }\n }\n}\n"]}
|
|
@@ -11,12 +11,16 @@ const _1786200000000_CarryLiveFeedCursor_js_1 = require("./1786200000000-CarryLi
|
|
|
11
11
|
const _1786300000000_IndexEventTypeByTime_js_1 = require("./1786300000000-IndexEventTypeByTime.js");
|
|
12
12
|
const _1786400000000_IndexEventCreatedAt_js_1 = require("./1786400000000-IndexEventCreatedAt.js");
|
|
13
13
|
const _1786500000000_CreateTwinSubjectEvents_js_1 = require("./1786500000000-CreateTwinSubjectEvents.js");
|
|
14
|
+
const _1786700000000_AddDispatchRetryColumns_js_1 = require("./1786700000000-AddDispatchRetryColumns.js");
|
|
15
|
+
const _1786600000000_RenameOperatoMesAdapterType_js_1 = require("./1786600000000-RenameOperatoMesAdapterType.js");
|
|
14
16
|
exports.migrations = [
|
|
15
17
|
_1786000000000_RenameTwinInstanceBoardToModel_js_1.RenameTwinInstanceBoardToModel1786000000000,
|
|
16
18
|
_1786100000000_PromoteEventActionAndSyncWarnings_js_1.PromoteEventActionAndSyncWarnings1786100000000,
|
|
17
19
|
_1786200000000_CarryLiveFeedCursor_js_1.CarryLiveFeedCursor1786200000000,
|
|
18
20
|
_1786300000000_IndexEventTypeByTime_js_1.IndexEventTypeByTime1786300000000,
|
|
19
21
|
_1786400000000_IndexEventCreatedAt_js_1.IndexEventCreatedAt1786400000000,
|
|
20
|
-
_1786500000000_CreateTwinSubjectEvents_js_1.CreateTwinSubjectEvents1786500000000
|
|
22
|
+
_1786500000000_CreateTwinSubjectEvents_js_1.CreateTwinSubjectEvents1786500000000,
|
|
23
|
+
_1786600000000_RenameOperatoMesAdapterType_js_1.RenameOperatoMesAdapterType1786600000000,
|
|
24
|
+
_1786700000000_AddDispatchRetryColumns_js_1.AddDispatchRetryColumns1786700000000
|
|
21
25
|
];
|
|
22
26
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/migrations/index.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACH,wHAA+G;AAC/G,8HAAqH;AACrH,kGAAyF;AACzF,oGAA2F;AAC3F,kGAAyF;AACzF,0GAAiG;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/migrations/index.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACH,wHAA+G;AAC/G,8HAAqH;AACrH,kGAAyF;AACzF,oGAA2F;AAC3F,kGAAyF;AACzF,0GAAiG;AACjG,0GAAiG;AACjG,kHAAyG;AAE5F,QAAA,UAAU,GAAG;IACxB,8FAA2C;IAC3C,oGAA8C;IAC9C,wEAAgC;IAChC,0EAAiC;IACjC,wEAAgC;IAChC,gFAAoC;IACpC,wFAAwC;IACxC,gFAAoC;CACrC,CAAA","sourcesContent":["/*\n * 마이그레이션 묶음 — 프레임워크 규약(`@things-factory/shell` · `auth-base` 와 같은 방식).\n * 파일마다 하나씩 export 하고 여기서 모아 `server/index.ts` 가 내보낸다.\n */\nimport { RenameTwinInstanceBoardToModel1786000000000 } from './1786000000000-RenameTwinInstanceBoardToModel.js'\nimport { PromoteEventActionAndSyncWarnings1786100000000 } from './1786100000000-PromoteEventActionAndSyncWarnings.js'\nimport { CarryLiveFeedCursor1786200000000 } from './1786200000000-CarryLiveFeedCursor.js'\nimport { IndexEventTypeByTime1786300000000 } from './1786300000000-IndexEventTypeByTime.js'\nimport { IndexEventCreatedAt1786400000000 } from './1786400000000-IndexEventCreatedAt.js'\nimport { CreateTwinSubjectEvents1786500000000 } from './1786500000000-CreateTwinSubjectEvents.js'\nimport { AddDispatchRetryColumns1786700000000 } from './1786700000000-AddDispatchRetryColumns.js'\nimport { RenameOperatoMesAdapterType1786600000000 } from './1786600000000-RenameOperatoMesAdapterType.js'\n\nexport const migrations = [\n RenameTwinInstanceBoardToModel1786000000000,\n PromoteEventActionAndSyncWarnings1786100000000,\n CarryLiveFeedCursor1786200000000,\n IndexEventTypeByTime1786300000000,\n IndexEventCreatedAt1786400000000,\n CreateTwinSubjectEvents1786500000000,\n RenameOperatoMesAdapterType1786600000000,\n AddDispatchRetryColumns1786700000000\n]\n"]}
|
package/dist-server/routes.js
CHANGED
|
@@ -17,8 +17,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
* 대신 **연결마다 비밀값**을 두고 그것으로 확인한다. 비밀값을 선언하지 않은 연결은 훅을 받지 않는다 —
|
|
18
18
|
* 인증 없는 입구를 열어 두는 것보다 훅을 못 쓰는 것이 낫다.
|
|
19
19
|
*/
|
|
20
|
-
const
|
|
21
|
-
const
|
|
20
|
+
const live_ingest_js_1 = require("./service/reference/live-ingest.js");
|
|
21
|
+
const fill_loop_js_1 = require("./service/reference/fill-loop.js");
|
|
22
22
|
const reference_hook_js_1 = require("./service/reference/reference-hook.js");
|
|
23
23
|
const hook_store_js_1 = require("./service/reference/hook-store.js");
|
|
24
24
|
process.on('bootstrap-module-domain-public-route', (_app, router) => {
|
|
@@ -51,24 +51,16 @@ process.on('bootstrap-module-domain-public-route', (_app, router) => {
|
|
|
51
51
|
* 폴링이 쓰는 길을 그대로 쓴다 — 검증도 유입도 같은 자리다. 그래서 트윈 쪽에서는 물어서 받은
|
|
52
52
|
* 것과 밀어 받은 것이 구별되지 않는다. 구별되는 것은 유입 장부의 `sources` 한 줄뿐이다.
|
|
53
53
|
*/
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const undelivered = Math.max(0, accepted.length - applied - duplicates);
|
|
65
|
-
twin_engine_js_1.TwinEngine.recordIngestResult(domain.id, instanceId, records.length, rejected, Date.now(), undelivered);
|
|
66
|
-
/*
|
|
67
|
-
* 떨어진 것을 **수가 아니라 목록으로** 넘긴다 — 응답에 실어야 보내는 쪽이 그것만 따로 둘 수
|
|
68
|
-
* 있다. 수만 주면 배치 전체를 다시 보내거나 전부 버리는 두 갈래밖에 없다.
|
|
69
|
-
*/
|
|
70
|
-
return { applied, duplicates, rejected };
|
|
71
|
-
}
|
|
54
|
+
/*
|
|
55
|
+
* 유입 몸통은 `live-ingest.ts` 한 곳에 있다 — backfill 이 같은 것을 쓴다. 여기에 두면 두 길이
|
|
56
|
+
* 서로 다른 규칙으로 받게 되고, 한쪽만 수정되는 날이 온다.
|
|
57
|
+
*/
|
|
58
|
+
ingest: (instanceId, records) => (0, live_ingest_js_1.liveIngest)(domain.id, instanceId, records, 'hook'),
|
|
59
|
+
/*
|
|
60
|
+
* 구멍을 본 그 자리에서 바로 받는다 — 번호가 비었다는 것을 아는 가장 이른 순간이다.
|
|
61
|
+
* 기다리지 않는다(§`requestFill`): 훅의 응답이 늦으면 보내는 쪽이 되풀이한다.
|
|
62
|
+
*/
|
|
63
|
+
onGap: scope => (0, fill_loop_js_1.requestFill)(domain.id, String(ctx.params.instanceId ?? ''), scope)
|
|
72
64
|
});
|
|
73
65
|
ctx.status = outcome.status;
|
|
74
66
|
ctx.body = outcome.body;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../server/routes.ts"],"names":[],"mappings":";;AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../server/routes.ts"],"names":[],"mappings":";;AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,uEAA+D;AAC/D,mEAA8D;AAC9D,6EAAkE;AAClE,qEAAiE;AAEjE,OAAO,CAAC,EAAE,CAAC,sCAA6C,EAAE,CAAC,IAAS,EAAE,MAAW,EAAE,EAAE;IACnF;;;;;OAKG;IACH,MAAM,CAAC,IAAI,CAAC,gCAAgC,EAAE,KAAK,EAAE,GAAQ,EAAE,EAAE;QAC/D,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,EAAE,MAAM,CAAA;QAChC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,GAAG,CAAC,MAAM,GAAG,GAAG,CAAA;YAChB,GAAG,CAAC,IAAI,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAA;YACpD,OAAM;QACR,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,IAAA,8BAAU,EAAC;YAC/B,QAAQ,EAAE,MAAM,CAAC,EAAE;YACnB,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC;YACvC,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC;YAC/C,OAAO,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,IAAI,EAAE,CAA4B;YAChE,IAAI,EAAE,GAAG,CAAC,OAAO,EAAE,IAAI;YACvB;;;eAGG;YACH,OAAO,EAAE,OAAO,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;YACnF,KAAK,EAAE,IAAA,6BAAa,GAAE;YACtB;;;eAGG;YACH;;;eAGG;YACH,MAAM,EAAE,CAAC,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC,IAAA,2BAAU,EAAC,MAAM,CAAC,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,CAAC;YACnF;;;eAGG;YACH,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,IAAA,0BAAW,EAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC,EAAE,KAAK,CAAC;SACnF,CAAC,CAAA;QACF,GAAG,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;QAC3B,GAAG,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAA;IACzB,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,2DAA2D;AAC3D,OAAO,CAAC,EAAE,CAAC,sCAA6C,EAAE,CAAC,IAAS,EAAE,OAAY,EAAE,EAAE,GAAE,CAAC,CAAC,CAAA;AAC1F,OAAO,CAAC,EAAE,CAAC,uCAA8C,EAAE,CAAC,IAAS,EAAE,OAAY,EAAE,EAAE,GAAE,CAAC,CAAC,CAAA;AAC3F,OAAO,CAAC,EAAE,CAAC,uCAA8C,EAAE,CAAC,IAAS,EAAE,OAAY,EAAE,EAAE,GAAE,CAAC,CAAC,CAAA","sourcesContent":["/*\n * 이 패키지가 여는 HTTP 자리 — 지금은 웹훅 하나다.\n *\n * ── 왜 여기인가 ─────────────────────────────────────────────────────────────\n * 커넥터는 주소를 만들 수 없다. 어댑터 계약에 주소를 등록하는 자리가 없고, 있어서도 안 된다 — 주소는\n * 앱의 것이고 커넥터마다 제각기 열면 인증도 제각기가 된다.\n *\n * 그래서 입구는 **하나**다. 어느 연결의 것인지는 주소의 이름으로 가려내고, 본문을 우리 어휘로 옮기는\n * 것은 그 연결의 커넥터가 한다(§`reference-hook`).\n *\n * ── 왜 도메인 공개 라우터인가 ───────────────────────────────────────────────\n * 밀어 주는 쪽은 사람이 아니다. 사람 인증(JWT)을 요구하면 연결된 시스템이 우리 토큰을 발급받아\n * 갱신하며 관리해야 하고, 대부분의 현장에서 그것 때문에 연동이 서지 않는다.\n *\n * 대신 **연결마다 비밀값**을 두고 그것으로 확인한다. 비밀값을 선언하지 않은 연결은 훅을 받지 않는다 —\n * 인증 없는 입구를 열어 두는 것보다 훅을 못 쓰는 것이 낫다.\n */\nimport { liveIngest } from './service/reference/live-ingest.js'\nimport { requestFill } from './service/reference/fill-loop.js'\nimport { handleHook } from './service/reference/reference-hook.js'\nimport { databaseStore } from './service/reference/hook-store.js'\n\nprocess.on('bootstrap-module-domain-public-route' as any, (_app: any, router: any) => {\n /*\n * 주소에 트윈까지 적는다 — 어느 트윈에 넣을지 짐작하지 않는다.\n *\n * 한 연결이 여러 사이트를 만들면 트윈도 여럿이다. 본문을 보고 고르게 하면 그 판단이 커넥터마다\n * 달라지고, 틀리면 다른 공장의 사실이 이 공장에 들어간다. 주소가 말하게 하면 그 일이 없다.\n */\n router.post('/twin/hook/:source/:instanceId', async (ctx: any) => {\n const domain = ctx.state?.domain\n if (!domain) {\n ctx.status = 400\n ctx.body = { ok: false, error: 'no domain context' }\n return\n }\n const outcome = await handleHook({\n domainId: domain.id,\n source: String(ctx.params.source ?? ''),\n instanceId: String(ctx.params.instanceId ?? ''),\n headers: (ctx.request?.headers ?? {}) as Record<string, unknown>,\n body: ctx.request?.body,\n /*\n * 받은 바이트 그대로 — 서명을 확인하려면 이것이 있어야 한다. `koa-bodyparser` 가 파싱한 본문과\n * 함께 남겨 준다. 파싱본을 다시 문자열로 만들면 키 순서와 공백이 달라져 서명이 맞지 않는다.\n */\n rawBody: typeof ctx.request?.rawBody === 'string' ? ctx.request.rawBody : undefined,\n store: databaseStore(),\n /*\n * 폴링이 쓰는 길을 그대로 쓴다 — 검증도 유입도 같은 자리다. 그래서 트윈 쪽에서는 물어서 받은\n * 것과 밀어 받은 것이 구별되지 않는다. 구별되는 것은 유입 장부의 `sources` 한 줄뿐이다.\n */\n /*\n * 유입 몸통은 `live-ingest.ts` 한 곳에 있다 — backfill 이 같은 것을 쓴다. 여기에 두면 두 길이\n * 서로 다른 규칙으로 받게 되고, 한쪽만 수정되는 날이 온다.\n */\n ingest: (instanceId, records) => liveIngest(domain.id, instanceId, records, 'hook'),\n /*\n * 구멍을 본 그 자리에서 바로 받는다 — 번호가 비었다는 것을 아는 가장 이른 순간이다.\n * 기다리지 않는다(§`requestFill`): 훅의 응답이 늦으면 보내는 쪽이 되풀이한다.\n */\n onGap: scope => requestFill(domain.id, String(ctx.params.instanceId ?? ''), scope)\n })\n ctx.status = outcome.status\n ctx.body = outcome.body\n })\n})\n\n/* 나머지 셋은 이 패키지가 쓰지 않는다 — 자리는 남겨 둔다(다른 모듈이 같은 신호를 기다린다). */\nprocess.on('bootstrap-module-global-public-route' as any, (_app: any, _router: any) => {})\nprocess.on('bootstrap-module-global-private-route' as any, (_app: any, _router: any) => {})\nprocess.on('bootstrap-module-domain-private-route' as any, (_app: any, _router: any) => {})\n"]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { StoredCommand } from './command-dispatcher.js';
|
|
2
|
+
import type { TwinNow } from './twin-now.js';
|
|
3
|
+
/**
|
|
4
|
+
* AI 가 어떤 태도를 냈나 — **세 갈래여야 한다.**
|
|
5
|
+
*
|
|
6
|
+
* 「모르겠다」가 없으면 모를 때 동의로 답한다. 그리고 승인자가 그것을 「AI 가 괜찮다고 했다」로 읽는다.
|
|
7
|
+
* 자동 승인 편향이 거기서 시작한다.
|
|
8
|
+
*/
|
|
9
|
+
export type AdvisorStance = 'object' | 'concur' | 'unsure';
|
|
10
|
+
export interface AdvisorOpinion {
|
|
11
|
+
stance: AdvisorStance;
|
|
12
|
+
/** 사람이 읽는 문장. AI 가 쓴 그대로 — 우리가 다듬지 않는다(다듬으면 누가 말한 것인지 흐려진다). */
|
|
13
|
+
text: string;
|
|
14
|
+
/** 무엇을 보고 말했나 — 주의 식별자·값. 승인자가 근거를 되짚을 수 있게. */
|
|
15
|
+
basis?: string[];
|
|
16
|
+
}
|
|
17
|
+
/** 의견을 내는 자리 — 앱이 채운다(headless-twin 은 `twin-ai` 를 부르지 않는다). */
|
|
18
|
+
export interface ActuationAdvisor {
|
|
19
|
+
/** 화면이 출처를 말해야 한다 — 「AI 의견」. 사람의 의견과 섞이면 안 된다. */
|
|
20
|
+
label: string;
|
|
21
|
+
/**
|
|
22
|
+
* 이 조치에 대해 한마디.
|
|
23
|
+
*
|
|
24
|
+
* 실패·시간초과면 `undefined` 를 돌려준다. 지어내지 않는다.
|
|
25
|
+
*/
|
|
26
|
+
advise(input: {
|
|
27
|
+
command: StoredCommand;
|
|
28
|
+
twinNow: TwinNow;
|
|
29
|
+
locale?: string;
|
|
30
|
+
}): Promise<AdvisorOpinion | undefined>;
|
|
31
|
+
}
|
|
32
|
+
/** 앱이 부팅에서 한 번 부른다. */
|
|
33
|
+
export declare function setActuationAdvisor(next: ActuationAdvisor): void;
|
|
34
|
+
export declare function actuationAdvisor(): ActuationAdvisor | undefined;
|
|
35
|
+
/** 의견을 물은 결과 — **없음도 사실이다.** */
|
|
36
|
+
export interface AdvisorResult {
|
|
37
|
+
/** 누가 말했나. 없으면 아무도 말하지 않았다. */
|
|
38
|
+
by?: string;
|
|
39
|
+
opinion?: AdvisorOpinion;
|
|
40
|
+
/**
|
|
41
|
+
* 의견이 없으면 왜 없나.
|
|
42
|
+
*
|
|
43
|
+
* 빈칸을 「AI 가 반대하지 않았다」로 읽으면 안 된다. 화면이 이 문장을 그대로 보여야 한다.
|
|
44
|
+
*/
|
|
45
|
+
absent?: string;
|
|
46
|
+
}
|
|
47
|
+
/** 몇 초까지 기다리나 — 승인 화면이 이것 때문에 안 열리면 안 된다. */
|
|
48
|
+
export declare const ADVISOR_BUDGET_MS = 8000;
|
|
49
|
+
/**
|
|
50
|
+
* 의견을 묻는다 — **없으면 없다고 답한다.**
|
|
51
|
+
*
|
|
52
|
+
* 실패를 삼키지 않는다. AI 가 죽어 있어서 의견이 없는 것과 AI 가 반대할 것이 없어서 조용한 것은 다른
|
|
53
|
+
* 사실이고, 승인하는 사람에게 그 차이가 크다.
|
|
54
|
+
*/
|
|
55
|
+
export declare function askAdvisor(args: {
|
|
56
|
+
command: StoredCommand;
|
|
57
|
+
twinNow: TwinNow;
|
|
58
|
+
locale?: string;
|
|
59
|
+
budgetMs?: number;
|
|
60
|
+
}): Promise<AdvisorResult>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ADVISOR_BUDGET_MS = void 0;
|
|
4
|
+
exports.setActuationAdvisor = setActuationAdvisor;
|
|
5
|
+
exports.actuationAdvisor = actuationAdvisor;
|
|
6
|
+
exports.askAdvisor = askAdvisor;
|
|
7
|
+
let advisor;
|
|
8
|
+
/** 앱이 부팅에서 한 번 부른다. */
|
|
9
|
+
function setActuationAdvisor(next) {
|
|
10
|
+
advisor = next;
|
|
11
|
+
}
|
|
12
|
+
function actuationAdvisor() {
|
|
13
|
+
return advisor;
|
|
14
|
+
}
|
|
15
|
+
/** 몇 초까지 기다리나 — 승인 화면이 이것 때문에 안 열리면 안 된다. */
|
|
16
|
+
exports.ADVISOR_BUDGET_MS = 8_000;
|
|
17
|
+
/**
|
|
18
|
+
* 의견을 묻는다 — **없으면 없다고 답한다.**
|
|
19
|
+
*
|
|
20
|
+
* 실패를 삼키지 않는다. AI 가 죽어 있어서 의견이 없는 것과 AI 가 반대할 것이 없어서 조용한 것은 다른
|
|
21
|
+
* 사실이고, 승인하는 사람에게 그 차이가 크다.
|
|
22
|
+
*/
|
|
23
|
+
async function askAdvisor(args) {
|
|
24
|
+
const a = advisor;
|
|
25
|
+
if (!a)
|
|
26
|
+
return { absent: 'AI 의견을 내는 자리가 설치되지 않았습니다' };
|
|
27
|
+
const budget = args.budgetMs ?? exports.ADVISOR_BUDGET_MS;
|
|
28
|
+
let timer;
|
|
29
|
+
try {
|
|
30
|
+
const opinion = await Promise.race([
|
|
31
|
+
a.advise({ command: args.command, twinNow: args.twinNow, ...(args.locale ? { locale: args.locale } : {}) }),
|
|
32
|
+
new Promise(resolve => {
|
|
33
|
+
timer = setTimeout(() => resolve(TIMED_OUT), budget);
|
|
34
|
+
})
|
|
35
|
+
]);
|
|
36
|
+
if (opinion === TIMED_OUT) {
|
|
37
|
+
return { by: a.label, absent: `${Math.round(budget / 1000)}초 안에 답이 오지 않았습니다` };
|
|
38
|
+
}
|
|
39
|
+
if (!opinion?.text?.trim())
|
|
40
|
+
return { by: a.label, absent: 'AI 가 할 말이 없다고 답했습니다' };
|
|
41
|
+
return { by: a.label, opinion };
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
return { by: a.label, absent: `의견을 받지 못했습니다 — ${String(e?.message ?? e)}` };
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
if (timer)
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** 시간초과를 값으로 구별한다 — `undefined` 로 두면 「할 말 없음」과 섞인다. */
|
|
52
|
+
const TIMED_OUT = Symbol('advisor-timeout');
|
|
53
|
+
//# sourceMappingURL=actuation-advisor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actuation-advisor.js","sourceRoot":"","sources":["../../../server/service/actuation/actuation-advisor.ts"],"names":[],"mappings":";;;AA4DA,kDAEC;AAED,4CAEC;AAwBD,gCA8BC;AA/DD,IAAI,OAAqC,CAAA;AAEzC,uBAAuB;AACvB,SAAgB,mBAAmB,CAAC,IAAsB;IACxD,OAAO,GAAG,IAAI,CAAA;AAChB,CAAC;AAED,SAAgB,gBAAgB;IAC9B,OAAO,OAAO,CAAA;AAChB,CAAC;AAeD,6CAA6C;AAChC,QAAA,iBAAiB,GAAG,KAAK,CAAA;AAEtC;;;;;GAKG;AACI,KAAK,UAAU,UAAU,CAAC,IAKhC;IACC,MAAM,CAAC,GAAG,OAAO,CAAA;IACjB,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,0BAA0B,EAAE,CAAA;IAErD,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,IAAI,yBAAiB,CAAA;IACjD,IAAI,KAAU,CAAA;IACd,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;YACjC,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;YAC3G,IAAI,OAAO,CAAY,OAAO,CAAC,EAAE;gBAC/B,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,SAAgB,CAAC,EAAE,MAAM,CAAC,CAAA;YAC7D,CAAC,CAAC;SACH,CAAC,CAAA;QAEF,IAAK,OAAe,KAAK,SAAS,EAAE,CAAC;YACnC,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,kBAAkB,EAAE,CAAA;QAChF,CAAC;QACD,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE;YAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAA;QAEjF,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,CAAA;IACjC,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,kBAAkB,MAAM,CAAC,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC,EAAE,EAAE,CAAA;IAC7E,CAAC;YAAS,CAAC;QACT,IAAI,KAAK;YAAE,YAAY,CAAC,KAAK,CAAC,CAAA;IAChC,CAAC;AACH,CAAC;AAED,uDAAuD;AACvD,MAAM,SAAS,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAA","sourcesContent":["/*\n * **AI 가 의견을 내는 자리** — 판정이 아니라 발언.\n *\n * ── 왜 필요한가 (2026-09-01, 사용자 지적) ────────────────────────────────\n * 사용자가 물었다 — 「AI 자신은 결재/승인의 담당이 될 수 없나. 최소한 의견제시자라도 될 수 있지 않나.」\n *\n * 승인자는 안 된다. 승인 기록의 값어치는 사고 뒤에 **책임을 물을 수 있는 것**이고, 「AI 가 승인했다」로는\n * 아무에게도 묻지 못한다. 그리고 낸 쪽이 승인하면 두 사람이 아니다 — 자기 승인을 막는 규칙과 같은 이유다.\n *\n * 의견은 내야 한다. `twinNow` 로 「도금 대기가 여섯 배다」는 사실을 승인 화면에 실었지만, 그건 계산값이다.\n * 「그러니 더 만들면 대기가 늘어난다」가 의견이고 그 자리가 없었다.\n *\n * ── 언제 묻나 — 승인 화면을 열 때다 ─────────────────────────────────────\n * 결재선에 올릴 때 묻지 않는다. 그 자리는 트랜잭션 안이고(`raiseTwinCommand` 의 `@transaction`),\n * 거기서 LLM 을 기다리면 **DB 연결 하나를 몇 초 붙잡는다**(sqlite 는 연결이 하나다).\n *\n * 그리고 화면을 열 때 묻는 것이 더 옳다. 승인하는 사람은 **지금** 판단하고, 올린 시각의 의견은 낡았다.\n *\n * ── 판정에 쓰지 않는다 ───────────────────────────────────────────────────\n * 이 값을 읽어 무엇을 막는 코드가 없다. 승인·거절은 사람이 한다. AI 가 반대해도 승인되고, 동의해도\n * 거절된다. 그 사실이 코드에 드러나 있어야 한다 — 판정에 쓰면 AI 가 사실상 승인자가 된다.\n */\nimport type { StoredCommand } from './command-dispatcher.js'\nimport type { TwinNow } from './twin-now.js'\n\n/**\n * AI 가 어떤 태도를 냈나 — **세 갈래여야 한다.**\n *\n * 「모르겠다」가 없으면 모를 때 동의로 답한다. 그리고 승인자가 그것을 「AI 가 괜찮다고 했다」로 읽는다.\n * 자동 승인 편향이 거기서 시작한다.\n */\nexport type AdvisorStance = 'object' | 'concur' | 'unsure'\n\nexport interface AdvisorOpinion {\n stance: AdvisorStance\n /** 사람이 읽는 문장. AI 가 쓴 그대로 — 우리가 다듬지 않는다(다듬으면 누가 말한 것인지 흐려진다). */\n text: string\n /** 무엇을 보고 말했나 — 주의 식별자·값. 승인자가 근거를 되짚을 수 있게. */\n basis?: string[]\n}\n\n/** 의견을 내는 자리 — 앱이 채운다(headless-twin 은 `twin-ai` 를 부르지 않는다). */\nexport interface ActuationAdvisor {\n /** 화면이 출처를 말해야 한다 — 「AI 의견」. 사람의 의견과 섞이면 안 된다. */\n label: string\n /**\n * 이 조치에 대해 한마디.\n *\n * 실패·시간초과면 `undefined` 를 돌려준다. 지어내지 않는다.\n */\n advise(input: {\n command: StoredCommand\n twinNow: TwinNow\n locale?: string\n }): Promise<AdvisorOpinion | undefined>\n}\n\nlet advisor: ActuationAdvisor | undefined\n\n/** 앱이 부팅에서 한 번 부른다. */\nexport function setActuationAdvisor(next: ActuationAdvisor): void {\n advisor = next\n}\n\nexport function actuationAdvisor(): ActuationAdvisor | undefined {\n return advisor\n}\n\n/** 의견을 물은 결과 — **없음도 사실이다.** */\nexport interface AdvisorResult {\n /** 누가 말했나. 없으면 아무도 말하지 않았다. */\n by?: string\n opinion?: AdvisorOpinion\n /**\n * 의견이 없으면 왜 없나.\n *\n * 빈칸을 「AI 가 반대하지 않았다」로 읽으면 안 된다. 화면이 이 문장을 그대로 보여야 한다.\n */\n absent?: string\n}\n\n/** 몇 초까지 기다리나 — 승인 화면이 이것 때문에 안 열리면 안 된다. */\nexport const ADVISOR_BUDGET_MS = 8_000\n\n/**\n * 의견을 묻는다 — **없으면 없다고 답한다.**\n *\n * 실패를 삼키지 않는다. AI 가 죽어 있어서 의견이 없는 것과 AI 가 반대할 것이 없어서 조용한 것은 다른\n * 사실이고, 승인하는 사람에게 그 차이가 크다.\n */\nexport async function askAdvisor(args: {\n command: StoredCommand\n twinNow: TwinNow\n locale?: string\n budgetMs?: number\n}): Promise<AdvisorResult> {\n const a = advisor\n if (!a) return { absent: 'AI 의견을 내는 자리가 설치되지 않았습니다' }\n\n const budget = args.budgetMs ?? ADVISOR_BUDGET_MS\n let timer: any\n try {\n const opinion = await Promise.race([\n a.advise({ command: args.command, twinNow: args.twinNow, ...(args.locale ? { locale: args.locale } : {}) }),\n new Promise<undefined>(resolve => {\n timer = setTimeout(() => resolve(TIMED_OUT as any), budget)\n })\n ])\n\n if ((opinion as any) === TIMED_OUT) {\n return { by: a.label, absent: `${Math.round(budget / 1000)}초 안에 답이 오지 않았습니다` }\n }\n if (!opinion?.text?.trim()) return { by: a.label, absent: 'AI 가 할 말이 없다고 답했습니다' }\n\n return { by: a.label, opinion }\n } catch (e: any) {\n return { by: a.label, absent: `의견을 받지 못했습니다 — ${String(e?.message ?? e)}` }\n } finally {\n if (timer) clearTimeout(timer)\n }\n}\n\n/** 시간초과를 값으로 구별한다 — `undefined` 로 두면 「할 말 없음」과 섞인다. */\nconst TIMED_OUT = Symbol('advisor-timeout')\n"]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { StoredCommand } from './command-dispatcher.js';
|
|
2
|
+
/** 승인한 사람에게 돌려줄 것 — 화면이 겹쳐 볼 것이 없게 판정까지 낸다. */
|
|
3
|
+
export interface ActuationOutcome {
|
|
4
|
+
id: string;
|
|
5
|
+
/** proposed · approved · dispatching · acked · failed · rejected. */
|
|
6
|
+
state: string;
|
|
7
|
+
/** 저쪽이 준 이름 — 나갔을 때만. 없으면 무엇을 되돌릴지 지목할 수 없다. */
|
|
8
|
+
dispatchRef?: string;
|
|
9
|
+
/** 못 나간 사유 — 저쪽 문장 그대로. */
|
|
10
|
+
error?: string;
|
|
11
|
+
/** 저쪽이 받으면서 덧붙인 말 — **실패가 아니다.** 원인 쪽 문장. */
|
|
12
|
+
note?: string;
|
|
13
|
+
/**
|
|
14
|
+
* 그 말 중 **다음에 할 일** 한 줄.
|
|
15
|
+
*
|
|
16
|
+
* 왜 둘로 오나: 읽는 사람은 「그래서 내가 뭘 해야 하나」를 먼저 찾는다(UX 레인). 한 문장으로
|
|
17
|
+
* 주면 화면이 자르게 되고, **자르는 규칙이 화면마다 생긴다.** 어댑터가 이미 둘로 알고 있으니
|
|
18
|
+
* 붙이지 않고 그대로 나른다.
|
|
19
|
+
*
|
|
20
|
+
* 어댑터가 아직 갈라 주지 않으면 **비어 있다.** 그때 `note` 는 두 문장이 붙은 한 덩이이고,
|
|
21
|
+
* 화면은 그것을 통째로 그려야 한다 — 파싱해서 나누면 어댑터가 갈라 주는 날 조용히 깨진다.
|
|
22
|
+
*/
|
|
23
|
+
noteNext?: string;
|
|
24
|
+
/** 원천이 보고한 상태 — 낱말 그대로. */
|
|
25
|
+
effectStatus?: string;
|
|
26
|
+
/** 그 보고를 본 시각. */
|
|
27
|
+
effectAt?: string;
|
|
28
|
+
/**
|
|
29
|
+
* 되돌릴 수 있나 — 화면은 **참일 때만** 한 줄 낸다.
|
|
30
|
+
*
|
|
31
|
+
* 지금 규칙에서 결재함에 올라온 조치는 거의 다 되돌릴 수 없다(되돌릴 수 없는 것만 승인을
|
|
32
|
+
* 요구한다). 그래서 「되돌릴 수 없습니다」를 매번 적으면 늘 참인 경고가 되고, 늘 참인 경고는
|
|
33
|
+
* 사흘이면 안 읽힌다 — 그러면 그 자리에 다른 말이 필요한 날 자리가 이미 죽어 있다(UX 레인 판단).
|
|
34
|
+
*/
|
|
35
|
+
reversible?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* **저쪽에서 사람이 할 일이 남았나** — 이 판정이 이 타입의 존재 이유다(머리말).
|
|
38
|
+
*
|
|
39
|
+
* 「모른다」가 있다: 아직 넘기지 않았으면 물어볼 것이 없다. `false` 로 접으면 화면이 「할 일
|
|
40
|
+
* 없음」으로 그리고, 그것은 넘긴 뒤와 구별되지 않는다.
|
|
41
|
+
*/
|
|
42
|
+
pendingAtSource?: boolean;
|
|
43
|
+
/** 넘길 수 있는 상태인가 — `acked` 는 종착이라 저쪽이 준비돼 있어도 다시 넘기지 않는다. */
|
|
44
|
+
dispatchable: boolean;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 저장된 조치 하나를 사람이 읽을 사실로 옮긴다 — **순수하다.**
|
|
48
|
+
*
|
|
49
|
+
* DB 도 어댑터도 보지 않는다. 그래서 이 판정은 시험이 값만 바꿔 가며 확인할 수 있고, 화면이
|
|
50
|
+
* 같은 규칙을 다시 쓰지 않아도 된다.
|
|
51
|
+
*/
|
|
52
|
+
export declare function actuationOutcomeOf(command: StoredCommand, dispatchable: boolean): ActuationOutcome;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.actuationOutcomeOf = actuationOutcomeOf;
|
|
4
|
+
/** 넘김을 시도해 본 상태인가 — 이 뒤에야 「저쪽에 할 일이 남았나」를 물을 수 있다. */
|
|
5
|
+
const TRIED = new Set(['dispatching', 'acked', 'failed']);
|
|
6
|
+
/**
|
|
7
|
+
* 저장된 조치 하나를 사람이 읽을 사실로 옮긴다 — **순수하다.**
|
|
8
|
+
*
|
|
9
|
+
* DB 도 어댑터도 보지 않는다. 그래서 이 판정은 시험이 값만 바꿔 가며 확인할 수 있고, 화면이
|
|
10
|
+
* 같은 규칙을 다시 쓰지 않아도 된다.
|
|
11
|
+
*/
|
|
12
|
+
function actuationOutcomeOf(command, dispatchable) {
|
|
13
|
+
const note = command.dispatchNote;
|
|
14
|
+
const effect = command.effect;
|
|
15
|
+
/*
|
|
16
|
+
* 넘겨 보지 않았으면 판정하지 않는다. 넘긴 뒤라면 「말이 있는데 효과가 아직 없다」가 곧
|
|
17
|
+
* 「저쪽에서 사람이 할 일이 남았다」다.
|
|
18
|
+
*/
|
|
19
|
+
const pendingAtSource = TRIED.has(command.state) ? Boolean(note) && !effect?.status : undefined;
|
|
20
|
+
return {
|
|
21
|
+
id: command.id,
|
|
22
|
+
state: command.state,
|
|
23
|
+
...(command.dispatchRef ? { dispatchRef: command.dispatchRef } : {}),
|
|
24
|
+
...(command.error ? { error: command.error } : {}),
|
|
25
|
+
...(note ? { note } : {}),
|
|
26
|
+
...(command.dispatchNoteNext ? { noteNext: command.dispatchNoteNext } : {}),
|
|
27
|
+
...(effect?.status ? { effectStatus: effect.status } : {}),
|
|
28
|
+
...(effect?.at ? { effectAt: effect.at } : {}),
|
|
29
|
+
...(command.reversible === undefined ? {} : { reversible: command.reversible }),
|
|
30
|
+
...(pendingAtSource === undefined ? {} : { pendingAtSource }),
|
|
31
|
+
dispatchable
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=actuation-outcome.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actuation-outcome.js","sourceRoot":"","sources":["../../../server/service/actuation/actuation-outcome.ts"],"names":[],"mappings":";;AAyEA,gDAuBC;AAhCD,sDAAsD;AACtD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,aAAa,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAA;AAEzD;;;;;GAKG;AACH,SAAgB,kBAAkB,CAAC,OAAsB,EAAE,YAAqB;IAC9E,MAAM,IAAI,GAAG,OAAO,CAAC,YAAY,CAAA;IACjC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;IAE7B;;;OAGG;IACH,MAAM,eAAe,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,SAAS,CAAA;IAE/F,OAAO;QACL,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpE,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAClD,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzB,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3E,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1D,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9C,GAAG,CAAC,OAAO,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC;QAC/E,GAAG,CAAC,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC;QAC7D,YAAY;KACb,CAAA;AACH,CAAC","sourcesContent":["/*\n * **조치가 어떻게 됐나** — 승인한 사람에게 돌려줄 사실 한 묶음.\n *\n * ── 왜 판정을 커널이 하나 (2026-09-04) ─────────────────────────────────────\n * 「저쪽에서 사람이 할 일이 남았나」의 답이 한 필드에 없다. `dispatchNote` 만 보면 틀린다 —\n * 그 문장은 **넘길 때 저쪽이 한 말**이고, 사람이 나중에 지시서를 골라도 그대로 남는다. 저쪽에\n * 다시 물을 일이 없으니 갱신되지 않는다(인티그레이션 레인이 MES 코드를 읽어 찾았다: 다시 보내면\n * 지금 상태를 답하는데, `acked` 가 종착이라 다시 보낼 일이 없다).\n *\n * 그러니 답은 **「그 문장이 있고 효과가 아직 없을 때」**다. 그 겹침을 화면마다 하게 두면 목록·\n * 상세·알림이 각자 겹치고, 한 곳이 빠진 날 그 화면만 다른 말을 한다. 오늘 설비 등급 소속에서\n * 같은 이유로 합침을 계약 한 자리에 뒀다.\n *\n * ── 커널은 저쪽 말을 해석하지 않는다 ───────────────────────────────────────\n * `effectStatus` 는 원천이 보낸 낱말 그대로다(§`QualityDelta` 와 같은 규율). 여기서 보는 것은\n * **그 값이 있는가**뿐이고, `released` 인지 `closed` 인지는 도메인의 뜻이라 커널이 가르지 않는다.\n */\nimport type { StoredCommand } from './command-dispatcher.js'\n\n/** 승인한 사람에게 돌려줄 것 — 화면이 겹쳐 볼 것이 없게 판정까지 낸다. */\nexport interface ActuationOutcome {\n id: string\n /** proposed · approved · dispatching · acked · failed · rejected. */\n state: string\n /** 저쪽이 준 이름 — 나갔을 때만. 없으면 무엇을 되돌릴지 지목할 수 없다. */\n dispatchRef?: string\n /** 못 나간 사유 — 저쪽 문장 그대로. */\n error?: string\n /** 저쪽이 받으면서 덧붙인 말 — **실패가 아니다.** 원인 쪽 문장. */\n note?: string\n /**\n * 그 말 중 **다음에 할 일** 한 줄.\n *\n * 왜 둘로 오나: 읽는 사람은 「그래서 내가 뭘 해야 하나」를 먼저 찾는다(UX 레인). 한 문장으로\n * 주면 화면이 자르게 되고, **자르는 규칙이 화면마다 생긴다.** 어댑터가 이미 둘로 알고 있으니\n * 붙이지 않고 그대로 나른다.\n *\n * 어댑터가 아직 갈라 주지 않으면 **비어 있다.** 그때 `note` 는 두 문장이 붙은 한 덩이이고,\n * 화면은 그것을 통째로 그려야 한다 — 파싱해서 나누면 어댑터가 갈라 주는 날 조용히 깨진다.\n */\n noteNext?: string\n /** 원천이 보고한 상태 — 낱말 그대로. */\n effectStatus?: string\n /** 그 보고를 본 시각. */\n effectAt?: string\n /**\n * 되돌릴 수 있나 — 화면은 **참일 때만** 한 줄 낸다.\n *\n * 지금 규칙에서 결재함에 올라온 조치는 거의 다 되돌릴 수 없다(되돌릴 수 없는 것만 승인을\n * 요구한다). 그래서 「되돌릴 수 없습니다」를 매번 적으면 늘 참인 경고가 되고, 늘 참인 경고는\n * 사흘이면 안 읽힌다 — 그러면 그 자리에 다른 말이 필요한 날 자리가 이미 죽어 있다(UX 레인 판단).\n */\n reversible?: boolean\n /**\n * **저쪽에서 사람이 할 일이 남았나** — 이 판정이 이 타입의 존재 이유다(머리말).\n *\n * 「모른다」가 있다: 아직 넘기지 않았으면 물어볼 것이 없다. `false` 로 접으면 화면이 「할 일\n * 없음」으로 그리고, 그것은 넘긴 뒤와 구별되지 않는다.\n */\n pendingAtSource?: boolean\n /** 넘길 수 있는 상태인가 — `acked` 는 종착이라 저쪽이 준비돼 있어도 다시 넘기지 않는다. */\n dispatchable: boolean\n}\n\n/** 넘김을 시도해 본 상태인가 — 이 뒤에야 「저쪽에 할 일이 남았나」를 물을 수 있다. */\nconst TRIED = new Set(['dispatching', 'acked', 'failed'])\n\n/**\n * 저장된 조치 하나를 사람이 읽을 사실로 옮긴다 — **순수하다.**\n *\n * DB 도 어댑터도 보지 않는다. 그래서 이 판정은 시험이 값만 바꿔 가며 확인할 수 있고, 화면이\n * 같은 규칙을 다시 쓰지 않아도 된다.\n */\nexport function actuationOutcomeOf(command: StoredCommand, dispatchable: boolean): ActuationOutcome {\n const note = command.dispatchNote\n const effect = command.effect\n\n /*\n * 넘겨 보지 않았으면 판정하지 않는다. 넘긴 뒤라면 「말이 있는데 효과가 아직 없다」가 곧\n * 「저쪽에서 사람이 할 일이 남았다」다.\n */\n const pendingAtSource = TRIED.has(command.state) ? Boolean(note) && !effect?.status : undefined\n\n return {\n id: command.id,\n state: command.state,\n ...(command.dispatchRef ? { dispatchRef: command.dispatchRef } : {}),\n ...(command.error ? { error: command.error } : {}),\n ...(note ? { note } : {}),\n ...(command.dispatchNoteNext ? { noteNext: command.dispatchNoteNext } : {}),\n ...(effect?.status ? { effectStatus: effect.status } : {}),\n ...(effect?.at ? { effectAt: effect.at } : {}),\n ...(command.reversible === undefined ? {} : { reversible: command.reversible }),\n ...(pendingAtSource === undefined ? {} : { pendingAtSource }),\n dispatchable\n }\n}\n"]}
|