@things-factory/figure-service 10.1.74 → 10.1.76
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist-server/service/figure/figure-format.d.ts +6 -6
- package/dist-server/service/figure/figure-format.js +32 -32
- package/dist-server/service/figure/figure-format.js.map +1 -1
- package/dist-server/service/figure/figure-history.js +1 -1
- package/dist-server/service/figure/figure-history.js.map +1 -1
- package/dist-server/service/figure/figure-mutation.d.ts +1 -1
- package/dist-server/service/figure/figure-mutation.js +12 -12
- package/dist-server/service/figure/figure-mutation.js.map +1 -1
- package/dist-server/service/figure/{figure-propose-v3.d.ts → figure-propose-steps.d.ts} +16 -16
- package/dist-server/service/figure/{figure-propose-v3.js → figure-propose-steps.js} +27 -27
- package/dist-server/service/figure/figure-propose-steps.js.map +1 -0
- package/dist-server/service/figure/figure-propose-type.js +3 -3
- package/dist-server/service/figure/figure-propose-type.js.map +1 -1
- package/dist-server/service/figure/figure-query.d.ts +2 -2
- package/dist-server/service/figure/figure-query.js +5 -5
- package/dist-server/service/figure/figure-query.js.map +1 -1
- package/dist-server/service/figure/figure-tools.js +14 -14
- package/dist-server/service/figure/figure-tools.js.map +1 -1
- package/dist-server/service/figure/figure-type.js +2 -2
- package/dist-server/service/figure/figure-type.js.map +1 -1
- package/dist-server/service/figure/{figure-v3-write.d.ts → figure-write.d.ts} +3 -3
- package/dist-server/service/figure/{figure-v3-write.js → figure-write.js} +10 -10
- package/dist-server/service/figure/figure-write.js.map +1 -0
- package/dist-server/service/figure/figure.d.ts +2 -2
- package/dist-server/service/figure/figure.js +2 -2
- package/dist-server/service/figure/figure.js.map +1 -1
- package/dist-server/service/figure-recipe/figure-recipe-templates.d.ts +2 -2
- package/dist-server/service/figure-recipe/figure-recipe-templates.js +1 -1
- package/dist-server/service/figure-recipe/figure-recipe-templates.js.map +1 -1
- package/dist-server/service/figure-recipe/figure-recipe-type.js +1 -1
- package/dist-server/service/figure-recipe/figure-recipe-type.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/recipes/_lib/model.mts +19 -19
- package/recipes/air-cylinder/models/default.json +1 -1
- package/recipes/air-shower/models/default.json +1 -1
- package/recipes/barrier-gate/models/default.json +1 -1
- package/recipes/base-frame/models/default.json +1 -1
- package/recipes/belt-surface/models/default.json +1 -1
- package/recipes/bin-row/models/default.json +1 -1
- package/recipes/bollard-row/models/default.json +1 -1
- package/recipes/buzzer/models/default.json +1 -1
- package/recipes/cabinet-doors/models/default.json +1 -1
- package/recipes/cabinet-doors/models/doors=1.json +1 -1
- package/recipes/cabinet-doors/models/doors=2.json +1 -1
- package/recipes/cable-tray/models/default.json +1 -1
- package/recipes/camera-mount/models/default.json +1 -1
- package/recipes/canopy/models/default.json +1 -1
- package/recipes/cart/models/default.json +1 -1
- package/recipes/cart-handle/models/default.json +1 -1
- package/recipes/casters-four/models/default.json +1 -1
- package/recipes/ceiling-rail-carriage/models/default.json +1 -1
- package/recipes/chain-rails/models/default.json +1 -1
- package/recipes/charging-station/models/default.json +1 -1
- package/recipes/column-row/models/default.json +1 -1
- package/recipes/control-cabinet/models/default.json +1 -1
- package/recipes/conveyor-skirts/models/default.json +1 -1
- package/recipes/counter-display/models/default.json +1 -1
- package/recipes/dock-bumpers/models/default.json +1 -1
- package/recipes/dock-leveler/models/default.json +1 -1
- package/recipes/double-hinged-door/models/default.json +1 -1
- package/recipes/drawer-stack/models/default.json +1 -1
- package/recipes/drawer-stack/models/drawers=2.json +1 -1
- package/recipes/drawer-stack/models/drawers=3.json +1 -1
- package/recipes/drawer-stack/models/drawers=4.json +1 -1
- package/recipes/drip-tray/models/default.json +1 -1
- package/recipes/duct-fan/models/default.json +1 -1
- package/recipes/emergency-stop/models/default.json +1 -1
- package/recipes/end-covers/models/default.json +1 -1
- package/recipes/ffu-row/models/default.json +1 -1
- package/recipes/fire-extinguisher/models/default.json +1 -1
- package/recipes/first-aid-box/models/default.json +1 -1
- package/recipes/floor-zone/models/default.json +1 -1
- package/recipes/folding-door/models/default.json +1 -1
- package/recipes/fork/models/default.json +1 -1
- package/recipes/gantry-frame/models/default.json +1 -1
- package/recipes/gripper-2f/models/default.json +1 -1
- package/recipes/guard-cover/models/default.json +1 -1
- package/recipes/handrail/models/default.json +1 -1
- package/recipes/hatch/models/default.json +1 -1
- package/recipes/hinged-door/models/default.json +1 -1
- package/recipes/hinged-door/models/hinge=left.json +1 -1
- package/recipes/hinged-door/models/hinge=right.json +1 -1
- package/recipes/hmi-monitor/models/default.json +1 -1
- package/recipes/hopper/models/default.json +1 -1
- package/recipes/interlock-switch/models/default.json +1 -1
- package/recipes/junction-box/models/default.json +1 -1
- package/recipes/ladder/models/default.json +1 -1
- package/recipes/legs-four/models/default.json +1 -1
- package/recipes/leveling-feet/models/default.json +1 -1
- package/recipes/lift-updown/models/default.json +1 -1
- package/recipes/light-curtain/models/default.json +1 -1
- package/recipes/limit-switch/models/default.json +1 -1
- package/recipes/linear-guide/models/default.json +1 -1
- package/recipes/locker-row/models/default.json +1 -1
- package/recipes/motor-box/models/default.json +1 -1
- package/recipes/observation-window/models/default.json +1 -1
- package/recipes/operator-panel/models/buttons=2.json +1 -1
- package/recipes/operator-panel/models/buttons=3.json +1 -1
- package/recipes/operator-panel/models/buttons=4.json +1 -1
- package/recipes/operator-panel/models/buttons=5.json +1 -1
- package/recipes/operator-panel/models/default.json +1 -1
- package/recipes/pallet/models/default.json +1 -1
- package/recipes/partition-wall/models/default.json +1 -1
- package/recipes/pass-box/models/default.json +1 -1
- package/recipes/peg-board/models/default.json +1 -1
- package/recipes/photo-sensor-pair/models/default.json +1 -1
- package/recipes/pipe-run/models/default.json +1 -1
- package/recipes/platform/models/default.json +1 -1
- package/recipes/power-box/models/default.json +1 -1
- package/recipes/pressure-gauge/models/default.json +1 -1
- package/recipes/pull-cord/models/default.json +1 -1
- package/recipes/pump/models/default.json +1 -1
- package/recipes/pusher/models/default.json +1 -1
- package/recipes/robot-plinth/models/default.json +1 -1
- package/recipes/roll-up-shutter/models/default.json +1 -1
- package/recipes/roller-row/models/default.json +1 -1
- package/recipes/room-shell/models/default.json +1 -1
- package/recipes/rotating-beacon/models/default.json +1 -1
- package/recipes/safety-fence/models/default.json +1 -1
- package/recipes/safety-mat/models/default.json +1 -1
- package/recipes/safety-sign-stand/models/default.json +1 -1
- package/recipes/scanner-mount/models/default.json +1 -1
- package/recipes/selector-switch/models/default.json +1 -1
- package/recipes/shelf-tiers/models/default.json +1 -1
- package/recipes/shelf-tiers/models/tiers=2.json +1 -1
- package/recipes/shelf-tiers/models/tiers=3.json +1 -1
- package/recipes/shelf-tiers/models/tiers=4.json +1 -1
- package/recipes/shelf-tiers/models/tiers=5.json +1 -1
- package/recipes/side-guides/models/default.json +1 -1
- package/recipes/sign-board/models/default.json +1 -1
- package/recipes/signal-tower-3/models/default.json +1 -1
- package/recipes/sliding-door/models/default.json +1 -1
- package/recipes/sliding-door/models/leaves=1,lamp=false.json +1 -1
- package/recipes/sliding-door/models/leaves=1,lamp=true.json +1 -1
- package/recipes/sliding-door/models/leaves=2,lamp=false.json +1 -1
- package/recipes/sliding-door/models/leaves=2,lamp=true.json +1 -1
- package/recipes/stairs/models/default.json +1 -1
- package/recipes/status-lamp/models/default.json +1 -1
- package/recipes/stopper/models/default.json +1 -1
- package/recipes/suction-pads/models/default.json +1 -1
- package/recipes/tank/models/default.json +1 -1
- package/recipes/telescopic-arm/models/default.json +1 -1
- package/recipes/toe-board/models/default.json +1 -1
- package/recipes/tote/models/default.json +1 -1
- package/recipes/traffic-cones/models/default.json +1 -1
- package/recipes/transfer-belts/models/default.json +1 -1
- package/recipes/tray-stack/models/default.json +1 -1
- package/recipes/tray-stack/models/trays=3.json +1 -1
- package/recipes/tray-stack/models/trays=4.json +1 -1
- package/recipes/tray-stack/models/trays=5.json +1 -1
- package/recipes/trough/models/default.json +1 -1
- package/recipes/turntable/models/default.json +1 -1
- package/recipes/valve-handle/models/default.json +1 -1
- package/recipes/waste-bin/models/default.json +1 -1
- package/recipes/work-light/models/default.json +1 -1
- package/recipes/workbench-top/models/default.json +1 -1
- package/scripts/build-templates.mts +7 -7
- package/scripts/templates.test.mts +16 -16
- package/server/service/figure/figure-blueprint-query.test.ts +3 -3
- package/server/service/figure/figure-format.test.ts +57 -57
- package/server/service/figure/figure-format.ts +39 -39
- package/server/service/figure/figure-history.ts +1 -1
- package/server/service/figure/figure-inspect-db.test.ts +14 -14
- package/server/service/figure/figure-llm-smoke.test.ts +7 -7
- package/server/service/figure/figure-mutation.ts +12 -12
- package/server/service/figure/{figure-propose-v3.test.ts → figure-propose-steps.test.ts} +18 -18
- package/server/service/figure/{figure-propose-v3.ts → figure-propose-steps.ts} +33 -33
- package/server/service/figure/figure-propose-type.ts +3 -3
- package/server/service/figure/figure-query.ts +5 -5
- package/server/service/figure/figure-rename-db.test.ts +5 -5
- package/server/service/figure/figure-rename.test.ts +7 -7
- package/server/service/figure/figure-tools.test.ts +22 -22
- package/server/service/figure/figure-tools.ts +14 -14
- package/server/service/figure/figure-type.ts +2 -2
- package/server/service/figure/{figure-v3-write.ts → figure-write.ts} +10 -10
- package/server/service/figure/figure.ts +3 -3
- package/server/service/figure-recipe/figure-recipe-templates.test.ts +2 -2
- package/server/service/figure-recipe/figure-recipe-templates.ts +4 -4
- package/server/service/figure-recipe/figure-recipe-type.ts +1 -1
- package/translations/en.json +2 -3
- package/translations/ko.json +2 -3
- package/dist-server/service/figure/figure-propose-v3.js.map +0 -1
- package/dist-server/service/figure/figure-v3-write.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-query.js","sourceRoot":"","sources":["../../../server/service/figure/figure-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { In } from 'typeorm'\nimport { Args, Arg, Ctx, Directive, Query, Resolver, UseMiddleware } from 'type-graphql'\nimport { GraphQLJSON } from 'graphql-scalars'\n\nimport {\n Domain,\n getQueryBuilderFromListParams,\n getRepository,\n ListParam,\n Refusal,\n SpeakRefusals\n} from '@things-factory/shell'\n\nimport { Figure } from './figure.js'\nimport { FigureHistory } from './figure-history.js'\nimport { consumablePayloadOf, inspectSource } from './figure-format.js'\nimport { FigureInspection, FigureList } from './figure-type.js'\n\n@Resolver(Figure)\n@UseMiddleware(SpeakRefusals)\nexport class FigureQuery {\n @Directive('@privilege(category: \"figure\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => Figure, { nullable: true, description: 'Finds a single figure by its ID.' })\n async figure(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<Figure> {\n const { domain } = context.state\n\n // 부모 도메인의 Figure 도 보인다 — Board 와 같은 범위 규칙이다.\n return await getRepository(Figure).findOne({\n where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, id }\n })\n }\n\n @Directive('@privilege(category: \"figure\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => FigureList, { description: 'Lists figures. Sortable by score, triangles, name, updatedAt.' })\n async figures(@Args() params: ListParam, @Ctx() context: ResolverContext): Promise<FigureList> {\n const { domain } = context.state\n\n const queryBuilder = getQueryBuilderFromListParams({\n repository: getRepository(Figure),\n params,\n domain,\n alias: 'figure',\n /*\n `tags` 도 찾을 수 있어야 한다 — 태그의 값은 붙이는 데 있지 않고 **찾는 데** 있다.\n\n `simple-json` 이라 칸에는 `[\"반출\",\"자동\"]` 이 통째로 들어 있고, LIKE 는 그 글자를\n 본다. 「반출」로 찾으면 걸린다. 정확히 그 태그만 걸리는 것은 아니지만(이름에 「반출」이\n 든 도형도 걸린다) 찾기로는 그것이 오히려 낫다.\n */\n searchables: ['type', 'name', 'description', 'category', 'tags']\n })\n\n const [items, total] = await queryBuilder.getManyAndCount()\n\n return { items, total }\n }\n\n /**\n * 등록된 타입 이름 목록.\n *\n * 저작 화면이 **새 타입 이름이 이미 쓰이는지** 물어보는 데 쓴다. `type` 은 저장되는\n * 식별자라 만든 뒤에 고칠 수 없으므로, 만들기 전에 알려 줘야 한다.\n *\n * `state` 로 걸러지지 않는다 — `draft` 인 Figure 의 이름도 이미 점유된 것이다.\n */\n /**\n * 발행된 판들 — 최근 것부터.\n *\n * 저작 중 저장은 여기 없다. 판본은 발행에서만 생기므로(`releaseFigure`), 이 목록이 곧\n * 「이 도형이 몇 번 약속되었나」다.\n */\n @Directive('@privilege(category: \"figure\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => [FigureHistory], { description: 'Released versions of a figure, newest first.' })\n async figureVersions(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<FigureHistory[]> {\n const { domain } = context.state\n\n return await getRepository(FigureHistory).find({\n where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, originalId: id },\n relations: ['updater'],\n order: { version: 'DESC' },\n take: 10\n })\n }\n\n /**\n * 지금 소비처가 쓰는 판.\n *\n * 도형의 현재 정본은 저작 중일 수 있다(초안). 배치된 것이 보는 것은 **마지막으로 발행된 판**이고,\n * 그것을 이 자리가 답한다.\n */\n @Directive('@privilege(category: \"figure\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => FigureHistory, { nullable: true, description: 'The most recently released version of a figure.' })\n async figureReleased(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<FigureHistory> {\n const { domain } = context.state\n\n return await getRepository(FigureHistory).findOne({\n where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, originalId: id },\n order: { version: 'DESC' }\n })\n }\n\n /**\n * 발행해도 되나 — **누르기 전에** 묻는 자리.\n *\n * 판정은 `figure-gate.ts` 가 하고 이 자리는 정본을 꺼내 넘긴다. 같은 판정을 `releaseFigure` 도\n * 부른다 — 화면이 물어 보고 통과했더라도 그 사이에 정본이 바뀌었을 수 있으므로, 막는 것은 언제나\n * 뮤테이션이다. 이 질의는 **이유를 미리 보여 주기 위한 것**이고 허가증이 아니다.\n */\n @Directive('@privilege(category: \"figure\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => FigureInspection, { description: 'Whether a figure may be released, with the reasons.' })\n async inspectFigure(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<FigureInspection> {\n const { domain } = context.state\n\n const figure = await getRepository(Figure).findOne({\n where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, id },\n /*\n `type` is read too. A V3 asset carries no type, so the temporary-type refusal reads the row's; with only\n id and source selected it saw `undefined`, the panel enabled 「발행하기」, and the release mutation — which\n loads the whole row — refused on the press (2026-09-24).\n */\n select: { id: true, source: true, type: true }\n })\n\n if (!figure) {\n throw new Error(`Figure ${id} 를 찾을 수 없다`)\n }\n\n /*\n **화면이 발행 문과 같은 것을 본다.** 직전 발행판을 여기서도 함께 넘기지 않으면, 저작 화면은\n 조용한데 발행을 누른 순간 계약 위반으로 막힌다 — 그때는 이미 열 군데를 고친 뒤다.\n */\n const released = await getRepository(FigureHistory)\n .createQueryBuilder('history')\n .where('history.originalId = :id', { id })\n .orderBy('history.version', 'DESC')\n .limit(1)\n .getOne()\n\n return inspectSource(figure.source, released?.source, context.t, figure.type)\n }\n\n /**\n * The same judgement for a source that has not been saved — what the modeller shows **while** the author edits.\n *\n * Without it a V3 author saw nothing until they saved and opened the release panel. The judgement runs here, not\n * in the browser, because the sentences are made from the request's language (`inspectAnySource` with `context.t`)\n * and a second copy on the screen would drift from the one the release refuses with. When `id` names a saved\n * figure, its row type and last release are read as `inspectFigure` does; a figure never saved has neither.\n */\n @Directive('@privilege(category: \"figure\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => FigureInspection, { description: 'Whether an unsaved figure source would be released, with the reasons.' })\n async inspectFigureDraft(\n @Arg('source') source: string,\n @Ctx() context: ResolverContext,\n @Arg('id', { nullable: true }) id?: string\n ): Promise<FigureInspection> {\n const { domain } = context.state\n if (!id) return inspectSource(source, null, context.t)\n\n const figure = await getRepository(Figure).findOne({\n where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, id },\n select: { id: true, type: true }\n })\n if (!figure) throw new Error(`Figure ${id} 를 찾을 수 없다`)\n\n const released = await getRepository(FigureHistory)\n .createQueryBuilder('history')\n .where('history.originalId = :id', { id })\n .orderBy('history.version', 'DESC')\n .limit(1)\n .getOne()\n\n return inspectSource(source, released?.source, context.t, figure.type)\n }\n\n @Directive('@privilege(category: \"figure\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => [String], { description: 'All figure type names in scope — used to check name collisions before create.' })\n async figureTypeNames(@Ctx() context: ResolverContext): Promise<string[]> {\n const { domain } = context.state\n\n const rows = await getRepository(Figure).find({\n where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) } },\n select: { type: true }\n })\n\n return rows.map(r => r.type).filter(Boolean)\n }\n\n /**\n * 소비처(operato-board, operato-twin, things-scene)가 발행된 도형을 받아 가는 문.\n *\n * 이름은 보드와의 계약이라 그대로 둔다(바꾸는 일은 따로 한다). 돌려주는 것은 자산이다 —\n * `{ format: 'v3', type, asset }`. 그리는 쪽(`registerFigureV3`)이 커널로 컴파일한다.\n *\n * ## 규칙\n * 1. 소비처는 초안(draft)을 볼 수 없다 — **반드시 발행된(released) 판만 전달**한다.\n * 2. 현재 Figure가 released 상태이면 현재 source를 넘긴다.\n * 3. 현재 Figure가 저작 중(draft)이더라도 이전에 발행된 이력(FigureHistory)이 있으면\n * 가장 최근의 released 판본을 넘긴다.\n * 4. 한 번도 발행된 적 없는 초안 자산이거나 존재하지 않는 타입인 경우 null을 반환하여\n * 미완성 자산이 씬 화면에 유출되는 것을 엄격히 차단한다.\n * 5. 도메인 상속 시 자식 도메인의 타입이 부모 도메인의 동일 타입을 항상 오버라이드한다.\n * 6. 권한은 저작(`figure`)과 **다른 category** 인 `figure-published` 다. 소비처 사용자에게\n * 저작 권한을 요구하면 board·twin 의 팔레트가 안 뜨고, 그렇다고 선언을 비우면 이 집에서는\n * 로그인조차 안 한 사람에게 열린다(`auth-rebuild.md` §1). `<module>-published` 는\n * 「그 모듈이 발행한 것을 남이 읽는 문」이라는 규약이다 — 아키텍트 판정(2026-09-15).\n *\n * ## 열거는 막혀 있다 — 지금은 우연이다\n *\n * 이 문의 열쇠는 `type` 하나뿐이라 이름을 모르면 못 꺼낸다. 그런데 그것이 **설계가 아니다** —\n * 목록을 내는 `figures`·`figureTypeNames` 가 `figure` category 로 잠겨 있어서 결과적으로\n * 그런 것뿐이다. 나중에 누가 소비처용 목록 문을 하나 더 열고 `figure-published` 를 붙이면\n * 열거가 함께 열리고, 「이름을 알아야 한다」에 기대고 있던 가정이 조용히 사라진다.\n * 그 문을 낼 사람이 이 단락을 읽고 그때 정하면 된다.\n */\n /*\n This directive is attached by schema assembly, so a test that calls the resolver method\n directly never exercises it. `figure-blueprint-query.test.ts` reads this source for the\n declaration instead — see the test named for it there.\n */\n @Directive('@privilege(category: \"figure-published\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => GraphQLJSON, {\n nullable: true,\n description: 'A released figure for its consumers (board, twin, things-scene): { format, type, asset } with the figure asset. Only returns released figures.'\n })\n async figureBlueprint(\n @Arg('type') type: string,\n @Ctx() context: ResolverContext\n ): Promise<any> {\n const { domain } = context.state\n\n /*\n A child domain shadows the same type in its parent, which is how a tenant replaces the standard\n CONVEYOR with its own. Only a released version shadows. When the child has just a draft, the\n parent's release is used, so starting a draft never makes placed figures disappear (chief\n architect ruling, 2026-09-17). This used to skip the fallback as soon as a child row existed and\n return null.\n */\n const found =\n (await releasedSourceOf(domain.id, type)) ?? (domain.parentId ? await releasedSourceOf(domain.parentId, type) : null)\n\n if (!found) {\n return null\n }\n\n const { figure, source: sourceToCompile, version } = found\n\n try {\n const text = typeof sourceToCompile === 'string' ? sourceToCompile : JSON.stringify(sourceToCompile)\n return { format: 'v3', type, asset: consumablePayloadOf(text).asset }\n } catch (err) {\n console.error(`[figureBlueprint] 타입 '${type}' (figure id: ${figure.id}) 자산 컴파일 실패:`, err)\n\n /*\n Null says one thing here: nobody published this type (ADR-0063 결정 2 보탬). A released figure\n whose stored source no longer compiles is a different fact, and answering null for it makes the\n board draw a stand-in with the reason nowhere on screen — the author published, the catalogue\n says released, and the only trace is the line above. That is the silence ADR-0053 refuses.\n\n The board asks per type inside a Promise.all and collects a rejection into `failed`, so this\n refusal costs that one figure its drawing and the scene still opens.\n */\n throw new Refusal(\n 'figure-release-does-not-compile',\n { type, figureId: figure.id, version },\n 'the released source of {type} does not compile. open it in the modeller and release it again.'\n )\n }\n }\n}\n\n/**\n * Finds the released source of a type in one domain, or null.\n *\n * A released row gives its own source; a draft row gives the source of its latest released history.\n * A figure that was never released gives null, because drafts never reach consumers.\n */\nasync function releasedSourceOf(\n domainId: string,\n type: string\n): Promise<{ figure: Figure; source: string; version: number } | null> {\n const figure = await getRepository(Figure).findOne({ where: { domain: { id: domainId }, type } })\n if (!figure) return null\n\n if (figure.state === 'released' && figure.source) {\n return { figure, source: figure.source, version: figure.version }\n }\n\n const latestReleased = await getRepository(FigureHistory).findOne({\n where: { domain: { id: domainId }, originalId: figure.id, state: 'released' },\n order: { version: 'DESC' }\n })\n\n /* The version travels with the source: a refusal names the edition that does not compile, and the\n row's own version is the draft being edited, not the one the board was handed. */\n return latestReleased?.source\n ? { figure, source: latestReleased.source, version: latestReleased.version }\n : null\n}\n"]}
|
|
1
|
+
{"version":3,"file":"figure-query.js","sourceRoot":"","sources":["../../../server/service/figure/figure-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { In } from 'typeorm'\nimport { Args, Arg, Ctx, Directive, Query, Resolver, UseMiddleware } from 'type-graphql'\nimport { GraphQLJSON } from 'graphql-scalars'\n\nimport {\n Domain,\n getQueryBuilderFromListParams,\n getRepository,\n ListParam,\n Refusal,\n SpeakRefusals\n} from '@things-factory/shell'\n\nimport { Figure } from './figure.js'\nimport { FigureHistory } from './figure-history.js'\nimport { consumablePayloadOf, inspectSource } from './figure-format.js'\nimport { FigureInspection, FigureList } from './figure-type.js'\n\n@Resolver(Figure)\n@UseMiddleware(SpeakRefusals)\nexport class FigureQuery {\n @Directive('@privilege(category: \"figure\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => Figure, { nullable: true, description: 'Finds a single figure by its ID.' })\n async figure(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<Figure> {\n const { domain } = context.state\n\n // 부모 도메인의 Figure 도 보인다 — Board 와 같은 범위 규칙이다.\n return await getRepository(Figure).findOne({\n where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, id }\n })\n }\n\n @Directive('@privilege(category: \"figure\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => FigureList, { description: 'Lists figures. Sortable by score, triangles, name, updatedAt.' })\n async figures(@Args() params: ListParam, @Ctx() context: ResolverContext): Promise<FigureList> {\n const { domain } = context.state\n\n const queryBuilder = getQueryBuilderFromListParams({\n repository: getRepository(Figure),\n params,\n domain,\n alias: 'figure',\n /*\n `tags` 도 찾을 수 있어야 한다 — 태그의 값은 붙이는 데 있지 않고 **찾는 데** 있다.\n\n `simple-json` 이라 칸에는 `[\"반출\",\"자동\"]` 이 통째로 들어 있고, LIKE 는 그 글자를\n 본다. 「반출」로 찾으면 걸린다. 정확히 그 태그만 걸리는 것은 아니지만(이름에 「반출」이\n 든 도형도 걸린다) 찾기로는 그것이 오히려 낫다.\n */\n searchables: ['type', 'name', 'description', 'category', 'tags']\n })\n\n const [items, total] = await queryBuilder.getManyAndCount()\n\n return { items, total }\n }\n\n /**\n * 등록된 타입 이름 목록.\n *\n * 저작 화면이 **새 타입 이름이 이미 쓰이는지** 물어보는 데 쓴다. `type` 은 저장되는\n * 식별자라 만든 뒤에 고칠 수 없으므로, 만들기 전에 알려 줘야 한다.\n *\n * `state` 로 걸러지지 않는다 — `draft` 인 Figure 의 이름도 이미 점유된 것이다.\n */\n /**\n * 발행된 판들 — 최근 것부터.\n *\n * 저작 중 저장은 여기 없다. 판본은 발행에서만 생기므로(`releaseFigure`), 이 목록이 곧\n * 「이 도형이 몇 번 약속되었나」다.\n */\n @Directive('@privilege(category: \"figure\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => [FigureHistory], { description: 'Released versions of a figure, newest first.' })\n async figureVersions(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<FigureHistory[]> {\n const { domain } = context.state\n\n return await getRepository(FigureHistory).find({\n where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, originalId: id },\n relations: ['updater'],\n order: { version: 'DESC' },\n take: 10\n })\n }\n\n /**\n * 지금 소비처가 쓰는 판.\n *\n * 도형의 현재 정본은 저작 중일 수 있다(초안). 배치된 것이 보는 것은 **마지막으로 발행된 판**이고,\n * 그것을 이 자리가 답한다.\n */\n @Directive('@privilege(category: \"figure\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => FigureHistory, { nullable: true, description: 'The most recently released version of a figure.' })\n async figureReleased(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<FigureHistory> {\n const { domain } = context.state\n\n return await getRepository(FigureHistory).findOne({\n where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, originalId: id },\n order: { version: 'DESC' }\n })\n }\n\n /**\n * 발행해도 되나 — **누르기 전에** 묻는 자리.\n *\n * 판정은 `figure-gate.ts` 가 하고 이 자리는 정본을 꺼내 넘긴다. 같은 판정을 `releaseFigure` 도\n * 부른다 — 화면이 물어 보고 통과했더라도 그 사이에 정본이 바뀌었을 수 있으므로, 막는 것은 언제나\n * 뮤테이션이다. 이 질의는 **이유를 미리 보여 주기 위한 것**이고 허가증이 아니다.\n */\n @Directive('@privilege(category: \"figure\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => FigureInspection, { description: 'Whether a figure may be released, with the reasons.' })\n async inspectFigure(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<FigureInspection> {\n const { domain } = context.state\n\n const figure = await getRepository(Figure).findOne({\n where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, id },\n /*\n `type` is read too. A asset carries no type, so the temporary-type refusal reads the row's; with only\n id and source selected it saw `undefined`, the panel enabled 「발행하기」, and the release mutation — which\n loads the whole row — refused on the press (2026-09-24).\n */\n select: { id: true, source: true, type: true }\n })\n\n if (!figure) {\n throw new Error(`Figure ${id} 를 찾을 수 없다`)\n }\n\n /*\n **화면이 발행 문과 같은 것을 본다.** 직전 발행판을 여기서도 함께 넘기지 않으면, 저작 화면은\n 조용한데 발행을 누른 순간 계약 위반으로 막힌다 — 그때는 이미 열 군데를 고친 뒤다.\n */\n const released = await getRepository(FigureHistory)\n .createQueryBuilder('history')\n .where('history.originalId = :id', { id })\n .orderBy('history.version', 'DESC')\n .limit(1)\n .getOne()\n\n return inspectSource(figure.source, released?.source, context.t, figure.type)\n }\n\n /**\n * The same judgement for a source that has not been saved — what the modeller shows **while** the author edits.\n *\n * Without it a author saw nothing until they saved and opened the release panel. The judgement runs here, not\n * in the browser, because the sentences are made from the request's language (`inspectAnySource` with `context.t`)\n * and a second copy on the screen would drift from the one the release refuses with. When `id` names a saved\n * figure, its row type and last release are read as `inspectFigure` does; a figure never saved has neither.\n */\n @Directive('@privilege(category: \"figure\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => FigureInspection, { description: 'Whether an unsaved figure source would be released, with the reasons.' })\n async inspectFigureDraft(\n @Arg('source') source: string,\n @Ctx() context: ResolverContext,\n @Arg('id', { nullable: true }) id?: string\n ): Promise<FigureInspection> {\n const { domain } = context.state\n if (!id) return inspectSource(source, null, context.t)\n\n const figure = await getRepository(Figure).findOne({\n where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, id },\n select: { id: true, type: true }\n })\n if (!figure) throw new Error(`Figure ${id} 를 찾을 수 없다`)\n\n const released = await getRepository(FigureHistory)\n .createQueryBuilder('history')\n .where('history.originalId = :id', { id })\n .orderBy('history.version', 'DESC')\n .limit(1)\n .getOne()\n\n return inspectSource(source, released?.source, context.t, figure.type)\n }\n\n @Directive('@privilege(category: \"figure\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => [String], { description: 'All figure type names in scope — used to check name collisions before create.' })\n async figureTypeNames(@Ctx() context: ResolverContext): Promise<string[]> {\n const { domain } = context.state\n\n const rows = await getRepository(Figure).find({\n where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) } },\n select: { type: true }\n })\n\n return rows.map(r => r.type).filter(Boolean)\n }\n\n /**\n * 소비처(operato-board, operato-twin, things-scene)가 발행된 도형을 받아 가는 문.\n *\n * 이름은 보드와의 계약이라 그대로 둔다(바꾸는 일은 따로 한다). 돌려주는 것은 자산이다 —\n * `{ type, asset }`. 그리는 쪽(`registerFigure`)이 커널로 컴파일한다.\n *\n * ## 규칙\n * 1. 소비처는 초안(draft)을 볼 수 없다 — **반드시 발행된(released) 판만 전달**한다.\n * 2. 현재 Figure가 released 상태이면 현재 source를 넘긴다.\n * 3. 현재 Figure가 저작 중(draft)이더라도 이전에 발행된 이력(FigureHistory)이 있으면\n * 가장 최근의 released 판본을 넘긴다.\n * 4. 한 번도 발행된 적 없는 초안 자산이거나 존재하지 않는 타입인 경우 null을 반환하여\n * 미완성 자산이 씬 화면에 유출되는 것을 엄격히 차단한다.\n * 5. 도메인 상속 시 자식 도메인의 타입이 부모 도메인의 동일 타입을 항상 오버라이드한다.\n * 6. 권한은 저작(`figure`)과 **다른 category** 인 `figure-published` 다. 소비처 사용자에게\n * 저작 권한을 요구하면 board·twin 의 팔레트가 안 뜨고, 그렇다고 선언을 비우면 이 집에서는\n * 로그인조차 안 한 사람에게 열린다(`auth-rebuild.md` §1). `<module>-published` 는\n * 「그 모듈이 발행한 것을 남이 읽는 문」이라는 규약이다 — 아키텍트 판정(2026-09-15).\n *\n * ## 열거는 막혀 있다 — 지금은 우연이다\n *\n * 이 문의 열쇠는 `type` 하나뿐이라 이름을 모르면 못 꺼낸다. 그런데 그것이 **설계가 아니다** —\n * 목록을 내는 `figures`·`figureTypeNames` 가 `figure` category 로 잠겨 있어서 결과적으로\n * 그런 것뿐이다. 나중에 누가 소비처용 목록 문을 하나 더 열고 `figure-published` 를 붙이면\n * 열거가 함께 열리고, 「이름을 알아야 한다」에 기대고 있던 가정이 조용히 사라진다.\n * 그 문을 낼 사람이 이 단락을 읽고 그때 정하면 된다.\n */\n /*\n This directive is attached by schema assembly, so a test that calls the resolver method\n directly never exercises it. `figure-blueprint-query.test.ts` reads this source for the\n declaration instead — see the test named for it there.\n */\n @Directive('@privilege(category: \"figure-published\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => GraphQLJSON, {\n nullable: true,\n description: 'A released figure for its consumers (board, twin, things-scene): { type, asset } with the figure asset. Only returns released figures.'\n })\n async figureBlueprint(\n @Arg('type') type: string,\n @Ctx() context: ResolverContext\n ): Promise<any> {\n const { domain } = context.state\n\n /*\n A child domain shadows the same type in its parent, which is how a tenant replaces the standard\n CONVEYOR with its own. Only a released version shadows. When the child has just a draft, the\n parent's release is used, so starting a draft never makes placed figures disappear (chief\n architect ruling, 2026-09-17). This used to skip the fallback as soon as a child row existed and\n return null.\n */\n const found =\n (await releasedSourceOf(domain.id, type)) ?? (domain.parentId ? await releasedSourceOf(domain.parentId, type) : null)\n\n if (!found) {\n return null\n }\n\n const { figure, source: sourceToCompile, version } = found\n\n try {\n const text = typeof sourceToCompile === 'string' ? sourceToCompile : JSON.stringify(sourceToCompile)\n return { type, asset: consumablePayloadOf(text).asset }\n } catch (err) {\n console.error(`[figureBlueprint] 타입 '${type}' (figure id: ${figure.id}) 자산 컴파일 실패:`, err)\n\n /*\n Null says one thing here: nobody published this type (ADR-0063 결정 2 보탬). A released figure\n whose stored source no longer compiles is a different fact, and answering null for it makes the\n board draw a stand-in with the reason nowhere on screen — the author published, the catalogue\n says released, and the only trace is the line above. That is the silence ADR-0053 refuses.\n\n The board asks per type inside a Promise.all and collects a rejection into `failed`, so this\n refusal costs that one figure its drawing and the scene still opens.\n */\n throw new Refusal(\n 'figure-release-does-not-compile',\n { type, figureId: figure.id, version },\n 'the released source of {type} does not compile. open it in the modeller and release it again.'\n )\n }\n }\n}\n\n/**\n * Finds the released source of a type in one domain, or null.\n *\n * A released row gives its own source; a draft row gives the source of its latest released history.\n * A figure that was never released gives null, because drafts never reach consumers.\n */\nasync function releasedSourceOf(\n domainId: string,\n type: string\n): Promise<{ figure: Figure; source: string; version: number } | null> {\n const figure = await getRepository(Figure).findOne({ where: { domain: { id: domainId }, type } })\n if (!figure) return null\n\n if (figure.state === 'released' && figure.source) {\n return { figure, source: figure.source, version: figure.version }\n }\n\n const latestReleased = await getRepository(FigureHistory).findOne({\n where: { domain: { id: domainId }, originalId: figure.id, state: 'released' },\n order: { version: 'DESC' }\n })\n\n /* The version travels with the source: a refusal names the edition that does not compile, and the\n row's own version is the draft being edited, not the one the board was handed. */\n return latestReleased?.source\n ? { figure, source: latestReleased.source, version: latestReleased.version }\n : null\n}\n"]}
|
|
@@ -10,12 +10,12 @@ exports.registerFigureToolCategories = registerFigureToolCategories;
|
|
|
10
10
|
* 하려면 어시스턴트가 **지금 무엇이 있는지 읽을 수 있어야** 한다. 읽지 못하면 매번 사람이 정본을
|
|
11
11
|
* 붙여 넣어야 하고, 그러면 대화가 아니다.
|
|
12
12
|
*
|
|
13
|
-
* 능력을 복제하지 않는다. figure-service 가 이미 가진 진입점(`inspectSource` · `
|
|
13
|
+
* 능력을 복제하지 않는다. figure-service 가 이미 가진 진입점(`inspectSource` · `proposeFigureSteps` ·
|
|
14
14
|
* 조회)을 레지스트리에 투영한다.
|
|
15
15
|
*
|
|
16
16
|
* ── 전부 조회·제안 전용이다 ─────────────────────────────────────────────────
|
|
17
17
|
* 정본을 고치는 도구를 넣지 않는다. 형식의 설계가 그렇게 정해져 있다 — 어시스턴트는 **후보**를
|
|
18
|
-
* 내고, 받을지 버릴지는 저작자가 정한다(`figure-propose-
|
|
18
|
+
* 내고, 받을지 버릴지는 저작자가 정한다(`figure-propose-steps.ts`, ADR-0094). 되돌릴 수 없는 조작이 대화면으로
|
|
19
19
|
* 새지 않으므로 전부 `kind: 'read'` 다.
|
|
20
20
|
*
|
|
21
21
|
* 저장은 저작면이 `updateFigure` 로 한다. 그 mutation 은 `figure:mutation` 이 지키고, 대화는
|
|
@@ -55,16 +55,16 @@ function getDeps() {
|
|
|
55
55
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
56
56
|
const format = require('./figure-format.js');
|
|
57
57
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
58
|
-
const
|
|
58
|
+
const proposer = require('./figure-propose-steps.js');
|
|
59
59
|
_deps = {
|
|
60
60
|
getRepository: shell.getRepository,
|
|
61
61
|
Figure: entity.Figure,
|
|
62
62
|
/* The same doors a save and a release go through. The tool reads; it never saves. */
|
|
63
63
|
inspectSource: format.inspectSource,
|
|
64
64
|
parseSource: format.parseSource,
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
figureCostOf: model.figureCostOf,
|
|
66
|
+
figureScoreOf: model.figureScoreOf,
|
|
67
|
+
proposeFigureSteps: proposer.proposeFigureSteps,
|
|
68
68
|
isPlaceholderType: model.isPlaceholderType
|
|
69
69
|
};
|
|
70
70
|
}
|
|
@@ -117,7 +117,7 @@ async function resolveFigureTarget(figureId, domainId, _host) {
|
|
|
117
117
|
* The host context is the browser's, so it is no ground for access: the row is found in the domain first, and the
|
|
118
118
|
* draft is put through the same door a save goes through before anything reads it.
|
|
119
119
|
*/
|
|
120
|
-
const
|
|
120
|
+
const isFigure = (source) => source?.version === 'figure-asset-1';
|
|
121
121
|
/**
|
|
122
122
|
* A revision is applied by the modeller on the version the model saw (ADR-0094), so only the figure open there can be
|
|
123
123
|
* revised. Said as what is true, with what the assistant can do instead.
|
|
@@ -233,7 +233,7 @@ function figureToolSpecs() {
|
|
|
233
233
|
return { id: row.id, type: row.type, name: row.name, source: null, cost: null };
|
|
234
234
|
}
|
|
235
235
|
/* Measured as the catalogue measures it: triangles, draw groups, and its grade against one screen. */
|
|
236
|
-
const {
|
|
236
|
+
const { figureCostOf, figureScoreOf } = getDeps();
|
|
237
237
|
return {
|
|
238
238
|
id: row.id,
|
|
239
239
|
type: row.type,
|
|
@@ -241,8 +241,8 @@ function figureToolSpecs() {
|
|
|
241
241
|
state: row.state,
|
|
242
242
|
source: current.source,
|
|
243
243
|
draft: current.draft,
|
|
244
|
-
cost:
|
|
245
|
-
score:
|
|
244
|
+
cost: figureCostOf(current.source),
|
|
245
|
+
score: figureScoreOf(current.source) ?? null
|
|
246
246
|
};
|
|
247
247
|
}
|
|
248
248
|
},
|
|
@@ -326,7 +326,7 @@ function figureToolSpecs() {
|
|
|
326
326
|
if (!row)
|
|
327
327
|
return missingFigureTarget();
|
|
328
328
|
base = sourceForTool(row, host).source ?? undefined;
|
|
329
|
-
if (!
|
|
329
|
+
if (!isFigure(base))
|
|
330
330
|
return figureNotOpen();
|
|
331
331
|
if (typeof host?.context !== 'string' || !Number.isInteger(host?.revision))
|
|
332
332
|
return figureNotOpen();
|
|
@@ -339,10 +339,10 @@ function figureToolSpecs() {
|
|
|
339
339
|
const buf = typeof rawData === 'string' ? Buffer.from(rawData, 'base64') : Buffer.from(rawData);
|
|
340
340
|
hostImage = { data: buf, mediaType: rawImage.mediaType || 'image/png' };
|
|
341
341
|
}
|
|
342
|
-
const {
|
|
342
|
+
const { proposeFigureSteps } = getDeps();
|
|
343
343
|
let result;
|
|
344
344
|
try {
|
|
345
|
-
result = await
|
|
345
|
+
result = await proposeFigureSteps({
|
|
346
346
|
prompt,
|
|
347
347
|
base,
|
|
348
348
|
name: typeof args?.name === 'string' && args.name.trim() ? args.name.trim() : undefined,
|
|
@@ -351,7 +351,7 @@ function figureToolSpecs() {
|
|
|
351
351
|
}
|
|
352
352
|
catch (error) {
|
|
353
353
|
/*
|
|
354
|
-
* 세 번 거절된 끝의 사유를
|
|
354
|
+
* 세 번 거절된 끝의 사유를 ProposeFigureFailure.reasons 에만 두면 agentic-loop 은 「만들지 못했습니다」만
|
|
355
355
|
* 받아 화면에는 「응답이 비어있습니다」가 된다. 다시 시도할 수 있는 말로 경계를 넘긴다.
|
|
356
356
|
* 다른 예외(권한·통신)는 원문을 보존한다.
|
|
357
357
|
*/
|
|
@@ -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` · `proposeFigureV3` ·\n * 조회)을 레지스트리에 투영한다.\n *\n * ── 전부 조회·제안 전용이다 ─────────────────────────────────────────────────\n * 정본을 고치는 도구를 넣지 않는다. 형식의 설계가 그렇게 정해져 있다 — 어시스턴트는 **후보**를\n * 내고, 받을지 버릴지는 저작자가 정한다(`figure-propose-v3.ts`, ADR-0094). 되돌릴 수 없는 조작이 대화면으로\n * 새지 않으므로 전부 `kind: 'read'` 다.\n *\n * 저장은 저작면이 `updateFigure` 로 한다. 그 mutation 은 `figure:mutation` 이 지키고, 대화는\n * `ai-assistant:mutation` 이 지킨다 — 서로 다른 질문이라 서로 다른 권한이 지킨다. figure 가 board\n * 의 권한을 빌리지 않는다.\n *\n * ── No privilege here. The gate reads what the door declares ─────────────────────\n * Until 2026-09-18 each tool carried `privilege: { category: 'figure', ... }`. It matched the door's\n * directive then, and would drift the day the door changed its declaration — and the looser copy wins.\n * Now the gate reads the declaration of each door in `doors` from AUTH's table (ADR-0071 decision 2,\n * addendum). What stays here is **the doors it reaches**.\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 format = require('./figure-format.js')\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const proposeV3 = require('./figure-propose-v3.js')\n _deps = {\n getRepository: shell.getRepository,\n Figure: entity.Figure,\n /* The same doors a save and a release go through. The tool reads; it never saves. */\n inspectSource: format.inspectSource,\n parseSource: format.parseSource,\n v3CostOf: model.v3CostOf,\n v3ScoreOf: model.v3ScoreOf,\n proposeFigureV3: proposeV3.proposeFigureV3,\n isPlaceholderType: model.isPlaceholderType\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 * The figure a tool is about: by id, or by its type name, in the caller's domain. None named: none.\n */\nasync function resolveFigureTarget(figureId: string | undefined, domainId: string, _host: any): Promise<any> {\n if (!figureId) return undefined\n const { getRepository, Figure } = getDeps()\n const found =\n (await getRepository(Figure).findOne({ where: { id: figureId, domain: { id: domainId } } })) ??\n (await getRepository(Figure).findOne({ where: { type: figureId, domain: { id: domainId } } }))\n if (!found) throw new Error(`figure not found in this tenant: ${figureId}`)\n return found\n}\n\n/**\n * The source a tool reads: the draft on screen when the modeller has this figure open, else the stored one.\n *\n * The host context is the browser's, so it is no ground for access: the row is found in the domain first, and the\n * draft is put through the same door a save goes through before anything reads it.\n */\nconst isV3 = (source: any) => source?.version === 'figure-v3-asset-1'\n\n/**\n * A revision is applied by the modeller on the version the model saw (ADR-0094), so only the figure open there can be\n * revised. Said as what is true, with what the assistant can do instead.\n */\nfunction figureNotOpen() {\n return {\n error: 'FIGURE_NOT_OPEN',\n message: '고치는 제안은 모델러에 열려 있는 도형에만 낼 수 있습니다.',\n nextStep:\n 'Tell the person to open the figure in the modeller and ask again there. A new figure needs no open one: call proposeFigureCandidate with create: true.'\n }\n}\n\nfunction sourceForTool(row: any, host: any): { source: any; draft: boolean } {\n const draft = host?.figureId === row.id ? host?.draftSource : undefined\n if (!draft) return { source: row.source ? JSON.parse(row.source) : null, draft: false }\n\n if (typeof draft !== 'object' || Array.isArray(draft)) throw new Error('the unsaved draft is not a figure asset')\n const serialized = JSON.stringify(draft)\n if (serialized.length > 100_000) throw new Error('the unsaved draft is too large to safely review')\n try {\n getDeps().parseSource(serialized)\n } catch (e: any) {\n const findings = Array.isArray(e?.findings) ? e.findings.map((one: any) => one.message).join(' · ') : String(e?.message ?? e)\n throw new Error(`the unsaved draft is invalid: ${findings}`)\n }\n return { source: draft, draft: true }\n}\n\n/**\n * 무엇을 고치라는 것인지 대화면 문맥에서 해소한다.\n *\n * 모델이 대상을 안 줬으면 **저작면이 지금 열고 있는 도형**이다. 사용자에게 식별자를 되묻지\n * 않는다 — 화면에 그 도형이 떠 있는데 「어느 도형입니까」를 묻는 것은 문맥을 안 읽은 것이다.\n */\nfunction figureIdFrom(args: any, host: any): string | undefined {\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 return undefined\n}\n\nfunction missingFigureTarget() {\n return {\n error: 'FIGURE_TARGET_REQUIRED',\n message: '현재 화면에는 선택된 도형이 없습니다.',\n nextStep: 'For an existing figure, call listFigures and match the user’s requested name or type; ask which figure only if ambiguous. For a new figure, call proposeFigureCandidate with create: true and the user’s prompt. Do not invent an id or repeat this call without a target.'\n }\n}\n\nconst FIGURE_ID_ARG = {\n figureId: {\n type: 'string',\n description: 'The figure to read. Omit to read the one the person has open, saved or not — the server fills it in, and you cannot see which it is. On the catalog nothing is open, so resolve the figure the person asked for with listFigures first.'\n }\n} as const\n\n/**\n * 도구 넷.\n *\n * 목록 · 정본 · 발행 판정 · 후보. 저작 대화에서 실제로 묻는 것이 그 넷이다 — 「무엇이 있나」\n * 「지금 어떻게 생겼나」 「발행이 왜 안 되나」 「이렇게 고쳐 봐」.\n */\nfunction figureToolSpecs(): ToolSpec[] {\n return [\n {\n kind: 'read',\n name: 'listFigures',\n /* 이 도메인의 도형 목록 — 저장소를 직접 읽지만 같은 일을 하는 문은 이것이다. */\n doors: ['Query.figures'],\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 /* 저장된 도형의 원본. **저장 전 초안**을 물으면 저장소에 갈 것이 없어 화면이 보낸 것을 그대로 돌려준다 —\n * 그 갈래에는 닿는 문이 없지만 `doors: []` 로 적지 않는다. 그러면 저장된 도형을 읽는 권한 검사가 선언에서 사라진다. */\n doors: ['Query.figure'],\n description:\n 'Returns the source (the figure asset) of one figure, with its measured cost ' +\n '(triangles, part groups, and how many of it hold 60fps on one screen). 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?.host)\n const row = await resolveFigureTarget(figureId, domainId, ctx?.host)\n if (!row) return missingFigureTarget()\n\n const current = sourceForTool(row, ctx?.host)\n if (!current.source) {\n // 저장된 정본이 없는 상태가 실제로 있다(새로 만들고 저장 안 한 것). 지어내지 않는다.\n return { id: row.id, type: row.type, name: row.name, source: null, cost: null }\n }\n\n /* Measured as the catalogue measures it: triangles, draw groups, and its grade against one screen. */\n const { v3CostOf, v3ScoreOf } = getDeps()\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: v3CostOf(current.source),\n score: v3ScoreOf(current.source) ?? null\n }\n }\n },\n {\n kind: 'read',\n name: 'inspectFigure',\n /* 검사 결과 — 같은 일을 하는 문. */\n doors: ['Query.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?.host)\n const row = await resolveFigureTarget(figureId, domainId, ctx?.host)\n if (!row) return missingFigureTarget()\n\n const current = sourceForTool(row, ctx?.host)\n const { inspectSource } = getDeps()\n return { ...inspectSource(current.source ? JSON.stringify(current.source) : null, null, undefined, row.type), draft: current.draft }\n }\n },\n {\n kind: 'read',\n name: 'proposeFigureCandidate',\n /* 후보 생성 — 저장은 하지 않지만 같은 문의 권한을 쓴다. */\n doors: ['Mutation.proposeFigure'],\n description:\n 'Asks for a proposal: the modeller commands that make a new figure or revise the one open in the modeller. ' +\n 'Returns the commands with the score and cost of the figure they make. NOTHING IS SAVED — the author applies ' +\n 'the proposal in the modeller as one step, or leaves it. This is the only way a proposal 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; it must be the one open in the modeller. Omit to revise the open one; pass null to create anew.'\n },\n name: {\n type: 'string',\n description: 'The name a person reads for a new figure.'\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 고치는 제안은 모델러가 **요청 당시의 판(context · revision)** 위에 한 단계로 적용한다(ADR-0094).\n 그래서 고칠 수 있는 것은 모델러에 열린 도형뿐이고, 그 판을 문맥이 함께 실어 와야 한다.\n */\n const host = ctx?.host\n const create = args?.create === true || args?.figureId === null\n let base: unknown\n let figureId: string | undefined\n if (!create) {\n const said = typeof args?.figureId === 'string' ? args.figureId.trim() : ''\n const open = typeof host?.figureId === 'string' ? host.figureId.trim() : ''\n if (said && said !== open) {\n await figureInDomain(said, domainId)\n return figureNotOpen()\n }\n if (!open) return missingFigureTarget()\n const row = await resolveFigureTarget(open, domainId, host)\n if (!row) return missingFigureTarget()\n base = sourceForTool(row, host).source ?? undefined\n if (!isV3(base)) return figureNotOpen()\n if (typeof host?.context !== 'string' || !Number.isInteger(host?.revision)) return figureNotOpen()\n figureId = open\n }\n\n let hostImage: { data: Buffer; mediaType: any } | undefined\n const rawImage = host?.image || (Array.isArray(host?.attachments) ? host.attachments.find((a: any) => a?.mediaType?.startsWith('image/')) : undefined)\n if (rawImage?.data) {\n const rawData = typeof rawImage.data === 'string' ? rawImage.data.replace(/^data:image\\/[a-zA-Z]+;base64,/, '') : rawImage.data\n const buf = typeof rawData === 'string' ? Buffer.from(rawData, 'base64') : Buffer.from(rawData)\n hostImage = { data: buf, mediaType: rawImage.mediaType || 'image/png' }\n }\n\n const { proposeFigureV3 } = getDeps()\n let result: any\n try {\n result = await proposeFigureV3({\n prompt,\n base,\n name: typeof args?.name === 'string' && args.name.trim() ? args.name.trim() : undefined,\n image: hostImage\n })\n } catch (error) {\n /*\n * 세 번 거절된 끝의 사유를 ProposeV3Failure.reasons 에만 두면 agentic-loop 은 「만들지 못했습니다」만\n * 받아 화면에는 「응답이 비어있습니다」가 된다. 다시 시도할 수 있는 말로 경계를 넘긴다.\n * 다른 예외(권한·통신)는 원문을 보존한다.\n */\n const reasons = Array.isArray((error as any)?.reasons) ? (error as any).reasons : []\n if (reasons.length > 0) throw new Error([(error as Error).message, ...reasons].join('\\n'))\n throw error\n }\n\n /*\n **`proposed: true` 가 카드를 만든다.** 이 표시가 없으면 제안은 도구 추적 안에 묻히고 화면에는\n 아무것도 생기지 않는다. 카드의 실행 단추가 figure-ui 의 처리기에 넘기고, 처리기가 모델러에 명령\n 목록을 한 단계로 적용한다. `args` 는 같은 제안을 두 번 세지 않기 위한 열쇠의 일부다.\n */\n const name = result.source.name || 'Figure'\n return {\n proposed: true,\n icon: 'view_in_ar',\n label: create ? `${name} 만들기` : `${name} 고치기`,\n reason:\n `명령 ${result.steps.length}개 · 등급 ${result.score?.grade ?? '-'} · 삼각형 ${result.cost.triangles} · ` +\n `${result.attempts}번에 만들었다`,\n args: { prompt, figureId, create },\n create,\n figureId,\n context: create ? undefined : host.context,\n revision: create ? undefined : host.revision,\n steps: result.steps,\n score: result.score,\n cost: result.cost,\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 ' 고치는 제안은 모델러에 열린 도형에만 낼 수 있다. 결과의 `steps` 가 무엇을 하는지 그대로 전한다 — 명령에 없는 변경을 지어내지 않는다.',\n '- 정본을 직접 저장하지 않는다. 후보만 내고, 받을지 버릴지는 저작자가 화면에서 정한다.',\n '- 좌표와 크기는 X/Y/Z 축 이름으로 정확하게 표시한다. 확인되지 않은 가로·세로·높이 의미를 임의로 붙이지 않는다.',\n '- **고치라는 요청이면 먼저 `getFigureSource` 를 부른다.** 읽지 않고 고치면 짐작을 상대로 후보를 내게 되고, 부품 이름이 갈려 이미 배치된 보드의 바인딩이 끊긴다.',\n '- 목록 화면에는 열린 도형이 없다. 기존 도형을 조회할 때는 `listFigures`에서 사용자가 말한 이름·타입의 id를 찾는다. 대상이 모호할 때만 어느 도형인지 묻는다. 기존 도형을 고치려면 모델러에서 그 도형을 열고 거기서 요청하도록 안내한다. 새 제작 요청에는 기존 정본을 조회하지 않고 `proposeFigureCandidate`를 create: true로 호출한다.',\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` · `proposeFigureSteps` ·\n * 조회)을 레지스트리에 투영한다.\n *\n * ── 전부 조회·제안 전용이다 ─────────────────────────────────────────────────\n * 정본을 고치는 도구를 넣지 않는다. 형식의 설계가 그렇게 정해져 있다 — 어시스턴트는 **후보**를\n * 내고, 받을지 버릴지는 저작자가 정한다(`figure-propose-steps.ts`, ADR-0094). 되돌릴 수 없는 조작이 대화면으로\n * 새지 않으므로 전부 `kind: 'read'` 다.\n *\n * 저장은 저작면이 `updateFigure` 로 한다. 그 mutation 은 `figure:mutation` 이 지키고, 대화는\n * `ai-assistant:mutation` 이 지킨다 — 서로 다른 질문이라 서로 다른 권한이 지킨다. figure 가 board\n * 의 권한을 빌리지 않는다.\n *\n * ── No privilege here. The gate reads what the door declares ─────────────────────\n * Until 2026-09-18 each tool carried `privilege: { category: 'figure', ... }`. It matched the door's\n * directive then, and would drift the day the door changed its declaration — and the looser copy wins.\n * Now the gate reads the declaration of each door in `doors` from AUTH's table (ADR-0071 decision 2,\n * addendum). What stays here is **the doors it reaches**.\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 format = require('./figure-format.js')\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const proposer = require('./figure-propose-steps.js')\n _deps = {\n getRepository: shell.getRepository,\n Figure: entity.Figure,\n /* The same doors a save and a release go through. The tool reads; it never saves. */\n inspectSource: format.inspectSource,\n parseSource: format.parseSource,\n figureCostOf: model.figureCostOf,\n figureScoreOf: model.figureScoreOf,\n proposeFigureSteps: proposer.proposeFigureSteps,\n isPlaceholderType: model.isPlaceholderType\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 * The figure a tool is about: by id, or by its type name, in the caller's domain. None named: none.\n */\nasync function resolveFigureTarget(figureId: string | undefined, domainId: string, _host: any): Promise<any> {\n if (!figureId) return undefined\n const { getRepository, Figure } = getDeps()\n const found =\n (await getRepository(Figure).findOne({ where: { id: figureId, domain: { id: domainId } } })) ??\n (await getRepository(Figure).findOne({ where: { type: figureId, domain: { id: domainId } } }))\n if (!found) throw new Error(`figure not found in this tenant: ${figureId}`)\n return found\n}\n\n/**\n * The source a tool reads: the draft on screen when the modeller has this figure open, else the stored one.\n *\n * The host context is the browser's, so it is no ground for access: the row is found in the domain first, and the\n * draft is put through the same door a save goes through before anything reads it.\n */\nconst isFigure = (source: any) => source?.version === 'figure-asset-1'\n\n/**\n * A revision is applied by the modeller on the version the model saw (ADR-0094), so only the figure open there can be\n * revised. Said as what is true, with what the assistant can do instead.\n */\nfunction figureNotOpen() {\n return {\n error: 'FIGURE_NOT_OPEN',\n message: '고치는 제안은 모델러에 열려 있는 도형에만 낼 수 있습니다.',\n nextStep:\n 'Tell the person to open the figure in the modeller and ask again there. A new figure needs no open one: call proposeFigureCandidate with create: true.'\n }\n}\n\nfunction sourceForTool(row: any, host: any): { source: any; draft: boolean } {\n const draft = host?.figureId === row.id ? host?.draftSource : undefined\n if (!draft) return { source: row.source ? JSON.parse(row.source) : null, draft: false }\n\n if (typeof draft !== 'object' || Array.isArray(draft)) throw new Error('the unsaved draft is not a figure asset')\n const serialized = JSON.stringify(draft)\n if (serialized.length > 100_000) throw new Error('the unsaved draft is too large to safely review')\n try {\n getDeps().parseSource(serialized)\n } catch (e: any) {\n const findings = Array.isArray(e?.findings) ? e.findings.map((one: any) => one.message).join(' · ') : String(e?.message ?? e)\n throw new Error(`the unsaved draft is invalid: ${findings}`)\n }\n return { source: draft, draft: true }\n}\n\n/**\n * 무엇을 고치라는 것인지 대화면 문맥에서 해소한다.\n *\n * 모델이 대상을 안 줬으면 **저작면이 지금 열고 있는 도형**이다. 사용자에게 식별자를 되묻지\n * 않는다 — 화면에 그 도형이 떠 있는데 「어느 도형입니까」를 묻는 것은 문맥을 안 읽은 것이다.\n */\nfunction figureIdFrom(args: any, host: any): string | undefined {\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 return undefined\n}\n\nfunction missingFigureTarget() {\n return {\n error: 'FIGURE_TARGET_REQUIRED',\n message: '현재 화면에는 선택된 도형이 없습니다.',\n nextStep: 'For an existing figure, call listFigures and match the user’s requested name or type; ask which figure only if ambiguous. For a new figure, call proposeFigureCandidate with create: true and the user’s prompt. Do not invent an id or repeat this call without a target.'\n }\n}\n\nconst FIGURE_ID_ARG = {\n figureId: {\n type: 'string',\n description: 'The figure to read. Omit to read the one the person has open, saved or not — the server fills it in, and you cannot see which it is. On the catalog nothing is open, so resolve the figure the person asked for with listFigures first.'\n }\n} as const\n\n/**\n * 도구 넷.\n *\n * 목록 · 정본 · 발행 판정 · 후보. 저작 대화에서 실제로 묻는 것이 그 넷이다 — 「무엇이 있나」\n * 「지금 어떻게 생겼나」 「발행이 왜 안 되나」 「이렇게 고쳐 봐」.\n */\nfunction figureToolSpecs(): ToolSpec[] {\n return [\n {\n kind: 'read',\n name: 'listFigures',\n /* 이 도메인의 도형 목록 — 저장소를 직접 읽지만 같은 일을 하는 문은 이것이다. */\n doors: ['Query.figures'],\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 /* 저장된 도형의 원본. **저장 전 초안**을 물으면 저장소에 갈 것이 없어 화면이 보낸 것을 그대로 돌려준다 —\n * 그 갈래에는 닿는 문이 없지만 `doors: []` 로 적지 않는다. 그러면 저장된 도형을 읽는 권한 검사가 선언에서 사라진다. */\n doors: ['Query.figure'],\n description:\n 'Returns the source (the figure asset) of one figure, with its measured cost ' +\n '(triangles, part groups, and how many of it hold 60fps on one screen). 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?.host)\n const row = await resolveFigureTarget(figureId, domainId, ctx?.host)\n if (!row) return missingFigureTarget()\n\n const current = sourceForTool(row, ctx?.host)\n if (!current.source) {\n // 저장된 정본이 없는 상태가 실제로 있다(새로 만들고 저장 안 한 것). 지어내지 않는다.\n return { id: row.id, type: row.type, name: row.name, source: null, cost: null }\n }\n\n /* Measured as the catalogue measures it: triangles, draw groups, and its grade against one screen. */\n const { figureCostOf, figureScoreOf } = getDeps()\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: figureCostOf(current.source),\n score: figureScoreOf(current.source) ?? null\n }\n }\n },\n {\n kind: 'read',\n name: 'inspectFigure',\n /* 검사 결과 — 같은 일을 하는 문. */\n doors: ['Query.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?.host)\n const row = await resolveFigureTarget(figureId, domainId, ctx?.host)\n if (!row) return missingFigureTarget()\n\n const current = sourceForTool(row, ctx?.host)\n const { inspectSource } = getDeps()\n return { ...inspectSource(current.source ? JSON.stringify(current.source) : null, null, undefined, row.type), draft: current.draft }\n }\n },\n {\n kind: 'read',\n name: 'proposeFigureCandidate',\n /* 후보 생성 — 저장은 하지 않지만 같은 문의 권한을 쓴다. */\n doors: ['Mutation.proposeFigure'],\n description:\n 'Asks for a proposal: the modeller commands that make a new figure or revise the one open in the modeller. ' +\n 'Returns the commands with the score and cost of the figure they make. NOTHING IS SAVED — the author applies ' +\n 'the proposal in the modeller as one step, or leaves it. This is the only way a proposal 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; it must be the one open in the modeller. Omit to revise the open one; pass null to create anew.'\n },\n name: {\n type: 'string',\n description: 'The name a person reads for a new figure.'\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 고치는 제안은 모델러가 **요청 당시의 판(context · revision)** 위에 한 단계로 적용한다(ADR-0094).\n 그래서 고칠 수 있는 것은 모델러에 열린 도형뿐이고, 그 판을 문맥이 함께 실어 와야 한다.\n */\n const host = ctx?.host\n const create = args?.create === true || args?.figureId === null\n let base: unknown\n let figureId: string | undefined\n if (!create) {\n const said = typeof args?.figureId === 'string' ? args.figureId.trim() : ''\n const open = typeof host?.figureId === 'string' ? host.figureId.trim() : ''\n if (said && said !== open) {\n await figureInDomain(said, domainId)\n return figureNotOpen()\n }\n if (!open) return missingFigureTarget()\n const row = await resolveFigureTarget(open, domainId, host)\n if (!row) return missingFigureTarget()\n base = sourceForTool(row, host).source ?? undefined\n if (!isFigure(base)) return figureNotOpen()\n if (typeof host?.context !== 'string' || !Number.isInteger(host?.revision)) return figureNotOpen()\n figureId = open\n }\n\n let hostImage: { data: Buffer; mediaType: any } | undefined\n const rawImage = host?.image || (Array.isArray(host?.attachments) ? host.attachments.find((a: any) => a?.mediaType?.startsWith('image/')) : undefined)\n if (rawImage?.data) {\n const rawData = typeof rawImage.data === 'string' ? rawImage.data.replace(/^data:image\\/[a-zA-Z]+;base64,/, '') : rawImage.data\n const buf = typeof rawData === 'string' ? Buffer.from(rawData, 'base64') : Buffer.from(rawData)\n hostImage = { data: buf, mediaType: rawImage.mediaType || 'image/png' }\n }\n\n const { proposeFigureSteps } = getDeps()\n let result: any\n try {\n result = await proposeFigureSteps({\n prompt,\n base,\n name: typeof args?.name === 'string' && args.name.trim() ? args.name.trim() : undefined,\n image: hostImage\n })\n } catch (error) {\n /*\n * 세 번 거절된 끝의 사유를 ProposeFigureFailure.reasons 에만 두면 agentic-loop 은 「만들지 못했습니다」만\n * 받아 화면에는 「응답이 비어있습니다」가 된다. 다시 시도할 수 있는 말로 경계를 넘긴다.\n * 다른 예외(권한·통신)는 원문을 보존한다.\n */\n const reasons = Array.isArray((error as any)?.reasons) ? (error as any).reasons : []\n if (reasons.length > 0) throw new Error([(error as Error).message, ...reasons].join('\\n'))\n throw error\n }\n\n /*\n **`proposed: true` 가 카드를 만든다.** 이 표시가 없으면 제안은 도구 추적 안에 묻히고 화면에는\n 아무것도 생기지 않는다. 카드의 실행 단추가 figure-ui 의 처리기에 넘기고, 처리기가 모델러에 명령\n 목록을 한 단계로 적용한다. `args` 는 같은 제안을 두 번 세지 않기 위한 열쇠의 일부다.\n */\n const name = result.source.name || 'Figure'\n return {\n proposed: true,\n icon: 'view_in_ar',\n label: create ? `${name} 만들기` : `${name} 고치기`,\n reason:\n `명령 ${result.steps.length}개 · 등급 ${result.score?.grade ?? '-'} · 삼각형 ${result.cost.triangles} · ` +\n `${result.attempts}번에 만들었다`,\n args: { prompt, figureId, create },\n create,\n figureId,\n context: create ? undefined : host.context,\n revision: create ? undefined : host.revision,\n steps: result.steps,\n score: result.score,\n cost: result.cost,\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 ' 고치는 제안은 모델러에 열린 도형에만 낼 수 있다. 결과의 `steps` 가 무엇을 하는지 그대로 전한다 — 명령에 없는 변경을 지어내지 않는다.',\n '- 정본을 직접 저장하지 않는다. 후보만 내고, 받을지 버릴지는 저작자가 화면에서 정한다.',\n '- 좌표와 크기는 X/Y/Z 축 이름으로 정확하게 표시한다. 확인되지 않은 가로·세로·높이 의미를 임의로 붙이지 않는다.',\n '- **고치라는 요청이면 먼저 `getFigureSource` 를 부른다.** 읽지 않고 고치면 짐작을 상대로 후보를 내게 되고, 부품 이름이 갈려 이미 배치된 보드의 바인딩이 끊긴다.',\n '- 목록 화면에는 열린 도형이 없다. 기존 도형을 조회할 때는 `listFigures`에서 사용자가 말한 이름·타입의 id를 찾는다. 대상이 모호할 때만 어느 도형인지 묻는다. 기존 도형을 고치려면 모델러에서 그 도형을 열고 거기서 요청하도록 안내한다. 새 제작 요청에는 기존 정본을 조회하지 않고 `proposeFigureCandidate`를 create: true로 호출한다.',\n '- 타입 이름을 지어내지 않는다 — `listFigures` 로 확인한다. **타입 이름은 저장되는 식별자**이고 보드가 그 이름으로 도형을 찾는다. 새 도형의 타입은 첫 저장 때 사람이 정한다.',\n '- 발행이 안 되는 이유를 물으면 `inspectFigure` 를 부른다. 판정 없이 \"발행할 수 있습니다\"라고 말하지 않는다.',\n ' 판정의 각 항목은 **왜 문제인지와 무엇을 바꾸면 되는지**를 함께 담고 있다. 그대로 전한다 — 요약하면 저작자가 어느 부품의 어느 축을 고칠지 다시 찾아야 한다.',\n '- 무게·삼각형 수를 물으면 `getFigureSource` 의 `cost` 를 쓴다. 눈대중으로 세지 않는다.'\n ].join('\\n'),\n specs: figureToolSpecs()\n }\n\n registerToolCategory(authoring)\n}\n"]}
|
|
@@ -34,7 +34,7 @@ tslib_1.__decorate([
|
|
|
34
34
|
tslib_1.__metadata("design:type", Array)
|
|
35
35
|
], NewFigure.prototype, "tags", void 0);
|
|
36
36
|
tslib_1.__decorate([
|
|
37
|
-
(0, type_graphql_1.Field)({ description: 'The figure asset JSON (figure-
|
|
37
|
+
(0, type_graphql_1.Field)({ description: 'The figure asset JSON (figure-asset-1).' }),
|
|
38
38
|
tslib_1.__metadata("design:type", String)
|
|
39
39
|
], NewFigure.prototype, "source", void 0);
|
|
40
40
|
tslib_1.__decorate([
|
|
@@ -87,7 +87,7 @@ tslib_1.__decorate([
|
|
|
87
87
|
tslib_1.__metadata("design:type", Array)
|
|
88
88
|
], FigurePatch.prototype, "tags", void 0);
|
|
89
89
|
tslib_1.__decorate([
|
|
90
|
-
(0, type_graphql_1.Field)({ nullable: true, description: 'The new figure asset JSON (figure-
|
|
90
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'The new figure asset JSON (figure-asset-1).' }),
|
|
91
91
|
tslib_1.__metadata("design:type", String)
|
|
92
92
|
], FigurePatch.prototype, "source", void 0);
|
|
93
93
|
tslib_1.__decorate([
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-type.js","sourceRoot":"","sources":["../../../server/service/figure/figure-type.ts"],"names":[],"mappings":";;;;AAAA,+CAAgE;AAEhE,2CAAoC;AAEpC;;;;;GAKG;AAEI,IAAM,SAAS,GAAf,MAAM,SAAS;CA2BrB,CAAA;AA3BY,8BAAS;AAEpB;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,iEAAiE,EAAE,CAAC;;uCAC9E;AAGZ;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,gCAAgC,EAAE,CAAC;;uCAC7C;AAGZ;IADC,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,wCAAwC,EAAE,CAAC;;8CAC7D;AAGpB;IADC,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,2DAA2D,EAAE,CAAC;;2CACnF;AAGjB;IADC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,6CAA6C,EAAE,CAAC;;uCACzF;AAGf;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,
|
|
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 figure asset JSON (figure-asset-1).' })\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` 은 **한 번도 발행되지 않은 초안에서만** 바뀐다(ADR-0063 결정 4). 저장되는 식별자라\n * 발행된 뒤에 바꾸면 이미 배치된 보드가 타입을 못 찾아 오류 없이 빈 화면이 된다. 발행 전에는\n * 그 도형을 가리키는 도면이 있을 수 없다 — 도면은 최신 발행판을 그리므로 발행된 적 없는\n * 도형은 그려질 자리가 없다.\n *\n * 닫아 두었더니 저작자가 이름을 한 번도 정하지 못했다. 새 Figure 에는 기계가 지은 이름이\n * 붙고(`FIGURE_MU6RJH47`) 칸에 자물쇠가 걸려서, 그 이름이 그대로 도면에 적히는 이름이 됐다.\n */\n@InputType({ description: 'Input for updating an existing figure. `type` is accepted only while the figure has never been released.' })\nexport class FigurePatch {\n @Field({ nullable: true, description: 'The stored identifier. Accepted only before the first release; rejected once a version exists.' })\n type?: string\n\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 figure asset JSON (figure-asset-1).' })\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 of the gate finding, for example part-outside-occupancy or parts-split-on-resize.' })\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,15 +1,15 @@
|
|
|
1
|
-
export interface
|
|
1
|
+
export interface FigureWriteCriteria {
|
|
2
2
|
id: string;
|
|
3
3
|
domain: {
|
|
4
4
|
id: string;
|
|
5
5
|
};
|
|
6
6
|
source: string;
|
|
7
7
|
}
|
|
8
|
-
export declare function
|
|
8
|
+
export declare function compareAndSwapFigure(update: (criteria: FigureWriteCriteria) => Promise<{
|
|
9
9
|
affected?: number | null;
|
|
10
10
|
}>, id: string, domainId: string, expectedSource: string | undefined): Promise<void>;
|
|
11
11
|
/** Early validation only. The repository must also compare expectedSource in its atomic UPDATE. */
|
|
12
|
-
export declare function
|
|
12
|
+
export declare function prepareFigureWrite(source: string, existing?: {
|
|
13
13
|
source?: string;
|
|
14
14
|
}, expectedSource?: string): {
|
|
15
15
|
source: string;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.
|
|
3
|
+
exports.compareAndSwapFigure = compareAndSwapFigure;
|
|
4
|
+
exports.prepareFigureWrite = prepareFigureWrite;
|
|
5
5
|
exports.pictureOf = pictureOf;
|
|
6
6
|
const figure_model_1 = require("@hatiolab/figure-model");
|
|
7
|
-
async function
|
|
7
|
+
async function compareAndSwapFigure(update, id, domainId, expectedSource) {
|
|
8
8
|
if (expectedSource === undefined)
|
|
9
9
|
throw Error('수정 전 정본이 필요합니다.');
|
|
10
10
|
const result = await update({ id, domain: { id: domainId }, source: expectedSource });
|
|
@@ -12,20 +12,20 @@ async function compareAndSwapV3(update, id, domainId, expectedSource) {
|
|
|
12
12
|
throw Error('다른 편집에서 모델이 변경되었습니다. 최신 모델을 다시 열어 주세요.');
|
|
13
13
|
}
|
|
14
14
|
/** Early validation only. The repository must also compare expectedSource in its atomic UPDATE. */
|
|
15
|
-
function
|
|
15
|
+
function prepareFigureWrite(source, existing, expectedSource) {
|
|
16
16
|
if (existing && (expectedSource === undefined || existing.source !== expectedSource)) {
|
|
17
17
|
throw Error('다른 편집에서 모델이 변경되었습니다. 최신 모델을 다시 연 뒤 적용해 주세요.');
|
|
18
18
|
}
|
|
19
|
-
const asset = (0, figure_model_1.
|
|
20
|
-
(0, figure_model_1.
|
|
19
|
+
const asset = (0, figure_model_1.parseFigureAsset)(source);
|
|
20
|
+
(0, figure_model_1.compileFigureAsset)(asset);
|
|
21
21
|
/*
|
|
22
22
|
The same measure `parseSource` uses, so the catalogue's columns do not depend on which mutation wrote the row.
|
|
23
23
|
*/
|
|
24
|
-
const cost = (0, figure_model_1.
|
|
24
|
+
const cost = (0, figure_model_1.figureCostOf)(asset);
|
|
25
25
|
return {
|
|
26
|
-
source: (0, figure_model_1.
|
|
26
|
+
source: (0, figure_model_1.serializeFigureAsset)(asset),
|
|
27
27
|
name: asset.name,
|
|
28
|
-
score: (0, figure_model_1.
|
|
28
|
+
score: (0, figure_model_1.figureScoreOf)(asset)?.score ?? null,
|
|
29
29
|
triangles: cost.triangles,
|
|
30
30
|
groups: cost.groups,
|
|
31
31
|
state: 'draft'
|
|
@@ -43,4 +43,4 @@ function pictureOf(thumbnail, existing) {
|
|
|
43
43
|
return {};
|
|
44
44
|
return { thumbnail, thumbnailUpdatedAt: new Date() };
|
|
45
45
|
}
|
|
46
|
-
//# sourceMappingURL=figure-
|
|
46
|
+
//# sourceMappingURL=figure-write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"figure-write.js","sourceRoot":"","sources":["../../../server/service/figure/figure-write.ts"],"names":[],"mappings":";;AAOA,oDASC;AAGD,gDAkBC;AASD,8BAGC;AAjDD,yDAAgI;AAOzH,KAAK,UAAU,oBAAoB,CACxC,MAAgF,EAChF,EAAU,EACV,QAAgB,EAChB,cAAkC;IAElC,IAAI,cAAc,KAAK,SAAS;QAAE,MAAM,KAAK,CAAC,iBAAiB,CAAC,CAAA;IAChE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAA;IACrF,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC;QAAE,MAAM,KAAK,CAAC,wCAAwC,CAAC,CAAA;AAClF,CAAC;AAED,mGAAmG;AACnG,SAAgB,kBAAkB,CAAC,MAAc,EAAE,QAA8B,EAAE,cAAuB;IACxG,IAAI,QAAQ,IAAI,CAAC,cAAc,KAAK,SAAS,IAAI,QAAQ,CAAC,MAAM,KAAK,cAAc,CAAC,EAAE,CAAC;QACrF,MAAM,KAAK,CAAC,6CAA6C,CAAC,CAAA;IAC5D,CAAC;IACD,MAAM,KAAK,GAAG,IAAA,+BAAgB,EAAC,MAAM,CAAC,CAAA;IACtC,IAAA,iCAAkB,EAAC,KAAK,CAAC,CAAA;IACzB;;MAEE;IACF,MAAM,IAAI,GAAG,IAAA,2BAAY,EAAC,KAAK,CAAC,CAAA;IAChC,OAAO;QACL,MAAM,EAAE,IAAA,mCAAoB,EAAC,KAAK,CAAC;QACnC,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,KAAK,EAAE,IAAA,4BAAa,EAAC,KAAK,CAAC,EAAE,KAAK,IAAI,IAAI;QAC1C,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,KAAK,EAAE,OAAgB;KACxB,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,SAAS,CAAC,SAAkB,EAAE,QAAiC;IAC7E,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,QAAQ,EAAE,SAAS;QAAE,OAAO,EAAE,CAAA;IAC3E,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,IAAI,IAAI,EAAE,EAAE,CAAA;AACtD,CAAC","sourcesContent":["import { parseFigureAsset, serializeFigureAsset, compileFigureAsset, figureCostOf, figureScoreOf } from '@hatiolab/figure-model'\n\nexport interface FigureWriteCriteria {\n id: string\n domain: { id: string }\n source: string\n}\nexport async function compareAndSwapFigure(\n update: (criteria: FigureWriteCriteria) => Promise<{ affected?: number | null }>,\n id: string,\n domainId: string,\n expectedSource: string | undefined\n) {\n if (expectedSource === undefined) throw Error('수정 전 정본이 필요합니다.')\n const result = await update({ id, domain: { id: domainId }, source: expectedSource })\n if (result.affected !== 1) throw Error('다른 편집에서 모델이 변경되었습니다. 최신 모델을 다시 열어 주세요.')\n}\n\n/** Early validation only. The repository must also compare expectedSource in its atomic UPDATE. */\nexport function prepareFigureWrite(source: string, existing?: { source?: string }, expectedSource?: string) {\n if (existing && (expectedSource === undefined || existing.source !== expectedSource)) {\n throw Error('다른 편집에서 모델이 변경되었습니다. 최신 모델을 다시 연 뒤 적용해 주세요.')\n }\n const asset = parseFigureAsset(source)\n compileFigureAsset(asset)\n /*\n The same measure `parseSource` uses, so the catalogue's columns do not depend on which mutation wrote the row.\n */\n const cost = figureCostOf(asset)\n return {\n source: serializeFigureAsset(asset),\n name: asset.name,\n score: figureScoreOf(asset)?.score ?? null,\n triangles: cost.triangles,\n groups: cost.groups,\n state: 'draft' as const\n }\n}\n\n/**\n * The picture a card shows, when the modeller sent one.\n *\n * Absent means the author saved without one — a tab in the background cannot draw, and the source matters more\n * than the picture, so the save goes on and the modeller fills it in next time. The timestamp moves only when\n * the picture itself is new, so renaming a figure does not make every card look freshly drawn.\n */\nexport function pictureOf(thumbnail?: string, existing?: { thumbnail?: string }) {\n if (thumbnail === undefined || thumbnail === existing?.thumbnail) return {}\n return { thumbnail, thumbnailUpdatedAt: new Date() }\n}\n"]}
|
|
@@ -7,7 +7,7 @@ import { Domain } from '@things-factory/shell';
|
|
|
7
7
|
*
|
|
8
8
|
* ## 무엇을 담나 — 도형 자산이다, 씬 모델이 아니다
|
|
9
9
|
*
|
|
10
|
-
* 형식은 `@hatiolab/figure-model` 이 정한다(`figure-
|
|
10
|
+
* 형식은 `@hatiolab/figure-model` 이 정한다(`figure-asset-1`). 씬 컴포넌트 트리를 담지 않는다.
|
|
11
11
|
* 컴파일한 것도 저장하지 않는다 — 원본에서 파생되므로 소비하는 쪽이 커널로 컴파일한다. 저장하면 원본과 갈린다.
|
|
12
12
|
*
|
|
13
13
|
* 그래서 부품·움직임·자리 같은 것을 컬럼으로 두지 않는다 — 전부 `source` 안에 이미 있다. 두 벌이면 갈린다.
|
|
@@ -60,7 +60,7 @@ export declare class Figure {
|
|
|
60
60
|
tags?: string[];
|
|
61
61
|
state?: 'draft' | 'released';
|
|
62
62
|
/**
|
|
63
|
-
* The figure asset JSON (`figure-
|
|
63
|
+
* The figure asset JSON (`figure-asset-1`).
|
|
64
64
|
*
|
|
65
65
|
* `Board.model` 자리이지만 이름을 바꿨다 — 「씬 모델이 아니다」를 필드 이름이
|
|
66
66
|
* 드러내야 한다.
|
|
@@ -13,7 +13,7 @@ const shell_1 = require("@things-factory/shell");
|
|
|
13
13
|
*
|
|
14
14
|
* ## 무엇을 담나 — 도형 자산이다, 씬 모델이 아니다
|
|
15
15
|
*
|
|
16
|
-
* 형식은 `@hatiolab/figure-model` 이 정한다(`figure-
|
|
16
|
+
* 형식은 `@hatiolab/figure-model` 이 정한다(`figure-asset-1`). 씬 컴포넌트 트리를 담지 않는다.
|
|
17
17
|
* 컴파일한 것도 저장하지 않는다 — 원본에서 파생되므로 소비하는 쪽이 커널로 컴파일한다. 저장하면 원본과 갈린다.
|
|
18
18
|
*
|
|
19
19
|
* 그래서 부품·움직임·자리 같은 것을 컬럼으로 두지 않는다 — 전부 `source` 안에 이미 있다. 두 벌이면 갈린다.
|
|
@@ -89,7 +89,7 @@ tslib_1.__decorate([
|
|
|
89
89
|
], Figure.prototype, "state", void 0);
|
|
90
90
|
tslib_1.__decorate([
|
|
91
91
|
(0, typeorm_1.Column)({ nullable: true, ...(0, shell_1.longTextColumn)() }),
|
|
92
|
-
(0, type_graphql_1.Field)({ nullable: true, description: 'The figure asset JSON (figure-
|
|
92
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'The figure asset JSON (figure-asset-1) authored by the modeller.' }),
|
|
93
93
|
tslib_1.__metadata("design:type", String)
|
|
94
94
|
], Figure.prototype, "source", void 0);
|
|
95
95
|
tslib_1.__decorate([
|