@things-factory/headless-twin 10.0.14 → 10.0.15
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.js +30 -0
- package/dist-server/engine/canonical-ingest.js.map +1 -1
- package/dist-server/engine/ingest-health.d.ts +35 -0
- package/dist-server/engine/ingest-health.js +45 -0
- package/dist-server/engine/ingest-health.js.map +1 -1
- package/dist-server/engine/twin-engine.d.ts +7 -0
- package/dist-server/engine/twin-engine.js +19 -0
- package/dist-server/engine/twin-engine.js.map +1 -1
- package/dist-server/service/reference/reference-adapter.d.ts +25 -0
- package/dist-server/service/reference/reference-adapter.js.map +1 -1
- package/dist-server/service/reference/reference-live.js +12 -0
- package/dist-server/service/reference/reference-live.js.map +1 -1
- package/dist-server/service/twin-event/twin-event-keys.d.ts +21 -1
- package/dist-server/service/twin-event/twin-event-keys.js +35 -3
- package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
- package/dist-server/service/twin-model/epcis-coverage.js +5 -1
- package/dist-server/service/twin-model/epcis-coverage.js.map +1 -1
- package/dist-server/service/twin-model/isa95-coverage.d.ts +10 -0
- package/dist-server/service/twin-model/isa95-coverage.js +3 -3
- package/dist-server/service/twin-model/isa95-coverage.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-query.js +71 -3
- package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
- package/package.json +3 -3
- package/server/engine/canonical-ingest.ts +30 -0
- package/server/engine/ingest-health.ts +65 -0
- package/server/engine/twin-engine.ts +20 -0
- package/server/service/reference/reference-adapter.ts +22 -0
- package/server/service/reference/reference-live.ts +12 -0
- package/server/service/twin-event/twin-event-keys.ts +35 -3
- package/server/service/twin-model/epcis-coverage.ts +5 -1
- package/server/service/twin-model/isa95-coverage.ts +13 -3
- package/server/service/twin-model/twin-model-query.ts +71 -3
- package/test/standard-coverage.test.ts +44 -0
- package/test/twin-event-keys.test.ts +48 -2
- package/test/withheld-door.test.ts +95 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ★ **원본에 있는데 세우지 않은 것** — 그 사실이 스쳐 지나가지 않게.
|
|
3
|
+
*
|
|
4
|
+
* ── 무엇이 조용했나 (2026-08-24) ────────────────────────────────────────────
|
|
5
|
+
* 커넥터가 원본의 사실 일부를 **일부러 받지 않는 일이 정상이다**(입자가 안 맞는다 · 받으면 상태가
|
|
6
|
+
* 거짓이 된다 · 커널 어휘로 옮길 수 없다). 그때 경고를 내는데, 그 경고의 유일한 소비처가 **프로비저닝
|
|
7
|
+
* 마법사**였다 — 트윈을 만드는 동안만 뜨는 화면이다.
|
|
8
|
+
*
|
|
9
|
+
* 그래서 「지시되지 않은 생산계획 8건을 세우지 않았다」가 **한 번 스쳐 지나갈 뿐**이었고, 나중에
|
|
10
|
+
* 사용자가 「원본보다 계획이 적다」를 물으면 답이 어디에도 없었다.
|
|
11
|
+
*
|
|
12
|
+
* 그 사실은 한 번의 사건이 아니라 **상태의 성질**이다 — 세우지 않은 것은 **지금도** 트윈에 없다.
|
|
13
|
+
*
|
|
14
|
+
* ── `unhandled` 의 형제다 ──────────────────────────────────────────────────
|
|
15
|
+
* `unhandled` 리듀서가 **받았는데** 담을 자리를 몰랐다
|
|
16
|
+
* 여기 커넥터가 **보지만 일부러 보내지 않았다** — 이유와 함께
|
|
17
|
+
*
|
|
18
|
+
* ── 그래서 문에서 조회까지 걷는다 ───────────────────────────────────────────
|
|
19
|
+
* 오늘 「계약에 자리는 있고 그 자리로 가는 길이 없다」를 여섯 번 만났다. 장부만 검사하면 어댑터가
|
|
20
|
+
* 부를 문이 없는 것을 못 잡는다 — 그것이 그 결함들이 살아남은 방식이다.
|
|
21
|
+
*/
|
|
22
|
+
import { test } from 'node:test'
|
|
23
|
+
import assert from 'node:assert/strict'
|
|
24
|
+
import { readFileSync } from 'node:fs'
|
|
25
|
+
import { createRequire } from 'node:module'
|
|
26
|
+
|
|
27
|
+
const req = createRequire(import.meta.url)
|
|
28
|
+
const { newIngestLedger, recordWithheld, ingestHealth } = req('../dist-server/engine/ingest-health.js')
|
|
29
|
+
|
|
30
|
+
const REASON = '아직 지시되지 않은 생산계획을 세우지 않았다 — 커널의 created 는 「할당해도 되는 오더」를 뜻한다'
|
|
31
|
+
|
|
32
|
+
test('★ 세우지 않은 것이 조회에 나온다 — 유입이 한 건도 없어도', () => {
|
|
33
|
+
/*
|
|
34
|
+
* 이 갈래가 중요하다: 원본이 말하는데 **전부** 안 받는 경우가 있고, 그때 유입은 0 이다.
|
|
35
|
+
* 유입 있는 갈래에서만 내면 바로 그 상황에서 조용해진다.
|
|
36
|
+
*/
|
|
37
|
+
const l = newIngestLedger()
|
|
38
|
+
recordWithheld(l, REASON, 8, Date.parse('2026-08-24T11:19:16.000Z'))
|
|
39
|
+
const view: any = ingestHealth(l)
|
|
40
|
+
assert.equal(view.withheld?.length, 1)
|
|
41
|
+
assert.equal(view.withheld[0].reason, REASON)
|
|
42
|
+
assert.equal(view.withheld[0].count, 8)
|
|
43
|
+
assert.equal(view.withheld[0].lastAt, '2026-08-24T11:19:16.000Z', '언제 그랬는지 함께 — 「지금도 그런가」를 사람이 판정한다')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('주기마다 불러도 부풀지 않는다 — 누적이 아니라 **지금 세우지 않은 수**다', () => {
|
|
47
|
+
/* 8건이 매 주기 다시 보고되면 8 이어야 한다. 더해서 800 이 되면 거짓이다. */
|
|
48
|
+
const l = newIngestLedger()
|
|
49
|
+
for (let i = 0; i < 100; i++) recordWithheld(l, REASON, 8, 1000 + i)
|
|
50
|
+
assert.equal(l.withheld.length, 1)
|
|
51
|
+
assert.equal(l.withheld[0].count, 8)
|
|
52
|
+
assert.equal(l.withheld[0].lastAtMs, 1099, '마지막 시각으로 움직인다')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('수가 달라지면 마지막 수로 덮는다 — 낡은 수가 남지 않게', () => {
|
|
56
|
+
const l = newIngestLedger()
|
|
57
|
+
recordWithheld(l, REASON, 8, 1000)
|
|
58
|
+
recordWithheld(l, REASON, 3, 2000)
|
|
59
|
+
assert.equal(l.withheld[0].count, 3)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('이유가 여럿이면 각자 한 줄 — 다른 사실을 한 줄로 뭉치지 않는다', () => {
|
|
63
|
+
const l = newIngestLedger()
|
|
64
|
+
recordWithheld(l, REASON, 8, 1000)
|
|
65
|
+
recordWithheld(l, '사람이 올린 문서라 자재와 이어지지 않는다', 46, 1000)
|
|
66
|
+
assert.equal((ingestHealth(l) as any).withheld.length, 2)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('말하지 않은 것은 만들지 않는다 — 빈 목록을 「세우지 않은 것이 있다」로 읽히게 하지 않는다', () => {
|
|
70
|
+
const l = newIngestLedger()
|
|
71
|
+
assert.equal((ingestHealth(l) as any).withheld, undefined, '키 자체가 없다')
|
|
72
|
+
recordWithheld(l, REASON, 0, 1000)
|
|
73
|
+
assert.equal(l.withheld, undefined, '0 은 적지 않는다')
|
|
74
|
+
recordWithheld(l, '', 5, 1000)
|
|
75
|
+
assert.equal(l.withheld, undefined, '이유 없는 것은 적지 않는다')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('★ 어댑터가 부를 문이 있고, 그 문이 장부에 닿는다', () => {
|
|
79
|
+
/*
|
|
80
|
+
* 이 단정이 오늘 여섯 번 만난 부류를 막는다. 장부만 검사하면 **어댑터가 부를 통로가 없는 것**을
|
|
81
|
+
* 못 잡고, 그러면 보내는 쪽에는 「알렸다」로 보이고 화면에는 「없다」로 보인다.
|
|
82
|
+
*/
|
|
83
|
+
const adapter = readFileSync(new URL('../server/service/reference/reference-adapter.ts', import.meta.url), 'utf-8')
|
|
84
|
+
assert.match(adapter, /onWithheld\?: \(info: \{ reason: string; count: number \}\) => void/, '계약에 통로가 있다')
|
|
85
|
+
|
|
86
|
+
const live = readFileSync(new URL('../server/service/reference/reference-live.ts', import.meta.url), 'utf-8')
|
|
87
|
+
const wired = live.slice(live.indexOf('onWithheld'), live.indexOf('onWithheld') + 260)
|
|
88
|
+
assert.match(wired, /recordIngestWithheld/, '그 통로가 장부로 이어진다')
|
|
89
|
+
|
|
90
|
+
const engine = readFileSync(new URL('../server/engine/twin-engine.ts', import.meta.url), 'utf-8')
|
|
91
|
+
const fn = engine.slice(engine.indexOf('static recordIngestWithheld'))
|
|
92
|
+
assert.match(fn.slice(0, 700), /recordWithheld\(ledger/, '엔진이 장부에 적는다')
|
|
93
|
+
/* 실패로 적지 않는다 — 어댑터의 옳은 판단이고, 오류로 적으면 화면이 원본을 의심한다. */
|
|
94
|
+
assert.doesNotMatch(fn.slice(0, 700), /lastError|status:/, '오류로 적지 않는다')
|
|
95
|
+
})
|