@things-factory/board-ai 10.1.24 → 10.1.26
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/PLAN-styling-tools.md +1 -1
- package/dist-client/server/service/assistant.d.ts +5 -4
- package/dist-client/server/service/assistant.js +35 -35
- package/dist-client/server/service/assistant.js.map +1 -1
- package/dist-client/server/service/styling/clear-tools.d.ts +2 -2
- package/dist-client/server/service/styling/clear-tools.js.map +1 -1
- package/dist-client/server/service/styling/copy-tools.d.ts +3 -3
- package/dist-client/server/service/styling/copy-tools.js +1 -1
- package/dist-client/server/service/styling/copy-tools.js.map +1 -1
- package/dist-client/server/service/styling/effect-tools.d.ts +2 -2
- package/dist-client/server/service/styling/effect-tools.js.map +1 -1
- package/dist-client/server/service/styling/fill-tools.d.ts +3 -3
- package/dist-client/server/service/styling/fill-tools.js +1 -1
- package/dist-client/server/service/styling/fill-tools.js.map +1 -1
- package/dist-client/server/service/styling/material-tools.d.ts +2 -2
- package/dist-client/server/service/styling/material-tools.js.map +1 -1
- package/dist-client/server/service/styling/registry.d.ts +6 -6
- package/dist-client/server/service/styling/registry.js +2 -2
- package/dist-client/server/service/styling/registry.js.map +1 -1
- package/dist-client/server/service/styling/stroke-tools.d.ts +2 -2
- package/dist-client/server/service/styling/stroke-tools.js.map +1 -1
- package/dist-client/server/service/styling/text-tools.d.ts +2 -2
- package/dist-client/server/service/styling/text-tools.js.map +1 -1
- package/dist-client/server/service/types.d.ts +6 -148
- package/dist-client/server/service/types.js.map +1 -1
- package/dist-client/server/service/validation/board-model-schema.d.ts +262 -262
- package/dist-client/server/service/validation/board-model-schema.js +4 -4
- package/dist-client/server/service/validation/board-model-schema.js.map +1 -1
- package/dist-client/server/service/validation/tool-validation.js +5 -5
- package/dist-client/server/service/validation/tool-validation.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/index.d.ts +2 -2
- package/dist-server/index.js +2 -2
- package/dist-server/index.js.map +1 -1
- package/dist-server/service/assistant.d.ts +5 -4
- package/dist-server/service/assistant.js +37 -37
- package/dist-server/service/assistant.js.map +1 -1
- package/dist-server/service/board-ai-resolver.js +2 -2
- package/dist-server/service/board-ai-resolver.js.map +1 -1
- package/dist-server/service/index.d.ts +0 -1
- package/dist-server/service/index.js +0 -1
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/patch-entry/board-patch-subscription.js +1 -1
- package/dist-server/service/patch-entry/board-patch-subscription.js.map +1 -1
- package/dist-server/service/patch-entry/patch-entry.d.ts +1 -1
- package/dist-server/service/patch-entry/patch-entry.js +1 -1
- package/dist-server/service/patch-entry/patch-entry.js.map +1 -1
- package/dist-server/service/styling/clear-tools.d.ts +2 -2
- package/dist-server/service/styling/clear-tools.js.map +1 -1
- package/dist-server/service/styling/copy-tools.d.ts +3 -3
- package/dist-server/service/styling/copy-tools.js +1 -1
- package/dist-server/service/styling/copy-tools.js.map +1 -1
- package/dist-server/service/styling/effect-tools.d.ts +2 -2
- package/dist-server/service/styling/effect-tools.js.map +1 -1
- package/dist-server/service/styling/fill-tools.d.ts +3 -3
- package/dist-server/service/styling/fill-tools.js +1 -1
- package/dist-server/service/styling/fill-tools.js.map +1 -1
- package/dist-server/service/styling/material-tools.d.ts +2 -2
- package/dist-server/service/styling/material-tools.js.map +1 -1
- package/dist-server/service/styling/registry.d.ts +6 -6
- package/dist-server/service/styling/registry.js +2 -2
- package/dist-server/service/styling/registry.js.map +1 -1
- package/dist-server/service/styling/stroke-tools.d.ts +2 -2
- package/dist-server/service/styling/stroke-tools.js.map +1 -1
- package/dist-server/service/styling/text-tools.d.ts +2 -2
- package/dist-server/service/styling/text-tools.js.map +1 -1
- package/dist-server/service/types.d.ts +6 -148
- package/dist-server/service/types.js.map +1 -1
- package/dist-server/service/validation/board-model-schema.d.ts +262 -262
- package/dist-server/service/validation/board-model-schema.js +4 -4
- package/dist-server/service/validation/board-model-schema.js.map +1 -1
- package/dist-server/service/validation/tool-validation.js +5 -5
- package/dist-server/service/validation/tool-validation.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/logs/.5e5d741d8b7784a2fbad65eedc0fd46946aaf6f2-audit.json +5 -0
- package/logs/connections-2026-09-17-16.log +0 -0
- package/package.json +8 -7
- package/server/index.ts +2 -2
- package/server/service/assistant-integration.test.ts +44 -44
- package/server/service/assistant-llm-smoke.test.ts +5 -5
- package/server/service/assistant.test.ts +86 -86
- package/server/service/assistant.ts +41 -43
- package/server/service/board-ai-resolver.ts +2 -2
- package/server/service/index.ts +0 -1
- package/server/service/patch-entry/board-patch-subscription.ts +1 -1
- package/server/service/patch-entry/patch-entry.ts +2 -2
- package/server/service/styling/clear-tools.ts +2 -2
- package/server/service/styling/copy-tools.ts +3 -3
- package/server/service/styling/effect-tools.ts +2 -2
- package/server/service/styling/fill-tools.test.ts +1 -1
- package/server/service/styling/fill-tools.ts +4 -4
- package/server/service/styling/material-tools.ts +2 -2
- package/server/service/styling/registry.ts +6 -6
- package/server/service/styling/stroke-tools.test.ts +1 -1
- package/server/service/styling/stroke-tools.ts +3 -3
- package/server/service/styling/text-tools.ts +2 -2
- package/server/service/tool-exposure.test.ts +7 -7
- package/server/service/types.ts +9 -110
- package/server/service/validation/board-model-schema.test.ts +3 -3
- package/server/service/validation/board-model-schema.ts +4 -4
- package/server/service/validation/tool-validation.ts +5 -5
- package/dist-server/service/apply-patch.d.ts +0 -47
- package/dist-server/service/apply-patch.js +0 -369
- package/dist-server/service/apply-patch.js.map +0 -1
- package/server/service/apply-patch-drift.test.ts +0 -831
- package/server/service/apply-patch.test.ts +0 -757
- package/server/service/apply-patch.ts +0 -411
|
@@ -302,7 +302,7 @@ export const ComponentSchema = ComponentBaseSchema.superRefine((component, ctx)
|
|
|
302
302
|
* • 식별: id, refid, type
|
|
303
303
|
*
|
|
304
304
|
* ⚠ `name` 은 things-scene model-layer 가 인식 안 함 — Board *엔티티* 의 컬럼이지
|
|
305
|
-
* scene MODEL 의 필드가 아님. AI 가
|
|
305
|
+
* scene MODEL 의 필드가 아님. AI 가 modifyScene 로 name 을 보내도 죽은 필드. 검증
|
|
306
306
|
* 시에도 제거 / 무시 정책.
|
|
307
307
|
*
|
|
308
308
|
* `passthrough` 인 이유 — 3D 광원/카메라/sky 등 키가 너무 많아 strict 로는 회귀 위험
|
|
@@ -347,14 +347,14 @@ export const ComponentPatchSchema = z
|
|
|
347
347
|
})
|
|
348
348
|
.passthrough(); // 위치 / 크기 / 컴포넌트 specific 통과
|
|
349
349
|
/**
|
|
350
|
-
*
|
|
350
|
+
* modifyScene op 의 patch — 보드 root partial.
|
|
351
351
|
*
|
|
352
352
|
* 모든 root 필드 optional (BoardModelSchema 와 달리 width/height 도 optional).
|
|
353
353
|
* passthrough 로 알려지지 않은 root 키 (3D atmosphere, sky, camera 등) 통과.
|
|
354
354
|
* 다만 *알려진 nested object* (fillStyle 등) 는 strict schema 재사용 — 그 안에
|
|
355
355
|
* unknown key 가 들어오면 거부 (예: fillStyle 안 gradientStops 같은 stale 이름).
|
|
356
356
|
*
|
|
357
|
-
* AI 가 setFill 등에서 학습한 vocabulary 를
|
|
357
|
+
* AI 가 setFill 등에서 학습한 vocabulary 를 modifyScene 로 echo 시킬 때 stale
|
|
358
358
|
* key 가 fillStyle / shadow 같은 sub-object 안에 끼면 본 schema 가 catch.
|
|
359
359
|
*/
|
|
360
360
|
export const BoardPatchSchema = z
|
|
@@ -392,7 +392,7 @@ export function validateComponent(component) {
|
|
|
392
392
|
return { valid: true };
|
|
393
393
|
return { valid: false, errors: formatIssues(r.error) };
|
|
394
394
|
}
|
|
395
|
-
/**
|
|
395
|
+
/** modifyScene op 의 patch 검증. 보드 root partial. */
|
|
396
396
|
export function validateBoardPatch(patch) {
|
|
397
397
|
const r = BoardPatchSchema.safeParse(patch);
|
|
398
398
|
if (r.success)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"board-model-schema.js","sourceRoot":"","sources":["../../../../server/service/validation/board-model-schema.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * BoardModel zod schema — 영속 / patch 적용 boundary 에서 데이터 무결성 검증.\n *\n * 핵심 정책:\n * - things-scene 의 실제 drawer / setter 구현을 진실로 봄 (인터페이스 declaration X)\n * - Component schema 는 `.passthrough()` — 알려진 필드 검증 + 미지 필드 통과\n * (사용자 정의 / 데이터 바인딩 / 컴포넌트별 specific 속성 허용)\n * - 그러나 styling sub-objects (FillStyle / Shadow 등) 는 `.strict()` — unknown key\n * 거부. stale 잔재 필드 (예: colorStops + colors 공존) 자동 발견.\n * - validation 실패 시 silent 누락 X — caller 가 명시적 에러 / 로그\n *\n * 적용 지점:\n * 1. server `UpdateBoard` mutation 진입 → DB 영속 직전 마지막 방어선\n * 2. server `BoardAIChat` mutation 응답 직전 — AI 가 만든 patch 의 안전성\n * 3. (선택) client apply-patch 적용 후 — 메모리 상태 무결성\n */\nimport { z } from 'zod'\n\n// ── Color ────────────────────────────────────────────────────────\n\n/**\n * 색상 — hex (#rgb / #rgba / #rrggbb / #rrggbbaa) / rgb / rgba / CSS named / 'transparent'.\n * 정규식은 느슨 — 정확한 검증은 normalizeColor 가, 여기선 형식만.\n */\nconst ColorSchema = z.string().min(1).max(50)\n\n// ── FillStyle (state.fillStyle) ──────────────────────────────────\n//\n// 실제 형식 (drawer/fill.ts 검증):\n// - solid: 색상 string 그대로\n// - gradient: { type: 'gradient', gradientType, colorStops: [{position, color}], rotation?, opacity? }\n// - pattern: { type: 'pattern', image, ...옵션들 }\n// - none: undefined / null\n\nconst GradientStopSchema = z\n .object({\n position: z.number().min(0).max(1),\n color: ColorSchema\n })\n .strict()\n\nconst FillStyleGradientSchema = z\n .object({\n type: z.literal('gradient'),\n gradientType: z.enum(['linear', 'radial']).optional(),\n colorStops: z.array(GradientStopSchema).min(2),\n rotation: z.number().optional(),\n opacity: z.number().min(0).max(1).optional(),\n center: z\n .enum([\n 'center',\n 'left-top',\n 'left-bottom',\n 'right-top',\n 'right-bottom',\n 'left',\n 'right',\n 'top',\n 'bottom'\n ])\n .optional()\n })\n .strict()\n\nconst FillStylePatternSchema = z\n .object({\n type: z.literal('pattern'),\n image: z.union([z.string(), z.unknown()]),\n color: ColorSchema.optional(),\n fitPattern: z.boolean().optional(),\n noRepeat: z.boolean().optional(),\n align: z\n .enum([\n 'center',\n 'left-top',\n 'left-bottom',\n 'right-top',\n 'right-bottom',\n 'left',\n 'right',\n 'top',\n 'bottom'\n ])\n .optional(),\n offsetX: z.number().optional(),\n offsetY: z.number().optional(),\n width: z.number().optional(),\n height: z.number().optional(),\n opacity: z.number().min(0).max(1).optional(),\n guideOnly: z.boolean().optional()\n })\n .strict()\n\n/** 'none' type — fillStyle 제거 의도 표현 (state.fillStyle = {type:'none'} 도 허용). */\nconst FillStyleNoneSchema = z.object({ type: z.literal('none') }).strict()\n\nexport const FillStyleSchema = z.union([\n ColorSchema, // solid: '#xxx'\n FillStyleGradientSchema,\n FillStylePatternSchema,\n FillStyleNoneSchema,\n z.null()\n])\n\n// ── Shadow (state.shadow) ────────────────────────────────────────\n//\n// Canonical (CSS box-shadow / Canvas API 표준): { color, offsetX, offsetY, blur }\n// Backwards-compat aliases (옛 이름, drawer 가 fallback 으로 읽음): left / top / blurSize.\n// 영속된 옛 데이터 호환을 위해 두 이름 다 schema 통과 — 새 코드는 canonical 사용.\n\nexport const ShadowSchema = z\n .object({\n color: ColorSchema.optional(),\n // canonical (CSS / Canvas 표준)\n offsetX: z.number().optional(),\n offsetY: z.number().optional(),\n blur: z.number().min(0).optional(),\n // legacy aliases — 옛 things-scene 데이터 호환용. 새 코드는 위 canonical 사용.\n left: z.number().optional(),\n top: z.number().optional(),\n blurSize: z.number().min(0).optional()\n })\n .strict()\n\n// ── Material3D (state.material3d) ────────────────────────────────\n//\n// `things-scene/src/threed/material-3d.ts` 의 Material3D 인터페이스와 일치.\n\nexport const Material3DSchema = z\n .object({\n preset: z\n .enum(['default', 'metal', 'glass', 'plastic', 'wood', 'ceramic', 'rubber', 'custom'])\n .optional(),\n metalness: z.number().min(0).max(1).optional(),\n roughness: z.number().min(0).max(1).optional(),\n emissive: ColorSchema.optional(),\n emissiveIntensity: z.number().min(0).max(5).optional(),\n opacity: z.number().min(0).max(1).optional(),\n envMapIntensity: z.number().min(0).max(3).optional(),\n side: z.enum(['double', 'front', 'back']).optional(),\n castShadow: z.boolean().optional(),\n receiveShadow: z.boolean().optional()\n })\n .strict()\n\n// ── DataMapping (state.mappings) ─────────────────────────────────\n//\n// 데이터 바인딩 — `interfaces/data.ts` Mapping. rule / target / accessor / converter / param.\n\nexport const MappingSchema = z\n .object({\n rule: z.string().optional(),\n target: z.string(),\n property: z.string().optional(),\n accessor: z.string().optional(),\n source: z.unknown().optional(),\n converter: z.union([z.string(), z.unknown()]).optional(),\n param: z.unknown().optional()\n })\n .passthrough() // mapping 의 자유 필드 (converter args 등) 허용\n\n// ── Component (one component in board.components) ────────────────\n//\n// 핵심 styling 필드는 명시 검증, 나머지는 passthrough — 컴포넌트별 specific 속성 자유.\n\nconst LineDashSchema = z.enum([\n 'solid',\n 'round-dot',\n 'square-dot',\n 'dash',\n 'dash-dot',\n 'long-dash',\n 'long-dash-dot',\n 'long-dash-dot-dot'\n])\n\n/**\n * 컴포넌트가 *어떤 형태든* 유효한 geometry 를 가졌는지 확인.\n *\n * things-scene 의 컴포넌트 type 별로 geometry 필드가 다양함 (rect 는 box,\n * line 은 endpoints, polyline 은 vertices/path 등). type → 필드 매핑을 strict\n * 하게 enumerate 하면 신규 type 등록 시 schema 도 동시 업데이트 필요 → forward\n * compatibility 손해.\n *\n * 대신 \"최소한 어떤 geometry 형식이라도 있어야 한다\" 는 약한 보장 — 빈 컴포넌트\n * (`{type:'rect'}` 만) 또는 type 에 안 맞는 geometry 구성 (`{type:'rect', cx:10, cy:20}`)\n * 같은 명백한 오류만 차단. 실제 렌더링 정합성은 things-scene 자체가 검증.\n */\nfunction componentHasValidGeometry(component: any): boolean {\n if (!component || typeof component !== 'object') return false\n\n // Box geometry — left/top 둘 다 있어야 (width/height 는 optional)\n const hasBox = typeof component.left === 'number' && typeof component.top === 'number'\n\n // Circle/ellipse geometry — cx/cy 둘 다 있어야\n const hasCircle = typeof component.cx === 'number' && typeof component.cy === 'number'\n\n // Line endpoints\n const hasLineEndpoints =\n typeof component.x1 === 'number' &&\n typeof component.y1 === 'number' &&\n typeof component.x2 === 'number' &&\n typeof component.y2 === 'number'\n\n // Path / points / vertices 계열\n const hasPath = typeof component.path === 'string' || Array.isArray(component.path)\n const hasPoints = Array.isArray(component.points) && component.points.length > 0\n const hasVertices =\n (Array.isArray(component.vertices) && component.vertices.length > 0) ||\n (Array.isArray(component.verticles) && component.verticles.length > 0) // legacy 오타\n\n // line connector — startPoint / endPoint 형태 (anchor 기반 line)\n const hasConnector =\n component.from && typeof component.from === 'object' &&\n component.to && typeof component.to === 'object'\n\n return hasBox || hasCircle || hasLineEndpoints || hasPath || hasPoints || hasVertices || hasConnector\n}\n\n/** type 이 'gauge' 또는 'gauge-*' 면 value 필드 필요 (선택: min/max). */\nfunction isGaugeType(type: string): boolean {\n return type === 'gauge' || type.startsWith('gauge-')\n}\n\n/** type 이 'chart' 또는 'chart-*' 면 data 필드 필요. */\nfunction isChartType(type: string): boolean {\n return type === 'chart' || type.startsWith('chart-')\n}\n\nconst ComponentBaseSchema = z\n .object({\n type: z.string().min(1),\n refid: z.number().int().optional(), // 신규 컴포넌트 추가 시점엔 미할당 가능\n // 위치 / 크기 — 컴포넌트 타입에 따라 다름. 본 schema 는 optional 로 두고\n // refinement 가 *어떤 형태든* 있는지만 검사.\n left: z.number().optional(),\n top: z.number().optional(),\n width: z.number().optional(),\n height: z.number().optional(),\n cx: z.number().optional(),\n cy: z.number().optional(),\n rx: z.number().optional(),\n ry: z.number().optional(),\n radius: z.number().optional(),\n x1: z.number().optional(),\n y1: z.number().optional(),\n x2: z.number().optional(),\n y2: z.number().optional(),\n rotation: z.number().optional(),\n // styling\n fillStyle: FillStyleSchema.optional(),\n strokeStyle: ColorSchema.optional(), // top-level string\n lineWidth: z.number().min(0).optional(),\n lineDash: LineDashSchema.optional(),\n lineCap: z.enum(['butt', 'round', 'square']).optional(),\n lineJoin: z.enum(['miter', 'round', 'bevel']).optional(),\n shadow: ShadowSchema.optional(),\n material3d: Material3DSchema.optional(),\n // text / font — top-level\n text: z.string().optional(),\n fontFamily: z.string().optional(),\n fontSize: z.number().positive().optional(),\n fontWeight: z.union([z.string(), z.number()]).optional(),\n fontStyle: z.string().optional(),\n fontColor: ColorSchema.optional(),\n textAlign: z.enum(['left', 'center', 'right', 'start', 'end', 'justify']).optional(),\n textBaseline: z\n .enum(['top', 'middle', 'bottom', 'hanging', 'alphabetic', 'ideographic'])\n .optional(),\n lineHeight: z.number().positive().optional(),\n // visibility / opacity\n alpha: z.number().min(0).max(1).optional(),\n hidden: z.boolean().optional(),\n // misc — 검증 안 함\n id: z.string().optional(),\n class: z.string().optional(),\n tag: z.string().optional(),\n // 데이터 바인딩\n mappings: z.array(MappingSchema).optional()\n })\n .passthrough() // 컴포넌트별 specific 속성 (vehicle wheels, gauge needle, points/path 등) 허용\n\n/**\n * Component schema — base 위에 type-aware refinement 두 가지:\n *\n * 1. 모든 컴포넌트는 *어떤 형태든* 유효한 geometry 가 있어야\n * (left+top / cx+cy / x1y1x2y2 / path / points / vertices / from+to)\n * 2. gauge-* type 은 value 필요, chart-* type 은 data 필요 (soft check —\n * 일부 prefab 이 미리 채워주는 케이스도 있어 strict 거부보다 미리 잡기 목적)\n *\n * 이 refinement 들이 LLM 의 잘못된 geometry (rect 에 cx/cy만, line 에 left/top만)\n * 를 영속 직전 차단.\n */\nexport const ComponentSchema = ComponentBaseSchema.superRefine((component, ctx) => {\n if (!componentHasValidGeometry(component)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message:\n `Component '${component.type}' has no valid geometry — provide left+top (box), cx+cy (circle/ellipse), x1+y1+x2+y2 (line), path / points / vertices, or from+to (connector).`\n })\n }\n\n if (isGaugeType(component.type) && component.value === undefined) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['value'],\n message: `Component '${component.type}' requires 'value' (current sensor reading or display number).`\n })\n }\n\n if (isChartType(component.type) && component.data === undefined) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['data'],\n message: `Component '${component.type}' requires 'data' (chart datapoints array or object).`\n })\n }\n})\n\n// ── BoardModel (top-level) ───────────────────────────────────────\n\n/**\n * BoardModel — things-scene 의 model-layer (보드 root).\n *\n * 인식되는 root 속성 (model-layer 의 NATURE.properties + 실제 코드에서 state.* 로\n * 참조되는 키):\n * • Layout: width, height, fitMode, translate, scale\n * • 2D 배경: fillStyle\n * • 3D Sky: sky, skyColor\n * • 3D Tone: exposure\n * • 3D Hemisphere: hemiSkyColor, hemiGroundColor, hemiIntensity\n * • 3D Directional light: dirLightEnabled, dirLightColor, dirLightIntensity,\n * dirLightFollowCamera, dirLightAzimuth, dirLightElevation\n * • 3D Shadow: dirShadowEnabled, dirShadowMapSize, dirShadowBias\n * • 3D Camera: cameraX, cameraY, cameraZ, cameraAutoPlay, autoRotate, initialCameraView\n * • 식별: id, refid, type\n *\n * ⚠ `name` 은 things-scene model-layer 가 인식 안 함 — Board *엔티티* 의 컬럼이지\n * scene MODEL 의 필드가 아님. AI 가 modifyBoard 로 name 을 보내도 죽은 필드. 검증\n * 시에도 제거 / 무시 정책.\n *\n * `passthrough` 인 이유 — 3D 광원/카메라/sky 등 키가 너무 많아 strict 로는 회귀 위험\n * 큼. 대신 패키지 외부 (operato 등) 가 임의 root 키 추가하는 걸 막지 않음.\n */\nexport const BoardModelSchema = z\n .object({\n width: z.number().positive(),\n height: z.number().positive(),\n fillStyle: FillStyleSchema.optional(), // 보드 배경\n components: z.array(ComponentSchema).optional()\n })\n .passthrough() // 보드 레벨 자유 필드 (3D atmosphere / sky / camera 등)\n\n// ── Patch op schemas — modify op 의 patch 가 invalid 이면 거부 ───\n\n/**\n * modify op 의 patch — 컴포넌트 partial. 모든 필드 optional. styling sub-object\n * 만 strict 하게 검증해 stale key 잔존 차단.\n */\nexport const ComponentPatchSchema = z\n .object({\n fillStyle: FillStyleSchema.nullable().optional(),\n strokeStyle: ColorSchema.nullable().optional(),\n lineWidth: z.number().min(0).optional(),\n lineDash: LineDashSchema.optional(),\n lineCap: z.enum(['butt', 'round', 'square']).optional(),\n lineJoin: z.enum(['miter', 'round', 'bevel']).optional(),\n shadow: ShadowSchema.nullable().optional(),\n material3d: Material3DSchema.nullable().optional(),\n text: z.string().optional(),\n fontFamily: z.string().optional(),\n fontSize: z.number().positive().optional(),\n fontWeight: z.union([z.string(), z.number()]).optional(),\n fontStyle: z.string().optional(),\n fontColor: ColorSchema.optional(),\n textAlign: z.enum(['left', 'center', 'right', 'start', 'end', 'justify']).optional(),\n textBaseline: z\n .enum(['top', 'middle', 'bottom', 'hanging', 'alphabetic', 'ideographic'])\n .optional(),\n lineHeight: z.number().positive().optional(),\n alpha: z.number().min(0).max(1).optional(),\n hidden: z.boolean().optional()\n })\n .passthrough() // 위치 / 크기 / 컴포넌트 specific 통과\n\n/**\n * modifyBoard op 의 patch — 보드 root partial.\n *\n * 모든 root 필드 optional (BoardModelSchema 와 달리 width/height 도 optional).\n * passthrough 로 알려지지 않은 root 키 (3D atmosphere, sky, camera 등) 통과.\n * 다만 *알려진 nested object* (fillStyle 등) 는 strict schema 재사용 — 그 안에\n * unknown key 가 들어오면 거부 (예: fillStyle 안 gradientStops 같은 stale 이름).\n *\n * AI 가 setFill 등에서 학습한 vocabulary 를 modifyBoard 로 echo 시킬 때 stale\n * key 가 fillStyle / shadow 같은 sub-object 안에 끼면 본 schema 가 catch.\n */\nexport const BoardPatchSchema = z\n .object({\n width: z.number().positive().optional(),\n height: z.number().positive().optional(),\n fillStyle: FillStyleSchema.nullable().optional(),\n components: z.array(ComponentSchema).optional()\n })\n .passthrough() // root 의 자유 필드 (sky / exposure / hemi*/ dirLight* / camera* 등)\n\n// ── 검증 헬퍼 ────────────────────────────────────────────────────\n\nexport interface ValidationResult {\n valid: boolean\n errors?: Array<{ path: string; message: string }>\n}\n\nfunction formatIssues(error: z.ZodError): Array<{ path: string; message: string }> {\n return error.issues.map(issue => ({\n path: issue.path.join('.'),\n message: issue.message\n }))\n}\n\n/** BoardModel 전체 검증. 영속 / 로드 시 사용. */\nexport function validateBoardModel(model: unknown): ValidationResult {\n const r = BoardModelSchema.safeParse(model)\n if (r.success) return { valid: true }\n return { valid: false, errors: formatIssues(r.error) }\n}\n\n/** modify patch 검증. AI 가 만든 patch 가 styling spec 에 맞는지. */\nexport function validateComponentPatch(patch: unknown): ValidationResult {\n const r = ComponentPatchSchema.safeParse(patch)\n if (r.success) return { valid: true }\n return { valid: false, errors: formatIssues(r.error) }\n}\n\n/** 한 컴포넌트 검증. addComponent 시 사용. */\nexport function validateComponent(component: unknown): ValidationResult {\n const r = ComponentSchema.safeParse(component)\n if (r.success) return { valid: true }\n return { valid: false, errors: formatIssues(r.error) }\n}\n\n/** modifyBoard op 의 patch 검증. 보드 root partial. */\nexport function validateBoardPatch(patch: unknown): ValidationResult {\n const r = BoardPatchSchema.safeParse(patch)\n if (r.success) return { valid: true }\n return { valid: false, errors: formatIssues(r.error) }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"board-model-schema.js","sourceRoot":"","sources":["../../../../server/service/validation/board-model-schema.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * BoardModel zod schema — 영속 / patch 적용 boundary 에서 데이터 무결성 검증.\n *\n * 핵심 정책:\n * - things-scene 의 실제 drawer / setter 구현을 진실로 봄 (인터페이스 declaration X)\n * - Component schema 는 `.passthrough()` — 알려진 필드 검증 + 미지 필드 통과\n * (사용자 정의 / 데이터 바인딩 / 컴포넌트별 specific 속성 허용)\n * - 그러나 styling sub-objects (FillStyle / Shadow 등) 는 `.strict()` — unknown key\n * 거부. stale 잔재 필드 (예: colorStops + colors 공존) 자동 발견.\n * - validation 실패 시 silent 누락 X — caller 가 명시적 에러 / 로그\n *\n * 적용 지점:\n * 1. server `UpdateBoard` mutation 진입 → DB 영속 직전 마지막 방어선\n * 2. server `BoardAIChat` mutation 응답 직전 — AI 가 만든 patch 의 안전성\n * 3. (선택) client apply-patch 적용 후 — 메모리 상태 무결성\n */\nimport { z } from 'zod'\n\n// ── Color ────────────────────────────────────────────────────────\n\n/**\n * 색상 — hex (#rgb / #rgba / #rrggbb / #rrggbbaa) / rgb / rgba / CSS named / 'transparent'.\n * 정규식은 느슨 — 정확한 검증은 normalizeColor 가, 여기선 형식만.\n */\nconst ColorSchema = z.string().min(1).max(50)\n\n// ── FillStyle (state.fillStyle) ──────────────────────────────────\n//\n// 실제 형식 (drawer/fill.ts 검증):\n// - solid: 색상 string 그대로\n// - gradient: { type: 'gradient', gradientType, colorStops: [{position, color}], rotation?, opacity? }\n// - pattern: { type: 'pattern', image, ...옵션들 }\n// - none: undefined / null\n\nconst GradientStopSchema = z\n .object({\n position: z.number().min(0).max(1),\n color: ColorSchema\n })\n .strict()\n\nconst FillStyleGradientSchema = z\n .object({\n type: z.literal('gradient'),\n gradientType: z.enum(['linear', 'radial']).optional(),\n colorStops: z.array(GradientStopSchema).min(2),\n rotation: z.number().optional(),\n opacity: z.number().min(0).max(1).optional(),\n center: z\n .enum([\n 'center',\n 'left-top',\n 'left-bottom',\n 'right-top',\n 'right-bottom',\n 'left',\n 'right',\n 'top',\n 'bottom'\n ])\n .optional()\n })\n .strict()\n\nconst FillStylePatternSchema = z\n .object({\n type: z.literal('pattern'),\n image: z.union([z.string(), z.unknown()]),\n color: ColorSchema.optional(),\n fitPattern: z.boolean().optional(),\n noRepeat: z.boolean().optional(),\n align: z\n .enum([\n 'center',\n 'left-top',\n 'left-bottom',\n 'right-top',\n 'right-bottom',\n 'left',\n 'right',\n 'top',\n 'bottom'\n ])\n .optional(),\n offsetX: z.number().optional(),\n offsetY: z.number().optional(),\n width: z.number().optional(),\n height: z.number().optional(),\n opacity: z.number().min(0).max(1).optional(),\n guideOnly: z.boolean().optional()\n })\n .strict()\n\n/** 'none' type — fillStyle 제거 의도 표현 (state.fillStyle = {type:'none'} 도 허용). */\nconst FillStyleNoneSchema = z.object({ type: z.literal('none') }).strict()\n\nexport const FillStyleSchema = z.union([\n ColorSchema, // solid: '#xxx'\n FillStyleGradientSchema,\n FillStylePatternSchema,\n FillStyleNoneSchema,\n z.null()\n])\n\n// ── Shadow (state.shadow) ────────────────────────────────────────\n//\n// Canonical (CSS box-shadow / Canvas API 표준): { color, offsetX, offsetY, blur }\n// Backwards-compat aliases (옛 이름, drawer 가 fallback 으로 읽음): left / top / blurSize.\n// 영속된 옛 데이터 호환을 위해 두 이름 다 schema 통과 — 새 코드는 canonical 사용.\n\nexport const ShadowSchema = z\n .object({\n color: ColorSchema.optional(),\n // canonical (CSS / Canvas 표준)\n offsetX: z.number().optional(),\n offsetY: z.number().optional(),\n blur: z.number().min(0).optional(),\n // legacy aliases — 옛 things-scene 데이터 호환용. 새 코드는 위 canonical 사용.\n left: z.number().optional(),\n top: z.number().optional(),\n blurSize: z.number().min(0).optional()\n })\n .strict()\n\n// ── Material3D (state.material3d) ────────────────────────────────\n//\n// `things-scene/src/threed/material-3d.ts` 의 Material3D 인터페이스와 일치.\n\nexport const Material3DSchema = z\n .object({\n preset: z\n .enum(['default', 'metal', 'glass', 'plastic', 'wood', 'ceramic', 'rubber', 'custom'])\n .optional(),\n metalness: z.number().min(0).max(1).optional(),\n roughness: z.number().min(0).max(1).optional(),\n emissive: ColorSchema.optional(),\n emissiveIntensity: z.number().min(0).max(5).optional(),\n opacity: z.number().min(0).max(1).optional(),\n envMapIntensity: z.number().min(0).max(3).optional(),\n side: z.enum(['double', 'front', 'back']).optional(),\n castShadow: z.boolean().optional(),\n receiveShadow: z.boolean().optional()\n })\n .strict()\n\n// ── DataMapping (state.mappings) ─────────────────────────────────\n//\n// 데이터 바인딩 — `interfaces/data.ts` Mapping. rule / target / accessor / converter / param.\n\nexport const MappingSchema = z\n .object({\n rule: z.string().optional(),\n target: z.string(),\n property: z.string().optional(),\n accessor: z.string().optional(),\n source: z.unknown().optional(),\n converter: z.union([z.string(), z.unknown()]).optional(),\n param: z.unknown().optional()\n })\n .passthrough() // mapping 의 자유 필드 (converter args 등) 허용\n\n// ── Component (one component in board.components) ────────────────\n//\n// 핵심 styling 필드는 명시 검증, 나머지는 passthrough — 컴포넌트별 specific 속성 자유.\n\nconst LineDashSchema = z.enum([\n 'solid',\n 'round-dot',\n 'square-dot',\n 'dash',\n 'dash-dot',\n 'long-dash',\n 'long-dash-dot',\n 'long-dash-dot-dot'\n])\n\n/**\n * 컴포넌트가 *어떤 형태든* 유효한 geometry 를 가졌는지 확인.\n *\n * things-scene 의 컴포넌트 type 별로 geometry 필드가 다양함 (rect 는 box,\n * line 은 endpoints, polyline 은 vertices/path 등). type → 필드 매핑을 strict\n * 하게 enumerate 하면 신규 type 등록 시 schema 도 동시 업데이트 필요 → forward\n * compatibility 손해.\n *\n * 대신 \"최소한 어떤 geometry 형식이라도 있어야 한다\" 는 약한 보장 — 빈 컴포넌트\n * (`{type:'rect'}` 만) 또는 type 에 안 맞는 geometry 구성 (`{type:'rect', cx:10, cy:20}`)\n * 같은 명백한 오류만 차단. 실제 렌더링 정합성은 things-scene 자체가 검증.\n */\nfunction componentHasValidGeometry(component: any): boolean {\n if (!component || typeof component !== 'object') return false\n\n // Box geometry — left/top 둘 다 있어야 (width/height 는 optional)\n const hasBox = typeof component.left === 'number' && typeof component.top === 'number'\n\n // Circle/ellipse geometry — cx/cy 둘 다 있어야\n const hasCircle = typeof component.cx === 'number' && typeof component.cy === 'number'\n\n // Line endpoints\n const hasLineEndpoints =\n typeof component.x1 === 'number' &&\n typeof component.y1 === 'number' &&\n typeof component.x2 === 'number' &&\n typeof component.y2 === 'number'\n\n // Path / points / vertices 계열\n const hasPath = typeof component.path === 'string' || Array.isArray(component.path)\n const hasPoints = Array.isArray(component.points) && component.points.length > 0\n const hasVertices =\n (Array.isArray(component.vertices) && component.vertices.length > 0) ||\n (Array.isArray(component.verticles) && component.verticles.length > 0) // legacy 오타\n\n // line connector — startPoint / endPoint 형태 (anchor 기반 line)\n const hasConnector =\n component.from && typeof component.from === 'object' &&\n component.to && typeof component.to === 'object'\n\n return hasBox || hasCircle || hasLineEndpoints || hasPath || hasPoints || hasVertices || hasConnector\n}\n\n/** type 이 'gauge' 또는 'gauge-*' 면 value 필드 필요 (선택: min/max). */\nfunction isGaugeType(type: string): boolean {\n return type === 'gauge' || type.startsWith('gauge-')\n}\n\n/** type 이 'chart' 또는 'chart-*' 면 data 필드 필요. */\nfunction isChartType(type: string): boolean {\n return type === 'chart' || type.startsWith('chart-')\n}\n\nconst ComponentBaseSchema = z\n .object({\n type: z.string().min(1),\n refid: z.number().int().optional(), // 신규 컴포넌트 추가 시점엔 미할당 가능\n // 위치 / 크기 — 컴포넌트 타입에 따라 다름. 본 schema 는 optional 로 두고\n // refinement 가 *어떤 형태든* 있는지만 검사.\n left: z.number().optional(),\n top: z.number().optional(),\n width: z.number().optional(),\n height: z.number().optional(),\n cx: z.number().optional(),\n cy: z.number().optional(),\n rx: z.number().optional(),\n ry: z.number().optional(),\n radius: z.number().optional(),\n x1: z.number().optional(),\n y1: z.number().optional(),\n x2: z.number().optional(),\n y2: z.number().optional(),\n rotation: z.number().optional(),\n // styling\n fillStyle: FillStyleSchema.optional(),\n strokeStyle: ColorSchema.optional(), // top-level string\n lineWidth: z.number().min(0).optional(),\n lineDash: LineDashSchema.optional(),\n lineCap: z.enum(['butt', 'round', 'square']).optional(),\n lineJoin: z.enum(['miter', 'round', 'bevel']).optional(),\n shadow: ShadowSchema.optional(),\n material3d: Material3DSchema.optional(),\n // text / font — top-level\n text: z.string().optional(),\n fontFamily: z.string().optional(),\n fontSize: z.number().positive().optional(),\n fontWeight: z.union([z.string(), z.number()]).optional(),\n fontStyle: z.string().optional(),\n fontColor: ColorSchema.optional(),\n textAlign: z.enum(['left', 'center', 'right', 'start', 'end', 'justify']).optional(),\n textBaseline: z\n .enum(['top', 'middle', 'bottom', 'hanging', 'alphabetic', 'ideographic'])\n .optional(),\n lineHeight: z.number().positive().optional(),\n // visibility / opacity\n alpha: z.number().min(0).max(1).optional(),\n hidden: z.boolean().optional(),\n // misc — 검증 안 함\n id: z.string().optional(),\n class: z.string().optional(),\n tag: z.string().optional(),\n // 데이터 바인딩\n mappings: z.array(MappingSchema).optional()\n })\n .passthrough() // 컴포넌트별 specific 속성 (vehicle wheels, gauge needle, points/path 등) 허용\n\n/**\n * Component schema — base 위에 type-aware refinement 두 가지:\n *\n * 1. 모든 컴포넌트는 *어떤 형태든* 유효한 geometry 가 있어야\n * (left+top / cx+cy / x1y1x2y2 / path / points / vertices / from+to)\n * 2. gauge-* type 은 value 필요, chart-* type 은 data 필요 (soft check —\n * 일부 prefab 이 미리 채워주는 케이스도 있어 strict 거부보다 미리 잡기 목적)\n *\n * 이 refinement 들이 LLM 의 잘못된 geometry (rect 에 cx/cy만, line 에 left/top만)\n * 를 영속 직전 차단.\n */\nexport const ComponentSchema = ComponentBaseSchema.superRefine((component, ctx) => {\n if (!componentHasValidGeometry(component)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message:\n `Component '${component.type}' has no valid geometry — provide left+top (box), cx+cy (circle/ellipse), x1+y1+x2+y2 (line), path / points / vertices, or from+to (connector).`\n })\n }\n\n if (isGaugeType(component.type) && component.value === undefined) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['value'],\n message: `Component '${component.type}' requires 'value' (current sensor reading or display number).`\n })\n }\n\n if (isChartType(component.type) && component.data === undefined) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['data'],\n message: `Component '${component.type}' requires 'data' (chart datapoints array or object).`\n })\n }\n})\n\n// ── BoardModel (top-level) ───────────────────────────────────────\n\n/**\n * BoardModel — things-scene 의 model-layer (보드 root).\n *\n * 인식되는 root 속성 (model-layer 의 NATURE.properties + 실제 코드에서 state.* 로\n * 참조되는 키):\n * • Layout: width, height, fitMode, translate, scale\n * • 2D 배경: fillStyle\n * • 3D Sky: sky, skyColor\n * • 3D Tone: exposure\n * • 3D Hemisphere: hemiSkyColor, hemiGroundColor, hemiIntensity\n * • 3D Directional light: dirLightEnabled, dirLightColor, dirLightIntensity,\n * dirLightFollowCamera, dirLightAzimuth, dirLightElevation\n * • 3D Shadow: dirShadowEnabled, dirShadowMapSize, dirShadowBias\n * • 3D Camera: cameraX, cameraY, cameraZ, cameraAutoPlay, autoRotate, initialCameraView\n * • 식별: id, refid, type\n *\n * ⚠ `name` 은 things-scene model-layer 가 인식 안 함 — Board *엔티티* 의 컬럼이지\n * scene MODEL 의 필드가 아님. AI 가 modifyScene 로 name 을 보내도 죽은 필드. 검증\n * 시에도 제거 / 무시 정책.\n *\n * `passthrough` 인 이유 — 3D 광원/카메라/sky 등 키가 너무 많아 strict 로는 회귀 위험\n * 큼. 대신 패키지 외부 (operato 등) 가 임의 root 키 추가하는 걸 막지 않음.\n */\nexport const BoardModelSchema = z\n .object({\n width: z.number().positive(),\n height: z.number().positive(),\n fillStyle: FillStyleSchema.optional(), // 보드 배경\n components: z.array(ComponentSchema).optional()\n })\n .passthrough() // 보드 레벨 자유 필드 (3D atmosphere / sky / camera 등)\n\n// ── Patch op schemas — modify op 의 patch 가 invalid 이면 거부 ───\n\n/**\n * modify op 의 patch — 컴포넌트 partial. 모든 필드 optional. styling sub-object\n * 만 strict 하게 검증해 stale key 잔존 차단.\n */\nexport const ComponentPatchSchema = z\n .object({\n fillStyle: FillStyleSchema.nullable().optional(),\n strokeStyle: ColorSchema.nullable().optional(),\n lineWidth: z.number().min(0).optional(),\n lineDash: LineDashSchema.optional(),\n lineCap: z.enum(['butt', 'round', 'square']).optional(),\n lineJoin: z.enum(['miter', 'round', 'bevel']).optional(),\n shadow: ShadowSchema.nullable().optional(),\n material3d: Material3DSchema.nullable().optional(),\n text: z.string().optional(),\n fontFamily: z.string().optional(),\n fontSize: z.number().positive().optional(),\n fontWeight: z.union([z.string(), z.number()]).optional(),\n fontStyle: z.string().optional(),\n fontColor: ColorSchema.optional(),\n textAlign: z.enum(['left', 'center', 'right', 'start', 'end', 'justify']).optional(),\n textBaseline: z\n .enum(['top', 'middle', 'bottom', 'hanging', 'alphabetic', 'ideographic'])\n .optional(),\n lineHeight: z.number().positive().optional(),\n alpha: z.number().min(0).max(1).optional(),\n hidden: z.boolean().optional()\n })\n .passthrough() // 위치 / 크기 / 컴포넌트 specific 통과\n\n/**\n * modifyScene op 의 patch — 보드 root partial.\n *\n * 모든 root 필드 optional (BoardModelSchema 와 달리 width/height 도 optional).\n * passthrough 로 알려지지 않은 root 키 (3D atmosphere, sky, camera 등) 통과.\n * 다만 *알려진 nested object* (fillStyle 등) 는 strict schema 재사용 — 그 안에\n * unknown key 가 들어오면 거부 (예: fillStyle 안 gradientStops 같은 stale 이름).\n *\n * AI 가 setFill 등에서 학습한 vocabulary 를 modifyScene 로 echo 시킬 때 stale\n * key 가 fillStyle / shadow 같은 sub-object 안에 끼면 본 schema 가 catch.\n */\nexport const BoardPatchSchema = z\n .object({\n width: z.number().positive().optional(),\n height: z.number().positive().optional(),\n fillStyle: FillStyleSchema.nullable().optional(),\n components: z.array(ComponentSchema).optional()\n })\n .passthrough() // root 의 자유 필드 (sky / exposure / hemi*/ dirLight* / camera* 등)\n\n// ── 검증 헬퍼 ────────────────────────────────────────────────────\n\nexport interface ValidationResult {\n valid: boolean\n errors?: Array<{ path: string; message: string }>\n}\n\nfunction formatIssues(error: z.ZodError): Array<{ path: string; message: string }> {\n return error.issues.map(issue => ({\n path: issue.path.join('.'),\n message: issue.message\n }))\n}\n\n/** BoardModel 전체 검증. 영속 / 로드 시 사용. */\nexport function validateBoardModel(model: unknown): ValidationResult {\n const r = BoardModelSchema.safeParse(model)\n if (r.success) return { valid: true }\n return { valid: false, errors: formatIssues(r.error) }\n}\n\n/** modify patch 검증. AI 가 만든 patch 가 styling spec 에 맞는지. */\nexport function validateComponentPatch(patch: unknown): ValidationResult {\n const r = ComponentPatchSchema.safeParse(patch)\n if (r.success) return { valid: true }\n return { valid: false, errors: formatIssues(r.error) }\n}\n\n/** 한 컴포넌트 검증. addComponent 시 사용. */\nexport function validateComponent(component: unknown): ValidationResult {\n const r = ComponentSchema.safeParse(component)\n if (r.success) return { valid: true }\n return { valid: false, errors: formatIssues(r.error) }\n}\n\n/** modifyScene op 의 patch 검증. 보드 root partial. */\nexport function validateBoardPatch(patch: unknown): ValidationResult {\n const r = BoardPatchSchema.safeParse(patch)\n if (r.success) return { valid: true }\n return { valid: false, errors: formatIssues(r.error) }\n}\n"]}
|
|
@@ -117,7 +117,7 @@ export function validateWriteToolCall(tc, components = []) {
|
|
|
117
117
|
}
|
|
118
118
|
return { valid: true };
|
|
119
119
|
}
|
|
120
|
-
case '
|
|
120
|
+
case 'modifyScene': {
|
|
121
121
|
// 보드 root partial 검증 — fillStyle/shadow 같은 nested 의 strict schema 가
|
|
122
122
|
// unknown key (e.g. fillStyle.gradientStops 같은 stale 이름) 를 거부.
|
|
123
123
|
// 이게 마지막 방어선 — 다른 styling tool 에서 학습된 vocabulary 가 leak 되면 catch.
|
|
@@ -129,7 +129,7 @@ export function validateWriteToolCall(tc, components = []) {
|
|
|
129
129
|
return {
|
|
130
130
|
valid: false,
|
|
131
131
|
errors: formatValidationErrors(v),
|
|
132
|
-
suggestion:
|
|
132
|
+
suggestion: modifySceneSuggestion()
|
|
133
133
|
};
|
|
134
134
|
}
|
|
135
135
|
return { valid: true };
|
|
@@ -144,7 +144,7 @@ export function validateWriteToolCall(tc, components = []) {
|
|
|
144
144
|
return {
|
|
145
145
|
valid: false,
|
|
146
146
|
errors: formatValidationErrors(v),
|
|
147
|
-
suggestion:
|
|
147
|
+
suggestion: modifySceneSuggestion()
|
|
148
148
|
};
|
|
149
149
|
}
|
|
150
150
|
return { valid: true };
|
|
@@ -154,8 +154,8 @@ export function validateWriteToolCall(tc, components = []) {
|
|
|
154
154
|
return { valid: true };
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
|
-
function
|
|
158
|
-
return ('
|
|
157
|
+
function modifySceneSuggestion() {
|
|
158
|
+
return ('modifyScene 의 patch 는 things-scene 의 model-layer 가 인식하는 root key 만 사용. ' +
|
|
159
159
|
'fillStyle / strokeStyle / shadow 같은 nested object 의 안쪽 필드는 canonical 이름 (colorStops 등) 정확히 사용. ' +
|
|
160
160
|
'rect 등 자식 컴포넌트 변경은 modifyComponentByRefid 로 분리.');
|
|
161
161
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-validation.js","sourceRoot":"","sources":["../../../../server/service/validation/tool-validation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Write tool 의 사전 검증 — agentic loop 의 retry 메커니즘.\n *\n * 흐름:\n * 1. LLM 이 write tool 호출\n * 2. 본 함수가 args 검증 (styling tool 의 build* 함수 또는 schema 직접)\n * 3. 실패 시 errors 반환 → caller 가 tool_result 에 error 메시지 담아 LLM 한테\n * 4. LLM 다음 turn 에서 정정 후 재호출 (자기 회복)\n *\n * 이게 silent 데이터 손상을 막는 마지막 능동 방어선. 검증 통과한 args 만 누적\n * → patch 로 변환되므로 깨진 데이터가 영속까지 못 감.\n */\nimport type { AIToolCall } from '@things-factory/ai-client-base'\nimport {\n validateComponentPatch,\n validateComponent,\n validateBoardPatch,\n type ValidationResult\n} from './board-model-schema'\nimport { buildSetFillOps } from '../styling/fill-tools'\nimport { buildSetStrokeOps } from '../styling/stroke-tools'\nimport { buildSetTextOps, buildSetFontOps } from '../styling/text-tools'\nimport {\n buildSetShadowOps,\n buildSetOpacityOps,\n buildSetVisibleOps\n} from '../styling/effect-tools'\nimport {\n buildSetMaterialOps,\n buildSetShadowPropertiesOps\n} from '../styling/material-tools'\nimport { buildClearStyleOps } from '../styling/clear-tools'\nimport { buildCopyStyleOps } from '../styling/copy-tools'\n\nexport interface ToolValidationResult {\n /** 검증 통과 — 누적 진행 가능 */\n valid: boolean\n /** 실패 시 LLM 한테 보일 에러 메시지 (자기 회복 정보 포함) */\n errors?: string[]\n /** LLM 한테 정정 가이드 — 가능한 경우 어떤 형식이 맞는지 */\n suggestion?: string\n}\n\n/**\n * write tool 의 args 사전 검증.\n *\n * 정책:\n * - styling tool: 해당 build* 함수가 errors 반환 — 그대로 LLM 한테\n * - modifyComponentByRefid: ComponentPatchSchema 로 검증 (raw fallback 영역)\n * - addComponent: ComponentSchema 로 검증\n * - 그 외: 기존 dispatcher 의 자체 검증에 위임 (재시도 보장 X)\n */\nexport function validateWriteToolCall(\n tc: AIToolCall,\n components: ReadonlyArray<any> = []\n): ToolValidationResult {\n const args = tc.arguments ?? {}\n\n // Styling tool 들 — 각 build 함수가 detailed errors 반환\n switch (tc.name) {\n case 'setFill': {\n const r = buildSetFillOps(args as any)\n if (r.errors.length > 0)\n return {\n valid: false,\n errors: r.errors,\n suggestion: setFillSuggestion()\n }\n return { valid: true }\n }\n case 'setStroke': {\n const r = buildSetStrokeOps(args as any)\n if (r.errors.length > 0) return { valid: false, errors: r.errors }\n return { valid: true }\n }\n case 'setText': {\n const r = buildSetTextOps(args as any)\n if (r.errors.length > 0) return { valid: false, errors: r.errors }\n return { valid: true }\n }\n case 'setFont': {\n const r = buildSetFontOps(args as any)\n if (r.errors.length > 0) return { valid: false, errors: r.errors }\n return { valid: true }\n }\n case 'setShadow': {\n const r = buildSetShadowOps(args as any)\n if (r.errors.length > 0) return { valid: false, errors: r.errors }\n return { valid: true }\n }\n case 'setOpacity': {\n const r = buildSetOpacityOps(args as any)\n if (r.errors.length > 0) return { valid: false, errors: r.errors }\n return { valid: true }\n }\n case 'setVisible': {\n const r = buildSetVisibleOps(args as any)\n if (r.errors.length > 0) return { valid: false, errors: r.errors }\n return { valid: true }\n }\n case 'setMaterial': {\n const r = buildSetMaterialOps(args as any)\n if (r.errors.length > 0) return { valid: false, errors: r.errors }\n return { valid: true }\n }\n case 'setShadowProperties': {\n const r = buildSetShadowPropertiesOps(args as any)\n if (r.errors.length > 0) return { valid: false, errors: r.errors }\n return { valid: true }\n }\n case 'clearStyle': {\n const r = buildClearStyleOps(args as any)\n if (r.errors.length > 0) return { valid: false, errors: r.errors }\n return { valid: true }\n }\n case 'copyStyle': {\n const r = buildCopyStyleOps(args as any, components)\n if (r.errors.length > 0) return { valid: false, errors: r.errors }\n return { valid: true }\n }\n\n // raw fallback — schema 검증\n case 'modifyComponentByRefid': {\n if (typeof args.refid !== 'number') {\n return { valid: false, errors: ['refid (number) is required'] }\n }\n if (!args.patch || typeof args.patch !== 'object') {\n return { valid: false, errors: ['patch (object) is required'] }\n }\n const v = validateComponentPatch(args.patch)\n if (!v.valid) {\n return {\n valid: false,\n errors: formatValidationErrors(v),\n suggestion: modifyPatchSuggestion()\n }\n }\n return { valid: true }\n }\n case 'addComponent': {\n const v = validateComponent(args)\n if (!v.valid) {\n return {\n valid: false,\n errors: formatValidationErrors(v)\n }\n }\n return { valid: true }\n }\n case 'modifyBoard': {\n // 보드 root partial 검증 — fillStyle/shadow 같은 nested 의 strict schema 가\n // unknown key (e.g. fillStyle.gradientStops 같은 stale 이름) 를 거부.\n // 이게 마지막 방어선 — 다른 styling tool 에서 학습된 vocabulary 가 leak 되면 catch.\n if (!args.patch || typeof args.patch !== 'object' || Array.isArray(args.patch)) {\n return { valid: false, errors: ['patch (object) is required'] }\n }\n const v = validateBoardPatch(args.patch)\n if (!v.valid) {\n return {\n valid: false,\n errors: formatValidationErrors(v),\n suggestion: modifyBoardSuggestion()\n }\n }\n return { valid: true }\n }\n case 'replaceBoard': {\n // 보드 통째 교체 — 영속 직전 가장 강력한 검증 필요. components 까지 한 번에\n // 검사. 자식 컴포넌트의 fillStyle.gradientStops 같은 stale 키도 즉시 catch.\n // (BoardPatchSchema 는 component 도 array 로 통과시키되 ComponentSchema 가\n // strict styling sub-objects 로 stale 차단.)\n const v = validateBoardPatch(args)\n if (!v.valid) {\n return {\n valid: false,\n errors: formatValidationErrors(v),\n suggestion: modifyBoardSuggestion()\n }\n }\n return { valid: true }\n }\n\n // 그 외 — 기존 dispatcher 검증에 위임\n default:\n return { valid: true }\n }\n}\n\nfunction modifyBoardSuggestion(): string {\n return (\n 'modifyBoard 의 patch 는 things-scene 의 model-layer 가 인식하는 root key 만 사용. ' +\n 'fillStyle / strokeStyle / shadow 같은 nested object 의 안쪽 필드는 canonical 이름 (colorStops 등) 정확히 사용. ' +\n 'rect 등 자식 컴포넌트 변경은 modifyComponentByRefid 로 분리.'\n )\n}\n\nfunction formatValidationErrors(v: ValidationResult): string[] {\n if (v.valid || !v.errors) return []\n return v.errors.map(e => `${e.path || '<root>'}: ${e.message}`)\n}\n\n/**\n * setFill 정정 가이드 — 자주 실수하는 패턴 + 올바른 예시.\n */\nfunction setFillSuggestion(): string {\n return [\n 'Valid setFill patterns:',\n \" - Solid: { refids:[35], mode:'solid', color:'#ff0000' }\",\n \" - Gradient: { refids:[35], mode:'gradient', gradientType:'linear', \" +\n \"colorStops:[{position:0,color:'#ff0000'},{position:1,color:'#0000ff'}], rotation:90 }\",\n \" - Pattern: { refids:[35], mode:'pattern', image:'url' }\",\n \" - Partial color update: { refids:[35], color:'green' } // mode 미지정\",\n 'Notes:',\n ' - rotation 은 degrees 로 입력 (서버가 라디안으로 변환해 fillStyle.rotation 에 저장)',\n ' - colorStops 는 { position: 0..1, color: hex/rgb/named } 객체 배열',\n ' - 옛 이름 (colors / stops / gradientStops) 사용 금지 — engine canonical 은 colorStops'\n ].join('\\n')\n}\n\nfunction modifyPatchSuggestion(): string {\n return [\n 'modifyComponentByRefid is the raw fallback — prefer specific styling tools (setFill/setStroke/setText/setFont/setShadow/setMaterial/setOpacity/setVisible) for any styling change.',\n 'If you must use raw modify:',\n ' - fillStyle string (solid) or {type:\"gradient\",gradientType,colorStops:[{position,color}]} or {type:\"pattern\",image,...}',\n ' - shadow {color, offsetX, offsetY, blur} (CSS box-shadow / Canvas 표준 이름)',\n ' - lineDash STRING preset only (NOT number array)'\n ].join('\\n')\n}\n"]}
|
|
1
|
+
{"version":3,"file":"tool-validation.js","sourceRoot":"","sources":["../../../../server/service/validation/tool-validation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Write tool 의 사전 검증 — agentic loop 의 retry 메커니즘.\n *\n * 흐름:\n * 1. LLM 이 write tool 호출\n * 2. 본 함수가 args 검증 (styling tool 의 build* 함수 또는 schema 직접)\n * 3. 실패 시 errors 반환 → caller 가 tool_result 에 error 메시지 담아 LLM 한테\n * 4. LLM 다음 turn 에서 정정 후 재호출 (자기 회복)\n *\n * 이게 silent 데이터 손상을 막는 마지막 능동 방어선. 검증 통과한 args 만 누적\n * → patch 로 변환되므로 깨진 데이터가 영속까지 못 감.\n */\nimport type { AIToolCall } from '@things-factory/ai-client-base'\nimport {\n validateComponentPatch,\n validateComponent,\n validateBoardPatch,\n type ValidationResult\n} from './board-model-schema'\nimport { buildSetFillOps } from '../styling/fill-tools'\nimport { buildSetStrokeOps } from '../styling/stroke-tools'\nimport { buildSetTextOps, buildSetFontOps } from '../styling/text-tools'\nimport {\n buildSetShadowOps,\n buildSetOpacityOps,\n buildSetVisibleOps\n} from '../styling/effect-tools'\nimport {\n buildSetMaterialOps,\n buildSetShadowPropertiesOps\n} from '../styling/material-tools'\nimport { buildClearStyleOps } from '../styling/clear-tools'\nimport { buildCopyStyleOps } from '../styling/copy-tools'\n\nexport interface ToolValidationResult {\n /** 검증 통과 — 누적 진행 가능 */\n valid: boolean\n /** 실패 시 LLM 한테 보일 에러 메시지 (자기 회복 정보 포함) */\n errors?: string[]\n /** LLM 한테 정정 가이드 — 가능한 경우 어떤 형식이 맞는지 */\n suggestion?: string\n}\n\n/**\n * write tool 의 args 사전 검증.\n *\n * 정책:\n * - styling tool: 해당 build* 함수가 errors 반환 — 그대로 LLM 한테\n * - modifyComponentByRefid: ComponentPatchSchema 로 검증 (raw fallback 영역)\n * - addComponent: ComponentSchema 로 검증\n * - 그 외: 기존 dispatcher 의 자체 검증에 위임 (재시도 보장 X)\n */\nexport function validateWriteToolCall(\n tc: AIToolCall,\n components: ReadonlyArray<any> = []\n): ToolValidationResult {\n const args = tc.arguments ?? {}\n\n // Styling tool 들 — 각 build 함수가 detailed errors 반환\n switch (tc.name) {\n case 'setFill': {\n const r = buildSetFillOps(args as any)\n if (r.errors.length > 0)\n return {\n valid: false,\n errors: r.errors,\n suggestion: setFillSuggestion()\n }\n return { valid: true }\n }\n case 'setStroke': {\n const r = buildSetStrokeOps(args as any)\n if (r.errors.length > 0) return { valid: false, errors: r.errors }\n return { valid: true }\n }\n case 'setText': {\n const r = buildSetTextOps(args as any)\n if (r.errors.length > 0) return { valid: false, errors: r.errors }\n return { valid: true }\n }\n case 'setFont': {\n const r = buildSetFontOps(args as any)\n if (r.errors.length > 0) return { valid: false, errors: r.errors }\n return { valid: true }\n }\n case 'setShadow': {\n const r = buildSetShadowOps(args as any)\n if (r.errors.length > 0) return { valid: false, errors: r.errors }\n return { valid: true }\n }\n case 'setOpacity': {\n const r = buildSetOpacityOps(args as any)\n if (r.errors.length > 0) return { valid: false, errors: r.errors }\n return { valid: true }\n }\n case 'setVisible': {\n const r = buildSetVisibleOps(args as any)\n if (r.errors.length > 0) return { valid: false, errors: r.errors }\n return { valid: true }\n }\n case 'setMaterial': {\n const r = buildSetMaterialOps(args as any)\n if (r.errors.length > 0) return { valid: false, errors: r.errors }\n return { valid: true }\n }\n case 'setShadowProperties': {\n const r = buildSetShadowPropertiesOps(args as any)\n if (r.errors.length > 0) return { valid: false, errors: r.errors }\n return { valid: true }\n }\n case 'clearStyle': {\n const r = buildClearStyleOps(args as any)\n if (r.errors.length > 0) return { valid: false, errors: r.errors }\n return { valid: true }\n }\n case 'copyStyle': {\n const r = buildCopyStyleOps(args as any, components)\n if (r.errors.length > 0) return { valid: false, errors: r.errors }\n return { valid: true }\n }\n\n // raw fallback — schema 검증\n case 'modifyComponentByRefid': {\n if (typeof args.refid !== 'number') {\n return { valid: false, errors: ['refid (number) is required'] }\n }\n if (!args.patch || typeof args.patch !== 'object') {\n return { valid: false, errors: ['patch (object) is required'] }\n }\n const v = validateComponentPatch(args.patch)\n if (!v.valid) {\n return {\n valid: false,\n errors: formatValidationErrors(v),\n suggestion: modifyPatchSuggestion()\n }\n }\n return { valid: true }\n }\n case 'addComponent': {\n const v = validateComponent(args)\n if (!v.valid) {\n return {\n valid: false,\n errors: formatValidationErrors(v)\n }\n }\n return { valid: true }\n }\n case 'modifyScene': {\n // 보드 root partial 검증 — fillStyle/shadow 같은 nested 의 strict schema 가\n // unknown key (e.g. fillStyle.gradientStops 같은 stale 이름) 를 거부.\n // 이게 마지막 방어선 — 다른 styling tool 에서 학습된 vocabulary 가 leak 되면 catch.\n if (!args.patch || typeof args.patch !== 'object' || Array.isArray(args.patch)) {\n return { valid: false, errors: ['patch (object) is required'] }\n }\n const v = validateBoardPatch(args.patch)\n if (!v.valid) {\n return {\n valid: false,\n errors: formatValidationErrors(v),\n suggestion: modifySceneSuggestion()\n }\n }\n return { valid: true }\n }\n case 'replaceBoard': {\n // 보드 통째 교체 — 영속 직전 가장 강력한 검증 필요. components 까지 한 번에\n // 검사. 자식 컴포넌트의 fillStyle.gradientStops 같은 stale 키도 즉시 catch.\n // (BoardPatchSchema 는 component 도 array 로 통과시키되 ComponentSchema 가\n // strict styling sub-objects 로 stale 차단.)\n const v = validateBoardPatch(args)\n if (!v.valid) {\n return {\n valid: false,\n errors: formatValidationErrors(v),\n suggestion: modifySceneSuggestion()\n }\n }\n return { valid: true }\n }\n\n // 그 외 — 기존 dispatcher 검증에 위임\n default:\n return { valid: true }\n }\n}\n\nfunction modifySceneSuggestion(): string {\n return (\n 'modifyScene 의 patch 는 things-scene 의 model-layer 가 인식하는 root key 만 사용. ' +\n 'fillStyle / strokeStyle / shadow 같은 nested object 의 안쪽 필드는 canonical 이름 (colorStops 등) 정확히 사용. ' +\n 'rect 등 자식 컴포넌트 변경은 modifyComponentByRefid 로 분리.'\n )\n}\n\nfunction formatValidationErrors(v: ValidationResult): string[] {\n if (v.valid || !v.errors) return []\n return v.errors.map(e => `${e.path || '<root>'}: ${e.message}`)\n}\n\n/**\n * setFill 정정 가이드 — 자주 실수하는 패턴 + 올바른 예시.\n */\nfunction setFillSuggestion(): string {\n return [\n 'Valid setFill patterns:',\n \" - Solid: { refids:[35], mode:'solid', color:'#ff0000' }\",\n \" - Gradient: { refids:[35], mode:'gradient', gradientType:'linear', \" +\n \"colorStops:[{position:0,color:'#ff0000'},{position:1,color:'#0000ff'}], rotation:90 }\",\n \" - Pattern: { refids:[35], mode:'pattern', image:'url' }\",\n \" - Partial color update: { refids:[35], color:'green' } // mode 미지정\",\n 'Notes:',\n ' - rotation 은 degrees 로 입력 (서버가 라디안으로 변환해 fillStyle.rotation 에 저장)',\n ' - colorStops 는 { position: 0..1, color: hex/rgb/named } 객체 배열',\n ' - 옛 이름 (colors / stops / gradientStops) 사용 금지 — engine canonical 은 colorStops'\n ].join('\\n')\n}\n\nfunction modifyPatchSuggestion(): string {\n return [\n 'modifyComponentByRefid is the raw fallback — prefer specific styling tools (setFill/setStroke/setText/setFont/setShadow/setMaterial/setOpacity/setVisible) for any styling change.',\n 'If you must use raw modify:',\n ' - fillStyle string (solid) or {type:\"gradient\",gradientType,colorStops:[{position,color}]} or {type:\"pattern\",image,...}',\n ' - shadow {color, offsetX, offsetY, blur} (CSS box-shadow / Canvas 표준 이름)',\n ' - lineDash STRING preset only (NOT number array)'\n ].join('\\n')\n}\n"]}
|