@things-factory/figure-service 10.1.75 → 10.1.79

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.
Files changed (185) hide show
  1. package/README.md +1 -1
  2. package/dist-server/service/figure/figure-format.d.ts +6 -6
  3. package/dist-server/service/figure/figure-format.js +32 -32
  4. package/dist-server/service/figure/figure-format.js.map +1 -1
  5. package/dist-server/service/figure/figure-history.js +1 -1
  6. package/dist-server/service/figure/figure-history.js.map +1 -1
  7. package/dist-server/service/figure/figure-mutation.d.ts +1 -1
  8. package/dist-server/service/figure/figure-mutation.js +12 -12
  9. package/dist-server/service/figure/figure-mutation.js.map +1 -1
  10. package/dist-server/service/figure/{figure-propose-v3.d.ts → figure-propose-steps.d.ts} +16 -16
  11. package/dist-server/service/figure/{figure-propose-v3.js → figure-propose-steps.js} +27 -27
  12. package/dist-server/service/figure/figure-propose-steps.js.map +1 -0
  13. package/dist-server/service/figure/figure-propose-type.js +3 -3
  14. package/dist-server/service/figure/figure-propose-type.js.map +1 -1
  15. package/dist-server/service/figure/figure-query.d.ts +2 -2
  16. package/dist-server/service/figure/figure-query.js +5 -5
  17. package/dist-server/service/figure/figure-query.js.map +1 -1
  18. package/dist-server/service/figure/figure-tools.js +14 -14
  19. package/dist-server/service/figure/figure-tools.js.map +1 -1
  20. package/dist-server/service/figure/figure-type.js +2 -2
  21. package/dist-server/service/figure/figure-type.js.map +1 -1
  22. package/dist-server/service/figure/{figure-v3-write.d.ts → figure-write.d.ts} +3 -3
  23. package/dist-server/service/figure/{figure-v3-write.js → figure-write.js} +10 -10
  24. package/dist-server/service/figure/figure-write.js.map +1 -0
  25. package/dist-server/service/figure/figure.d.ts +2 -2
  26. package/dist-server/service/figure/figure.js +2 -2
  27. package/dist-server/service/figure/figure.js.map +1 -1
  28. package/dist-server/service/figure-recipe/figure-recipe-templates.d.ts +2 -2
  29. package/dist-server/service/figure-recipe/figure-recipe-templates.js +1 -1
  30. package/dist-server/service/figure-recipe/figure-recipe-templates.js.map +1 -1
  31. package/dist-server/service/figure-recipe/figure-recipe-type.js +1 -1
  32. package/dist-server/service/figure-recipe/figure-recipe-type.js.map +1 -1
  33. package/dist-server/tsconfig.tsbuildinfo +1 -1
  34. package/package.json +5 -5
  35. package/recipes/_lib/model.mts +19 -19
  36. package/recipes/air-cylinder/models/default.json +1 -1
  37. package/recipes/air-shower/models/default.json +1 -1
  38. package/recipes/barrier-gate/models/default.json +1 -1
  39. package/recipes/base-frame/models/default.json +1 -1
  40. package/recipes/belt-surface/models/default.json +1 -1
  41. package/recipes/bin-row/models/default.json +1 -1
  42. package/recipes/bollard-row/models/default.json +1 -1
  43. package/recipes/buzzer/models/default.json +1 -1
  44. package/recipes/cabinet-doors/models/default.json +1 -1
  45. package/recipes/cabinet-doors/models/doors=1.json +1 -1
  46. package/recipes/cabinet-doors/models/doors=2.json +1 -1
  47. package/recipes/cable-tray/models/default.json +1 -1
  48. package/recipes/camera-mount/models/default.json +1 -1
  49. package/recipes/canopy/models/default.json +1 -1
  50. package/recipes/cart/models/default.json +1 -1
  51. package/recipes/cart-handle/models/default.json +1 -1
  52. package/recipes/casters-four/models/default.json +1 -1
  53. package/recipes/ceiling-rail-carriage/models/default.json +1 -1
  54. package/recipes/chain-rails/models/default.json +1 -1
  55. package/recipes/charging-station/models/default.json +1 -1
  56. package/recipes/column-row/models/default.json +1 -1
  57. package/recipes/control-cabinet/models/default.json +1 -1
  58. package/recipes/conveyor-skirts/models/default.json +1 -1
  59. package/recipes/counter-display/models/default.json +1 -1
  60. package/recipes/dock-bumpers/models/default.json +1 -1
  61. package/recipes/dock-leveler/models/default.json +1 -1
  62. package/recipes/double-hinged-door/models/default.json +1 -1
  63. package/recipes/drawer-stack/models/default.json +1 -1
  64. package/recipes/drawer-stack/models/drawers=2.json +1 -1
  65. package/recipes/drawer-stack/models/drawers=3.json +1 -1
  66. package/recipes/drawer-stack/models/drawers=4.json +1 -1
  67. package/recipes/drip-tray/models/default.json +1 -1
  68. package/recipes/duct-fan/models/default.json +1 -1
  69. package/recipes/emergency-stop/models/default.json +1 -1
  70. package/recipes/end-covers/models/default.json +1 -1
  71. package/recipes/ffu-row/models/default.json +1 -1
  72. package/recipes/fire-extinguisher/models/default.json +1 -1
  73. package/recipes/first-aid-box/models/default.json +1 -1
  74. package/recipes/floor-zone/models/default.json +1 -1
  75. package/recipes/folding-door/models/default.json +1 -1
  76. package/recipes/fork/models/default.json +1 -1
  77. package/recipes/gantry-frame/models/default.json +1 -1
  78. package/recipes/gripper-2f/models/default.json +1 -1
  79. package/recipes/guard-cover/models/default.json +1 -1
  80. package/recipes/handrail/models/default.json +1 -1
  81. package/recipes/hatch/models/default.json +1 -1
  82. package/recipes/hinged-door/models/default.json +1 -1
  83. package/recipes/hinged-door/models/hinge=left.json +1 -1
  84. package/recipes/hinged-door/models/hinge=right.json +1 -1
  85. package/recipes/hmi-monitor/models/default.json +1 -1
  86. package/recipes/hopper/models/default.json +1 -1
  87. package/recipes/interlock-switch/models/default.json +1 -1
  88. package/recipes/junction-box/models/default.json +1 -1
  89. package/recipes/ladder/models/default.json +1 -1
  90. package/recipes/legs-four/models/default.json +1 -1
  91. package/recipes/leveling-feet/models/default.json +1 -1
  92. package/recipes/lift-updown/models/default.json +1 -1
  93. package/recipes/light-curtain/models/default.json +1 -1
  94. package/recipes/limit-switch/models/default.json +1 -1
  95. package/recipes/linear-guide/models/default.json +1 -1
  96. package/recipes/locker-row/models/default.json +1 -1
  97. package/recipes/motor-box/models/default.json +1 -1
  98. package/recipes/observation-window/models/default.json +1 -1
  99. package/recipes/operator-panel/models/buttons=2.json +1 -1
  100. package/recipes/operator-panel/models/buttons=3.json +1 -1
  101. package/recipes/operator-panel/models/buttons=4.json +1 -1
  102. package/recipes/operator-panel/models/buttons=5.json +1 -1
  103. package/recipes/operator-panel/models/default.json +1 -1
  104. package/recipes/pallet/models/default.json +1 -1
  105. package/recipes/partition-wall/models/default.json +1 -1
  106. package/recipes/pass-box/models/default.json +1 -1
  107. package/recipes/peg-board/models/default.json +1 -1
  108. package/recipes/photo-sensor-pair/models/default.json +1 -1
  109. package/recipes/pipe-run/models/default.json +1 -1
  110. package/recipes/platform/models/default.json +1 -1
  111. package/recipes/power-box/models/default.json +1 -1
  112. package/recipes/pressure-gauge/models/default.json +1 -1
  113. package/recipes/pull-cord/models/default.json +1 -1
  114. package/recipes/pump/models/default.json +1 -1
  115. package/recipes/pusher/models/default.json +1 -1
  116. package/recipes/robot-plinth/models/default.json +1 -1
  117. package/recipes/roll-up-shutter/models/default.json +1 -1
  118. package/recipes/roller-row/models/default.json +1 -1
  119. package/recipes/room-shell/models/default.json +1 -1
  120. package/recipes/rotating-beacon/models/default.json +1 -1
  121. package/recipes/safety-fence/models/default.json +1 -1
  122. package/recipes/safety-mat/models/default.json +1 -1
  123. package/recipes/safety-sign-stand/models/default.json +1 -1
  124. package/recipes/scanner-mount/models/default.json +1 -1
  125. package/recipes/selector-switch/models/default.json +1 -1
  126. package/recipes/shelf-tiers/models/default.json +1 -1
  127. package/recipes/shelf-tiers/models/tiers=2.json +1 -1
  128. package/recipes/shelf-tiers/models/tiers=3.json +1 -1
  129. package/recipes/shelf-tiers/models/tiers=4.json +1 -1
  130. package/recipes/shelf-tiers/models/tiers=5.json +1 -1
  131. package/recipes/side-guides/models/default.json +1 -1
  132. package/recipes/sign-board/models/default.json +1 -1
  133. package/recipes/signal-tower-3/models/default.json +1 -1
  134. package/recipes/sliding-door/models/default.json +1 -1
  135. package/recipes/sliding-door/models/leaves=1,lamp=false.json +1 -1
  136. package/recipes/sliding-door/models/leaves=1,lamp=true.json +1 -1
  137. package/recipes/sliding-door/models/leaves=2,lamp=false.json +1 -1
  138. package/recipes/sliding-door/models/leaves=2,lamp=true.json +1 -1
  139. package/recipes/stairs/models/default.json +1 -1
  140. package/recipes/status-lamp/models/default.json +1 -1
  141. package/recipes/stopper/models/default.json +1 -1
  142. package/recipes/suction-pads/models/default.json +1 -1
  143. package/recipes/tank/models/default.json +1 -1
  144. package/recipes/telescopic-arm/models/default.json +1 -1
  145. package/recipes/toe-board/models/default.json +1 -1
  146. package/recipes/tote/models/default.json +1 -1
  147. package/recipes/traffic-cones/models/default.json +1 -1
  148. package/recipes/transfer-belts/models/default.json +1 -1
  149. package/recipes/tray-stack/models/default.json +1 -1
  150. package/recipes/tray-stack/models/trays=3.json +1 -1
  151. package/recipes/tray-stack/models/trays=4.json +1 -1
  152. package/recipes/tray-stack/models/trays=5.json +1 -1
  153. package/recipes/trough/models/default.json +1 -1
  154. package/recipes/turntable/models/default.json +1 -1
  155. package/recipes/valve-handle/models/default.json +1 -1
  156. package/recipes/waste-bin/models/default.json +1 -1
  157. package/recipes/work-light/models/default.json +1 -1
  158. package/recipes/workbench-top/models/default.json +1 -1
  159. package/scripts/build-templates.mts +7 -7
  160. package/scripts/templates.test.mts +16 -16
  161. package/server/service/figure/figure-blueprint-query.test.ts +3 -3
  162. package/server/service/figure/figure-format.test.ts +57 -57
  163. package/server/service/figure/figure-format.ts +39 -39
  164. package/server/service/figure/figure-history.ts +1 -1
  165. package/server/service/figure/figure-inspect-db.test.ts +14 -14
  166. package/server/service/figure/figure-llm-smoke.test.ts +7 -7
  167. package/server/service/figure/figure-mutation.ts +12 -12
  168. package/server/service/figure/{figure-propose-v3.test.ts → figure-propose-steps.test.ts} +18 -18
  169. package/server/service/figure/{figure-propose-v3.ts → figure-propose-steps.ts} +33 -33
  170. package/server/service/figure/figure-propose-type.ts +3 -3
  171. package/server/service/figure/figure-query.ts +5 -5
  172. package/server/service/figure/figure-rename-db.test.ts +5 -5
  173. package/server/service/figure/figure-rename.test.ts +7 -7
  174. package/server/service/figure/figure-tools.test.ts +22 -22
  175. package/server/service/figure/figure-tools.ts +14 -14
  176. package/server/service/figure/figure-type.ts +2 -2
  177. package/server/service/figure/{figure-v3-write.ts → figure-write.ts} +10 -10
  178. package/server/service/figure/figure.ts +3 -3
  179. package/server/service/figure-recipe/figure-recipe-templates.test.ts +2 -2
  180. package/server/service/figure-recipe/figure-recipe-templates.ts +4 -4
  181. package/server/service/figure-recipe/figure-recipe-type.ts +1 -1
  182. package/translations/en.json +2 -3
  183. package/translations/ko.json +2 -3
  184. package/dist-server/service/figure/figure-propose-v3.js.map +0 -1
  185. package/dist-server/service/figure/figure-v3-write.js.map +0 -1
@@ -6,24 +6,24 @@
6
6
  * - a source that does not name the format is refused -- what cannot be identified is not stored;
7
7
  * - the budget columns (`triangles`, `groups`) and the score are the kernel's.
8
8
  */
9
- import { createV3Asset, addV3Box, addV3Primitive, serializeV3Asset, applyV3Authoring, proposeV3Occupancy, placeholderType, v3ScoreOf } from '@hatiolab/figure-model'
10
- import type { V3Asset } from '@hatiolab/figure-model'
9
+ import { createFigureAsset, addFigureBox, addFigurePrimitive, serializeFigureAsset, applyFigureAuthoring, proposeFigureOccupancy, placeholderType, figureScoreOf } from '@hatiolab/figure-model'
10
+ import type { FigureAsset } from '@hatiolab/figure-model'
11
11
 
12
12
  import { FigureSourceError, consumablePayloadOf, inspectSource, parseSource } from './figure-format'
13
13
 
14
- /** 온전한 V3 자산 하나: 바닥에 선 상자와, 부품에서 제안받아 확정한 점유 부피. */
15
- function v3Asset(): V3Asset {
16
- let asset = createV3Asset('Mixer')
17
- asset = addV3Box(asset, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
18
- asset = applyV3Authoring(asset, { kind: 'attach', part: 'body', face: 'bottom', to: { plane: 'mounting-plane' } })
19
- asset = applyV3Authoring(asset, {
14
+ /** 온전한 자산 하나: 바닥에 선 상자와, 부품에서 제안받아 확정한 점유 부피. */
15
+ function figureAsset(): FigureAsset {
16
+ let asset = createFigureAsset('Mixer')
17
+ asset = addFigureBox(asset, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
18
+ asset = applyFigureAuthoring(asset, { kind: 'attach', part: 'body', face: 'bottom', to: { plane: 'mounting-plane' } })
19
+ asset = applyFigureAuthoring(asset, {
20
20
  kind: 'declare-occupancy',
21
- proposal: proposeV3Occupancy(asset, { over: 'range' }),
21
+ proposal: proposeFigureOccupancy(asset, { over: 'range' }),
22
22
  placement: 'floor'
23
23
  })
24
24
  return asset
25
25
  }
26
- const v3Source = () => serializeV3Asset(v3Asset())
26
+ const figureSource = () => serializeFigureAsset(figureAsset())
27
27
 
28
28
  describe('figure-format — 판을 알아보는 문', () => {
29
29
  describe('판은 자산이 적는다', () => {
@@ -49,50 +49,50 @@ describe('figure-format — 판을 알아보는 문', () => {
49
49
 
50
50
  describe('저장', () => {
51
51
  it('예산 칸을 커널이 잰다 — 상자 하나는 삼각형 12, 묶음 1', () => {
52
- const parsed = parseSource(v3Source())
52
+ const parsed = parseSource(figureSource())
53
53
  expect(parsed.derived.triangles).toBe(12)
54
54
  expect(parsed.derived.groups).toBe(1)
55
55
  })
56
56
 
57
- it('점수 칸은 v3ScoreOf 다 — 저작면의 성숙도 탭과 같은 수(수석 아키텍트 판정 09-24)', () => {
58
- const score = parseSource(v3Source()).derived.score
59
- expect(score).toBe(v3ScoreOf(v3Asset())!.score)
57
+ it('점수 칸은 figureScoreOf 다 — 저작면의 성숙도 탭과 같은 수(수석 아키텍트 판정 09-24)', () => {
58
+ const score = parseSource(figureSource()).derived.score
59
+ expect(score).toBe(figureScoreOf(figureAsset())!.score)
60
60
  expect(score).toBeGreaterThan(0)
61
61
  })
62
62
 
63
63
  it('형식을 어긴 자산은 거절한다', () => {
64
- const broken = JSON.parse(v3Source())
64
+ const broken = JSON.parse(figureSource())
65
65
  broken.appearance = []
66
66
  expect(() => parseSource(JSON.stringify(broken))).toThrow(FigureSourceError)
67
67
  })
68
68
  })
69
69
 
70
70
  describe('발행 판정', () => {
71
- it('제 부피 안에 든 V3 자산은 막지 않는다', () => {
72
- const inspection = inspectSource(v3Source())
71
+ it('제 부피 안에 든 자산은 막지 않는다', () => {
72
+ const inspection = inspectSource(figureSource())
73
73
  expect(inspection.blocked).toBe(false)
74
74
  })
75
75
 
76
76
  it('선언한 부피 밖으로 나간 부품은 이름과 함께 막는다', () => {
77
- let asset = v3Asset()
78
- asset = addV3Box(asset, { id: 'arm', size: { x: 100, y: 100, z: 100 }, position: { x: 900, y: 50, z: 0 } })
79
- const inspection = inspectSource(serializeV3Asset(asset))
77
+ let asset = figureAsset()
78
+ asset = addFigureBox(asset, { id: 'arm', size: { x: 100, y: 100, z: 100 }, position: { x: 900, y: 50, z: 0 } })
79
+ const inspection = inspectSource(serializeFigureAsset(asset))
80
80
  expect(inspection.blocked).toBe(true)
81
81
  expect(inspection.findings.some(f => f.code === 'part-outside-occupancy' && f.at === 'arm')).toBe(true)
82
82
  })
83
83
 
84
- it('부피를 선언하지 않은 V3 자산은 막는다', () => {
85
- let asset = createV3Asset('Bare')
86
- asset = addV3Box(asset, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
87
- const inspection = inspectSource(serializeV3Asset(asset))
84
+ it('부피를 선언하지 않은 자산은 막는다', () => {
85
+ let asset = createFigureAsset('Bare')
86
+ asset = addFigureBox(asset, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
87
+ const inspection = inspectSource(serializeFigureAsset(asset))
88
88
  expect(inspection.blocked).toBe(true)
89
89
  expect(inspection.findings.some(f => f.code === 'occupancy-undeclared')).toBe(true)
90
90
  })
91
91
 
92
92
  it('전 범위를 증명하지 못한 판정은 막지 않고 그 사실을 말한다', () => {
93
93
  /* 상태에 따라 도는 부품은 모서리만으로 증명되지 않는다 — 표본 검사였음을 발행 화면이 읽는다. */
94
- let asset = v3Asset()
95
- asset = addV3Primitive(asset, { id: 'wheel', kind: 'cylinder', dimensions: { radius: 20, length: 20 }, position: { x: 0, y: 20, z: 0 } })
94
+ let asset = figureAsset()
95
+ asset = addFigurePrimitive(asset, { id: 'wheel', kind: 'cylinder', dimensions: { radius: 20, length: 20 }, position: { x: 0, y: 20, z: 0 } })
96
96
  const m = asset.document.model as any
97
97
  m.inputs.push({ id: 'spin', unit: 'deg', min: -180, max: 180, role: 'state' })
98
98
  asset.stateDefaults['spin'] = 0
@@ -108,13 +108,13 @@ describe('figure-format — 판을 알아보는 문', () => {
108
108
  pose.params = { ...pose.params, to: 'wheel.spun' }
109
109
  m.nodes.push({ id: 'wheel.world', op: 'compose@1', args: ['wheel.turn.p', pose.outputs.pose], outputs: { pose: 'wheel.wp' } })
110
110
  m.nodes.find((n: any) => n.id === 'wheel').args = [m.nodes.find((n: any) => n.id === 'wheel.shape').outputs.shape, 'wheel.wp']
111
- asset = applyV3Authoring(asset, {
111
+ asset = applyFigureAuthoring(asset, {
112
112
  kind: 'declare-occupancy',
113
- proposal: proposeV3Occupancy(asset, { over: 'range' }),
113
+ proposal: proposeFigureOccupancy(asset, { over: 'range' }),
114
114
  placement: 'floor'
115
115
  })
116
116
 
117
- const inspection = inspectSource(serializeV3Asset(asset))
117
+ const inspection = inspectSource(serializeFigureAsset(asset))
118
118
  const sampled = inspection.findings.find(f => f.code === 'state-range-sampled')
119
119
  expect(sampled).toBeDefined()
120
120
  expect(sampled!.blocking).toBe(false)
@@ -127,16 +127,16 @@ describe('figure-format — 판을 알아보는 문', () => {
127
127
  expect(inspectSource(null).findings[0].code).toBe('no-source')
128
128
  })
129
129
 
130
- it('바뀐 것 없는 V3 재발행은 아무 말도 없이 지난다', () => {
131
- const again = inspectSource(v3Source(), v3Source())
130
+ it('바뀐 것 없는 재발행은 아무 말도 없이 지난다', () => {
131
+ const again = inspectSource(figureSource(), figureSource())
132
132
  expect(again.blocked).toBe(false)
133
133
  expect(again.findings.filter(f => f.code !== 'state-range-sampled')).toEqual([])
134
134
  })
135
135
 
136
- it('놓인 인스턴스가 기대던 것을 없앤 V3 재발행은 막는다 — 없어진 능력을 이름으로 댄다', () => {
137
- const before = v3Asset()
136
+ it('놓인 인스턴스가 기대던 것을 없앤 재발행은 막는다 — 없어진 능력을 이름으로 댄다', () => {
137
+ const before = figureAsset()
138
138
  before.document.capabilities.names = ['Holdable']
139
- const inspection = inspectSource(v3Source(), serializeV3Asset(before))
139
+ const inspection = inspectSource(figureSource(), serializeFigureAsset(before))
140
140
  const gone = inspection.findings.find(f => f.code === 'capability-gone')
141
141
  expect(gone).toBeDefined()
142
142
  expect(gone!.blocking).toBe(true)
@@ -151,7 +151,7 @@ describe('figure-format — 판을 알아보는 문', () => {
151
151
  이 판정은 한때 발행 패널에 영어 문장(`detail`)을 그대로 냈고 왜 · 어떻게가 없었다 — 사용자가 2026-09-19 에
152
152
  「가장 큰 문제」로 짚은 모양이다. 문장은 요청의 언어를 아는 서버가 코드와 값에서 만든다(ADR-0054).
153
153
  */
154
- describe('V3 판정의 문장 — 발행 패널의 말로', () => {
154
+ describe('판정의 문장 — 발행 패널의 말로', () => {
155
155
  const KO = require('../../../translations/ko.json') as Record<string, string>
156
156
  const EN = require('../../../translations/en.json') as Record<string, string>
157
157
 
@@ -164,14 +164,14 @@ describe('figure-format — 판을 알아보는 문', () => {
164
164
  }
165
165
 
166
166
  const outside = () => {
167
- let asset = v3Asset()
168
- asset = addV3Box(asset, { id: 'arm', size: { x: 100, y: 100, z: 100 }, position: { x: 900, y: 50, z: 0 } })
169
- return serializeV3Asset(asset)
167
+ let asset = figureAsset()
168
+ asset = addFigureBox(asset, { id: 'arm', size: { x: 100, y: 100, z: 100 }, position: { x: 900, y: 50, z: 0 } })
169
+ return serializeFigureAsset(asset)
170
170
  }
171
171
  const bare = () => {
172
- let asset = createV3Asset('Bare')
173
- asset = addV3Box(asset, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
174
- return serializeV3Asset(asset)
172
+ let asset = createFigureAsset('Bare')
173
+ asset = addFigureBox(asset, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
174
+ return serializeFigureAsset(asset)
175
175
  }
176
176
 
177
177
  it('넘은 부품은 이름 · 축 · 넘은 양을 화면 말투로 말하고, 왜와 어떻게를 함께 낸다', () => {
@@ -193,7 +193,7 @@ describe('figure-format — 판을 알아보는 문', () => {
193
193
  })
194
194
 
195
195
  it('부품이 없는 새 도형에는 도형을 먼저 추가하라고 한다 — 화면은 그때 맞추기 단추를 내지 않는다', () => {
196
- const empty = serializeV3Asset(createV3Asset('Empty'))
196
+ const empty = serializeFigureAsset(createFigureAsset('Empty'))
197
197
  const finding = inspectSource(empty, null, translatorOf(KO)).findings.find(f => f.code === 'occupancy-undeclared')!
198
198
 
199
199
  expect(finding.how).toBe('먼저 도형을 추가한 뒤, 「부품에 맞추기」로 공간을 선언하세요.')
@@ -205,16 +205,16 @@ describe('figure-format — 판을 알아보는 문', () => {
205
205
  여기서는 손으로 만든다: 램프의 높이가 도형 높이(size.y)를 따라 올라가고, 몸통은 그대로다.
206
206
  */
207
207
  const splitting = () => {
208
- let asset = createV3Asset('Lamp post')
209
- asset = addV3Box(asset, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
210
- asset = addV3Box(asset, { id: 'lamp', size: { x: 40, y: 20, z: 40 }, position: { x: 0, y: 10, z: 0 } })
208
+ let asset = createFigureAsset('Lamp post')
209
+ asset = addFigureBox(asset, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
210
+ asset = addFigureBox(asset, { id: 'lamp', size: { x: 40, y: 20, z: 40 }, position: { x: 0, y: 10, z: 0 } })
211
211
  const model = asset.document.model
212
212
  model.inputs.push({ id: 'size.y', unit: 'mm', min: 50, max: 20000, role: 'design' } as never)
213
213
  asset.designInputs['size.y'] = 100
214
214
  model.nodes.push({ id: 'lamp.lift', op: 'add@1', args: ['size.y', 'lamp.position.y'], outputs: { value: 'lamp.lift.value' } } as never)
215
215
  const pose = model.nodes.find(n => n.id === 'lamp.pose')!
216
216
  pose.args[1] = 'lamp.lift.value'
217
- return serializeV3Asset(asset)
217
+ return serializeFigureAsset(asset)
218
218
  }
219
219
 
220
220
  it('크기를 바꾸면 떨어지는 부품을 막고, 어느 부품이 어느 축에서인지 · 왜 · 어떻게를 말한다', () => {
@@ -230,14 +230,14 @@ describe('figure-format — 판을 알아보는 문', () => {
230
230
 
231
231
  /* 한 비율로 커진다고 선언한 도형에서 제 숫자를 가진 부품(수석 아키텍트 판정 2026-09-24 조건 ②). */
232
232
  it('한 비율로 커진다고 선언한 도형에서 따라 커지지 않는 부품을 막고 이름을 댄다', () => {
233
- let asset = createV3Asset('Uniform')
234
- asset = addV3Box(asset, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
233
+ let asset = createFigureAsset('Uniform')
234
+ asset = addFigureBox(asset, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
235
235
  for (const axis of ['x', 'y', 'z']) {
236
236
  asset.document.model.inputs.push({ id: `size.${axis}`, unit: 'mm', min: 1, max: 20000, role: 'design' } as never)
237
237
  asset.designInputs[`size.${axis}`] = 100
238
238
  }
239
- asset = applyV3Authoring(asset, { kind: 'scale-uniformly' } as never)
240
- const finding = inspectSource(serializeV3Asset(asset), null, translatorOf(KO)).findings.find(f => f.code === 'sizing-not-uniform')
239
+ asset = applyFigureAuthoring(asset, { kind: 'scale-uniformly' } as never)
240
+ const finding = inspectSource(serializeFigureAsset(asset), null, translatorOf(KO)).findings.find(f => f.code === 'sizing-not-uniform')
241
241
 
242
242
  expect(finding?.blocking).toBe(true)
243
243
  expect(finding!.message).toBe("이 도형은 한 비율로 커지는데, 크기를 X 로 2배로 하면 'body' 부품은 같이 커지지 않습니다.")
@@ -283,13 +283,13 @@ describe('figure-format — 판을 알아보는 문', () => {
283
283
  it('임시 이름인 도형은 발행을 막는다 — 이름은 행에 있다', () => {
284
284
  /* The name a new figure gets — the format's rule, the one the gate recognises. */
285
285
  const placeholder = placeholderType()
286
- const finding = inspectSource(v3Source(), null, translatorOf(KO), placeholder).findings.find(f => f.code === 'type-is-placeholder')
286
+ const finding = inspectSource(figureSource(), null, translatorOf(KO), placeholder).findings.find(f => f.code === 'type-is-placeholder')
287
287
 
288
288
  expect(finding?.blocking).toBe(true)
289
289
  expect(finding!.message).toBe(`'${placeholder}' 타입은 처음 저장할 때 붙은 임시 타입입니다.`)
290
290
  /* 화면의 칸 이름은 「타입」이다(`figure.label.figure-type`). */
291
291
  expect(finding!.how).toContain('타입 칸')
292
- expect(inspectSource(v3Source(), null, translatorOf(KO), 'ROLLER_CONVEYOR').blocked).toBe(false)
292
+ expect(inspectSource(figureSource(), null, translatorOf(KO), 'ROLLER_CONVEYOR').blocked).toBe(false)
293
293
  })
294
294
 
295
295
  /* 코드가 묻는 키가 두 언어에 다 있어야 한다 — 없으면 그 언어의 화면에 영어가 섞인다. */
@@ -298,8 +298,8 @@ describe('figure-format — 판을 알아보는 문', () => {
298
298
  const t = translatorOf(KO, asked)
299
299
  inspectSource(outside(), null, t)
300
300
  inspectSource(bare(), null, t)
301
- inspectSource(v3Source(), v3Source(), t)
302
- inspectSource(v3Source(), null, t, 'FIGURE_ABCDEF12')
301
+ inspectSource(figureSource(), figureSource(), t)
302
+ inspectSource(figureSource(), null, t, 'FIGURE_ABCDEF12')
303
303
 
304
304
  const keys = [...new Set(asked)]
305
305
  expect(keys.length).toBeGreaterThan(5)
@@ -310,12 +310,12 @@ describe('figure-format — 판을 알아보는 문', () => {
310
310
 
311
311
  describe('소비처', () => {
312
312
  it('자산을 그대로 받는다 — 그리는 쪽이 커널로 컴파일한다', () => {
313
- const payload = consumablePayloadOf(v3Source())
314
- expect(payload.asset.version).toBe('figure-v3-asset-1')
313
+ const payload = consumablePayloadOf(figureSource())
314
+ expect(payload.asset.version).toBe('figure-asset-1')
315
315
  })
316
316
 
317
317
  it('컴파일되지 않는 자산은 소비처에 넘기지 않고 던진다', () => {
318
- const broken = JSON.parse(v3Source())
318
+ const broken = JSON.parse(figureSource())
319
319
  broken.designInputs = {}
320
320
  expect(() => consumablePayloadOf(JSON.stringify(broken))).toThrow()
321
321
  })
@@ -1,15 +1,15 @@
1
1
  /*
2
2
  * The doors a stored source goes through: saving (`parseSource`), publishing (`inspectSource`) and handing it to a
3
- * board (`consumablePayloadOf`). The format is `figure-v3-asset-1`, and the asset says so in its `version`; a source
3
+ * board (`consumablePayloadOf`). The format is `figure-asset-1`, and the asset says so in its `version`; a source
4
4
  * that does not is refused by name, because a source the server cannot identify is one it must not save.
5
5
  */
6
- import { compileV3Asset, inspectV3Asset, isPlaceholderType, parseV3Asset, serializeV3Asset, v3ContractChanges, v3CostOf, v3ScoreOf } from '@hatiolab/figure-model'
7
- import type { V3Asset, V3ContractChange, V3ContractError, V3Violation } from '@hatiolab/figure-model'
6
+ import { compileFigureAsset, inspectFigureAsset, isPlaceholderType, parseFigureAsset, serializeFigureAsset, figureContractChanges, figureCostOf, figureScoreOf } from '@hatiolab/figure-model'
7
+ import type { FigureAsset, FigureContractChange, FigureContractError, FigureViolation } from '@hatiolab/figure-model'
8
8
 
9
9
  import type { FigureFinding } from './figure-type.js'
10
10
 
11
11
  /** The version the format stamps on its own source. */
12
- export const V3_ASSET_VERSION = 'figure-v3-asset-1'
12
+ export const FIGURE_ASSET_VERSION = 'figure-asset-1'
13
13
 
14
14
  /** A source that passed the save door: what is stored, and the numbers the catalogue shows, computed here. */
15
15
  export interface ParsedSource {
@@ -54,7 +54,7 @@ export interface Inspection {
54
54
  /**
55
55
  * That the source is JSON and names the format.
56
56
  *
57
- * @throws {FigureSourceError} when the text is not JSON, or does not carry `version: 'figure-v3-asset-1'`.
57
+ * @throws {FigureSourceError} when the text is not JSON, or does not carry `version: 'figure-asset-1'`.
58
58
  */
59
59
  function assertFormat(sourceJson: string): void {
60
60
  let parsed: unknown
@@ -65,14 +65,14 @@ function assertFormat(sourceJson: string): void {
65
65
  { code: 'source-not-json', message: `정본이 JSON 이 아니다: ${(e as Error).message}`, at: 'source' }
66
66
  ])
67
67
  }
68
- if ((parsed as { version?: unknown } | null)?.version !== V3_ASSET_VERSION)
68
+ if ((parsed as { version?: unknown } | null)?.version !== FIGURE_ASSET_VERSION)
69
69
  throw new FigureSourceError([
70
- { code: 'source-format-unknown', message: `정본의 판을 알 수 없다 — 도형 자산은 version '${V3_ASSET_VERSION}' 을 적는다`, at: 'version' }
70
+ { code: 'source-format-unknown', message: `정본의 판을 알 수 없다 — 도형 자산은 version '${FIGURE_ASSET_VERSION}' 을 적는다`, at: 'version' }
71
71
  ])
72
72
  }
73
73
 
74
74
  /**
75
- * Validates a source and computes what the catalogue shows: the cost from `v3CostOf`, and the score from `v3ScoreOf`
75
+ * Validates a source and computes what the catalogue shows: the cost from `figureCostOf`, and the score from `figureScoreOf`
76
76
  * -- the headroom against placing `LIMITS.screenInstances` of it on one screen (chief architect, 2026-09-24), the
77
77
  * same number the modeller's maturity tab shows. An asset that draws nothing has no score, not a full one.
78
78
  *
@@ -82,18 +82,18 @@ export function parseSource(sourceJson: string): ParsedSource {
82
82
  assertFormat(sourceJson)
83
83
  let asset
84
84
  try {
85
- asset = parseV3Asset(sourceJson)
86
- compileV3Asset(asset)
85
+ asset = parseFigureAsset(sourceJson)
86
+ compileFigureAsset(asset)
87
87
  } catch (e) {
88
- const error = e as V3ContractError & { path?: string }
88
+ const error = e as FigureContractError & { path?: string }
89
89
  throw new FigureSourceError([
90
- { code: error.code ?? 'v3-asset-invalid', message: error.message ?? String(e), at: error.path }
90
+ { code: error.code ?? 'asset-invalid', message: error.message ?? String(e), at: error.path }
91
91
  ])
92
92
  }
93
- const cost = v3CostOf(asset)
94
- const score = v3ScoreOf(asset)
93
+ const cost = figureCostOf(asset)
94
+ const score = figureScoreOf(asset)
95
95
  return {
96
- source: serializeV3Asset(asset),
96
+ source: serializeFigureAsset(asset),
97
97
  derived: { score: (score?.score ?? null) as unknown as number, triangles: cost.triangles, groups: cost.groups },
98
98
  violations: []
99
99
  }
@@ -102,7 +102,7 @@ export function parseSource(sourceJson: string): ParsedSource {
102
102
  /**
103
103
  * Whether this source may be published, and why not.
104
104
  *
105
- * The judgement is `inspectV3Asset`: every part inside the volume the asset declares, over the whole
105
+ * The judgement is `inspectFigureAsset`: every part inside the volume the asset declares, over the whole
106
106
  * state range, and the face it says it is mounted on lying in that plane. A verdict reached by sampling is
107
107
  * reported as a finding of its own rather than hidden — an author releasing a figure the gate could only
108
108
  * sample should be told so, because nothing proved it holds between the states that were checked.
@@ -111,7 +111,7 @@ export function parseSource(sourceJson: string): ParsedSource {
111
111
  export type Translate = (key: string, options?: Record<string, unknown>) => string
112
112
 
113
113
  /**
114
- * A V3 finding in the words of the release panel.
114
+ * A finding in the words of the release panel.
115
115
  *
116
116
  * The gate says what it measured as a code and values (`part`, `axis`, `mm`, `degrees`) and an English `detail`
117
117
  * for logs. The sentence an author reads is made here, where the request's language is known (ADR-0054) — not in
@@ -122,7 +122,7 @@ export type Translate = (key: string, options?: Record<string, unknown>) => stri
122
122
  * The words are the modeller's: 「공간 선언」 for the volume a figure takes up and 「장착면」 for the face it stands on
123
123
  * (`figure.text.mounting-face-explained`). A fact, then the way out.
124
124
  */
125
- function v3Wording(violation: V3Violation, say: Say, hasParts: boolean): Pick<GateFinding, 'message' | 'why' | 'how'> {
125
+ function figureWording(violation: FigureViolation, say: Say, hasParts: boolean): Pick<GateFinding, 'message' | 'why' | 'how'> {
126
126
  const part = violation.part ?? ''
127
127
  const axis = (violation.axis ?? '').toUpperCase()
128
128
  const mm = violation.mm ?? 0
@@ -227,25 +227,25 @@ export function inspectSource(
227
227
  }
228
228
 
229
229
  let inspection
230
- let asset: V3Asset | undefined
230
+ let asset: FigureAsset | undefined
231
231
  let hasParts = true
232
232
  try {
233
- asset = parseV3Asset(sourceJson)
234
- inspection = inspectV3Asset(asset)
235
- const cost = v3CostOf(asset)
233
+ asset = parseFigureAsset(sourceJson)
234
+ inspection = inspectFigureAsset(asset)
235
+ const cost = figureCostOf(asset)
236
236
  hasParts = cost.groups + cost.hidden > 0
237
237
  } catch (e) {
238
- const error = e as V3ContractError & { path?: string }
238
+ const error = e as FigureContractError & { path?: string }
239
239
  return {
240
240
  blocked: true,
241
- findings: [{ code: error.code ?? 'v3-asset-invalid', message: error.message ?? String(e), at: error.path, blocking: true }]
241
+ findings: [{ code: error.code ?? 'asset-invalid', message: error.message ?? String(e), at: error.path, blocking: true }]
242
242
  }
243
243
  }
244
244
 
245
245
  const say = sayerOf(t)
246
246
  const findings: GateFinding[] = inspection.violations.map(violation => ({
247
247
  code: violation.code.toLowerCase().replace(/_/g, '-'),
248
- ...v3Wording(violation, say, hasParts),
248
+ ...figureWording(violation, say, hasParts),
249
249
  at: violation.part,
250
250
  blocking: true
251
251
  }))
@@ -279,41 +279,41 @@ export function inspectSource(
279
279
  blocking: false
280
280
  })
281
281
  }
282
- if (releasedJson) findings.push(...v3ContractFindings(asset!, releasedJson, say))
282
+ if (releasedJson) findings.push(...figureContractFindings(asset!, releasedJson, say))
283
283
  return { blocked: findings.some(finding => finding.blocking), findings }
284
284
  }
285
285
 
286
286
  /**
287
287
  * The last release against this one — what the drawings that already hold this type lean on (ADR-0063 decision 3).
288
288
  *
289
- * figure-model names the changes and whether each blocks (`v3ContractChanges`); the sentence is made here. When
289
+ * figure-model names the changes and whether each blocks (`figureContractChanges`); the sentence is made here. When
290
290
  * the last release no longer parses, the gate says so rather than passing in silence: a release that was not checked
291
291
  * against the last one is a different fact from one that was checked and found safe.
292
292
  */
293
- function v3ContractFindings(asset: V3Asset, releasedJson: string, say: Say): GateFinding[] {
294
- let before: V3Asset
293
+ function figureContractFindings(asset: FigureAsset, releasedJson: string, say: Say): GateFinding[] {
294
+ let before: FigureAsset
295
295
  try {
296
- before = parseV3Asset(releasedJson)
296
+ before = parseFigureAsset(releasedJson)
297
297
  } catch (e) {
298
298
  return [
299
299
  {
300
300
  code: 'released-unreadable',
301
- message: say('figure.gate.v3-released-not-compared.message', { reason: (e as Error).message }, `The last release could not be read, so this release was not compared with it: ${(e as Error).message}`),
302
- why: say('figure.gate.v3-released-not-compared.why', {}, 'Whether this release breaks the instances already placed has not been checked.'),
301
+ message: say('figure.gate.released-not-compared.message', { reason: (e as Error).message }, `The last release could not be read, so this release was not compared with it: ${(e as Error).message}`),
302
+ why: say('figure.gate.released-not-compared.why', {}, 'Whether this release breaks the instances already placed has not been checked.'),
303
303
  blocking: false
304
304
  }
305
305
  ]
306
306
  }
307
- return v3ContractChanges(before, asset).map(change => ({
307
+ return figureContractChanges(before, asset).map(change => ({
308
308
  code: change.code,
309
- ...v3ContractWording(change, say),
309
+ ...figureContractWording(change, say),
310
310
  at: change.subject,
311
311
  blocking: change.blocking
312
312
  }))
313
313
  }
314
314
 
315
315
  /** One contract change in the words of the release panel. The English line is the fallback for a missing key. */
316
- function v3ContractWording(change: V3ContractChange, say: Say): Pick<GateFinding, 'message' | 'why' | 'how'> {
316
+ function figureContractWording(change: FigureContractChange, say: Say): Pick<GateFinding, 'message' | 'why' | 'how'> {
317
317
  const v = change.values
318
318
  const key = (field: string) => `figure.gate.contract.${change.code}.${field}`
319
319
  const gone = {
@@ -423,12 +423,12 @@ function v3ContractWording(change: V3ContractChange, say: Say): Pick<GateFinding
423
423
  }
424
424
 
425
425
  /**
426
- * What a consumer is handed for a released figure: the asset itself, because what draws it -- `registerFigureV3` in
426
+ * What a consumer is handed for a released figure: the asset itself, because what draws it -- `registerFigure` in
427
427
  * things-scene -- takes the asset and compiles it with the kernel.
428
428
  */
429
- export function consumablePayloadOf(sourceJson: string): { asset: V3Asset } {
429
+ export function consumablePayloadOf(sourceJson: string): { asset: FigureAsset } {
430
430
  assertFormat(sourceJson)
431
- const asset = parseV3Asset(sourceJson)
432
- compileV3Asset(asset)
431
+ const asset = parseFigureAsset(sourceJson)
432
+ compileFigureAsset(asset)
433
433
  return { asset }
434
434
  }
@@ -64,7 +64,7 @@ export class FigureHistory implements HistoryEntityInterface<Figure> {
64
64
  state?: 'draft' | 'released'
65
65
 
66
66
  @Column({ nullable: true, ...longTextColumn() })
67
- @Field({ nullable: true, description: 'The figure asset JSON (figure-v3-asset-1) at this version.' })
67
+ @Field({ nullable: true, description: 'The figure asset JSON (figure-asset-1) at this version.' })
68
68
  source?: string
69
69
 
70
70
  @Column({ nullable: true, ...longTextColumn() })
@@ -9,12 +9,12 @@
9
9
  */
10
10
  import type { DataSource } from 'typeorm'
11
11
  import {
12
- addV3Box,
13
- applyV3Authoring,
14
- createV3Asset,
12
+ addFigureBox,
13
+ applyFigureAuthoring,
14
+ createFigureAsset,
15
15
  placeholderType,
16
- proposeV3Occupancy,
17
- serializeV3Asset
16
+ proposeFigureOccupancy,
17
+ serializeFigureAsset
18
18
  } from '@hatiolab/figure-model'
19
19
 
20
20
  const { DataSource: TypeOrmDataSource } = require('typeorm')
@@ -37,12 +37,12 @@ function loadCompiled(modulePath: string): any {
37
37
  return require(resolved)
38
38
  }
39
39
 
40
- /** A V3 figure that passes the gate on its own: a box standing on the floor, with its space declared. */
40
+ /** A figure that passes the gate on its own: a box standing on the floor, with its space declared. */
41
41
  function sound(): string {
42
- let asset = createV3Asset('Probe')
43
- asset = addV3Box(asset, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
44
- asset = applyV3Authoring(asset, { kind: 'declare-occupancy', proposal: proposeV3Occupancy(asset, { over: 'range' }), placement: 'floor' })
45
- return serializeV3Asset(asset)
42
+ let asset = createFigureAsset('Probe')
43
+ asset = addFigureBox(asset, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
44
+ asset = applyFigureAuthoring(asset, { kind: 'declare-occupancy', proposal: proposeFigureOccupancy(asset, { over: 'range' }), placement: 'floor' })
45
+ return serializeFigureAsset(asset)
46
46
  }
47
47
 
48
48
  beforeAll(async () => {
@@ -82,7 +82,7 @@ const save = (type: string, source = sound()) =>
82
82
  dataSource!.getRepository(Figure).save({ domain, type, name: 'probe', state: 'draft', source, version: 0 })
83
83
 
84
84
  describe('inspectFigure — the release panel', () => {
85
- it('refuses a V3 figure still under its temporary type — the type comes from the row', async () => {
85
+ it('refuses a figure still under its temporary type — the type comes from the row', async () => {
86
86
  const figure = await save(placeholderType())
87
87
 
88
88
  const inspection = await new FigureQuery().inspectFigure(figure.id, context())
@@ -101,10 +101,10 @@ describe('inspectFigure — the release panel', () => {
101
101
  describe('inspectFigureDraft — the verdict while editing', () => {
102
102
  it('judges the unsaved source, not the stored one, and still reads the row type', async () => {
103
103
  const figure = await save(placeholderType())
104
- let edited = createV3Asset('Probe')
105
- edited = addV3Box(edited, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
104
+ let edited = createFigureAsset('Probe')
105
+ edited = addFigureBox(edited, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
106
106
 
107
- const inspection = await new FigureQuery().inspectFigureDraft(serializeV3Asset(edited), context(), figure.id)
107
+ const inspection = await new FigureQuery().inspectFigureDraft(serializeFigureAsset(edited), context(), figure.id)
108
108
  const codes = inspection.findings.map((f: any) => f.code)
109
109
 
110
110
  expect(codes).toContain('occupancy-undeclared')
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 실제 LLM Figure 후보 생성 smoke test (기본 비활성).
3
3
  *
4
- * 결정적 시험은 프롬프트·도구·검사 배선을 고정하지만, 실제 모델이 V3 명령 목록(ADR-0094)을 써서 게이트를
4
+ * 결정적 시험은 프롬프트·도구·검사 배선을 고정하지만, 실제 모델이 명령 목록(ADR-0094)을 써서 게이트를
5
5
  * 지나는 도형을 만드는지는 대신할 수 없다. 이 시험은 `RUN_FIGURE_LLM_SMOKE=1`과 공급자 API 키가 모두 있을 때만 실행한다.
6
6
  * DB·GraphQL·Figure 저장을 건드리지 않고 후보 생성 함수만 호출하므로, 외부 영향은 API 비용뿐이다.
7
7
  *
@@ -11,9 +11,9 @@
11
11
  */
12
12
  import { createAIClient, setDefaultAIClient } from '@things-factory/ai-client-base'
13
13
 
14
- import { applyV3Proposal, createV3Asset, inspectV3Asset } from '@hatiolab/figure-model'
14
+ import { applyFigureProposal, createFigureAsset, inspectFigureAsset } from '@hatiolab/figure-model'
15
15
 
16
- import { proposeFigureV3 } from './figure-propose-v3'
16
+ import { proposeFigureSteps } from './figure-propose-steps'
17
17
 
18
18
  // Provider modules register themselves on import. Keep this separate from the deterministic tests.
19
19
  import '@things-factory/ai-client-base'
@@ -50,13 +50,13 @@ describeIf('Figure 실제 LLM smoke', () => {
50
50
  test(
51
51
  '새 Figure 는 게이트를 지나는 명령 목록으로 온다',
52
52
  async () => {
53
- const result = await proposeFigureV3({
53
+ const result = await proposeFigureSteps({
54
54
  prompt: 'two cylindrical rollers inside a simple rectangular press frame; make the rollers visually distinct',
55
55
  name: 'Roll press'
56
56
  })
57
57
 
58
58
  expect(result.steps.length).toBeGreaterThan(0)
59
- expect(inspectV3Asset(result.source).violations).toEqual([])
59
+ expect(inspectFigureAsset(result.source).violations).toEqual([])
60
60
  expect(result.cost.triangles).toBeGreaterThan(0)
61
61
  // eslint-disable-next-line no-console
62
62
  console.log(`[figure-llm-smoke] new: ${result.steps.length} steps, ${result.attempts} attempt(s)`)
@@ -67,14 +67,14 @@ describeIf('Figure 실제 LLM smoke', () => {
67
67
  test(
68
68
  '고치는 제안은 건드리지 않은 부품을 그대로 둔다',
69
69
  async () => {
70
- const draft = applyV3Proposal(createV3Asset('Mixer'), [
70
+ const draft = applyFigureProposal(createFigureAsset('Mixer'), [
71
71
  { kind: 'add-part', part: { id: 'body', kind: 'cylinder', dimensions: { radius: 140, length: 320 } } },
72
72
  { kind: 'add-part', part: { id: 'port', kind: 'box', dimensions: { width: 80, height: 60, depth: 30 } } },
73
73
  { kind: 'attach', part: 'body', face: 'bottom', to: { plane: 'mounting-plane' } },
74
74
  { kind: 'attach', part: 'port', face: 'back', to: { part: 'body', face: 'front' } },
75
75
  { kind: 'fit-room', placement: 'floor' }
76
76
  ] as any)
77
- const result = await proposeFigureV3({ prompt: 'make the port twice as wide and give it an accent colour', base: draft })
77
+ const result = await proposeFigureSteps({ prompt: 'make the port twice as wide and give it an accent colour', base: draft })
78
78
 
79
79
  const ids = result.source.document.model.nodes.map((n: any) => n.id)
80
80
  expect(ids.some((id: string) => id.includes('body'))).toBe(true)