@things-factory/headless-twin 10.1.27 → 10.1.29
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/index.js +6 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/service/reference/actuation-verification.d.ts +22 -0
- package/dist-server/service/reference/actuation-verification.js +37 -0
- package/dist-server/service/reference/actuation-verification.js.map +1 -1
- package/dist-server/service/reference/reference-resolver.js +32 -2
- package/dist-server/service/reference/reference-resolver.js.map +1 -1
- package/dist-server/service/twin-model/external-resolver.d.ts +13 -3
- package/dist-server/service/twin-model/external-resolver.js +2 -2
- package/dist-server/service/twin-model/external-resolver.js.map +1 -1
- package/dist-server/service/twin-model/twin-lineage-query.js +14 -0
- package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-item-query.js +41 -12
- package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-query.js +14 -0
- package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
- package/dist-server/service/twin-space/area-resolver.d.ts +6 -0
- package/dist-server/service/twin-space/area-resolver.js +68 -0
- package/dist-server/service/twin-space/area-resolver.js.map +1 -0
- package/package.json +8 -8
- package/server/index.ts +6 -0
- package/server/service/reference/actuation-verification.ts +42 -0
- package/server/service/reference/reference-resolver.ts +33 -2
- package/server/service/twin-model/external-resolver.ts +15 -3
- package/server/service/twin-model/twin-lineage-query.ts +13 -0
- package/server/service/twin-model/twin-model-item-query.ts +62 -12
- package/server/service/twin-model/twin-model-query.ts +14 -0
- package/server/service/twin-space/area-resolver.ts +75 -0
- package/test/actuation-verification-state.test.ts +46 -1
- package/test/area-resolver-db.test.ts +110 -0
- package/test/axis-declaration-passthrough-db.test.ts +152 -0
- package/test/external-resolver.test.ts +11 -5
- package/test/lineage-survives-restart.test.ts +15 -0
- package/test/twin-model-item-db.test.ts +18 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -28,6 +28,12 @@ import {
|
|
|
28
28
|
|
|
29
29
|
const AREA = 'space.area'
|
|
30
30
|
|
|
31
|
+
/*
|
|
32
|
+
* 어느 트윈의 관계인지 — `resolve` 는 이것을 함께 받는다(2026-09-18). 밖의 id 는 도메인에서 유일하지
|
|
33
|
+
* 않아서(구역 id 는 공간 안에서만 유일), 문맥 없이 답하면 남의 공간 이름이 이 자리의 부모로 보인다.
|
|
34
|
+
*/
|
|
35
|
+
const CTX = { instanceId: 'probe-twin' }
|
|
36
|
+
|
|
31
37
|
/* ── 못 풀면 못 푼다고 말한다 ────────────────────────────────────────────── */
|
|
32
38
|
|
|
33
39
|
test('★ 꽂힌 것이 없으면 「해소기가 없다」고 말한다 — 「없다」가 아니다', async () => {
|
|
@@ -36,7 +42,7 @@ test('★ 꽂힌 것이 없으면 「해소기가 없다」고 말한다 — 「
|
|
|
36
42
|
* 사람이 할 일이 다르다 — 앞은 자료를 채울 일이고 뒤는 해소기를 꽂을 일이다.
|
|
37
43
|
*/
|
|
38
44
|
clearExternalResolvers()
|
|
39
|
-
const got = await resolveExternal(AREA, 'd1', ['LINE-CAM'])
|
|
45
|
+
const got = await resolveExternal(AREA, 'd1', ['LINE-CAM'], CTX)
|
|
40
46
|
|
|
41
47
|
assert.equal(got.unresolved, 'no-resolver')
|
|
42
48
|
assert.equal(got.refs, undefined)
|
|
@@ -47,7 +53,7 @@ test('★ 해소기가 터져도 360 을 죽이지 않는다 — 알리고 못
|
|
|
47
53
|
const said: string[] = []
|
|
48
54
|
registerExternalResolver({ entity: AREA, resolve: async () => { throw new Error('DB down') } })
|
|
49
55
|
|
|
50
|
-
const got = await resolveExternal(AREA, 'd1', ['X'], m => said.push(m))
|
|
56
|
+
const got = await resolveExternal(AREA, 'd1', ['X'], CTX, m => said.push(m))
|
|
51
57
|
|
|
52
58
|
assert.equal(got.unresolved, 'failed')
|
|
53
59
|
assert.ok(said.some(m => m.includes('DB down')), '사유를 삼켰다')
|
|
@@ -62,7 +68,7 @@ test('★ 이름과 갈 곳을 낸다 — 막다른 링크를 만들지 않으
|
|
|
62
68
|
resolve: async (_d, ids) => ids.map(id => ({ id, name: `라인 ${id}`, href: `/areas/${id}` }))
|
|
63
69
|
})
|
|
64
70
|
|
|
65
|
-
const got = await resolveExternal(AREA, 'd1', ['LINE-CAM'])
|
|
71
|
+
const got = await resolveExternal(AREA, 'd1', ['LINE-CAM'], CTX)
|
|
66
72
|
assert.equal(got.refs?.[0].name, '라인 LINE-CAM')
|
|
67
73
|
assert.equal(got.refs?.[0].href, '/areas/LINE-CAM')
|
|
68
74
|
})
|
|
@@ -72,7 +78,7 @@ test('★ 없는 id 는 없다고 말한다 — 답에서 빼지 않는다', asy
|
|
|
72
78
|
clearExternalResolvers()
|
|
73
79
|
registerExternalResolver({ entity: AREA, resolve: async (_d, ids) => ids.map(id => ({ id, missing: true })) })
|
|
74
80
|
|
|
75
|
-
const got = await resolveExternal(AREA, 'd1', ['사라진구역'])
|
|
81
|
+
const got = await resolveExternal(AREA, 'd1', ['사라진구역'], CTX)
|
|
76
82
|
assert.equal(got.refs?.length, 1)
|
|
77
83
|
assert.equal(got.refs?.[0].missing, true)
|
|
78
84
|
})
|
|
@@ -166,6 +172,6 @@ test('★ 이 검사가 실제로 무언가를 잰다 — 견준 것이 0이면
|
|
|
166
172
|
clearExternalResolvers()
|
|
167
173
|
assert.deepEqual(externalResolverEntities(), [], '지우기가 안 듣는다')
|
|
168
174
|
registerExternalResolver({ entity: AREA, resolve: async (_d, ids) => ids.map(id => ({ id })) })
|
|
169
|
-
const got = await resolveExternal(AREA, 'd', ['X'])
|
|
175
|
+
const got = await resolveExternal(AREA, 'd', ['X'], CTX)
|
|
170
176
|
assert.equal(got.refs?.length, 1, '꽂았는데 안 불린다')
|
|
171
177
|
})
|
|
@@ -209,6 +209,21 @@ test('계보를 거슬러 올라가 선언과 나란히 놓는다 — 그 차이
|
|
|
209
209
|
assert.equal(steel.matches, true)
|
|
210
210
|
})
|
|
211
211
|
|
|
212
|
+
test('★ 만든 것을 관측하지 못했으면 「다르다」고 하지 않는다 — 안 본 것과 어긋난 것은 다른 사실이다', async () => {
|
|
213
|
+
const q = new TwinLineageQuery()
|
|
214
|
+
const ctx = { state: { domain: { id: domainId } } } as any
|
|
215
|
+
/*
|
|
216
|
+
* 완제품 클래스의 개체이지만 저널에 그것을 낸 변환이 없다 — 자재로도 이어지고 레시피도 하나다.
|
|
217
|
+
* 예전에는 여기서 셈이 그대로 돌아 관측을 전부 0 으로 적고 「레시피와 다릅니다」를 냈다
|
|
218
|
+
* (2026-09-18 :3000 에서 카메라 로트 23 줄이 그렇게 보였다).
|
|
219
|
+
*/
|
|
220
|
+
const lin = await q.twinItemLineage(INSTANCE, `urn:epc:id:sgtin:${CP}.078101.999999`, ctx)
|
|
221
|
+
assert.equal(lin.conformance.comparable, false, `관측이 없는데 견주었다: ${JSON.stringify(lin.conformance?.lines?.slice(0, 2))}`)
|
|
222
|
+
assert.equal(lin.conformance.reason, 'made-not-observed')
|
|
223
|
+
assert.ok(lin.conformance.recipe, '어느 레시피와 견주려 했는지는 말한다 — 사람이 다음 걸음을 안다')
|
|
224
|
+
assert.equal(lin.basis.transformationsRead >= 0, true)
|
|
225
|
+
})
|
|
226
|
+
|
|
212
227
|
test('바인딩이 없으면 비교하지 않고 이유를 알린다 — 짐작으로 이으면 거짓 진단이 된다', async () => {
|
|
213
228
|
const q = new TwinLineageQuery()
|
|
214
229
|
const ctx = { state: { domain: { id: domainId } } } as any
|
|
@@ -114,12 +114,29 @@ test('아직 문서에 사는 축도 열린다 — 이주 전이라고 안 보
|
|
|
114
114
|
|
|
115
115
|
test('검색이 전수 조회라는 사실을 드러낸다 — 감추면 한계를 아무도 모른다', async () => {
|
|
116
116
|
const r = await q.twinModelEntities(INSTANCE, 'equipment', ctx, 'weld')
|
|
117
|
-
assert.equal(r.basis, 'rows-scanned', '아직 DB 로 내리지 않았다는 사실이 응답에 있다')
|
|
118
117
|
assert.equal(r.total, 2)
|
|
118
|
+
/* 훑어 걸렀다는 사실은 **이 두 칸이** 말한다(근거가 아니라 셈의 한계다). */
|
|
119
|
+
assert.equal(typeof r.scanned, 'number', '몇 줄을 읽었는지 말하지 않는다')
|
|
119
120
|
assert.equal(r.truncated, false, '상한에 닿지 않았으면 잘리지 않았다고 말한다')
|
|
120
121
|
assert.deepEqual(r.items.map((i: any) => i.id).sort(), ['welder-1', 'welder-2'])
|
|
121
122
|
})
|
|
122
123
|
|
|
124
|
+
test('★ 근거는 계약이 닫아 둔 다섯 안에서만 답한다', async () => {
|
|
125
|
+
/*
|
|
126
|
+
* 2026-09-18 판정(ADR-0075 결정 3 을 코드에 대어 본 결과): 검색 갈래가 `'rows-scanned'` 를 냈다.
|
|
127
|
+
* 그것은 출처가 아니라 우리가 어떻게 셌나이고, 목록 소비자는 계약에 없는 값을 만난다. 출처는
|
|
128
|
+
* 훑었든 DB 가 골랐든 같은 행이므로 `rows` 다.
|
|
129
|
+
*/
|
|
130
|
+
const { READ_BASIS } = req('@operato/ops-contract')
|
|
131
|
+
const answers = [
|
|
132
|
+
await q.twinModelEntities(INSTANCE, 'equipment', ctx, 'weld'),
|
|
133
|
+
await q.twinModelEntities(INSTANCE, 'equipment', ctx),
|
|
134
|
+
await q.twinModelEntities(INSTANCE, 'recipes', ctx)
|
|
135
|
+
]
|
|
136
|
+
for (const a of answers) assert.ok(READ_BASIS.includes(a.basis), `계약 밖의 근거 '${a.basis}'`)
|
|
137
|
+
assert.equal(answers[0].basis, 'rows', '훑어 세도 값이 온 곳은 행이다')
|
|
138
|
+
})
|
|
139
|
+
|
|
123
140
|
test('모르는 축은 거절한다 — 빈 목록은 "없다" 로 읽힌다', async () => {
|
|
124
141
|
await assert.rejects(() => q.twinModelEntities(INSTANCE, 'nosuch', ctx), /unknown concept axis/)
|
|
125
142
|
await assert.rejects(() => q.twinModelItem(INSTANCE, 'nosuch', 'x', ctx), /unknown concept axis/)
|