@things-factory/figure-service 10.1.6 → 10.1.8

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 (42) hide show
  1. package/dist-server/service/figure/figure-gate.d.ts +18 -0
  2. package/dist-server/service/figure/figure-gate.js +52 -1
  3. package/dist-server/service/figure/figure-gate.js.map +1 -1
  4. package/dist-server/service/figure/figure-mutation.js +8 -0
  5. package/dist-server/service/figure/figure-mutation.js.map +1 -1
  6. package/dist-server/service/figure/figure-propose-type.d.ts +30 -0
  7. package/dist-server/service/figure/figure-propose-type.js +112 -1
  8. package/dist-server/service/figure/figure-propose-type.js.map +1 -1
  9. package/dist-server/service/figure/figure-propose.d.ts +25 -1
  10. package/dist-server/service/figure/figure-propose.js +102 -4
  11. package/dist-server/service/figure/figure-propose.js.map +1 -1
  12. package/dist-server/service/figure/figure-quality.d.ts +29 -0
  13. package/dist-server/service/figure/figure-quality.js +62 -0
  14. package/dist-server/service/figure/figure-quality.js.map +1 -0
  15. package/dist-server/service/figure/figure-query.js +8 -1
  16. package/dist-server/service/figure/figure-query.js.map +1 -1
  17. package/dist-server/service/figure/figure-tools.js +55 -9
  18. package/dist-server/service/figure/figure-tools.js.map +1 -1
  19. package/dist-server/service/figure/figure-type.d.ts +13 -0
  20. package/dist-server/service/figure/figure-type.js +51 -1
  21. package/dist-server/service/figure/figure-type.js.map +1 -1
  22. package/dist-server/service/figure/figure.d.ts +17 -1
  23. package/dist-server/service/figure/figure.js +6 -1
  24. package/dist-server/service/figure/figure.js.map +1 -1
  25. package/dist-server/tsconfig.tsbuildinfo +1 -1
  26. package/package.json +6 -6
  27. package/server/service/figure/catalog-db.test.ts +162 -0
  28. package/server/service/figure/figure-ai-flow.test.ts +95 -0
  29. package/server/service/figure/figure-gate.test.ts +32 -0
  30. package/server/service/figure/figure-gate.ts +71 -2
  31. package/server/service/figure/figure-llm-smoke.test.ts +91 -0
  32. package/server/service/figure/figure-mutation.ts +8 -0
  33. package/server/service/figure/figure-propose-type.ts +80 -0
  34. package/server/service/figure/figure-propose.test.ts +77 -0
  35. package/server/service/figure/figure-propose.ts +137 -4
  36. package/server/service/figure/figure-quality.test.ts +43 -0
  37. package/server/service/figure/figure-quality.ts +98 -0
  38. package/server/service/figure/figure-query.ts +8 -1
  39. package/server/service/figure/figure-tools.test.ts +62 -1
  40. package/server/service/figure/figure-tools.ts +60 -8
  41. package/server/service/figure/figure-type.ts +41 -0
  42. package/server/service/figure/figure.ts +22 -2
@@ -1 +1 @@
1
- {"version":3,"file":"figure-tools.js","sourceRoot":"","sources":["../../../server/service/figure/figure-tools.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * figure 저작 도구를 어시스턴트 도구 레지스트리에 등록 — **대화면(도크)에서 저작을 돕기 위함.**\n *\n * 왜 필요한가: `proposeFigure` 는 한 번 시키고 한 번 받는다. 그것으로 되는 일이 있지만, 저작은\n * 대개 「이걸 이렇게 고쳐 봐 → 무게가 얼마나 되나 → 발행은 되나」처럼 여러 차례 오간다. 그 대화를\n * 하려면 어시스턴트가 **지금 무엇이 있는지 읽을 수 있어야** 한다. 읽지 못하면 매번 사람이 정본을\n * 붙여 넣어야 하고, 그러면 대화가 아니다.\n *\n * 능력을 복제하지 않는다. figure-service 가 이미 가진 진입점(`inspectSource` · `proposeFigure` ·\n * 조회)을 레지스트리에 투영한다.\n *\n * ── 전부 조회·제안 전용이다 ─────────────────────────────────────────────────\n * 정본을 고치는 도구를 넣지 않는다. 형식의 설계가 그렇게 정해져 있다 — 어시스턴트는 **후보**를\n * 내고, 받을지 버릴지는 저작자가 정한다(`figure-propose.ts`). 되돌릴 수 없는 조작이 대화면으로\n * 새지 않으므로 전부 `kind: 'read'` 다.\n *\n * 저장은 저작면이 `updateFigure` 로 한다. 그 mutation 은 `figure:mutation` 이 지키고, 대화는\n * `ai-assistant:mutation` 이 지킨다 — 서로 다른 질문이라 서로 다른 권한이 지킨다. figure 가 board\n * 의 권한을 빌리지 않는다.\n *\n * ── 규율을 도구와 함께 보낸다 ───────────────────────────────────────────────\n * 도구 목록만으로는 「언제 반드시 불러야 하는지」를 모른다. twin 이 그 공백에서 사고를 냈다 —\n * 조치 요청에 도구를 부르지 않고 「제안해 두었습니다」라고만 답해, 사용자에게 **없는 버튼을 누르라고\n * 안내했다.** figure 도 똑같은 자리가 있다: 후보 화면(「지금 / 후보」 두 칸)은 **도구 호출 결과에서만**\n * 만들어진다. 문장으로는 아무 일도 일어나지 않는다.\n *\n * ── LLM 이 넘긴 id 를 믿지 않는다 ───────────────────────────────────────────\n * 사람이 목록에서 고르는 길에서는 드러나지 않지만, 모델은 대화 내용에 이끌려 임의의 id 를 넘길 수\n * 있다. 그래서 호출자 도메인 소속을 먼저 확인한다.\n */\nimport { registerToolCategory, type ToolCategory, type ToolSpec } from '@things-factory/ai-client-base'\n\n/*\n 서버 의존성은 lazy require 다. 등록은 부팅 초반에 평가되고 그때 typeorm DataSource 가 준비되지\n 않았을 수 있다(board-import · twin-ai 와 같은 정책).\n*/\nlet _deps: any | undefined\n\nfunction getDeps(): any {\n if (!_deps) {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const shell = require('@things-factory/shell')\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const model = require('@hatiolab/figure-model')\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const entity = require('./figure.js')\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const gate = require('./figure-gate.js')\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const propose = require('./figure-propose.js')\n _deps = {\n getRepository: shell.getRepository,\n Figure: entity.Figure,\n inspectSource: gate.inspectSource,\n proposeFigure: propose.proposeFigure,\n compile: model.compile,\n costOf: model.costOf,\n scoreOf: model.scoreOf\n }\n }\n return _deps\n}\n\n/** 시험·검증 하네스 전용 주입 시임. 운영 경로에서 호출 금지. */\nexport function __setFigureToolDepsForTest(deps: any): void {\n _deps = deps\n}\n\nfunction requireDomainId(state: any): string {\n const id = state?.domain?.id\n if (!id) {\n // 도메인을 모르면 무엇을 읽어도 남의 것일 수 있다. 조용히 넘기지 않는다.\n throw new Error('domain context is missing — the figure tools cannot resolve tenant scope')\n }\n return id\n}\n\n/**\n * 이 도형이 호출자 도메인 것인가.\n *\n * 모델이 넘긴 id 를 그대로 쓰지 않는다. 없으면 못 찾았다고 말한다 — 「권한이 없다」와 구별해\n * 알려 줄 것이 없다(다른 테넌트에 그 id 가 있다는 사실 자체가 알려 줄 것이 아니다).\n */\nasync function figureInDomain(figureId: string, domainId: string): Promise<any> {\n const { getRepository, Figure } = getDeps()\n const found = await getRepository(Figure).findOne({\n where: { id: figureId, domain: { id: domainId } }\n })\n if (!found) {\n throw new Error(`figure not found in this tenant: ${figureId}`)\n }\n return found\n}\n\n/**\n * 무엇을 고치라는 것인지 대화면 문맥에서 해소한다.\n *\n * 모델이 대상을 안 줬으면 **저작면이 지금 열고 있는 도형**이다. 사용자에게 식별자를 되묻지\n * 않는다 — 화면에 그 도형이 떠 있는데 「어느 도형입니까」를 묻는 것은 문맥을 안 읽은 것이다.\n */\nfunction figureIdFrom(args: any, host: any): string {\n const said = typeof args?.figureId === 'string' ? args.figureId.trim() : ''\n if (said) return said\n\n const open = typeof host?.figureId === 'string' ? host.figureId.trim() : ''\n if (open) return open\n\n throw new Error('figureId is required: this surface has no figure open to resolve it from')\n}\n\nconst FIGURE_ID_ARG = {\n figureId: {\n type: 'string',\n description: 'The figure to read. Omit to use the one open in the editor.'\n }\n} as const\n\n/**\n * 도구 넷.\n *\n * 목록 · 정본 · 발행 판정 · 후보. 저작 대화에서 실제로 묻는 것이 그 넷이다 — 「무엇이 있나」\n * 「지금 어떻게 생겼나」 「발행이 왜 안 되나」 「이렇게 고쳐 봐」.\n */\nfunction figureToolSpecs(): ToolSpec[] {\n return [\n {\n kind: 'read',\n name: 'listFigures',\n description:\n 'Lists the figures in this tenant with their type names, states and part counts. ' +\n 'Call it before naming a type — type names are stored identifiers and must not be invented.',\n schema: { type: 'object', properties: {} },\n builder: async (_args: any, ctx: any) => {\n const domainId = requireDomainId(ctx?.state)\n const { getRepository, Figure } = getDeps()\n const rows = await getRepository(Figure).find({\n where: { domain: { id: domainId } },\n order: { updatedAt: 'DESC' },\n take: 200\n })\n return {\n figures: rows.map((row: any) => ({\n id: row.id,\n type: row.type,\n name: row.name,\n state: row.state,\n score: row.score,\n triangles: row.triangles\n }))\n }\n }\n },\n {\n kind: 'read',\n name: 'getFigureSource',\n description:\n 'Returns the canonical FigureSource of one figure, with its measured cost ' +\n '(triangles, draw calls, how many fit on one board). Call it before proposing a revision — ' +\n 'revising without reading it means proposing against a guess.',\n schema: { type: 'object', properties: { ...FIGURE_ID_ARG } },\n builder: async (args: any, ctx: any) => {\n const domainId = requireDomainId(ctx?.state)\n const figureId = figureIdFrom(args, ctx?.state?.host)\n const row = await figureInDomain(figureId, domainId)\n\n if (!row.source) {\n // 저장된 정본이 없는 상태가 실제로 있다(새로 만들고 저장 안 한 것). 지어내지 않는다.\n return { id: row.id, type: row.type, name: row.name, source: null, cost: null }\n }\n\n const { compile, costOf, scoreOf } = getDeps()\n const source = JSON.parse(row.source)\n const blueprint = compile(source)\n\n return {\n id: row.id,\n type: row.type,\n name: row.name,\n state: row.state,\n source,\n cost: costOf(blueprint, 100),\n score: scoreOf(blueprint)\n }\n }\n },\n {\n kind: 'read',\n name: 'inspectFigure',\n description:\n 'Says whether a figure may be released and, when it may not, what is wrong — each finding ' +\n 'carries why it is a problem and what to change. Call it when asked about releasing, or ' +\n 'when asked to fix a figure that will not release.',\n schema: { type: 'object', properties: { ...FIGURE_ID_ARG } },\n builder: async (args: any, ctx: any) => {\n const domainId = requireDomainId(ctx?.state)\n const figureId = figureIdFrom(args, ctx?.state?.host)\n const row = await figureInDomain(figureId, domainId)\n\n const { inspectSource } = getDeps()\n return inspectSource(row.source)\n }\n },\n {\n kind: 'read',\n name: 'proposeFigureCandidate',\n description:\n 'Asks for a figure candidate from a description, optionally revising an existing one. ' +\n 'Returns the candidate with its score and cost. NOTHING IS SAVED — the author accepts or ' +\n 'discards it on screen. This is the only way a candidate reaches the screen.',\n schema: {\n type: 'object',\n properties: {\n prompt: {\n type: 'string',\n description: 'What to make or change, in the words the author used.'\n },\n figureId: {\n type: 'string',\n description: 'Revise this figure. Omit to revise the one open in the editor; pass null to create anew.'\n },\n type: {\n type: 'string',\n description: 'Type name for a new figure. Check listFigures first — it is a stored identifier.'\n },\n create: {\n type: 'boolean',\n description: 'True to create rather than revise, even with a figure open.'\n }\n },\n required: ['prompt']\n },\n builder: async (args: any, ctx: any) => {\n const domainId = requireDomainId(ctx?.state)\n const prompt = typeof args?.prompt === 'string' ? args.prompt.trim() : ''\n if (!prompt) throw new Error('prompt is required: there is nothing to ask for')\n\n /*\n 고치는 것인지 만드는 것인지를 **문맥에서 정한다.** 열린 도형이 있으면 고치는 것이 기본이고,\n 모델이 `create` 를 켜면 만드는 것이다. 이것을 모델의 짐작에 맡기면 열려 있는 도형을 덮어쓰는\n 후보가 온다.\n */\n let base: unknown\n if (!args?.create) {\n const open = typeof args?.figureId === 'string' ? args.figureId : ctx?.state?.host?.figureId\n if (typeof open === 'string' && open.trim()) {\n const row = await figureInDomain(open.trim(), domainId)\n if (row.source) base = JSON.parse(row.source)\n }\n }\n\n const palette = Array.isArray(ctx?.state?.host?.palette) ? ctx.state.host.palette : []\n if (palette.length === 0) {\n /*\n 팔레트를 모르면 모델이 색을 지어내고, 그 후보는 그리는 시점에 `resolveToken` 이 던진다 —\n 저장까지 되고 나서 화면에서 죽는다. 빈 목록으로 진행하지 않는다.\n */\n throw new Error('the palette is missing from the host context — the candidate would invent colours')\n }\n\n const { proposeFigure } = getDeps()\n const result = await proposeFigure({\n prompt,\n palette,\n base,\n type: typeof args?.type === 'string' ? args.type : undefined\n })\n\n /*\n **`proposed: true` 가 카드를 만든다.** 이 표시가 없으면 후보는 도구 추적 안에 묻히고,\n 화면에는 아무것도 생기지 않는다 — 그러면 모델이 「후보를 만들었습니다」라고 쓴 것이\n 거짓말이 된다(사용자가 없는 단추를 찾는다).\n\n `label`·`reason` 이 카드에 그려지는 두 줄이고, `args` 는 같은 제안을 두 번 세지 않기\n 위한 열쇠의 일부다 — 같은 요청을 두 번 내면 카드가 하나로 접힌다.\n */\n return {\n proposed: true,\n label: base ? `${result.source.type} 고치기` : `${result.source.type} 만들기`,\n reason:\n `등급 ${result.score.grade} · 삼각형 ${result.cost.triangles} · ` +\n `한 도면 ${result.cost.at.drawCalls}회 · ${result.attempts}번에 만들었다`,\n args: { prompt, figureId: base ? figureIdFrom(args, ctx?.state?.host) : undefined },\n source: result.source,\n score: result.score,\n cost: result.cost,\n violations: result.violations,\n attempts: result.attempts\n }\n }\n }\n ] as ToolSpec[]\n}\n\n/**\n * 등록 — 부팅 시 1회.\n *\n * 범주가 하나다. twin 은 관찰과 생성을 둘로 갈랐는데(운영 협의에 생성 도구가 들려 있으면 대화의\n * 성격이 흐려진다), figure 의 대화면은 저작면 하나뿐이고 그 자리에서 읽기와 후보 요청이 함께 필요하다.\n * 대화면이 늘면 그때 가른다.\n */\nexport function registerFigureToolCategories(): void {\n const authoring: ToolCategory = {\n name: 'figure-authoring',\n description: '저작 중인 Figure 의 정본·비용·발행 판정 조회와 후보 제안(저장 아님).',\n guidance: [\n '- **도형을 만들거나 고쳐 달라는 요청에는 반드시 `proposeFigureCandidate` 를 호출한다.**',\n ' 호출 없이 \"후보를 만들었습니다\" · \"적용 버튼을 누르세요\" 라고 쓰는 것은 **거짓이다** — 화면에 후보 칸이 생기지 않으므로 사용자는 존재하지 않는 버튼을 찾는다.',\n ' 후보 화면(「지금 / 후보」 두 칸)은 **도구 호출 결과에서만** 만들어진다. 문장으로는 아무 일도 일어나지 않는다.',\n '- 정본을 직접 저장하지 않는다. 후보만 내고, 받을지 버릴지는 저작자가 화면에서 정한다.',\n '- **고치라는 요청이면 먼저 `getFigureSource` 를 부른다.** 읽지 않고 고치면 짐작을 상대로 후보를 내게 되고, 부품 이름이 갈려 이미 배치된 보드의 바인딩이 끊긴다.',\n '- 타입 이름을 지어내지 않는다 — `listFigures` 로 확인한다. **타입 이름은 저장되는 식별자**이고 보드가 그 이름으로 도형을 찾는다.',\n '- 발행이 안 되는 이유를 물으면 `inspectFigure` 를 부른다. 판정 없이 \"발행할 수 있습니다\"라고 말하지 않는다.',\n ' 판정의 각 항목은 **왜 문제인지와 무엇을 바꾸면 되는지**를 함께 담고 있다. 그대로 전한다 — 요약하면 저작자가 어느 부품의 어느 축을 고칠지 다시 찾아야 한다.',\n '- 무게·삼각형 수를 물으면 `getFigureSource` 의 `cost` 를 쓴다. 눈대중으로 세지 않는다.'\n ].join('\\n'),\n specs: figureToolSpecs()\n }\n\n registerToolCategory(authoring)\n}\n"]}
1
+ {"version":3,"file":"figure-tools.js","sourceRoot":"","sources":["../../../server/service/figure/figure-tools.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * figure 저작 도구를 어시스턴트 도구 레지스트리에 등록 — **대화면(도크)에서 저작을 돕기 위함.**\n *\n * 왜 필요한가: `proposeFigure` 는 한 번 시키고 한 번 받는다. 그것으로 되는 일이 있지만, 저작은\n * 대개 「이걸 이렇게 고쳐 봐 → 무게가 얼마나 되나 → 발행은 되나」처럼 여러 차례 오간다. 그 대화를\n * 하려면 어시스턴트가 **지금 무엇이 있는지 읽을 수 있어야** 한다. 읽지 못하면 매번 사람이 정본을\n * 붙여 넣어야 하고, 그러면 대화가 아니다.\n *\n * 능력을 복제하지 않는다. figure-service 가 이미 가진 진입점(`inspectSource` · `proposeFigure` ·\n * 조회)을 레지스트리에 투영한다.\n *\n * ── 전부 조회·제안 전용이다 ─────────────────────────────────────────────────\n * 정본을 고치는 도구를 넣지 않는다. 형식의 설계가 그렇게 정해져 있다 — 어시스턴트는 **후보**를\n * 내고, 받을지 버릴지는 저작자가 정한다(`figure-propose.ts`). 되돌릴 수 없는 조작이 대화면으로\n * 새지 않으므로 전부 `kind: 'read'` 다.\n *\n * 저장은 저작면이 `updateFigure` 로 한다. 그 mutation 은 `figure:mutation` 이 지키고, 대화는\n * `ai-assistant:mutation` 이 지킨다 — 서로 다른 질문이라 서로 다른 권한이 지킨다. figure 가 board\n * 의 권한을 빌리지 않는다.\n *\n * ── 규율을 도구와 함께 보낸다 ───────────────────────────────────────────────\n * 도구 목록만으로는 「언제 반드시 불러야 하는지」를 모른다. twin 이 그 공백에서 사고를 냈다 —\n * 조치 요청에 도구를 부르지 않고 「제안해 두었습니다」라고만 답해, 사용자에게 **없는 버튼을 누르라고\n * 안내했다.** figure 도 똑같은 자리가 있다: 후보 화면(「지금 / 후보」 두 칸)은 **도구 호출 결과에서만**\n * 만들어진다. 문장으로는 아무 일도 일어나지 않는다.\n *\n * ── LLM 이 넘긴 id 를 믿지 않는다 ───────────────────────────────────────────\n * 사람이 목록에서 고르는 길에서는 드러나지 않지만, 모델은 대화 내용에 이끌려 임의의 id 를 넘길 수\n * 있다. 그래서 호출자 도메인 소속을 먼저 확인한다.\n */\nimport { registerToolCategory, type ToolCategory, type ToolSpec } from '@things-factory/ai-client-base'\n\n/*\n 서버 의존성은 lazy require 다. 등록은 부팅 초반에 평가되고 그때 typeorm DataSource 가 준비되지\n 않았을 수 있다(board-import · twin-ai 와 같은 정책).\n*/\nlet _deps: any | undefined\n\nfunction getDeps(): any {\n if (!_deps) {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const shell = require('@things-factory/shell')\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const model = require('@hatiolab/figure-model')\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const entity = require('./figure.js')\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const gate = require('./figure-gate.js')\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const propose = require('./figure-propose.js')\n _deps = {\n getRepository: shell.getRepository,\n Figure: entity.Figure,\n inspectSource: gate.inspectSource,\n proposeFigure: propose.proposeFigure,\n compile: model.compile,\n costOf: model.costOf,\n scoreOf: model.scoreOf,\n validate: model.validate\n }\n }\n return _deps\n}\n\n/** 시험·검증 하네스 전용 주입 시임. 운영 경로에서 호출 금지. */\nexport function __setFigureToolDepsForTest(deps: any): void {\n _deps = deps\n}\n\nfunction requireDomainId(state: any): string {\n const id = state?.domain?.id\n if (!id) {\n // 도메인을 모르면 무엇을 읽어도 남의 것일 수 있다. 조용히 넘기지 않는다.\n throw new Error('domain context is missing — the figure tools cannot resolve tenant scope')\n }\n return id\n}\n\n/**\n * 이 도형이 호출자 도메인 것인가.\n *\n * 모델이 넘긴 id 를 그대로 쓰지 않는다. 없으면 못 찾았다고 말한다 — 「권한이 없다」와 구별해\n * 알려 줄 것이 없다(다른 테넌트에 그 id 가 있다는 사실 자체가 알려 줄 것이 아니다).\n */\nasync function figureInDomain(figureId: string, domainId: string): Promise<any> {\n const { getRepository, Figure } = getDeps()\n const found = await getRepository(Figure).findOne({\n where: { id: figureId, domain: { id: domainId } }\n })\n if (!found) {\n throw new Error(`figure not found in this tenant: ${figureId}`)\n }\n return found\n}\n\n/**\n * 모델러가 아직 저장하지 않은 source를 도구가 읽는 유일한 길.\n *\n * host context는 브라우저가 보낸 값이므로 권한 근거가 될 수 없다. 먼저 `row`를 현재 도메인에서\n * 찾은 뒤에만 보며, Figure의 불변 type과 형식 검증을 다시 확인한다. 검증되지 않은 큰 객체를 LLM\n * 프롬프트로 옮기면 비용만 태우고, DB에 있던 Figure와 다른 것을 고친다고 잘못 말할 수 있다.\n */\nfunction sourceForTool(row: any, host: any): { source: any; draft: boolean } {\n const draft = host?.draftSource\n if (!draft) {\n return { source: row.source ? JSON.parse(row.source) : null, draft: false }\n }\n\n if (typeof draft !== 'object' || Array.isArray(draft)) {\n throw new Error('the unsaved draft is not a FigureSource object')\n }\n\n const serialized = JSON.stringify(draft)\n if (serialized.length > 100_000) {\n throw new Error('the unsaved draft is too large to safely review')\n }\n\n const { validate } = getDeps()\n const checked = validate(draft)\n if (checked.errors.length > 0) {\n throw new Error(`the unsaved draft is invalid: ${checked.errors.map((one: any) => one.message).join(' · ')}`)\n }\n if (draft.type !== row.type) {\n throw new Error(`the unsaved draft type '${draft.type}' does not match Figure '${row.type}'`)\n }\n\n return { source: draft, draft: true }\n}\n\n/**\n * 후보를 냈던 바로 그 Figure의 세션 반응만 후보 생성기에 넘긴다.\n *\n * 도크의 host context는 브라우저 값이므로 이 함수는 접근 권한을 판단하지 않는다. 이미 현재\n * 도메인에서 Figure를 찾은 뒤, `proposeFigure`가 다시 길이·형식을 정리할 참고 데이터만 건넨다.\n */\nfunction feedbackForTool(host: any, figureId?: string): unknown[] | undefined {\n if (!figureId || host?.figureId !== figureId || !Array.isArray(host?.proposalFeedback)) return undefined\n return host.proposalFeedback.slice(-5)\n}\n\n/**\n * 무엇을 고치라는 것인지 대화면 문맥에서 해소한다.\n *\n * 모델이 대상을 안 줬으면 **저작면이 지금 열고 있는 도형**이다. 사용자에게 식별자를 되묻지\n * 않는다 — 화면에 그 도형이 떠 있는데 「어느 도형입니까」를 묻는 것은 문맥을 안 읽은 것이다.\n */\nfunction figureIdFrom(args: any, host: any): string {\n const said = typeof args?.figureId === 'string' ? args.figureId.trim() : ''\n if (said) return said\n\n const open = typeof host?.figureId === 'string' ? host.figureId.trim() : ''\n if (open) return open\n\n throw new Error('figureId is required: this surface has no figure open to resolve it from')\n}\n\nconst FIGURE_ID_ARG = {\n figureId: {\n type: 'string',\n description: 'The figure to read. Omit to use the one open in the editor.'\n }\n} as const\n\n/**\n * 도구 넷.\n *\n * 목록 · 정본 · 발행 판정 · 후보. 저작 대화에서 실제로 묻는 것이 그 넷이다 — 「무엇이 있나」\n * 「지금 어떻게 생겼나」 「발행이 왜 안 되나」 「이렇게 고쳐 봐」.\n */\nfunction figureToolSpecs(): ToolSpec[] {\n return [\n {\n kind: 'read',\n name: 'listFigures',\n description:\n 'Lists the figures in this tenant with their type names, states and part counts. ' +\n 'Call it before naming a type — type names are stored identifiers and must not be invented.',\n schema: { type: 'object', properties: {} },\n builder: async (_args: any, ctx: any) => {\n const domainId = requireDomainId(ctx?.state)\n const { getRepository, Figure } = getDeps()\n const rows = await getRepository(Figure).find({\n where: { domain: { id: domainId } },\n order: { updatedAt: 'DESC' },\n take: 200\n })\n return {\n figures: rows.map((row: any) => ({\n id: row.id,\n type: row.type,\n name: row.name,\n state: row.state,\n score: row.score,\n triangles: row.triangles\n }))\n }\n }\n },\n {\n kind: 'read',\n name: 'getFigureSource',\n description:\n 'Returns the canonical FigureSource of one figure, with its measured cost ' +\n '(triangles, draw calls, how many fit on one board). Call it before proposing a revision — ' +\n 'revising without reading it means proposing against a guess.',\n schema: { type: 'object', properties: { ...FIGURE_ID_ARG } },\n builder: async (args: any, ctx: any) => {\n const domainId = requireDomainId(ctx?.state)\n const figureId = figureIdFrom(args, ctx?.state?.host)\n const row = await figureInDomain(figureId, domainId)\n\n const current = sourceForTool(row, ctx?.state?.host)\n if (!current.source) {\n // 저장된 정본이 없는 상태가 실제로 있다(새로 만들고 저장 안 한 것). 지어내지 않는다.\n return { id: row.id, type: row.type, name: row.name, source: null, cost: null }\n }\n\n const { compile, costOf, scoreOf } = getDeps()\n const blueprint = compile(current.source)\n\n return {\n id: row.id,\n type: row.type,\n name: row.name,\n state: row.state,\n source: current.source,\n draft: current.draft,\n cost: costOf(blueprint, 100),\n score: scoreOf(blueprint)\n }\n }\n },\n {\n kind: 'read',\n name: 'inspectFigure',\n description:\n 'Says whether a figure may be released and, when it may not, what is wrong — each finding ' +\n 'carries why it is a problem and what to change. Call it when asked about releasing, or ' +\n 'when asked to fix a figure that will not release.',\n schema: { type: 'object', properties: { ...FIGURE_ID_ARG } },\n builder: async (args: any, ctx: any) => {\n const domainId = requireDomainId(ctx?.state)\n const figureId = figureIdFrom(args, ctx?.state?.host)\n const row = await figureInDomain(figureId, domainId)\n\n const current = sourceForTool(row, ctx?.state?.host)\n const { inspectSource } = getDeps()\n return { ...inspectSource(current.source ? JSON.stringify(current.source) : null), draft: current.draft }\n }\n },\n {\n kind: 'read',\n name: 'proposeFigureCandidate',\n description:\n 'Asks for a figure candidate from a description, optionally revising an existing one. ' +\n 'Returns the candidate with its score and cost. NOTHING IS SAVED — the author accepts or ' +\n 'discards it on screen. This is the only way a candidate reaches the screen.',\n schema: {\n type: 'object',\n properties: {\n prompt: {\n type: 'string',\n description: 'What to make or change, in the words the author used.'\n },\n figureId: {\n type: 'string',\n description: 'Revise this figure. Omit to revise the one open in the editor; pass null to create anew.'\n },\n type: {\n type: 'string',\n description: 'Type name for a new figure. Check listFigures first — it is a stored identifier.'\n },\n create: {\n type: 'boolean',\n description: 'True to create rather than revise, even with a figure open.'\n }\n },\n required: ['prompt']\n },\n builder: async (args: any, ctx: any) => {\n const domainId = requireDomainId(ctx?.state)\n const prompt = typeof args?.prompt === 'string' ? args.prompt.trim() : ''\n if (!prompt) throw new Error('prompt is required: there is nothing to ask for')\n\n /*\n 고치는 것인지 만드는 것인지를 **문맥에서 정한다.** 열린 도형이 있으면 고치는 것이 기본이고,\n 모델이 `create` 를 켜면 만드는 것이다. 이것을 모델의 짐작에 맡기면 열려 있는 도형을 덮어쓰는\n 후보가 온다.\n */\n let base: unknown\n if (!args?.create) {\n const open = typeof args?.figureId === 'string' ? args.figureId : ctx?.state?.host?.figureId\n if (typeof open === 'string' && open.trim()) {\n const row = await figureInDomain(open.trim(), domainId)\n base = sourceForTool(row, ctx?.state?.host).source ?? undefined\n }\n }\n\n const palette = Array.isArray(ctx?.state?.host?.palette) ? ctx.state.host.palette : []\n if (palette.length === 0) {\n /*\n 팔레트를 모르면 모델이 색을 지어내고, 그 후보는 그리는 시점에 `resolveToken` 이 던진다 —\n 저장까지 되고 나서 화면에서 죽는다. 빈 목록으로 진행하지 않는다.\n */\n throw new Error('the palette is missing from the host context — the candidate would invent colours')\n }\n\n const { proposeFigure } = getDeps()\n const feedback = feedbackForTool(ctx?.state?.host, base ? figureIdFrom(args, ctx?.state?.host) : undefined)\n const result = await proposeFigure({\n prompt,\n palette,\n base,\n type: typeof args?.type === 'string' ? args.type : undefined,\n feedback,\n refine: !!feedback?.length\n })\n\n /*\n **`proposed: true` 가 카드를 만든다.** 이 표시가 없으면 후보는 도구 추적 안에 묻히고,\n 화면에는 아무것도 생기지 않는다 — 그러면 모델이 「후보를 만들었습니다」라고 쓴 것이\n 거짓말이 된다(사용자가 없는 단추를 찾는다).\n\n `label`·`reason` 이 카드에 그려지는 두 줄이고, `args` 는 같은 제안을 두 번 세지 않기\n 위한 열쇠의 일부다 — 같은 요청을 두 번 내면 카드가 하나로 접힌다.\n */\n return {\n proposed: true,\n label: base ? `${result.source.type} 고치기` : `${result.source.type} 만들기`,\n reason:\n `등급 ${result.score.grade} · 삼각형 ${result.cost.triangles} · ` +\n `한 도면 ${result.cost.at.drawCalls}회 · ${result.attempts}번에 만들었다`,\n args: { prompt, figureId: base ? figureIdFrom(args, ctx?.state?.host) : undefined },\n source: result.source,\n score: result.score,\n cost: result.cost,\n quality: result.quality,\n violations: result.violations,\n attempts: result.attempts\n }\n }\n }\n ] as ToolSpec[]\n}\n\n/**\n * 등록 — 부팅 시 1회.\n *\n * 범주가 하나다. twin 은 관찰과 생성을 둘로 갈랐는데(운영 협의에 생성 도구가 들려 있으면 대화의\n * 성격이 흐려진다), figure 의 대화면은 저작면 하나뿐이고 그 자리에서 읽기와 후보 요청이 함께 필요하다.\n * 대화면이 늘면 그때 가른다.\n */\nexport function registerFigureToolCategories(): void {\n const authoring: ToolCategory = {\n name: 'figure-authoring',\n description: '저작 중인 Figure 의 정본·비용·발행 판정 조회와 후보 제안(저장 아님).',\n guidance: [\n '- **도형을 만들거나 고쳐 달라는 요청에는 반드시 `proposeFigureCandidate` 를 호출한다.**',\n ' 호출 없이 \"후보를 만들었습니다\" · \"적용 버튼을 누르세요\" 라고 쓰는 것은 **거짓이다** — 화면에 후보 칸이 생기지 않으므로 사용자는 존재하지 않는 버튼을 찾는다.',\n ' 후보 화면(「지금 / 후보」 두 칸)은 **도구 호출 결과에서만** 만들어진다. 문장으로는 아무 일도 일어나지 않는다.',\n '- 정본을 직접 저장하지 않는다. 후보만 내고, 받을지 버릴지는 저작자가 화면에서 정한다.',\n '- **고치라는 요청이면 먼저 `getFigureSource` 를 부른다.** 읽지 않고 고치면 짐작을 상대로 후보를 내게 되고, 부품 이름이 갈려 이미 배치된 보드의 바인딩이 끊긴다.',\n '- 타입 이름을 지어내지 않는다 — `listFigures` 로 확인한다. **타입 이름은 저장되는 식별자**이고 보드가 그 이름으로 도형을 찾는다.',\n '- 발행이 안 되는 이유를 물으면 `inspectFigure` 를 부른다. 판정 없이 \"발행할 수 있습니다\"라고 말하지 않는다.',\n ' 판정의 각 항목은 **왜 문제인지와 무엇을 바꾸면 되는지**를 함께 담고 있다. 그대로 전한다 — 요약하면 저작자가 어느 부품의 어느 축을 고칠지 다시 찾아야 한다.',\n '- 무게·삼각형 수를 물으면 `getFigureSource` 의 `cost` 를 쓴다. 눈대중으로 세지 않는다.'\n ].join('\\n'),\n specs: figureToolSpecs()\n }\n\n registerToolCategory(authoring)\n}\n"]}
@@ -10,6 +10,7 @@ export declare class NewFigure {
10
10
  name: string;
11
11
  description?: string;
12
12
  category?: string;
13
+ tags?: string[];
13
14
  source: string;
14
15
  properties?: string;
15
16
  state?: 'draft' | 'released';
@@ -25,6 +26,7 @@ export declare class FigurePatch {
25
26
  name?: string;
26
27
  description?: string;
27
28
  category?: string;
29
+ tags?: string[];
28
30
  source?: string;
29
31
  properties?: string;
30
32
  thumbnail?: string;
@@ -61,12 +63,23 @@ export declare class FigureSaveResult {
61
63
  * 것들이라 그 칸이 필요 없었다. 발행 판정은 막는 것과 알리는 것을 함께 내므로, 어느 쪽인지를 값이
62
64
  * 스스로 말해야 한다 — 코드를 보고 화면이 다시 분류하면 규칙이 두 벌이 된다.
63
65
  */
66
+ /** A measured, one-anchor repair candidate for a release-gate sizing split. */
67
+ export declare class FigureGateFix {
68
+ findingWay: string;
69
+ part: string;
70
+ axis: string;
71
+ from?: string;
72
+ to: string;
73
+ resolved: number;
74
+ safe: boolean;
75
+ }
64
76
  export declare class FigureGateFinding {
65
77
  code: string;
66
78
  message: string;
67
79
  why?: string;
68
80
  how?: string;
69
81
  at?: string;
82
+ fixes?: FigureGateFix[];
70
83
  blocking: boolean;
71
84
  }
72
85
  /**
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FigureInspection = exports.FigureGateFinding = exports.FigureSaveResult = exports.FigureFinding = exports.FigureList = exports.FigurePatch = exports.NewFigure = void 0;
3
+ exports.FigureInspection = exports.FigureGateFinding = exports.FigureGateFix = exports.FigureSaveResult = exports.FigureFinding = exports.FigureList = exports.FigurePatch = exports.NewFigure = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const type_graphql_1 = require("type-graphql");
6
6
  const figure_js_1 = require("./figure.js");
@@ -29,6 +29,10 @@ tslib_1.__decorate([
29
29
  (0, type_graphql_1.Field)({ nullable: true, description: 'Classification code used to group figures in the palette.' }),
30
30
  tslib_1.__metadata("design:type", String)
31
31
  ], NewFigure.prototype, "category", void 0);
32
+ tslib_1.__decorate([
33
+ (0, type_graphql_1.Field)(type => [String], { nullable: true, description: 'Free-form tags. A figure may carry several.' }),
34
+ tslib_1.__metadata("design:type", Array)
35
+ ], NewFigure.prototype, "tags", void 0);
32
36
  tslib_1.__decorate([
33
37
  (0, type_graphql_1.Field)({ description: 'The canonical FigureSource JSON.' }),
34
38
  tslib_1.__metadata("design:type", String)
@@ -69,6 +73,10 @@ tslib_1.__decorate([
69
73
  (0, type_graphql_1.Field)({ nullable: true, description: 'The new classification code.' }),
70
74
  tslib_1.__metadata("design:type", String)
71
75
  ], FigurePatch.prototype, "category", void 0);
76
+ tslib_1.__decorate([
77
+ (0, type_graphql_1.Field)(type => [String], { nullable: true, description: 'The new tags. An empty list clears them; omitting the field leaves them.' }),
78
+ tslib_1.__metadata("design:type", Array)
79
+ ], FigurePatch.prototype, "tags", void 0);
72
80
  tslib_1.__decorate([
73
81
  (0, type_graphql_1.Field)({ nullable: true, description: 'The new FigureSource JSON.' }),
74
82
  tslib_1.__metadata("design:type", String)
@@ -149,6 +157,41 @@ exports.FigureSaveResult = FigureSaveResult = tslib_1.__decorate([
149
157
  * 것들이라 그 칸이 필요 없었다. 발행 판정은 막는 것과 알리는 것을 함께 내므로, 어느 쪽인지를 값이
150
158
  * 스스로 말해야 한다 — 코드를 보고 화면이 다시 분류하면 규칙이 두 벌이 된다.
151
159
  */
160
+ /** A measured, one-anchor repair candidate for a release-gate sizing split. */
161
+ let FigureGateFix = class FigureGateFix {
162
+ };
163
+ exports.FigureGateFix = FigureGateFix;
164
+ tslib_1.__decorate([
165
+ (0, type_graphql_1.Field)({ description: 'Sizing test that this candidate improves, for example y or x-y.' }),
166
+ tslib_1.__metadata("design:type", String)
167
+ ], FigureGateFix.prototype, "findingWay", void 0);
168
+ tslib_1.__decorate([
169
+ (0, type_graphql_1.Field)({ description: 'Stable FigureSource part name to change.' }),
170
+ tslib_1.__metadata("design:type", String)
171
+ ], FigureGateFix.prototype, "part", void 0);
172
+ tslib_1.__decorate([
173
+ (0, type_graphql_1.Field)({ description: 'Axis whose anchor will change.' }),
174
+ tslib_1.__metadata("design:type", String)
175
+ ], FigureGateFix.prototype, "axis", void 0);
176
+ tslib_1.__decorate([
177
+ (0, type_graphql_1.Field)({ nullable: true, description: 'Current explicit anchor, if one is present.' }),
178
+ tslib_1.__metadata("design:type", String)
179
+ ], FigureGateFix.prototype, "from", void 0);
180
+ tslib_1.__decorate([
181
+ (0, type_graphql_1.Field)({ description: 'Suggested explicit anchor: min, center, max, or span.' }),
182
+ tslib_1.__metadata("design:type", String)
183
+ ], FigureGateFix.prototype, "to", void 0);
184
+ tslib_1.__decorate([
185
+ (0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'Number of blocking sizing-split checks removed by this one change.' }),
186
+ tslib_1.__metadata("design:type", Number)
187
+ ], FigureGateFix.prototype, "resolved", void 0);
188
+ tslib_1.__decorate([
189
+ (0, type_graphql_1.Field)({ description: 'True only when every blocking sizing check is clear after remeasurement.' }),
190
+ tslib_1.__metadata("design:type", Boolean)
191
+ ], FigureGateFix.prototype, "safe", void 0);
192
+ exports.FigureGateFix = FigureGateFix = tslib_1.__decorate([
193
+ (0, type_graphql_1.ObjectType)({ description: 'A reversible anchor change measured to reduce a sizing split.' })
194
+ ], FigureGateFix);
152
195
  let FigureGateFinding = class FigureGateFinding {
153
196
  };
154
197
  exports.FigureGateFinding = FigureGateFinding;
@@ -172,6 +215,13 @@ tslib_1.__decorate([
172
215
  (0, type_graphql_1.Field)({ nullable: true, description: 'Where it points: part names that came apart, or a path in the source.' }),
173
216
  tslib_1.__metadata("design:type", String)
174
217
  ], FigureGateFinding.prototype, "at", void 0);
218
+ tslib_1.__decorate([
219
+ (0, type_graphql_1.Field)(type => [FigureGateFix], {
220
+ nullable: true,
221
+ description: 'Measured repair candidates. They are previews until the author explicitly applies one.'
222
+ }),
223
+ tslib_1.__metadata("design:type", Array)
224
+ ], FigureGateFinding.prototype, "fixes", void 0);
175
225
  tslib_1.__decorate([
176
226
  (0, type_graphql_1.Field)({ description: 'True when this finding blocks the release.' }),
177
227
  tslib_1.__metadata("design:type", Boolean)
@@ -1 +1 @@
1
- {"version":3,"file":"figure-type.js","sourceRoot":"","sources":["../../../server/service/figure/figure-type.ts"],"names":[],"mappings":"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","sourcesContent":["import { Field, InputType, Int, ObjectType } from 'type-graphql'\n\nimport { Figure } from './figure.js'\n\n/**\n * 새 Figure.\n *\n * `score`·`triangles`·`groups` 는 **받지 않는다.** 서버가 `source` 에서 계산한다 —\n * 받으면 거짓이 들어오고 목록이 실물과 다른 것을 보여 준다.\n */\n@InputType({ description: 'Input for creating a new figure.' })\nexport class NewFigure {\n @Field({ description: 'Component type name. Immutable once created, unique per domain.' })\n type: string\n\n @Field({ description: 'Display name shown to authors.' })\n name: string\n\n @Field({ nullable: true, description: 'A detailed description for the figure.' })\n description?: string\n\n @Field({ nullable: true, description: 'Classification code used to group figures in the palette.' })\n category?: string\n\n @Field({ description: 'The canonical FigureSource JSON.' })\n source: string\n\n @Field({ nullable: true, description: 'JSON definition of the master-data fields an instance carries.' })\n properties?: string\n\n @Field({ nullable: true, description: \"Lifecycle state — defaults to 'draft'.\" })\n state?: 'draft' | 'released'\n\n @Field({ nullable: true, description: 'A base64 encoded thumbnail image.' })\n thumbnail?: string\n}\n\n/**\n * 수정.\n *\n * `type` 이 없다. **저장되는 식별자라 고칠 수 없다** — 바꾸면 이미 배치된 보드가\n * 타입을 못 찾아 오류 없이 빈 화면이 된다. 고칠 수 있는 이름은 `name` 뿐이다.\n */\n@InputType({ description: 'Input for updating an existing figure. The `type` field is absent by design — it is a stored identifier.' })\nexport class FigurePatch {\n @Field({ nullable: true, description: 'The new display name.' })\n name?: string\n\n @Field({ nullable: true, description: 'The new description.' })\n description?: string\n\n @Field({ nullable: true, description: 'The new classification code.' })\n category?: string\n\n @Field({ nullable: true, description: 'The new FigureSource JSON.' })\n source?: string\n\n @Field({ nullable: true, description: 'The new master-data field definition.' })\n properties?: string\n\n /*\n `state` 는 여기 없다 — **상태는 저장이 정하지 않는다.**\n\n 발행은 `releaseFigure` 가 한다. 저장으로 상태를 바꿀 수 있게 두면 발행된 판을 덮어쓰면서 같은\n 판 번호로 판본을 또 뜨게 되고(유니크 충돌), 무엇이 약속된 판인지도 흐려진다. board 도 같다.\n */\n\n @Field({ nullable: true, description: 'A base64 encoded thumbnail image.' })\n thumbnail?: string\n}\n\n@ObjectType({ description: 'A paginated list of figures.' })\nexport class FigureList {\n @Field(type => [Figure], { description: 'The figures on this page.' })\n items: Figure[]\n\n @Field(type => Int, { description: 'Total number of figures matching the query.' })\n total: number\n}\n\n/**\n * 검증 결과 한 줄.\n *\n * 정본 형식의 검증은 `@hatiolab/figure-model` 이 두 단계로 나눈다 — `errors` 는\n * 막고, `violations` 는 알린다. 그 경계를 서버가 다시 정하지 않는다.\n */\n@ObjectType({ description: 'One validation finding from figure-model.' })\nexport class FigureFinding {\n @Field({ description: 'Stable code from figure-model (ERROR_CODES / VIOLATION_CODES).' })\n code: string\n\n @Field({ description: 'Human-readable message.' })\n message: string\n\n @Field({ nullable: true, description: 'Path within the source that the finding points at.' })\n at?: string\n}\n\n/**\n * 저장 결과.\n *\n * `violations` 가 있어도 저장은 된다 — 막는 것과 알리는 것을 가른다. 화면이\n * 이것을 그대로 보여 줘야 한다. 조용히 버리면 저작자가 모른다.\n */\n@ObjectType({ description: 'Result of saving a figure, carrying non-blocking violations.' })\nexport class FigureSaveResult {\n @Field(type => Figure, { description: 'The saved figure.' })\n figure: Figure\n\n @Field(type => [FigureFinding], { description: 'Non-blocking findings. The save succeeded despite these.' })\n violations: FigureFinding[]\n}\n\n/**\n * 발행 판정 한 줄.\n *\n * `FigureFinding` 과 칸이 겹치지만 **`blocking` 이 하나 더 있다.** 저장 쪽 발견은 정의상 막지 않는\n * 것들이라 그 칸이 필요 없었다. 발행 판정은 막는 것과 알리는 것을 함께 내므로, 어느 쪽인지를 값이\n * 스스로 말해야 한다 — 코드를 보고 화면이 다시 분류하면 규칙이 두 벌이 된다.\n */\n@ObjectType({ description: 'One finding from the release gate.' })\nexport class FigureGateFinding {\n @Field({ description: 'Stable code — figure-model validation codes, or sizing-splits / sizing-loose.' })\n code: string\n\n @Field({ description: 'Human-readable message.' })\n message: string\n\n @Field({ nullable: true, description: 'Why this is a problem — what a person sees when it happens.' })\n why?: string\n\n @Field({ nullable: true, description: 'What to change, naming the part and the value.' })\n how?: string\n\n @Field({ nullable: true, description: 'Where it points: part names that came apart, or a path in the source.' })\n at?: string\n\n @Field({ description: 'True when this finding blocks the release.' })\n blocking: boolean\n}\n\n/**\n * 발행 판정.\n *\n * 화면이 **발행을 누르기 전에** 물어보는 자리다. 눌러 보고 거절당하는 것으로 알게 되면, 무엇을\n * 고쳐야 하는지 모른 채 단추만 두드리게 된다.\n */\n@ObjectType({ description: 'Whether a figure may be released, and why not.' })\nexport class FigureInspection {\n @Field({ description: 'True when at least one finding blocks the release.' })\n blocked: boolean\n\n @Field(type => [FigureGateFinding], { description: 'Findings, blocking and advisory together.' })\n findings: FigureGateFinding[]\n}\n"]}
1
+ {"version":3,"file":"figure-type.js","sourceRoot":"","sources":["../../../server/service/figure/figure-type.ts"],"names":[],"mappings":"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","sourcesContent":["import { Field, InputType, Int, ObjectType } from 'type-graphql'\n\nimport { Figure } from './figure.js'\n\n/**\n * 새 Figure.\n *\n * `score`·`triangles`·`groups` 는 **받지 않는다.** 서버가 `source` 에서 계산한다 —\n * 받으면 거짓이 들어오고 목록이 실물과 다른 것을 보여 준다.\n */\n@InputType({ description: 'Input for creating a new figure.' })\nexport class NewFigure {\n @Field({ description: 'Component type name. Immutable once created, unique per domain.' })\n type: string\n\n @Field({ description: 'Display name shown to authors.' })\n name: string\n\n @Field({ nullable: true, description: 'A detailed description for the figure.' })\n description?: string\n\n @Field({ nullable: true, description: 'Classification code used to group figures in the palette.' })\n category?: string\n\n @Field(type => [String], { nullable: true, description: 'Free-form tags. A figure may carry several.' })\n tags?: string[]\n\n @Field({ description: 'The canonical FigureSource JSON.' })\n source: string\n\n @Field({ nullable: true, description: 'JSON definition of the master-data fields an instance carries.' })\n properties?: string\n\n @Field({ nullable: true, description: \"Lifecycle state — defaults to 'draft'.\" })\n state?: 'draft' | 'released'\n\n @Field({ nullable: true, description: 'A base64 encoded thumbnail image.' })\n thumbnail?: string\n}\n\n/**\n * 수정.\n *\n * `type` 이 없다. **저장되는 식별자라 고칠 수 없다** — 바꾸면 이미 배치된 보드가\n * 타입을 못 찾아 오류 없이 빈 화면이 된다. 고칠 수 있는 이름은 `name` 뿐이다.\n */\n@InputType({ description: 'Input for updating an existing figure. The `type` field is absent by design — it is a stored identifier.' })\nexport class FigurePatch {\n @Field({ nullable: true, description: 'The new display name.' })\n name?: string\n\n @Field({ nullable: true, description: 'The new description.' })\n description?: string\n\n @Field({ nullable: true, description: 'The new classification code.' })\n category?: string\n\n /*\n 빈 배열과 안 준 것을 가른다. `undefined` 는 「건드리지 않는다」, `[]` 는 「전부 지운다」다.\n 둘을 뭉개면 태그를 다 지우려는 저작자가 지울 방법을 잃는다.\n */\n @Field(type => [String], { nullable: true, description: 'The new tags. An empty list clears them; omitting the field leaves them.' })\n tags?: string[]\n\n @Field({ nullable: true, description: 'The new FigureSource JSON.' })\n source?: string\n\n @Field({ nullable: true, description: 'The new master-data field definition.' })\n properties?: string\n\n /*\n `state` 는 여기 없다 — **상태는 저장이 정하지 않는다.**\n\n 발행은 `releaseFigure` 가 한다. 저장으로 상태를 바꿀 수 있게 두면 발행된 판을 덮어쓰면서 같은\n 판 번호로 판본을 또 뜨게 되고(유니크 충돌), 무엇이 약속된 판인지도 흐려진다. board 도 같다.\n */\n\n @Field({ nullable: true, description: 'A base64 encoded thumbnail image.' })\n thumbnail?: string\n}\n\n@ObjectType({ description: 'A paginated list of figures.' })\nexport class FigureList {\n @Field(type => [Figure], { description: 'The figures on this page.' })\n items: Figure[]\n\n @Field(type => Int, { description: 'Total number of figures matching the query.' })\n total: number\n}\n\n/**\n * 검증 결과 한 줄.\n *\n * 정본 형식의 검증은 `@hatiolab/figure-model` 이 두 단계로 나눈다 — `errors` 는\n * 막고, `violations` 는 알린다. 그 경계를 서버가 다시 정하지 않는다.\n */\n@ObjectType({ description: 'One validation finding from figure-model.' })\nexport class FigureFinding {\n @Field({ description: 'Stable code from figure-model (ERROR_CODES / VIOLATION_CODES).' })\n code: string\n\n @Field({ description: 'Human-readable message.' })\n message: string\n\n @Field({ nullable: true, description: 'Path within the source that the finding points at.' })\n at?: string\n}\n\n/**\n * 저장 결과.\n *\n * `violations` 가 있어도 저장은 된다 — 막는 것과 알리는 것을 가른다. 화면이\n * 이것을 그대로 보여 줘야 한다. 조용히 버리면 저작자가 모른다.\n */\n@ObjectType({ description: 'Result of saving a figure, carrying non-blocking violations.' })\nexport class FigureSaveResult {\n @Field(type => Figure, { description: 'The saved figure.' })\n figure: Figure\n\n @Field(type => [FigureFinding], { description: 'Non-blocking findings. The save succeeded despite these.' })\n violations: FigureFinding[]\n}\n\n/**\n * 발행 판정 한 줄.\n *\n * `FigureFinding` 과 칸이 겹치지만 **`blocking` 이 하나 더 있다.** 저장 쪽 발견은 정의상 막지 않는\n * 것들이라 그 칸이 필요 없었다. 발행 판정은 막는 것과 알리는 것을 함께 내므로, 어느 쪽인지를 값이\n * 스스로 말해야 한다 — 코드를 보고 화면이 다시 분류하면 규칙이 두 벌이 된다.\n */\n/** A measured, one-anchor repair candidate for a release-gate sizing split. */\n@ObjectType({ description: 'A reversible anchor change measured to reduce a sizing split.' })\nexport class FigureGateFix {\n @Field({ description: 'Sizing test that this candidate improves, for example y or x-y.' })\n findingWay: string\n\n @Field({ description: 'Stable FigureSource part name to change.' })\n part: string\n\n @Field({ description: 'Axis whose anchor will change.' })\n axis: string\n\n @Field({ nullable: true, description: 'Current explicit anchor, if one is present.' })\n from?: string\n\n @Field({ description: 'Suggested explicit anchor: min, center, max, or span.' })\n to: string\n\n @Field(type => Int, { description: 'Number of blocking sizing-split checks removed by this one change.' })\n resolved: number\n\n @Field({ description: 'True only when every blocking sizing check is clear after remeasurement.' })\n safe: boolean\n}\n\n@ObjectType({ description: 'One finding from the release gate.' })\nexport class FigureGateFinding {\n @Field({ description: 'Stable code — figure-model validation codes, or sizing-splits / sizing-loose.' })\n code: string\n\n @Field({ description: 'Human-readable message.' })\n message: string\n\n @Field({ nullable: true, description: 'Why this is a problem — what a person sees when it happens.' })\n why?: string\n\n @Field({ nullable: true, description: 'What to change, naming the part and the value.' })\n how?: string\n\n @Field({ nullable: true, description: 'Where it points: part names that came apart, or a path in the source.' })\n at?: string\n\n @Field(type => [FigureGateFix], {\n nullable: true,\n description: 'Measured repair candidates. They are previews until the author explicitly applies one.'\n })\n fixes?: FigureGateFix[]\n\n @Field({ description: 'True when this finding blocks the release.' })\n blocking: boolean\n}\n\n/**\n * 발행 판정.\n *\n * 화면이 **발행을 누르기 전에** 물어보는 자리다. 눌러 보고 거절당하는 것으로 알게 되면, 무엇을\n * 고쳐야 하는지 모른 채 단추만 두드리게 된다.\n */\n@ObjectType({ description: 'Whether a figure may be released, and why not.' })\nexport class FigureInspection {\n @Field({ description: 'True when at least one finding blocks the release.' })\n blocked: boolean\n\n @Field(type => [FigureGateFinding], { description: 'Findings, blocking and advisory together.' })\n findings: FigureGateFinding[]\n}\n"]}
@@ -45,8 +45,24 @@ export declare class Figure {
45
45
  type?: string;
46
46
  name?: string;
47
47
  description?: string;
48
- /** 분류 코드. 팔레트에서 묶어 보이는 기준. */
48
+ /**
49
+ * 분류 하나. **팔레트에서 묶어 보이는 기준이다.**
50
+ *
51
+ * 도형 하나에 하나뿐이다 — 팔레트는 목록을 한 벌로 나눠 보여 주므로, 한 도형이 두 묶음에
52
+ * 동시에 있으면 저작자가 같은 것을 두 번 만난다.
53
+ */
49
54
  category?: string;
55
+ /**
56
+ * 태그. **여럿 붙는다.**
57
+ *
58
+ * 분류와 다른 일을 한다. 분류는 「이것이 무엇인가」를 한 번 정하는 것이고, 태그는 「어디에
59
+ * 걸리는가」를 여러 갈래로 적는 것이다 — 컨베이어이면서 반출 구역의 것일 수 있다. 그래서
60
+ * 분류로는 못 하는 찾기가 된다.
61
+ *
62
+ * `simple-json` 으로 담는다. 사내 선례가 그것이고(`ops-master` · `organization` ·
63
+ * `personalization`), 드라이버마다 배열 타입이 다른 것을 피한다.
64
+ */
65
+ tags?: string[];
50
66
  state?: 'draft' | 'released';
51
67
  /**
52
68
  * `FigureSource` JSON.
@@ -79,9 +79,14 @@ tslib_1.__decorate([
79
79
  ], Figure.prototype, "description", void 0);
80
80
  tslib_1.__decorate([
81
81
  (0, typeorm_1.Column)({ nullable: true }),
82
- (0, type_graphql_1.Field)({ nullable: true, description: 'Classification code used to group figures in the palette.' }),
82
+ (0, type_graphql_1.Field)({ nullable: true, description: 'Classification code used to group figures in the palette. One per figure.' }),
83
83
  tslib_1.__metadata("design:type", String)
84
84
  ], Figure.prototype, "category", void 0);
85
+ tslib_1.__decorate([
86
+ (0, typeorm_1.Column)('simple-json', { nullable: true, default: null }),
87
+ (0, type_graphql_1.Field)(type => [String], { nullable: true, description: 'Free-form tags. A figure may carry several.' }),
88
+ tslib_1.__metadata("design:type", Array)
89
+ ], Figure.prototype, "tags", void 0);
85
90
  tslib_1.__decorate([
86
91
  (0, typeorm_1.Column)({ nullable: true, default: 'draft' }),
87
92
  (0, type_graphql_1.Field)({ nullable: true, description: "Lifecycle state — 'draft' figures do not appear in the board palette." }),
@@ -1 +1 @@
1
- {"version":3,"file":"figure.js","sourceRoot":"","sources":["../../../server/service/figure/figure.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Column,\n CreateDateColumn,\n DeleteDateColumn,\n Entity,\n Index,\n ManyToOne,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\nimport { Field, Float, ID, Int, ObjectType } from 'type-graphql'\n\nimport { User } from '@things-factory/auth-base'\nimport { Domain, longTextColumn } from '@things-factory/shell'\n\n/**\n * Figure — 저작된 3D 컴포넌트 한 종류.\n *\n * `Board` 를 준용한다. 다른 점만 주석으로 적는다.\n *\n * ## 무엇을 담나 — `FigureSource` 다, 씬 모델이 아니다\n *\n * 정본 형식은 `@hatiolab/figure-model` 이 정한다(`FigureSource`). 씬 컴포넌트 트리를\n * 담지 않는다. 담으면 저작도구가 씬 편집기가 되고, 부품 이름·상태 슬롯·애니메이션\n * 자리가 형식에 없어 따로 관리해야 한다.\n *\n * 청사진(`FigureBlueprint`)은 **저장하지 않는다.** 정본에서 파생되는 캐시이므로\n * 소비하는 쪽이 `compile()` 로 만든다. 저장하면 정본과 갈린다.\n *\n * 그래서 `baseWidth`·`anchor`·`statusSlots`·`parts`·`animations` 같은 것을 컬럼으로\n * 두지 않는다 — 전부 `source` 안에 이미 있는 자리다. 두 벌이면 갈린다.\n *\n * 설계: `docs/design/figure-modules.md` §2\n */\n@Entity()\n@Index('ix_figure_1', (figure: Figure) => [figure.domain, figure.type], {\n unique: true,\n where: '\"deleted_at\" IS NULL'\n})\n@Index('ix_figure_2', (figure: Figure) => [figure.domain, figure.category])\n@ObjectType({ description: 'An authored 3D component type — the canonical FigureSource plus its metadata.' })\nexport class Figure {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { nullable: true, description: 'Unique identifier for the figure.' })\n readonly id?: string\n\n /**\n * 발행된 판의 번호 — **발행할 때만 올라간다.**\n *\n * 저장 횟수가 아니다. 저작 중에는 몇 번을 고쳐 저장하든 그대로이고, `draft` 가 `released` 가\n * 되는 순간에만 하나 오른다. 소비처가 「몇 번째 판을 쓰고 있나」를 이 값으로 말한다.\n *\n * 한 번도 발행되지 않았으면 **0** 이다. 1 로 시작하면 아직 아무에게도 약속하지 않은 초안이\n * 첫 판인 것처럼 보인다.\n */\n @Column({ nullable: true, default: 0 })\n @Field(type => Int, {\n nullable: true,\n description: 'Published version. Raised when the figure is released, not when it is saved. 0 means never released.'\n })\n version?: number = 0\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'The domain this figure belongs to.' })\n domain?: Domain\n\n @RelationId((figure: Figure) => figure.domain)\n domainId?: string\n\n /**\n * 컴포넌트 타입 이름. **저장되는 식별자다.**\n *\n * 배치된 보드가 이 이름으로 타입을 찾는다. 바꾸면 그 보드는 타입을 못 찾아\n * **오류 없이 빈 화면**이 된다. 그래서 만들 때 정하고 잠근다 — 고칠 수 있는 것은\n * 표시 이름(`name`)뿐이다. 수정 리졸버가 이 필드를 받지 않는다.\n *\n * `Board.type`(main/sub/popup)과 뜻이 전혀 다르다. 혼동하지 않는다.\n */\n @Column()\n @Field({ description: 'Component type name. A stored identifier — unique per domain and immutable once created.' })\n type?: string\n\n @Column()\n @Field({ description: 'Display name shown to authors. Safe to rename.' })\n name?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'A detailed description for the figure.' })\n description?: string\n\n /** 분류 코드. 팔레트에서 묶어 보이는 기준. */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Classification code used to group figures in the palette.' })\n category?: string\n\n @Column({ nullable: true, default: 'draft' })\n @Field({ nullable: true, description: \"Lifecycle state — 'draft' figures do not appear in the board palette.\" })\n state?: 'draft' | 'released'\n\n /**\n * `FigureSource` JSON.\n *\n * `Board.model` 자리이지만 이름을 바꿨다 — 「씬 모델이 아니다」를 필드 이름이\n * 드러내야 한다.\n */\n @Column({ nullable: true, ...longTextColumn() })\n @Field({ nullable: true, description: 'The canonical FigureSource JSON authored by the modeller.' })\n source?: string\n\n // ── 파생값 — 저장하는 이유가 하나뿐이다 ──────────────────────────────────\n //\n // 셋 다 `source` 에서 계산된다. 그런데도 컬럼으로 두는 이유는 **목록 화면이\n // 정렬·필터를 해야** 하기 때문이다. JSON 을 파싱해 정렬할 수는 없다.\n //\n // 저장할 때 `figure-model` 로 계산해 채운다. **클라이언트가 준 값을 받지 않는다** —\n // 받으면 거짓이 들어오고, 목록이 실물과 다른 것을 보여 준다.\n\n /** 재활용 점수 0~100. `scoreOf()` 가 계산한다. */\n @Column({ type: 'float', nullable: true })\n @Field(type => Float, { nullable: true, description: 'Reuse score 0-100, computed from source on save.' })\n score?: number\n\n /** 삼각형 수. `costOf()` 가 계산한다. */\n @Column({ nullable: true })\n @Field(type => Int, { nullable: true, description: 'Triangle count, computed from source on save.' })\n triangles?: number\n\n /** 재질 묶음 수 — 인스턴스당 draw call 의 근거. */\n @Column({ nullable: true })\n @Field(type => Int, { nullable: true, description: 'Material group count — the basis of per-instance draw calls.' })\n groups?: number\n\n /** 기준정보 항목 정의. Figure 를 배치할 때 인스턴스가 채우는 자리. */\n @Column({ nullable: true, ...longTextColumn() })\n @Field({ nullable: true, description: 'JSON definition of the master-data fields an instance of this figure carries.' })\n properties?: string\n\n @Column({ nullable: true, ...longTextColumn() })\n @Field({ nullable: true, description: 'A base64 encoded thumbnail image of the figure.' })\n thumbnail?: string\n\n /**\n * 썸네일이 **썸네일로서** 마지막으로 갱신된 시각. `updatedAt` 과는 별개 도메인이다 —\n * 이름·설명만 고쳤을 때는 갱신되지 않는다. `Board` 와 같은 방식.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Timestamp of the last thumbnail generation. Separate lifecycle from updatedAt.' })\n thumbnailUpdatedAt?: Date\n\n @Column({ type: 'float', nullable: true, default: 0 })\n @Field(type => Float, { nullable: true, description: 'Sort order within a listing.' })\n sortOrder?: number\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'Creation timestamp.' })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true, description: 'Last update timestamp.' })\n updatedAt?: Date\n\n @DeleteDateColumn()\n @Field({ nullable: true, description: 'Soft-delete timestamp.' })\n deletedAt?: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'The user who created this figure.' })\n creator?: User\n\n @RelationId((figure: Figure) => figure.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'The user who last updated this figure.' })\n updater?: User\n\n @RelationId((figure: Figure) => figure.updater)\n updaterId?: string\n}\n"]}
1
+ {"version":3,"file":"figure.js","sourceRoot":"","sources":["../../../server/service/figure/figure.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Column,\n CreateDateColumn,\n DeleteDateColumn,\n Entity,\n Index,\n ManyToOne,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\nimport { Field, Float, ID, Int, ObjectType } from 'type-graphql'\n\nimport { User } from '@things-factory/auth-base'\nimport { Domain, longTextColumn } from '@things-factory/shell'\n\n/**\n * Figure — 저작된 3D 컴포넌트 한 종류.\n *\n * `Board` 를 준용한다. 다른 점만 주석으로 적는다.\n *\n * ## 무엇을 담나 — `FigureSource` 다, 씬 모델이 아니다\n *\n * 정본 형식은 `@hatiolab/figure-model` 이 정한다(`FigureSource`). 씬 컴포넌트 트리를\n * 담지 않는다. 담으면 저작도구가 씬 편집기가 되고, 부품 이름·상태 슬롯·애니메이션\n * 자리가 형식에 없어 따로 관리해야 한다.\n *\n * 청사진(`FigureBlueprint`)은 **저장하지 않는다.** 정본에서 파생되는 캐시이므로\n * 소비하는 쪽이 `compile()` 로 만든다. 저장하면 정본과 갈린다.\n *\n * 그래서 `baseWidth`·`anchor`·`statusSlots`·`parts`·`animations` 같은 것을 컬럼으로\n * 두지 않는다 — 전부 `source` 안에 이미 있는 자리다. 두 벌이면 갈린다.\n *\n * 설계: `docs/design/figure-modules.md` §2\n */\n@Entity()\n@Index('ix_figure_1', (figure: Figure) => [figure.domain, figure.type], {\n unique: true,\n where: '\"deleted_at\" IS NULL'\n})\n@Index('ix_figure_2', (figure: Figure) => [figure.domain, figure.category])\n@ObjectType({ description: 'An authored 3D component type — the canonical FigureSource plus its metadata.' })\nexport class Figure {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { nullable: true, description: 'Unique identifier for the figure.' })\n readonly id?: string\n\n /**\n * 발행된 판의 번호 — **발행할 때만 올라간다.**\n *\n * 저장 횟수가 아니다. 저작 중에는 몇 번을 고쳐 저장하든 그대로이고, `draft` 가 `released` 가\n * 되는 순간에만 하나 오른다. 소비처가 「몇 번째 판을 쓰고 있나」를 이 값으로 말한다.\n *\n * 한 번도 발행되지 않았으면 **0** 이다. 1 로 시작하면 아직 아무에게도 약속하지 않은 초안이\n * 첫 판인 것처럼 보인다.\n */\n @Column({ nullable: true, default: 0 })\n @Field(type => Int, {\n nullable: true,\n description: 'Published version. Raised when the figure is released, not when it is saved. 0 means never released.'\n })\n version?: number = 0\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'The domain this figure belongs to.' })\n domain?: Domain\n\n @RelationId((figure: Figure) => figure.domain)\n domainId?: string\n\n /**\n * 컴포넌트 타입 이름. **저장되는 식별자다.**\n *\n * 배치된 보드가 이 이름으로 타입을 찾는다. 바꾸면 그 보드는 타입을 못 찾아\n * **오류 없이 빈 화면**이 된다. 그래서 만들 때 정하고 잠근다 — 고칠 수 있는 것은\n * 표시 이름(`name`)뿐이다. 수정 리졸버가 이 필드를 받지 않는다.\n *\n * `Board.type`(main/sub/popup)과 뜻이 전혀 다르다. 혼동하지 않는다.\n */\n @Column()\n @Field({ description: 'Component type name. A stored identifier — unique per domain and immutable once created.' })\n type?: string\n\n @Column()\n @Field({ description: 'Display name shown to authors. Safe to rename.' })\n name?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'A detailed description for the figure.' })\n description?: string\n\n /**\n * 분류 하나. **팔레트에서 묶어 보이는 기준이다.**\n *\n * 도형 하나에 하나뿐이다 — 팔레트는 목록을 한 벌로 나눠 보여 주므로, 한 도형이 두 묶음에\n * 동시에 있으면 저작자가 같은 것을 두 번 만난다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Classification code used to group figures in the palette. One per figure.' })\n category?: string\n\n /**\n * 태그. **여럿 붙는다.**\n *\n * 분류와 다른 일을 한다. 분류는 「이것이 무엇인가」를 한 번 정하는 것이고, 태그는 「어디에\n * 걸리는가」를 여러 갈래로 적는 것이다 — 컨베이어이면서 반출 구역의 것일 수 있다. 그래서\n * 분류로는 못 하는 찾기가 된다.\n *\n * `simple-json` 으로 담는다. 사내 선례가 그것이고(`ops-master` · `organization` ·\n * `personalization`), 드라이버마다 배열 타입이 다른 것을 피한다.\n */\n /* `simple-json` 과 `default: null` 은 사내 선례 그대로다 — board-template · attachment 가 같은 모양이다. */\n @Column('simple-json', { nullable: true, default: null })\n @Field(type => [String], { nullable: true, description: 'Free-form tags. A figure may carry several.' })\n tags?: string[]\n\n @Column({ nullable: true, default: 'draft' })\n @Field({ nullable: true, description: \"Lifecycle state — 'draft' figures do not appear in the board palette.\" })\n state?: 'draft' | 'released'\n\n /**\n * `FigureSource` JSON.\n *\n * `Board.model` 자리이지만 이름을 바꿨다 — 「씬 모델이 아니다」를 필드 이름이\n * 드러내야 한다.\n */\n @Column({ nullable: true, ...longTextColumn() })\n @Field({ nullable: true, description: 'The canonical FigureSource JSON authored by the modeller.' })\n source?: string\n\n // ── 파생값 — 저장하는 이유가 하나뿐이다 ──────────────────────────────────\n //\n // 셋 다 `source` 에서 계산된다. 그런데도 컬럼으로 두는 이유는 **목록 화면이\n // 정렬·필터를 해야** 하기 때문이다. JSON 을 파싱해 정렬할 수는 없다.\n //\n // 저장할 때 `figure-model` 로 계산해 채운다. **클라이언트가 준 값을 받지 않는다** —\n // 받으면 거짓이 들어오고, 목록이 실물과 다른 것을 보여 준다.\n\n /** 재활용 점수 0~100. `scoreOf()` 가 계산한다. */\n @Column({ type: 'float', nullable: true })\n @Field(type => Float, { nullable: true, description: 'Reuse score 0-100, computed from source on save.' })\n score?: number\n\n /** 삼각형 수. `costOf()` 가 계산한다. */\n @Column({ nullable: true })\n @Field(type => Int, { nullable: true, description: 'Triangle count, computed from source on save.' })\n triangles?: number\n\n /** 재질 묶음 수 — 인스턴스당 draw call 의 근거. */\n @Column({ nullable: true })\n @Field(type => Int, { nullable: true, description: 'Material group count — the basis of per-instance draw calls.' })\n groups?: number\n\n /** 기준정보 항목 정의. Figure 를 배치할 때 인스턴스가 채우는 자리. */\n @Column({ nullable: true, ...longTextColumn() })\n @Field({ nullable: true, description: 'JSON definition of the master-data fields an instance of this figure carries.' })\n properties?: string\n\n @Column({ nullable: true, ...longTextColumn() })\n @Field({ nullable: true, description: 'A base64 encoded thumbnail image of the figure.' })\n thumbnail?: string\n\n /**\n * 썸네일이 **썸네일로서** 마지막으로 갱신된 시각. `updatedAt` 과는 별개 도메인이다 —\n * 이름·설명만 고쳤을 때는 갱신되지 않는다. `Board` 와 같은 방식.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Timestamp of the last thumbnail generation. Separate lifecycle from updatedAt.' })\n thumbnailUpdatedAt?: Date\n\n @Column({ type: 'float', nullable: true, default: 0 })\n @Field(type => Float, { nullable: true, description: 'Sort order within a listing.' })\n sortOrder?: number\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'Creation timestamp.' })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true, description: 'Last update timestamp.' })\n updatedAt?: Date\n\n @DeleteDateColumn()\n @Field({ nullable: true, description: 'Soft-delete timestamp.' })\n deletedAt?: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'The user who created this figure.' })\n creator?: User\n\n @RelationId((figure: Figure) => figure.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'The user who last updated this figure.' })\n updater?: User\n\n @RelationId((figure: Figure) => figure.updater)\n updaterId?: string\n}\n"]}