{"version":3,"sources":["../src/index.ts","../src/block.ts","../src/bindings.ts","../src/collections.ts","../src/category-tree.ts","../src/public-path.ts","../src/delimiter.ts","../src/embed.ts","../src/faq.ts","../src/image-alt.ts","../src/imported-html-sanitize.ts","../src/internal-content-links.ts","../src/preview-token.ts","../src/sanitize.ts","../src/stable-id.ts","../src/site-patterns.ts","../src/editor-formatting.ts","../src/section-locks.ts"],"sourcesContent":["// @roottale/cms-core — ADR-0034 §3 §5 (Phase 1 Block JSON foundation)\nexport * from \"./block\";\nexport * from \"./bindings\";\nexport * from \"./collections\";\nexport * from \"./category-tree\";\nexport * from \"./public-path\";\nexport * from \"./block-definition\";\nexport * from \"./delimiter\";\nexport * from \"./embed\";\nexport * from \"./faq\";\nexport * from \"./image-alt\";\nexport * from \"./imported-html-sanitize\";\nexport * from \"./internal-content-links\";\nexport * from \"./preview-token\";\nexport * from \"./sanitize\";\nexport * from \"./stable-id\";\nexport * from \"./site-patterns\";\nexport * from \"./editor-formatting\";\n\nexport * from \"./section-locks\";\n","// ADR-0034 §5 — Block JSON 스펙 (WP 호환)\n// Gutenberg block shape + 우리 확장:\n//   - `_id` (uuidv7 stable across revisions, ADR §2 P5)\n//   - `rawHtml` (WP delimiter innerHTML 보존, codex v2 verdict #7)\n//   - `metadata.reusableRef` (Phase 2 = flat expand)\n\nimport { z } from \"zod\";\n\n/** WP-compatible name pattern: `core/<x>` | `roottale/<x>` */\nexport const BLOCK_NAME_PATTERN = /^(core|roottale)\\/[a-z0-9-]+$/;\n\nconst blockMetadataSchema = z\n  .object({\n    bindings: z\n      .record(\n        z.object({\n          source: z.string(),\n          args: z.record(z.unknown()).optional(),\n        }),\n      )\n      .optional(),\n    name: z.string().optional(),\n    reusableRef: z.string().optional(),\n  })\n  .strict()\n  .optional();\n\n/**\n * Block schema (recursive — innerBlocks).\n * `_id` 는 internal-only (export 시 strip, ADR §5).\n */\nexport const blockSchema: z.ZodType<Block> = z.lazy(() =>\n  z\n    .object({\n      _id: z.string().uuid(),\n      name: z.string().regex(BLOCK_NAME_PATTERN),\n      attributes: z.record(z.unknown()),\n      innerBlocks: z.array(blockSchema),\n      rawHtml: z.string().optional(),\n      metadata: blockMetadataSchema,\n    })\n    .strict(),\n);\n\nexport interface Block {\n  /** uuidv7. Internal-only — never exported as WP delimiter attribute. */\n  _id: string;\n  /** `core/<x>` or `roottale/<x>` */\n  name: string;\n  attributes: Record<string, unknown>;\n  innerBlocks: Block[];\n  /** WP delimiter innerHTML (preserved for round-trip — codex v2 verdict #7). */\n  rawHtml?: string;\n  metadata?: {\n    bindings?: Record<string, { source: string; args?: Record<string, unknown> }>;\n    name?: string;\n    reusableRef?: string;\n  };\n}\n\nexport function isValidBlock(value: unknown): value is Block {\n  return blockSchema.safeParse(value).success;\n}\n\nexport function parseBlock(value: unknown): Block {\n  return blockSchema.parse(value);\n}\n\n/**\n * Phase 1 core block names (10) — ADR-0034 §5.\n * Phase 2+ 에 `core/embed`, `core/gallery`, `core/cover` 등 추가.\n */\nexport const PHASE_1_CORE_BLOCKS = [\n  \"core/paragraph\",\n  \"core/heading\",\n  \"core/image\",\n  \"core/list\",\n  \"core/quote\",\n  \"core/code\",\n  \"core/columns\",\n  \"core/group\",\n  \"core/separator\",\n  \"core/spacer\",\n] as const;\nexport type Phase1CoreBlockName = (typeof PHASE_1_CORE_BLOCKS)[number];\n","// ADR-0041 §7 — Block Bindings: ACF 필드 → 블록 attribute.\n//\n// 블록의 `metadata.bindings[attr] = { source, args }` 를 만나면 attribute 를\n// 바인딩 값으로 치환한다. 렌더 직전 pure transform — renderBlock 시그니처 불변.\n//\n// canonical source id = `roottale/field`. WP round-trip 위해 `acf/field` alias\n// 인식. resolver 는 주입(렌더러는 cms-fields 비종속) — 값 record 만 흐른다.\n\nimport type { Block } from \"./block\";\n\nexport interface BlockBinding {\n  source: string;\n  args?: Record<string, unknown>;\n}\n\n/**\n * 바인딩 해석기. 주어진 binding(+attribute/block)에 대한 값을 반환.\n * `undefined` 반환 = 미해석(원래 attribute 유지).\n */\nexport type BindingResolver = (\n  binding: BlockBinding,\n  attribute: string,\n  block: Block,\n) => unknown;\n\n/** ACF 필드 바인딩 source id. */\nexport const FIELD_BINDING_SOURCE = \"roottale/field\";\n/** WP ACF 호환 alias (import round-trip). */\nexport const FIELD_BINDING_SOURCE_ALIAS = \"acf/field\";\n\n/**\n * acf 값 record 로부터 필드 바인딩 resolver 생성.\n * `roottale/field` / `acf/field` source 의 `args.name` 을 record 에서 조회.\n */\nexport function createFieldBindingResolver(\n  fields: Record<string, unknown> | undefined | null,\n): BindingResolver {\n  const values = fields ?? {};\n  return (binding) => {\n    if (\n      binding.source !== FIELD_BINDING_SOURCE &&\n      binding.source !== FIELD_BINDING_SOURCE_ALIAS\n    ) {\n      return undefined;\n    }\n    const name = binding.args?.name;\n    if (typeof name !== \"string\") return undefined;\n    const v = values[name];\n    return v === undefined ? undefined : v;\n  };\n}\n\n/** 여러 resolver 를 순서대로 시도(첫 non-undefined 채택). */\nexport function chainResolvers(\n  ...resolvers: BindingResolver[]\n): BindingResolver {\n  return (binding, attribute, block) => {\n    for (const r of resolvers) {\n      const v = r(binding, attribute, block);\n      if (v !== undefined) return v;\n    }\n    return undefined;\n  };\n}\n\nfunction applyToBlock(block: Block, resolver: BindingResolver): Block {\n  const bindings = block.metadata?.bindings;\n  let attributes = block.attributes;\n  if (bindings) {\n    let mutated = false;\n    const next = { ...block.attributes };\n    for (const [attr, binding] of Object.entries(bindings)) {\n      const value = resolver(binding, attr, block);\n      if (value !== undefined) {\n        next[attr] = value;\n        mutated = true;\n      }\n    }\n    if (mutated) attributes = next;\n  }\n  const innerBlocks = block.innerBlocks.length\n    ? block.innerBlocks.map((b) => applyToBlock(b, resolver))\n    : block.innerBlocks;\n  if (attributes === block.attributes && innerBlocks === block.innerBlocks) {\n    return block;\n  }\n  return { ...block, attributes, innerBlocks };\n}\n\n/**\n * 블록 트리에 바인딩을 적용해 새 트리 반환(immutable). 바인딩 없으면 원본 그대로.\n */\nexport function applyBlockBindings(\n  blocks: readonly Block[],\n  resolver: BindingResolver,\n): Block[] {\n  return blocks.map((b) => applyToBlock(b, resolver));\n}\n\n// ── 라이브 Tiptap 렌더 경로용 헬퍼 (cms-field-binding-editor.md D3·D4) ─────────\n//\n// Tiptap doc 의 node.attrs.bindings (BlockBindings extension 이 기록, Block JSON\n// metadata.bindings 와 동일 형태) 를 렌더 시점에 필드 값으로 치환할 때 사용.\n\n/**\n * bindings 에서 `attribute` 의 필드 값을 조회. `roottale/field`/`acf/field`\n * source 만 해석. 빈 값(undefined/null/\"\") = undefined → caller 가 fallback 렌더.\n */\nexport function boundFieldValue(\n  bindings: unknown,\n  attribute: string,\n  fields: Record<string, unknown> | null | undefined,\n): unknown {\n  if (!bindings || typeof bindings !== \"object\" || !fields) return undefined;\n  const binding = (bindings as Record<string, BlockBinding | undefined>)[\n    attribute\n  ];\n  if (\n    !binding ||\n    (binding.source !== FIELD_BINDING_SOURCE &&\n      binding.source !== FIELD_BINDING_SOURCE_ALIAS)\n  ) {\n    return undefined;\n  }\n  const name = binding.args?.name;\n  if (typeof name !== \"string\") return undefined;\n  const value = fields[name];\n  if (value === undefined || value === null || value === \"\") return undefined;\n  return value;\n}\n\n/**\n * content 바인딩 값의 텍스트화. **항상 plain text** — caller 가 escape 해\n * 삽입한다 (바인딩 경로로 raw HTML 진입 금지, D4). string/number 외 = undefined.\n */\nexport function bindingTextValue(value: unknown): string | undefined {\n  if (typeof value === \"string\") return value;\n  if (typeof value === \"number\" && Number.isFinite(value)) return String(value);\n  return undefined;\n}\n\n/**\n * image `src` 바인딩 값 → URL. 포맷된 MediaRef(`{url}`) 또는 http(s) 문자열만\n * 인정 — raw media id(uuid) 는 URL 이 아니므로 fallback.\n */\nexport function bindingSrcValue(value: unknown): string | undefined {\n  if (value && typeof value === \"object\") {\n    const url = (value as { url?: unknown }).url;\n    if (typeof url === \"string\" && /^https?:\\/\\//i.test(url)) return url;\n    return undefined;\n  }\n  if (typeof value === \"string\" && /^https?:\\/\\//i.test(value)) return value;\n  return undefined;\n}\n","/**\n * 다중 스트림 콘텐츠 라우팅 프리미티브 (ADR-0060).\n *\n * 같은 글 풀(post)을 둘 이상의 스트림으로 분리(공지=게시판 /notice, 블로그=그리드\n * /blog 등). `cms-renderer-next` / `cms-renderer-astro` 가 동일 로직을 공유하도록\n * 여기(cms-core)에 둔다 — cms-client·프레임워크 비의존(구조적 타입만).\n *\n * 설계(codex consult `019ef577` 반영):\n * - **활성화 = `collections.length > 0`.** 빈 배열·미전달 = 레거시 단일 /blog.\n * - `RouteCollection` 은 **라우팅 최소셋**만(key/basePath/categories/feed/archives와\n *   경로 정책) 가진다.\n *   label/layout/icon 등 표시·작성 메타는 templates `CollectionSchema` 에 둔다.\n */\n\n/** 콘텐츠 스트림 1개 — sitemap/feed/아카이브 파생의 라우팅 단위. */\nexport interface RouteCollection {\n  /** 안정 식별자 (예: \"notice\" | \"blog\"). */\n  readonly key: string;\n  /**\n   * 라우트 base path (예: \"/notice\" | \"/blog\"). 앞 슬래시 포함, 뒤 슬래시 없이.\n   * **`\"\"`(빈 문자열) = 주소 없는 유형** — 페이지 안에서 불러와 쓰는 콘텐츠(강사·리뷰\n   * 등)로, 라우트/사이트맵/피드/아카이브를 일절 파생하지 않는다. 외부 사이트는\n   * posts API 의 `collectionKey` 필터로 내용만 가져다 렌더한다. `resolvePostUrl`/\n   * `resolvePostPath` 는 이 값이 빈 문자열이면 항상 null. 소속 판정\n   * (`resolvePostCollection`)에는 영향 없음 — key 만으로 소속이 성립한다.\n   */\n  readonly basePath: string;\n  /**\n   * 이 섹션이 제공하는 **주제(category) allowlist** — `${basePath}/categories/{slug}`\n   * 아카이브 라우트 범위. ADR-0060 Amendment 1 (M4): 섹션 소속은 글의 `collectionKey`\n   * 로 정해지므로 이 배열은 더 이상 라우팅 소유권이 아니다(catch-all 개념 폐기). 빈\n   * 배열 = 이 섹션은 주제 없음.\n   */\n  readonly categories: readonly string[];\n  /** RSS 피드 포함 여부 (기본 false). */\n  readonly feed?: boolean;\n  /** `${basePath}/categories/{slug}` 아카이브 emit 여부 (기본 false). */\n  readonly archives?: boolean;\n  /**\n   * 글별 상세 라우트 존재 여부 (기본 true — 하위 호환). false 면 이 스트림은\n   * \"목록 전용\" 유형(예: 후기 — 목록에서 전문 노출, 글마다 별도 상세 URL 없음).\n   * `resolvePostUrl`/`resolvePostPath` 가 null 을 반환해 sitemap/feed 에서 글\n   * URL 을 발행하지 않는다. 소속 판정(`resolvePostCollection`)에는 영향 없음\n   * — 소속과 상세 URL 존재 여부는 별개(M4 원칙 연장).\n   */\n  readonly detail?: boolean;\n  /**\n   * 글 상세 주소 형식. `flat`(기본) = `${basePath}/{postSlug}`,\n   * `category` = `${basePath}/{categorySlug}/{postSlug}`.\n   * category 모드는 URL을 하나로 결정해야 하므로 `categoryCardinality: \"exactly-one\"`\n   * 과 함께 쓴다.\n   */\n  readonly detailPath?: \"flat\" | \"category\";\n  /**\n   * 카테고리 허브 주소 형식. `namespaced`(기본) =\n   * `${basePath}/categories/{slug}`, `direct` = `${basePath}/{slug}`.\n   */\n  readonly categoryPath?: \"namespaced\" | \"direct\";\n  /** 글에 허용하는 category term 개수. 기본 `multiple`(기존 동작). */\n  readonly categoryCardinality?: \"multiple\" | \"exactly-one\";\n}\n\n/** resolve 입력 — 글의 구조적 최소 형태(cms-client `CmsPostContent` 와 구조 호환). */\nexport interface RoutablePost {\n  readonly slug: string;\n  /** 글의 주제(category) terms — 아카이브 라우트 필터용. 섹션 판정엔 쓰지 않는다(M4). */\n  readonly terms: ReadonlyArray<{ readonly taxonomy: string; readonly slug: string }>;\n  /**\n   * ADR-0060 Amendment 1 (Option A, M4) — 글에 명시된 섹션 key. 섹션 판정의 유일한\n   * 근거. 미설정/미존재면 어느 스트림에도 안 속함(null). 카테고리 파생 fallback 은 제거됨.\n   */\n  readonly collectionKey?: string | null;\n}\n\nfunction trimTrailingSlash(value: string): string {\n  return value.replace(/\\/+$/, \"\");\n}\n\n/** collection 모드인지 — 길이 0(또는 미전달)이면 레거시 단일 /blog 동작. */\nexport function isInCollectionMode(\n  collections: readonly RouteCollection[] | undefined,\n): boolean {\n  return !!collections && collections.length > 0;\n}\n\n/**\n * 글이 속한 collection 1개를 해석 — ADR-0060 Amendment 1 (Option A, M4): **글의\n * 명시 `collectionKey` 만** 본다. 미설정/미존재 key 면 null(상세 라우트 없는 글 →\n * sitemap 제외 신호). 카테고리 slug 파생·catch-all·first-wins 는 M4 에서 제거됨 —\n * 섹션은 카테고리가 아니라 글에 명시한다. (`categories` 는 이제 주제 allowlist 로만,\n * 아카이브 라우트에서 쓰임.)\n */\nexport function resolvePostCollection(\n  post: RoutablePost,\n  collections: readonly RouteCollection[],\n): RouteCollection | null {\n  if (!post.collectionKey) return null;\n  return collections.find((c) => c.key === post.collectionKey) ?? null;\n}\n\n/**\n * 글의 정규 절대 URL — 소속 스트림 basePath 기준. 어느 스트림에도 안 속하거나,\n * 소속 스트림이 `detail: false`(목록 전용 유형) 이거나, `basePath` 가 빈 문자열\n * (주소 없는 유형)이면 null. slug 는 percent-encoding (한글 slug 안전).\n */\nexport function resolvePostUrl(\n  siteUrl: string,\n  post: RoutablePost,\n  collections: readonly RouteCollection[],\n): string | null {\n  const path = resolvePostPath(post, collections);\n  return path ? `${trimTrailingSlash(siteUrl)}${path}` : null;\n}\n\n/**\n * category 기반 상세 주소에 사용할 단일 category slug. collection 에 연결된\n * category만 세고 정확히 1개일 때만 반환한다. 0개·2개 이상이면 정규 URL을\n * 임의 선택하지 않고 null 로 닫는다.\n */\nexport function resolvePostCategorySlug(\n  post: RoutablePost,\n  collection: RouteCollection,\n): string | null {\n  const allowed = new Set(collection.categories);\n  const slugs = [\n    ...new Set(\n      post.terms\n        .filter(\n          (term) => term.taxonomy === \"category\" && allowed.has(term.slug),\n        )\n        .map((term) => term.slug),\n    ),\n  ];\n  return slugs.length === 1 ? slugs[0]! : null;\n}\n\n/** 카테고리 허브 경로. archives 가 꺼졌거나 주소가 없으면 null. */\nexport function resolveCategoryPath(\n  collection: RouteCollection,\n  categorySlug: string,\n): string | null {\n  if (!collection.archives || !collection.basePath || !categorySlug) return null;\n  const basePath = trimTrailingSlash(collection.basePath);\n  const encodedSlug = encodeURIComponent(categorySlug);\n  return collection.categoryPath === \"direct\"\n    ? `${basePath}/${encodedSlug}`\n    : `${basePath}/categories/${encodedSlug}`;\n}\n\n/**\n * 글의 *경로*(siteUrl 없는 basePath-상대) — 예: \"/blog/hello\". 소속 스트림이\n * 없거나, `detail: false`(목록 전용 유형) 이거나, `basePath` 가 빈 문자열\n * (주소 없는 유형)이면 null. astro 처럼 상대 href 가 필요한 소비자용\n * (`resolvePostUrl` 은 절대 URL).\n */\nexport function resolvePostPath(\n  post: RoutablePost,\n  collections: readonly RouteCollection[],\n): string | null {\n  const collection = resolvePostCollection(post, collections);\n  if (!collection) return null;\n  if (collection.detail === false) return null;\n  if (!collection.basePath) return null;\n  const basePath = trimTrailingSlash(collection.basePath);\n  const encodedPostSlug = encodeURIComponent(post.slug);\n  if (collection.detailPath !== \"category\") {\n    return `${basePath}/${encodedPostSlug}`;\n  }\n  const categorySlug = resolvePostCategorySlug(post, collection);\n  return categorySlug\n    ? `${basePath}/${encodeURIComponent(categorySlug)}/${encodedPostSlug}`\n    : null;\n}\n\n/**\n * 특정 collection에 명시적으로 귀속된 글만 남긴다.\n */\nexport function postsForCollection<T extends RoutablePost>(\n  posts: readonly T[],\n  collections: readonly RouteCollection[],\n  key: string,\n): T[] {\n  return posts.filter((p) => resolvePostCollection(p, collections)?.key === key);\n}\n\n/** feed/sitemap 등 파생에서 제외할, 어느 스트림에도 안 속한 글 제거. */\nexport function postsInAnyCollection<T extends RoutablePost>(\n  posts: readonly T[],\n  collections: readonly RouteCollection[],\n): T[] {\n  return posts.filter((p) => resolvePostCollection(p, collections) !== null);\n}\n\n/**\n * `postsInAnyCollection` 의 대칭 — **어느 스트림에도 안 속한(\"미소속\") 글만** 남긴다.\n * 판정 시맨틱 = `resolvePostCollection(post, collections) === null`(collectionKey 가\n * NULL 이거나 사이트 선언 컬렉션 밖). `/blog` 처럼 \"선언 스트림에 안 잡힌 글 = 블로그\"로\n * 취급하는 표면에서 스트림 글 누출을 막는 데 쓴다(`blog-list-collection-scope.md` §2①).\n */\nexport function postsNotInAnyCollection<T extends RoutablePost>(\n  posts: readonly T[],\n  collections: readonly RouteCollection[],\n): T[] {\n  return posts.filter((p) => resolvePostCollection(p, collections) === null);\n}\n\n/**\n * config 검증 — 중복 key/basePath, 그리고 **두 스트림에 동시 등장하는 category slug**\n * (라우팅 모호성)를 탐지. `basePath === \"\"`(주소 없는 유형)는 basePath 형식·중복\n * 검사를 건너뛴다 — 라우트가 없으므로 여러 개 공존해도 모호성이 없다. key 중복·\n * category 중복 검사는 주소 유무와 무관하게 전 항목에 적용된다. 문제 메시지 배열\n * 반환(빈 = 정상). 빌드 타임 fail-fast 용.\n */\nexport function validateCollections(collections: readonly RouteCollection[]): string[] {\n  const problems: string[] = [];\n  const seenKey = new Set<string>();\n  const seenBase = new Set<string>();\n  const categoryOwner = new Map<string, string>();\n\n  for (const c of collections) {\n    if (seenKey.has(c.key)) problems.push(`중복 collection key: \"${c.key}\"`);\n    seenKey.add(c.key);\n\n    if (c.basePath !== \"\") {\n      const base = trimTrailingSlash(c.basePath);\n      if (seenBase.has(base)) problems.push(`중복 basePath: \"${c.basePath}\"`);\n      seenBase.add(base);\n\n      if (!c.basePath.startsWith(\"/\")) {\n        problems.push(`basePath 는 \"/\" 로 시작해야 함: \"${c.basePath}\" (${c.key})`);\n      } else if (\n        base.length < 2 ||\n        base.startsWith(\"//\") ||\n        /[\\\\?#\\s]/.test(base) ||\n        base.split(\"/\").some((segment) => segment === \".\" || segment === \"..\")\n      ) {\n        problems.push(\n          `basePath 는 사이트 내부 절대 경로여야 함: \"${c.basePath}\" (${c.key})`,\n        );\n      }\n    }\n\n    for (const slug of c.categories) {\n      const owner = categoryOwner.get(slug);\n      if (owner && owner !== c.key) {\n        problems.push(\n          `category \"${slug}\" 가 collection \"${owner}\" 와 \"${c.key}\" 에 중복 — 한 카테고리는 한 스트림에만.`,\n        );\n      }\n      categoryOwner.set(slug, c.key);\n    }\n\n    if (c.detailPath === \"category\") {\n      if (!c.basePath) {\n        problems.push(\n          `category 상세 경로는 basePath 가 필요함: \"${c.key}\"`,\n        );\n      }\n      if (c.detail === false) {\n        problems.push(\n          `category 상세 경로는 글별 상세 페이지가 필요함: \"${c.key}\"`,\n        );\n      }\n      if (!c.archives) {\n        problems.push(\n          `category 상세 경로는 카테고리 아카이브가 필요함: \"${c.key}\"`,\n        );\n      }\n      if (c.categoryCardinality !== \"exactly-one\") {\n        problems.push(\n          `category 상세 경로는 categoryCardinality \"exactly-one\" 이 필요함: \"${c.key}\"`,\n        );\n      }\n    }\n  }\n  return problems;\n}\n","/**\n * 콘텐츠 모델의 계층형 분류 주소 계산기.\n *\n * FAQ·도움말·문서처럼 하나의 모델이 여러 단계 분류 허브를 가지는 경우에 쓴다.\n * 저장소나 프레임워크를 모르며, 공개 API와 ROOT-ADMIN이 같은 계산을 재사용한다.\n */\n\nexport interface CategoryTreePresentation {\n  readonly basePath: string;\n  readonly categoryDepth: number;\n}\n\nexport interface CategoryTreeTerm {\n  readonly id: string;\n  readonly slug: string;\n  readonly parentId?: string | null;\n}\n\nexport interface CategoryTreeEntry {\n  readonly slug: string;\n  readonly terms: ReadonlyArray<{\n    readonly id: string;\n    readonly taxonomy: string;\n  }>;\n}\n\nfunction normalizeBasePath(basePath: string): string {\n  return basePath.replace(/\\/+$/, \"\");\n}\n\n/**\n * 선택한 말단 분류부터 루트까지의 사슬을 루트→말단 순서로 돌려준다.\n * 끊긴 부모·순환·중복 id·깊이 불일치·말단이 아닌 선택은 null로 닫는다.\n */\nexport function resolveCategoryTreeChain(\n  presentation: CategoryTreePresentation,\n  leafId: string,\n  terms: readonly CategoryTreeTerm[],\n): CategoryTreeTerm[] | null {\n  if (!Number.isInteger(presentation.categoryDepth) || presentation.categoryDepth < 1) {\n    return null;\n  }\n  const termsById = new Map(terms.map((term) => [term.id, term]));\n  if (termsById.size !== terms.length) return null;\n  if (terms.some((term) => term.parentId === leafId)) return null;\n\n  const reversed: CategoryTreeTerm[] = [];\n  const visited = new Set<string>();\n  let currentId: string | null = leafId;\n  while (currentId) {\n    if (visited.has(currentId)) return null;\n    visited.add(currentId);\n    const current = termsById.get(currentId);\n    if (!current || !current.slug) return null;\n    reversed.push(current);\n    currentId = current.parentId ?? null;\n  }\n  if (reversed.length !== presentation.categoryDepth) return null;\n  return reversed.reverse();\n}\n\n/** 루트부터 선택 분류까지 모든 허브 주소. */\nexport function resolveCategoryTreeHubPaths(\n  presentation: CategoryTreePresentation,\n  termId: string,\n  terms: readonly CategoryTreeTerm[],\n): string[] | null {\n  const chain = resolveCategoryTreeChain(presentation, termId, terms);\n  const basePath = normalizeBasePath(presentation.basePath);\n  if (!chain || !basePath) return null;\n  const paths = [basePath];\n  const segments: string[] = [];\n  for (const term of chain) {\n    segments.push(encodeURIComponent(term.slug));\n    paths.push(`${basePath}/${segments.join(\"/\")}`);\n  }\n  return paths;\n}\n\n/** 계층형 분류 모델 항목의 정규 상세 주소. */\nexport function resolveCategoryTreeEntryPath(\n  presentation: CategoryTreePresentation,\n  entry: CategoryTreeEntry,\n  terms: readonly CategoryTreeTerm[],\n): string | null {\n  if (!entry.slug) return null;\n  const knownIds = new Set(terms.map((term) => term.id));\n  const categoryIds = [\n    ...new Set(\n      entry.terms\n        .filter((term) => term.taxonomy === \"category\" && knownIds.has(term.id))\n        .map((term) => term.id),\n    ),\n  ];\n  if (categoryIds.length !== 1) return null;\n  const hubs = resolveCategoryTreeHubPaths(\n    presentation,\n    categoryIds[0]!,\n    terms,\n  );\n  const leafPath = hubs?.at(-1);\n  return leafPath ? `${leafPath}/${encodeURIComponent(entry.slug)}` : null;\n}\n","/**\n * 글의 정규 공개 경로 — 플랫폼 전체가 쓰는 **하나의** 계산기 (ADR-0105 개정).\n *\n * 우선순위: 콘텐츠 모델 presentation(fixed_page → detail → category_tree) →\n * 호환 컬렉션(`resolvePostPath`) → 레거시 `/blog/{slug}`. `data_only` 모델은\n * 상세 주소가 없으므로 호환 컬렉션이 있으면 그 규칙을, 없으면 null 을 돌려준다.\n *\n * 이 값은 저장 시 `posts.public_path` 로 물질화되고(경로 이력 포함), 웹훅·사이트맵·\n * 단축링크·내부 링크 키·리다이렉트는 저장된 값을 읽는다. 여기 말고 다른 곳에서\n * 주소를 다시 계산하지 않는다.\n */\nimport {\n  resolveCategoryTreeEntryPath,\n  type CategoryTreeTerm,\n} from \"./category-tree\";\nimport {\n  resolvePostCollection,\n  resolvePostPath,\n  type RouteCollection,\n} from \"./collections\";\n\n/** 콘텐츠 모델 presentation 의 구조적 최소형(cms-models 스키마와 호환). */\nexport type PublicPathPresentation =\n  | { readonly kind: \"data_only\" }\n  | { readonly kind: \"fixed_page\"; readonly path: string }\n  | { readonly kind: \"detail\"; readonly detailPath: string }\n  | {\n      readonly kind: \"category_tree\";\n      readonly basePath: string;\n      readonly categoryDepth: number;\n    };\n\nexport interface PublicPathPost {\n  readonly slug: string;\n  readonly collectionKey?: string | null;\n  readonly modelKey?: string | null;\n  readonly terms: ReadonlyArray<{\n    readonly id: string;\n    readonly taxonomy: string;\n    readonly slug: string;\n  }>;\n}\n\nexport interface PublicPathContext {\n  /** 글의 모델 presentation. 모델 글이 아니거나 모델을 못 찾으면 null. */\n  readonly presentation: PublicPathPresentation | null;\n  /** 모델의 호환 컬렉션 key(`compatibilityCollectionKey`, 미지정이면 모델 key). */\n  readonly compatibilityCollectionKey?: string | null;\n  readonly collections: readonly RouteCollection[];\n  readonly categories: readonly CategoryTreeTerm[];\n}\n\nexport const LEGACY_BLOG_DETAIL_PATH = \"/blog\";\n\n/** 글의 정규 공개 경로(origin 제외). 상세 주소가 없으면 null. */\nexport function resolvePostPublicPath(\n  post: PublicPathPost,\n  context: PublicPathContext,\n): string | null {\n  const presentation = context.presentation;\n  if (presentation && presentation.kind !== \"data_only\") {\n    if (presentation.kind === \"fixed_page\") return presentation.path;\n    if (presentation.kind === \"detail\") {\n      if (!presentation.detailPath.includes(\":slug\")) return null;\n      return presentation.detailPath.replace(\":slug\", encodeURIComponent(post.slug));\n    }\n    return resolveCategoryTreeEntryPath(presentation, post, context.categories);\n  }\n\n  const collectionKey =\n    post.collectionKey ??\n    (presentation ? context.compatibilityCollectionKey ?? post.modelKey ?? null : null);\n  const routable = { slug: post.slug, terms: post.terms, collectionKey };\n  if (!resolvePostCollection(routable, context.collections)) {\n    // 모델 글인데 규칙도 컬렉션도 없으면 주소가 없다. 레거시 단일 블로그만 /blog 폴백.\n    if (presentation) return null;\n    return `${LEGACY_BLOG_DETAIL_PATH}/${post.slug}`;\n  }\n  return resolvePostPath(routable, context.collections);\n}\n","// ADR-0034 §5 + §6 — WP delimiter parse/serialize\n//\n// WP 의 block serialization 은 HTML 주석 형태:\n//   <!-- wp:paragraph {\"attr\":1} -->\n//   <p>본문</p>\n//   <!-- /wp:paragraph -->\n//\n// Round-trip 룰 (codex v2 verdict #7 + v3 verdict #4):\n//   - `_id` 는 export 시 strip (WP-side import 가 unknown attribute 로 인식해 깨짐 차단)\n//   - `rawHtml` ↔ innerHTML\n//   - 우리가 schema 정의 안 한 block = Tiptap opaque atom node (편집 불가, 보존)\n\nimport type { Block } from \"./block\";\n\n/**\n * Block → WP delimiter 형식 직렬화 (export).\n * `_id` 와 `metadata.reusableRef` 는 strip — WP 호환.\n */\nexport function serializeBlock(block: Block): string {\n  const exportable = sanitizeAttributesForExport(block);\n  const attrsJson = serializeAttributes(exportable.attributes);\n  const innerHtml = block.rawHtml ?? \"\";\n  const innerSerialized = (block.innerBlocks ?? [])\n    .map((b) => serializeBlock(b))\n    .join(\"\\n\");\n\n  // self-closing 형태 (innerHTML + innerBlocks 모두 없으면)\n  if (!innerHtml && innerSerialized.length === 0) {\n    return attrsJson\n      ? `<!-- wp:${normalizeName(block.name)} ${attrsJson} /-->`\n      : `<!-- wp:${normalizeName(block.name)} /-->`;\n  }\n\n  const open = attrsJson\n    ? `<!-- wp:${normalizeName(block.name)} ${attrsJson} -->`\n    : `<!-- wp:${normalizeName(block.name)} -->`;\n  const close = `<!-- /wp:${normalizeName(block.name)} -->`;\n\n  const body = [innerHtml, innerSerialized].filter(Boolean).join(\"\\n\");\n  return `${open}\\n${body}\\n${close}`;\n}\n\n/** WP-side 에서 `core/` 는 보통 namespace 생략 (e.g. `<!-- wp:paragraph -->`) */\nfunction normalizeName(name: string): string {\n  return name.startsWith(\"core/\") ? name.slice(\"core/\".length) : name;\n}\n\nfunction serializeAttributes(attrs: Record<string, unknown>): string {\n  const keys = Object.keys(attrs);\n  if (keys.length === 0) return \"\";\n  return JSON.stringify(attrs);\n}\n\nfunction sanitizeAttributesForExport(block: Block): {\n  attributes: Record<string, unknown>;\n} {\n  // `_id` 와 `metadata.reusableRef` 제거. attributes 안의 임의 키는 그대로 export.\n  return { attributes: { ...block.attributes } };\n}\n","// ADR-0034 §5 — `core/embed` 영상 임베드 헬퍼 (provider allowlist)\n//\n// 본 모듈은 editor (URL → embed 노드 정규화) 와 renderer (저장된 embed src 의\n// 출력 시 재검증, defense-in-depth) 양쪽에서 공유. 순수 모듈 — `URL` 만 사용해\n// 브라우저/서버 모두 안전.\n//\n// 보안 원칙: 임의 iframe src 를 신뢰하지 않는다. 신규 삽입은 `normalizeEmbed`\n// 가 allowlist provider 의 canonical embed URL 로만 변환하고, 출력은\n// `isAllowedEmbedSrc` 로 host 를 한 번 더 검증한다 (legacy/import/외부 테넌트가\n// 임의 src 를 DB 에 남겨도 렌더 단계에서 차단).\n\nexport type EmbedProvider = \"youtube\" | \"vimeo\";\n\nexport interface NormalizedEmbed {\n  provider: EmbedProvider;\n  /** allowlist host 의 canonical iframe src (가능 시 privacy-enhanced). */\n  embedSrc: string;\n  /** 사용자가 입력한 원본 URL — fallback 링크 / round-trip 보존용. */\n  originalUrl: string;\n}\n\nconst YOUTUBE_HOSTS = new Set([\n  \"youtube.com\",\n  \"www.youtube.com\",\n  \"m.youtube.com\",\n  \"youtu.be\",\n  \"www.youtu.be\",\n]);\n\nconst VIMEO_HOSTS = new Set([\"vimeo.com\", \"www.vimeo.com\", \"player.vimeo.com\"]);\n\n/** 출력(iframe) 시 허용되는 embed src host — render 단계 defense-in-depth. */\nconst ALLOWED_EMBED_HOSTS = new Set([\n  \"www.youtube-nocookie.com\",\n  \"youtube-nocookie.com\",\n  \"www.youtube.com\",\n  \"youtube.com\",\n  \"player.vimeo.com\",\n]);\n\nconst YOUTUBE_ID = /^[A-Za-z0-9_-]{11}$/;\nconst VIMEO_ID = /^\\d+$/;\n\n/**\n * 사용자 입력 URL → allowlist provider 의 canonical embed. 지원하지 않는\n * provider / 파싱 실패 시 `null`.\n */\nexport function normalizeEmbed(input: unknown): NormalizedEmbed | null {\n  if (typeof input !== \"string\") return null;\n  const raw = input.trim();\n  if (raw.length === 0) return null;\n\n  let url: URL;\n  try {\n    url = new URL(raw);\n  } catch {\n    return null;\n  }\n  if (url.protocol !== \"https:\" && url.protocol !== \"http:\") return null;\n\n  const host = url.hostname.toLowerCase();\n\n  if (YOUTUBE_HOSTS.has(host)) {\n    const id = extractYouTubeId(url, host);\n    if (id) {\n      return {\n        provider: \"youtube\",\n        embedSrc: `https://www.youtube-nocookie.com/embed/${id}`,\n        originalUrl: raw,\n      };\n    }\n  }\n\n  if (VIMEO_HOSTS.has(host)) {\n    const id = extractVimeoId(url, host);\n    if (id) {\n      return {\n        provider: \"vimeo\",\n        embedSrc: `https://player.vimeo.com/video/${id}`,\n        originalUrl: raw,\n      };\n    }\n  }\n\n  return null;\n}\n\n/**\n * 이미 저장된 embed src 가 allowlist host(https) 인지 검증 — 렌더러가 iframe\n * 출력 전 호출. 임의 host / 비 https 는 거부.\n */\nexport function isAllowedEmbedSrc(value: unknown): value is string {\n  if (typeof value !== \"string\") return false;\n  let url: URL;\n  try {\n    url = new URL(value);\n  } catch {\n    return false;\n  }\n  if (url.protocol !== \"https:\") return false;\n  const host = url.hostname.toLowerCase();\n  if (!ALLOWED_EMBED_HOSTS.has(host)) return false;\n  // host 뿐 아니라 canonical embed path 형태까지 검증 — 허용 host 의 임의 경로\n  // (예: 스크립트·홈) 가 iframe 으로 로드되는 것을 차단.\n  if (host === \"player.vimeo.com\") {\n    return /^\\/video\\/\\d+$/.test(url.pathname);\n  }\n  // youtube / youtube-nocookie → /embed/<11-char id>\n  return new RegExp(\"^/embed/[A-Za-z0-9_-]{11}$\").test(url.pathname);\n}\n\n/** 지도 embed 허용 host — keyless embed 만 (API 키 의존 provider 금지). */\nconst GOOGLE_MAP_HOSTS = new Set([\"www.google.com\", \"google.com\", \"maps.google.com\"]);\nconst OSM_HOSTS = new Set([\"www.openstreetmap.org\", \"openstreetmap.org\"]);\n\n/**\n * 지도 embed src allowlist — 렌더러가 contact 지도 iframe 출력 전 호출\n * (W8-7, `isAllowedEmbedSrc` 의 영상 allowlist 와 별도).\n *\n * 별도 함수인 이유: 지도는 \"외부 API 키가 필요 없는 keyless embed 만\"이라는\n * 추가 제약이 있다. 허용 형태:\n *   - Google Maps 공유 embed:  https://www.google.com/maps/embed?pb=…\n *   - Google Maps keyless 쿼리 embed: https://maps.google.com/maps?q=…&output=embed\n *   - OpenStreetMap export embed: https://www.openstreetmap.org/export/embed.html?…\n * `/maps/embed/v1/*`(Embed API — 키 필요) 와 그 밖의 host/경로는 거부 —\n * 거부 시 렌더러는 기존 facade 이미지/길찾기 링크 폴백을 유지한다(fail-soft).\n */\nexport function isAllowedMapEmbedSrc(value: unknown): value is string {\n  if (typeof value !== \"string\") return false;\n  let url: URL;\n  try {\n    url = new URL(value);\n  } catch {\n    return false;\n  }\n  if (url.protocol !== \"https:\") return false;\n  const host = url.hostname.toLowerCase();\n  if (GOOGLE_MAP_HOSTS.has(host)) {\n    if (url.pathname === \"/maps/embed\") return true;\n    if (url.pathname === \"/maps\" || url.pathname === \"/maps/\") {\n      return url.searchParams.get(\"output\") === \"embed\";\n    }\n    return false;\n  }\n  if (OSM_HOSTS.has(host)) {\n    return url.pathname === \"/export/embed.html\";\n  }\n  return false;\n}\n\nfunction extractYouTubeId(url: URL, host: string): string | null {\n  if (host === \"youtu.be\" || host === \"www.youtu.be\") {\n    const id = url.pathname.split(\"/\").filter(Boolean)[0] ?? \"\";\n    return YOUTUBE_ID.test(id) ? id : null;\n  }\n  const v = url.searchParams.get(\"v\");\n  if (v && YOUTUBE_ID.test(v)) return v;\n  const parts = url.pathname.split(\"/\").filter(Boolean);\n  if (\n    parts.length >= 2 &&\n    (parts[0] === \"embed\" || parts[0] === \"shorts\" || parts[0] === \"v\")\n  ) {\n    return YOUTUBE_ID.test(parts[1]) ? parts[1] : null;\n  }\n  return null;\n}\n\nfunction extractVimeoId(url: URL, host: string): string | null {\n  const parts = url.pathname.split(\"/\").filter(Boolean);\n  let candidate: string | undefined;\n  if (host === \"player.vimeo.com\") {\n    const vi = parts.indexOf(\"video\");\n    candidate = vi >= 0 ? parts[vi + 1] : parts[parts.length - 1];\n  } else {\n    candidate = parts[0];\n  }\n  return candidate && VIMEO_ID.test(candidate) ? candidate : null;\n}\n","// FAQ 추출 — Tiptap doc 에서 faq 블록의 Q/A 를 평문으로 수집.\n//\n// 두 렌더러(cms-renderer-astro / cms-renderer-next)가 공유하여 FAQPage\n// 구조화데이터(JSON-LD)를 생성한다. 직렬화(HTML <details>)는 각 렌더러가\n// 자체 처리(마크업 패리티), 본 모듈은 schema.org 용 평문 추출만 담당.\n\ninterface DocNode {\n  type?: string;\n  text?: string;\n  content?: DocNode[];\n}\n\nexport interface FaqEntry {\n  question: string;\n  answer: string;\n}\n\n/** 노드 트리의 모든 text 를 이어붙여 평문화 (공백 정규화). */\nfunction plainText(node: DocNode | undefined): string {\n  if (!node) return \"\";\n  if (typeof node.text === \"string\") return node.text;\n  if (!Array.isArray(node.content)) return \"\";\n  // 블록 경계는 공백으로 — 문단 사이가 붙지 않도록.\n  return node.content.map(plainText).join(\" \");\n}\n\nfunction collapse(s: string): string {\n  return s.replace(/\\s+/g, \" \").trim();\n}\n\n/** doc 을 순회하며 faqItem(질문/답변)을 순서대로 수집. */\nexport function extractFaqEntries(doc: unknown): FaqEntry[] {\n  const out: FaqEntry[] = [];\n  const walk = (node: DocNode | undefined): void => {\n    if (!node || typeof node !== \"object\") return;\n    if (node.type === \"faqItem\" && Array.isArray(node.content)) {\n      const q = node.content.find((c) => c.type === \"faqQuestion\");\n      const a = node.content.find((c) => c.type === \"faqAnswer\");\n      const question = collapse(plainText(q));\n      const answer = collapse(plainText(a));\n      if (question && answer) out.push({ question, answer });\n      return; // faqItem 내부는 더 내려가지 않음\n    }\n    if (Array.isArray(node.content)) node.content.forEach(walk);\n  };\n  if (doc && typeof doc === \"object\") {\n    walk(doc as DocNode);\n  }\n  return out;\n}\n\n/** FAQPage JSON-LD 객체 (script 삽입은 렌더러 책임). null = 항목 없음. */\nexport function faqPageJsonLd(\n  entries: FaqEntry[],\n): Record<string, unknown> | null {\n  if (entries.length === 0) return null;\n  return {\n    \"@context\": \"https://schema.org\",\n    \"@type\": \"FAQPage\",\n    mainEntity: entries.map((e) => ({\n      \"@type\": \"Question\",\n      name: e.question,\n      acceptedAnswer: { \"@type\": \"Answer\", text: e.answer },\n    })),\n  };\n}\n","/**\n * 본문 이미지 대체텍스트의 제품 안전 상한.\n *\n * HTML 표준의 길이 제한이 아니라, 본문·캡션 전체가 alt 에 잘못 저장돼\n * 스크린리더와 검색 결과를 오염시키는 사고를 막기 위한 RootTale 계약이다.\n */\nexport const IMAGE_ALT_MAX_LENGTH = 160;\n\n/**\n * 이미지 alt 를 한 줄 설명으로 정규화한다.\n *\n * - 문자열이 아니면 빈 alt\n * - 개행·연속 공백을 한 칸으로 축약\n * - 160자를 넘으면 말줄임표를 포함해 상한 안에서 자름\n */\nexport function normalizeImageAlt(value: unknown): string {\n  if (typeof value !== \"string\") return \"\";\n  const normalized = value.replace(/\\s+/g, \" \").trim();\n  const characters = Array.from(normalized);\n  if (characters.length <= IMAGE_ALT_MAX_LENGTH) return normalized;\n\n  const visible = characters\n    .slice(0, IMAGE_ALT_MAX_LENGTH - 1)\n    .join(\"\")\n    .trimEnd();\n  return `${visible}…`;\n}\n","import postcss, { type Container, type Rule } from \"postcss\";\nimport sanitizeHtmlLibrary from \"sanitize-html\";\nimport type sanitizeHtmlTypes from \"sanitize-html\";\n\nconst IMPORT_SCOPE_RE = /^rt-import-[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\nconst ALLOWED_AT_RULES = new Set([\n  \"container\",\n  \"font-face\",\n  \"keyframes\",\n  \"-webkit-keyframes\",\n  \"layer\",\n  \"media\",\n  \"supports\",\n]);\nconst URL_ATTRIBUTES = new Set([\"href\", \"src\"]);\nconst ALLOWED_URL_SCHEMES = new Set([\"http\", \"https\", \"mailto\", \"tel\"]);\nconst UNSAFE_CSS_VALUE_RE =\n  /(?:expression\\s*\\(|javascript\\s*:|vbscript\\s*:|@import|<\\/?(?:style|script)|\\\\0|\\\\0000)/i;\nconst UNSAFE_CSS_PROPERTY_RE = /^(?:behavior|-moz-binding)$/i;\nconst FIXED_POSITION_RE = /^(?:fixed|sticky)$/i;\nconst ORIGINAL_H1_ATTRIBUTE = \"data-rt-original-heading\";\nconst SCOPE_ROOT_BOUNDARY_PROPERTIES = new Set([\n  \"bottom\",\n  \"inset\",\n  \"inset-block\",\n  \"inset-inline\",\n  \"left\",\n  \"position\",\n  \"right\",\n  \"rotate\",\n  \"scale\",\n  \"top\",\n  \"transform\",\n  \"translate\",\n  \"z-index\",\n]);\n\nfunction fromCodePointSafe(value: string, radix: number): string {\n  const codePoint = Number.parseInt(value, radix);\n  if (!Number.isFinite(codePoint) || codePoint < 0 || codePoint > 0x10ffff) {\n    return \"\";\n  }\n  try {\n    return String.fromCodePoint(codePoint);\n  } catch {\n    return \"\";\n  }\n}\n\nfunction isSafeUrl(tagName: string, attributeName: string, value: string): boolean {\n  const normalized = value\n    .replace(/&#x([0-9a-f]+);?/gi, (_, hex: string) => fromCodePointSafe(hex, 16))\n    .replace(/&#(\\d+);?/g, (_, decimal: string) => fromCodePointSafe(decimal, 10))\n    .replace(/[\\u0000-\\u0020\\u007f]/g, \"\")\n    .toLowerCase();\n  const scheme = /^([a-z][a-z0-9+.-]*):/.exec(normalized)?.[1];\n  if (!scheme) return true;\n  if (ALLOWED_URL_SCHEMES.has(scheme)) return true;\n  return (\n    tagName === \"img\" &&\n    attributeName === \"src\" &&\n    scheme === \"data\" &&\n    normalized.startsWith(\"data:image/\") &&\n    !normalized.startsWith(\"data:image/svg\")\n  );\n}\n\nfunction cssUrlsAreSafe(\n  value: string,\n  allowedAssetUrls: ReadonlySet<string>,\n): boolean {\n  const urlRe = /url\\(\\s*([\"']?)([^)'\"\\s]+)\\1\\s*\\)/gi;\n  let matched = false;\n  for (const match of value.matchAll(urlRe)) {\n    matched = true;\n    const url = match[2]?.trim() ?? \"\";\n    if (!url.startsWith(\"#\") && !allowedAssetUrls.has(url)) return false;\n  }\n  return !/url\\s*\\(/i.test(value) || matched;\n}\n\nfunction declarationIsSafe(\n  property: string,\n  value: string,\n  allowedAssetUrls: ReadonlySet<string>,\n): boolean {\n  if (UNSAFE_CSS_PROPERTY_RE.test(property) || UNSAFE_CSS_VALUE_RE.test(value)) {\n    return false;\n  }\n  if (!cssUrlsAreSafe(value, allowedAssetUrls)) return false;\n  if (property === \"position\" && /\\b(?:fixed|sticky)\\b/i.test(value)) {\n    return false;\n  }\n  if (property.startsWith(\"--\") && FIXED_POSITION_RE.test(value.trim())) {\n    return false;\n  }\n  if (property === \"z-index\") {\n    const normalized = value.trim().toLowerCase();\n    if (normalized !== \"auto\") {\n      const zIndex = Number(normalized);\n      if (!Number.isInteger(zIndex) || Math.abs(zIndex) > 100) return false;\n    }\n  }\n  for (const match of value.matchAll(/(-?\\d+(?:\\.\\d+)?)(px|vh|vw)/gi)) {\n    const magnitude = Math.abs(Number(match[1]));\n    const limit = match[2].toLowerCase() === \"px\" ? 4_000 : 200;\n    if (magnitude > limit) return false;\n  }\n  return true;\n}\n\nfunction cleanCssContainer(\n  container: Container,\n  allowedAssetUrls: ReadonlySet<string>,\n): void {\n  container.walkComments((comment) => {\n    comment.remove();\n  });\n  container.walkAtRules((atRule) => {\n    const name = atRule.name.toLowerCase();\n    const isKeyframes = name === \"keyframes\" || name === \"-webkit-keyframes\";\n    if (\n      !ALLOWED_AT_RULES.has(name) ||\n      !atRule.nodes?.length ||\n      (name === \"font-face\" && atRule.parent?.type !== \"root\") ||\n      (isKeyframes && !/^[a-z_][\\w-]*$/i.test(atRule.params.trim())) ||\n      /<\\/?(?:style|script)/i.test(atRule.params)\n    ) {\n      atRule.remove();\n    }\n  });\n  container.walkDecls((declaration) => {\n    const property = declaration.prop.trim().toLowerCase();\n    if (\n      !property ||\n      !declarationIsSafe(property, declaration.value, allowedAssetUrls)\n    ) {\n      declaration.remove();\n      return;\n    }\n  });\n  container.walkRules((rule) => {\n    if (\n      !rule.selector.trim() ||\n      !rule.nodes?.length ||\n      /<\\/?(?:style|script)/i.test(rule.selector)\n    ) {\n      rule.remove();\n    }\n  });\n  container.walkAtRules(\"font-face\", (fontFace) => {\n    const declarations = fontFace.nodes ?? [];\n    const hasFamily = declarations.some(\n      (node) => node.type === \"decl\" && node.prop.toLowerCase() === \"font-family\",\n    );\n    const hasSource = declarations.some(\n      (node) => node.type === \"decl\" && node.prop.toLowerCase() === \"src\",\n    );\n    if (!hasFamily || !hasSource) fontFace.remove();\n  });\n}\n\n/** ChatGPT HTML의 style 블록에서 실행·외부 로드 위험만 제거한다. */\nexport function sanitizeImportedCss(\n  css: string,\n  allowedAssetUrls: readonly string[] = [],\n): string {\n  if (!css.trim()) return \"\";\n  try {\n    const root = postcss.parse(css);\n    cleanCssContainer(root, new Set(allowedAssetUrls));\n    return root.toString().trim();\n  } catch {\n    return \"\";\n  }\n}\n\nfunction sanitizeInlineStyle(\n  style: string,\n  allowedAssetUrls: ReadonlySet<string>,\n): string {\n  try {\n    const root = postcss.parse(`x{${style}}`);\n    cleanCssContainer(root, allowedAssetUrls);\n    const rule = root.first;\n    if (!rule || rule.type !== \"rule\") return \"\";\n    return rule.nodes.map((node) => node.toString()).join(\";\");\n  } catch {\n    return \"\";\n  }\n}\n\nfunction sanitizeImportedAttributes(\n  tagName: string,\n  attributes: sanitizeHtmlTypes.Attributes,\n  allowedAssetUrls: ReadonlySet<string>,\n): sanitizeHtmlTypes.Tag {\n  const next: sanitizeHtmlTypes.Attributes = {};\n  for (const [name, value] of Object.entries(attributes)) {\n    const attributeName = name.toLowerCase();\n    if (attributeName === \"style\") {\n      const style = sanitizeInlineStyle(value, allowedAssetUrls);\n      if (style) next[name] = style;\n      continue;\n    }\n    if (attributeName === \"srcset\") {\n      const srcset = sanitizeSrcset(value);\n      if (srcset) next[name] = srcset;\n      continue;\n    }\n    if (\n      (attributeName === \"fill\" || attributeName === \"stroke\") &&\n      /url\\s*\\(/i.test(value) &&\n      !/^url\\(#[a-z_][\\w:.-]*\\)$/i.test(value.trim())\n    ) {\n      continue;\n    }\n    if (\n      (tagName === \"use\" || tagName === \"image\") &&\n      (attributeName === \"href\" || attributeName === \"xlink:href\") &&\n      !value.trim().startsWith(\"#\")\n    ) {\n      continue;\n    }\n    next[name] =\n      URL_ATTRIBUTES.has(attributeName) &&\n      !isSafeUrl(tagName, attributeName, value)\n        ? \"#\"\n        : value;\n  }\n  if (tagName === \"a\" && next.target === \"_blank\") {\n    next.rel = \"noopener noreferrer\";\n  }\n  if (tagName === \"h1\") {\n    next[ORIGINAL_H1_ATTRIBUTE] = \"h1\";\n    return { tagName: \"h2\", attribs: next };\n  }\n  return { tagName, attribs: next };\n}\n\nfunction sanitizeSrcset(value: string): string {\n  const candidates: string[] = [];\n  for (const rawCandidate of value.split(\",\")) {\n    const candidate = rawCandidate.trim();\n    if (!candidate) continue;\n    const [url, ...descriptorParts] = candidate.split(/\\s+/);\n    if (!url || !isSafeUrl(\"img\", \"src\", url)) continue;\n    const descriptor = descriptorParts.join(\" \");\n    if (descriptor && !/^(?:\\d+(?:\\.\\d+)?x|\\d+w)$/.test(descriptor)) continue;\n    candidates.push(descriptor ? `${url} ${descriptor}` : url);\n  }\n  return candidates.join(\", \");\n}\n\nconst IMPORTED_HTML_OPTIONS: sanitizeHtmlTypes.IOptions = {\n  allowedTags: [\n    \"a\", \"abbr\", \"address\", \"article\", \"aside\", \"b\", \"bdi\", \"bdo\",\n    \"blockquote\", \"br\", \"caption\", \"cite\", \"code\", \"col\", \"colgroup\",\n    \"dd\", \"del\", \"details\", \"dfn\", \"div\", \"dl\", \"dt\", \"em\",\n    \"figcaption\", \"figure\", \"footer\", \"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\",\n    \"header\", \"hr\", \"i\", \"img\", \"kbd\", \"li\", \"main\", \"mark\", \"nav\",\n    \"ol\", \"p\", \"picture\", \"pre\", \"q\", \"s\", \"samp\", \"section\", \"small\",\n    \"source\", \"svg\", \"g\", \"path\", \"circle\", \"ellipse\", \"line\", \"polyline\",\n    \"polygon\", \"rect\", \"defs\", \"lineargradient\", \"radialgradient\", \"stop\",\n    \"use\", \"clippath\", \"mask\", \"pattern\", \"title\", \"desc\",\n    \"span\", \"strong\", \"sub\", \"summary\", \"sup\", \"table\", \"tbody\", \"td\",\n    \"tfoot\", \"th\", \"thead\", \"time\", \"tr\", \"u\", \"ul\", \"var\", \"wbr\",\n  ],\n  allowedAttributes: {\n    \"*\": [\"aria-*\", \"class\", \"data-*\", \"dir\", \"id\", \"lang\", \"role\", \"style\", \"title\"],\n    a: [\n      \"href\",\n      { name: \"rel\", multiple: true, values: [\"noopener\", \"noreferrer\"] },\n      { name: \"target\", values: [\"_blank\"] },\n    ],\n    col: [\"span\", \"style\"],\n    img: [\"alt\", \"height\", \"loading\", \"sizes\", \"src\", \"srcset\", \"style\", \"width\"],\n    source: [\"height\", \"media\", \"sizes\", \"src\", \"srcset\", \"type\", \"width\"],\n    svg: [\"aria-*\", \"class\", \"fill\", \"height\", \"preserveAspectRatio\", \"role\", \"stroke\", \"style\", \"viewBox\", \"viewbox\", \"width\", \"xmlns\"],\n    g: [\"class\", \"fill\", \"stroke\", \"stroke-width\", \"style\", \"transform\"],\n    path: [\"class\", \"d\", \"fill\", \"fill-rule\", \"stroke\", \"stroke-linecap\", \"stroke-linejoin\", \"stroke-width\", \"style\", \"transform\"],\n    circle: [\"class\", \"cx\", \"cy\", \"fill\", \"r\", \"stroke\", \"stroke-width\", \"style\", \"transform\"],\n    ellipse: [\"class\", \"cx\", \"cy\", \"fill\", \"rx\", \"ry\", \"stroke\", \"stroke-width\", \"style\", \"transform\"],\n    line: [\"class\", \"stroke\", \"stroke-linecap\", \"stroke-width\", \"style\", \"transform\", \"x1\", \"x2\", \"y1\", \"y2\"],\n    polyline: [\"class\", \"fill\", \"points\", \"stroke\", \"stroke-linecap\", \"stroke-linejoin\", \"stroke-width\", \"style\", \"transform\"],\n    polygon: [\"class\", \"fill\", \"points\", \"stroke\", \"stroke-width\", \"style\", \"transform\"],\n    rect: [\"class\", \"fill\", \"height\", \"rx\", \"ry\", \"stroke\", \"stroke-width\", \"style\", \"transform\", \"width\", \"x\", \"y\"],\n    lineargradient: [\"gradientTransform\", \"gradientUnits\", \"id\", \"spreadMethod\", \"x1\", \"x2\", \"y1\", \"y2\"],\n    radialgradient: [\"cx\", \"cy\", \"fx\", \"fy\", \"gradientTransform\", \"gradientUnits\", \"id\", \"r\", \"spreadMethod\"],\n    stop: [\"offset\", \"stop-color\", \"stop-opacity\", \"style\"],\n    use: [\"fill\", \"height\", \"href\", \"stroke\", \"style\", \"width\", \"x\", \"xlink:href\", \"y\"],\n    clippath: [\"clipPathUnits\", \"id\", \"transform\"],\n    mask: [\"height\", \"id\", \"maskContentUnits\", \"maskUnits\", \"width\", \"x\", \"y\"],\n    pattern: [\"height\", \"id\", \"patternContentUnits\", \"patternTransform\", \"patternUnits\", \"preserveAspectRatio\", \"viewBox\", \"width\", \"x\", \"y\"],\n    td: [\"colspan\", \"rowspan\", \"style\"],\n    th: [\n      \"colspan\",\n      \"rowspan\",\n      \"style\",\n      { name: \"scope\", values: [\"col\", \"row\", \"colgroup\", \"rowgroup\"] },\n    ],\n    time: [\"datetime\"],\n  },\n  allowedSchemes: [\"http\", \"https\", \"mailto\", \"tel\"],\n  allowedSchemesByTag: { img: [\"http\", \"https\", \"data\"] },\n  allowedSchemesAppliedToAttributes: [\"href\", \"src\"],\n  allowProtocolRelative: false,\n  nonTextTags: [\"script\", \"style\", \"textarea\", \"option\", \"iframe\", \"object\", \"embed\", \"foreignobject\", \"math\", \"template\"],\n};\n\n/** class·id·안전한 inline style을 보존하는 원문 디자인 전용 HTML 경계. */\nexport function sanitizeImportedHtml(\n  html: string,\n  allowedAssetUrls: readonly string[] = [],\n): string {\n  if (!html) return html;\n  const allowedSet = new Set(allowedAssetUrls);\n  return sanitizeHtmlLibrary(html, {\n    ...IMPORTED_HTML_OPTIONS,\n    transformTags: {\n      \"*\": (tagName, attributes) =>\n        sanitizeImportedAttributes(tagName, attributes, allowedSet),\n    },\n  });\n}\n\n/** 구 버전 저장값도 현재의 html/body 가상 루트 계약으로 올린다. */\nexport function ensureImportedHtmlDocumentRoots(\n  html: string,\n  allowedAssetUrls: readonly string[] = [],\n): string {\n  const safeHtml = sanitizeImportedHtml(html, allowedAssetUrls).trim();\n  if (!safeHtml) return safeHtml;\n  if (\n    /^<div\\b[^>]*\\bdata-rt-import-document(?:=(?:\"\"|'')|(?=[\\s>]))[^>]*>\\s*<div\\b[^>]*\\bdata-rt-import-body(?:=(?:\"\"|'')|(?=[\\s>]))/i.test(\n      safeHtml,\n    )\n  ) {\n    return safeHtml;\n  }\n  return `<div data-rt-import-document><div data-rt-import-body>${safeHtml}</div></div>`;\n}\n\nfunction normalizeDocumentSelector(selector: string): string {\n  const typeBoundary = \"(?=$|[\\\\s>+~,.#:\\\\[\\\\)])\";\n  return selector\n    .replace(/html\\s*>\\s*body/gi, \"[data-rt-import-document] > [data-rt-import-body]\")\n    .replace(/html\\s+body/gi, \"[data-rt-import-document] [data-rt-import-body]\")\n    .replace(new RegExp(`(^|[\\\\s>+~,\\\\(])html${typeBoundary}`, \"gi\"), \"$1[data-rt-import-document]\")\n    .replace(new RegExp(`(^|[\\\\s>+~,\\\\(])body${typeBoundary}`, \"gi\"), \"$1[data-rt-import-body]\")\n    .replace(\n      new RegExp(`(^|[\\\\s>+~,\\\\(])h1${typeBoundary}`, \"gi\"),\n      `$1[${ORIGINAL_H1_ATTRIBUTE}=\"h1\"]`,\n    )\n    .replace(/:root/g, \"[data-rt-import-document]\");\n}\n\nfunction selectorTargetsDocumentBoundary(selector: string): boolean {\n  return selector.split(\",\").some((part) => {\n    const normalized = part.trim();\n    const targetsDocumentRoot =\n      normalized.startsWith(\"[data-rt-import-document]\") &&\n      !/[\\s>+~]/.test(normalized.slice(\"[data-rt-import-document]\".length));\n    const targetsBodyRoot =\n      /\\[data-rt-import-body\\](?:[.#:][\\w-]+|\\[[^\\]]+\\])*\\s*$/.test(\n        normalized,\n      );\n    return targetsDocumentRoot || targetsBodyRoot;\n  });\n}\n\nfunction replaceAnimationName(value: string, names: ReadonlyMap<string, string>): string {\n  let next = value;\n  for (const [name, scopedName] of names) {\n    const escaped = name.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n    next = next.replace(new RegExp(`(^|[^\\\\w-])${escaped}(?=$|[^\\\\w-])`, \"g\"), `$1${scopedName}`);\n  }\n  return next;\n}\n\nfunction unquoteCssIdentifier(value: string): string | null {\n  const normalized = value.trim();\n  const match = /^(?:\"([^\"]+)\"|'([^']+)'|([a-z_][\\w -]*))$/i.exec(normalized);\n  return (match?.[1] ?? match?.[2] ?? match?.[3] ?? \"\").trim() || null;\n}\n\nfunction replaceFontFamilyName(\n  value: string,\n  names: ReadonlyMap<string, string>,\n): string {\n  let next = value;\n  for (const [name, scopedName] of names) {\n    const escaped = name.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n    next = next.replace(\n      new RegExp(`([\"'])${escaped}\\\\1|(^|[,\\\\s])${escaped}(?=$|[,\\\\s/])`, \"gi\"),\n      (matched, quote: string | undefined, prefix: string | undefined) =>\n        quote ? `${quote}${scopedName}${quote}` : `${prefix ?? \"\"}${scopedName}`,\n    );\n  }\n  return next;\n}\n\n/** 저장된 안전 CSS가 같은 importedHtml 블록 밖으로 새지 않도록 범위를 고정한다. */\nexport function scopeImportedCss(\n  css: string,\n  scope: string,\n  allowedAssetUrls: readonly string[] = [],\n): string {\n  if (!IMPORT_SCOPE_RE.test(scope)) return \"\";\n  const safeCss = sanitizeImportedCss(css, allowedAssetUrls);\n  if (!safeCss) return \"\";\n  try {\n    const root = postcss.parse(safeCss);\n    const keyframeNames = new Map<string, string>();\n    const fontFamilyNames = new Map<string, string>();\n    const fontFaces = root.nodes.filter(\n      (node) => node.type === \"atrule\" && node.name.toLowerCase() === \"font-face\",\n    );\n    for (const fontFace of fontFaces) {\n      if (fontFace.type !== \"atrule\") continue;\n      const family = fontFace.nodes?.find(\n        (node) => node.type === \"decl\" && node.prop.toLowerCase() === \"font-family\",\n      );\n      if (!family || family.type !== \"decl\") {\n        fontFace.remove();\n        continue;\n      }\n      const originalName = unquoteCssIdentifier(family.value);\n      const hasSupportedSource = fontFace.nodes?.some(\n        (node) =>\n          node.type === \"decl\" &&\n          node.prop.toLowerCase() === \"src\" &&\n          /url\\(\\s*[\"']?https:\\/\\/[^)'\"\\s]+\\.woff2?(?:[?#][^)'\"\\s]*)?[\"']?\\s*\\)/i.test(\n            node.value,\n          ),\n      );\n      if (!originalName || !hasSupportedSource) {\n        fontFace.remove();\n        continue;\n      }\n      const scopedName = `${scope}-${originalName}`;\n      fontFamilyNames.set(originalName, scopedName);\n      family.value = family.value.replace(originalName, scopedName);\n    }\n    root.walkAtRules((atRule) => {\n      const name = atRule.name.toLowerCase();\n      if (name !== \"keyframes\" && name !== \"-webkit-keyframes\") return;\n      const originalName = atRule.params.trim();\n      const scopedName = `${scope}-${originalName}`;\n      keyframeNames.set(originalName, scopedName);\n      atRule.params = scopedName;\n    });\n    root.walkDecls((declaration) => {\n      const property = declaration.prop.trim().toLowerCase();\n      if (property === \"animation\" || property === \"animation-name\") {\n        declaration.value = replaceAnimationName(declaration.value, keyframeNames);\n      }\n      if (property === \"font-family\" || property === \"font\") {\n        declaration.value = replaceFontFamilyName(\n          declaration.value,\n          fontFamilyNames,\n        );\n      }\n    });\n    root.walkRules((rule: Rule) => {\n      const parent = rule.parent;\n      if (\n        parent?.type === \"atrule\" &&\n        (parent.name.toLowerCase() === \"keyframes\" ||\n          parent.name.toLowerCase() === \"-webkit-keyframes\")\n      ) {\n        return;\n      }\n      rule.selector = normalizeDocumentSelector(rule.selector);\n      if (selectorTargetsDocumentBoundary(rule.selector)) {\n        rule.walkDecls((declaration) => {\n          if (\n            SCOPE_ROOT_BOUNDARY_PROPERTIES.has(\n              declaration.prop.trim().toLowerCase(),\n            )\n          ) {\n            declaration.remove();\n          }\n        });\n      }\n    });\n    const scoped = postcss.atRule({\n      name: \"scope\",\n      params: `([data-rt-import-scope=\"${scope}\"])`,\n    });\n    const preservedFontFaces = fontFaces.filter((fontFace) => fontFace.parent);\n    for (const fontFace of preservedFontFaces) fontFace.remove();\n    scoped.append(root.nodes);\n    return [...preservedFontFaces.map((fontFace) => fontFace.toString()), scoped.toString()]\n      .filter(Boolean)\n      .join(\"\\n\");\n  } catch {\n    return \"\";\n  }\n}\n","// 본문 예약 내부 링크 토큰 — `[[internal:{키}|표시 문구]]`.\n//\n// 키는 대상 글의 정규 공개 경로 조각을 점으로 이은 값(`faq.두통.편두통.slug`,\n// `blog.my-post`). 토큰은 body_json 의 일반 텍스트로 저장되고, 편집기는 칩으로\n// 보여 주며, 공개 렌더러는 발행 원장의 경로와 대조해 링크로 바꾼다. 여기 정규식\n// 하나가 편집기·렌더러·FRONT 예제의 SSoT 다(어드민 `buildInternalContentLinkToken`\n// 이 만드는 형식과 같다).\n\nexport const INTERNAL_CONTENT_LINK_TOKEN_SOURCE = String.raw`\\[\\[internal:([a-z0-9가-힣]+(?:[.-][a-z0-9가-힣]+)*)\\|([^\\]\\n|]+)\\]\\]`;\n\nexport interface InternalContentLinkToken {\n  /** 토큰이 시작하는 문자 인덱스(텍스트 기준). */\n  readonly index: number;\n  /** 원문 토큰 전체(`[[internal:…|…]]`). */\n  readonly raw: string;\n  /** 소문자로 정규화한 키. */\n  readonly key: string;\n  /** 표시 문구(앞뒤 공백 제거). */\n  readonly label: string;\n}\n\n/** 텍스트 안의 토큰을 앞에서부터 모두 찾는다. 겹치지 않는다. */\nexport function findInternalContentLinkTokens(\n  text: string,\n): InternalContentLinkToken[] {\n  const pattern = new RegExp(INTERNAL_CONTENT_LINK_TOKEN_SOURCE, \"giu\");\n  const tokens: InternalContentLinkToken[] = [];\n  for (const match of text.matchAll(pattern)) {\n    const [raw, key, label] = match;\n    if (!key || !label || match.index === undefined) continue;\n    tokens.push({\n      index: match.index,\n      raw,\n      key: key.toLowerCase(),\n      label: label.trim(),\n    });\n  }\n  return tokens;\n}\n\n/** 공개 경로(`/faq/a/b/slug`) → 키(`faq.a.b.slug`). 조각이 없으면 null. */\nexport function internalContentKeyFromPath(\n  path: string | null | undefined,\n): string | null {\n  if (!path) return null;\n  const segments = path\n    .split(\"/\")\n    .filter(Boolean)\n    .map((segment) => {\n      try {\n        return decodeURIComponent(segment).trim().toLowerCase();\n      } catch {\n        return \"\";\n      }\n    });\n  if (\n    segments.length === 0 ||\n    segments.some((segment) => segment === \"\" || segment.includes(\".\"))\n  ) {\n    return null;\n  }\n  return segments.join(\".\");\n}\n\n/** 키 → 공개 경로. 없으면 null(문구만 표시). */\nexport type InternalContentLinkResolver = (key: string) => string | null;\n\ninterface InternalContentPathEntry {\n  /** 저장된 정규 공개 경로(`/faq/a/b/slug`). 없으면 대상이 아니다. */\n  readonly path?: string | null;\n  /** 옛 slug 목록 — 옛 키로 남은 본문도 현재 주소로 이어 준다. */\n  readonly previousSlugs?: readonly string[] | null;\n}\n\n/**\n * 발행 글 목록에서 \"키 → 현재 경로\" 색인을 만든다. 옛 slug 도 같은 글로 등록한다\n * (경로 마지막 조각을 옛 slug 로 바꾼 키). 현재 경로가 옛 키와 겹치면 현재가 이긴다.\n */\nexport function internalContentPathIndex(\n  entries: Iterable<InternalContentPathEntry>,\n): Map<string, string> {\n  const index = new Map<string, string>();\n  const previous = new Map<string, string>();\n  for (const entry of entries) {\n    const path = entry.path;\n    const key = internalContentKeyFromPath(path);\n    if (!path || !key) continue;\n    index.set(key, path);\n    const segments = key.split(\".\");\n    for (const oldSlug of entry.previousSlugs ?? []) {\n      const oldKey = [...segments.slice(0, -1), oldSlug.trim().toLowerCase()].join(\".\");\n      if (oldKey && !oldKey.includes(\"..\")) previous.set(oldKey, path);\n    }\n  }\n  for (const [key, path] of previous) {\n    if (!index.has(key)) index.set(key, path);\n  }\n  return index;\n}\n\n/** Map 이나 함수 어느 쪽이든 resolver 로. */\nexport function internalContentLinkResolverFrom(\n  source: InternalContentLinkResolver | ReadonlyMap<string, string> | null | undefined,\n): InternalContentLinkResolver {\n  if (!source) return () => null;\n  if (typeof source === \"function\") return source;\n  return (key) => source.get(key.toLowerCase()) ?? null;\n}\n\nfunction isSafeInternalPath(path: string | null): path is string {\n  return typeof path === \"string\" && path.startsWith(\"/\") && !path.startsWith(\"//\");\n}\n\nfunction escapeHtmlText(value: string): string {\n  return value\n    .replaceAll(\"&\", \"&amp;\")\n    .replaceAll(\"<\", \"&lt;\")\n    .replaceAll(\">\", \"&gt;\")\n    .replaceAll('\"', \"&quot;\")\n    .replaceAll(\"'\", \"&#39;\");\n}\n\n/** 링크로 렌더된 토큰의 class — 사이트 CSS 가 잡을 고리. */\nexport const INTERNAL_CONTENT_LINK_CLASS = \"rt-internal-link\";\n/** 대상이 아직 없어 문구만 남긴 토큰의 class·data 속성. */\nexport const INTERNAL_CONTENT_LINK_PENDING_CLASS = \"rt-internal-link--pending\";\nexport const INTERNAL_CONTENT_LINK_PENDING_ATTR = \"data-rt-internal-link-pending\";\n\n/**\n * 이미 이스케이프된 텍스트 한 구간의 토큰을 HTML 로 바꾼다. 텍스트 부분은 다시\n * 이스케이프하지 않는다(정화기가 이미 처리한 값). 토큰이 없으면 원문 그대로.\n */\nexport function renderInternalContentLinksInText(\n  text: string,\n  resolve: InternalContentLinkResolver,\n): { readonly html: string; readonly hasTokens: boolean } {\n  const tokens = findInternalContentLinkTokens(text);\n  if (tokens.length === 0) return { html: text, hasTokens: false };\n  let cursor = 0;\n  let html = \"\";\n  for (const token of tokens) {\n    html += text.slice(cursor, token.index);\n    const path = resolve(token.key);\n    const label = escapeHtmlText(token.label);\n    html += isSafeInternalPath(path)\n      ? `<a href=\"${escapeHtmlText(path)}\" class=\"${INTERNAL_CONTENT_LINK_CLASS}\">${label}</a>`\n      : `<span class=\"${INTERNAL_CONTENT_LINK_CLASS} ${INTERNAL_CONTENT_LINK_PENDING_CLASS}\" ${INTERNAL_CONTENT_LINK_PENDING_ATTR}=\"${escapeHtmlText(token.key)}\">${label}</span>`;\n    cursor = token.index + token.raw.length;\n  }\n  html += text.slice(cursor);\n  return { html, hasTokens: true };\n}\n\nconst INTERNAL_LINK_BLOCKED_HTML_TAGS = new Set([\n  \"a\",\n  \"code\",\n  \"pre\",\n  \"script\",\n  \"style\",\n  \"textarea\",\n]);\nconst VOID_HTML_TAGS = new Set([\"br\", \"hr\", \"img\", \"input\", \"source\", \"wbr\"]);\n\nfunction htmlTagEnd(html: string, start: number): number {\n  let quote: '\"' | \"'\" | null = null;\n  for (let index = start + 1; index < html.length; index += 1) {\n    const character = html[index];\n    if (quote) {\n      if (character === quote) quote = null;\n      continue;\n    }\n    if (character === '\"' || character === \"'\") {\n      quote = character;\n      continue;\n    }\n    if (character === \">\") return index;\n  }\n  return -1;\n}\n\n/**\n * 정화된 HTML(원문 보존 블록 등)의 텍스트 구간에만 토큰을 적용한다. `<a>`·`<code>`·\n * `<pre>` 안과 태그 속성값은 건드리지 않는다.\n */\nexport function renderInternalContentLinksInHtml(\n  html: string,\n  resolve: InternalContentLinkResolver,\n): { readonly html: string; readonly hasTokens: boolean } {\n  if (!html.includes(\"[[internal:\")) return { html, hasTokens: false };\n  let blockedDepth = 0;\n  let cursor = 0;\n  let out = \"\";\n  let hasTokens = false;\n  const emitText = (text: string) => {\n    if (blockedDepth > 0 || !text) {\n      out += text;\n      return;\n    }\n    const rendered = renderInternalContentLinksInText(text, resolve);\n    hasTokens ||= rendered.hasTokens;\n    out += rendered.html;\n  };\n  while (cursor < html.length) {\n    const tagStart = html.indexOf(\"<\", cursor);\n    if (tagStart < 0) {\n      emitText(html.slice(cursor));\n      break;\n    }\n    emitText(html.slice(cursor, tagStart));\n    const tagEnd = htmlTagEnd(html, tagStart);\n    if (tagEnd < 0) {\n      out += html.slice(tagStart);\n      break;\n    }\n    const tag = html.slice(tagStart, tagEnd + 1);\n    out += tag;\n    const parsed = /^<\\s*(\\/?)\\s*([a-z0-9-]+)/i.exec(tag);\n    const tagName = parsed?.[2]?.toLowerCase();\n    if (tagName && INTERNAL_LINK_BLOCKED_HTML_TAGS.has(tagName)) {\n      const isClosing = parsed?.[1] === \"/\";\n      const isVoid = VOID_HTML_TAGS.has(tagName) || /\\/\\s*>$/.test(tag);\n      if (isClosing) blockedDepth = Math.max(0, blockedDepth - 1);\n      else if (!isVoid) blockedDepth += 1;\n    }\n    cursor = tagEnd + 1;\n  }\n  return { html: out, hasTokens };\n}\n","// ADR-0034 §7.1.1 test #10 — Draft preview token sign/verify.\n//\n// HMAC-SHA256 signed token + DB row (post_preview_tokens_v2) lookup.\n// Atomic consume = consumedAt update at verify time.\n//\n// Token format: `<postId>.<expiresAt-ms>.<hmac-base64url>`\n\nimport { createHmac, randomBytes, timingSafeEqual } from \"node:crypto\";\n\nexport interface PreviewTokenPayload {\n  postId: string;\n  expiresAt: Date;\n}\n\nconst DELIMITER = \".\";\n\nfunction base64url(buf: Buffer): string {\n  return buf.toString(\"base64url\");\n}\n\nfunction fromBase64url(s: string): Buffer {\n  return Buffer.from(s, \"base64url\");\n}\n\nfunction sign(secret: string, message: string): string {\n  return base64url(createHmac(\"sha256\", secret).update(message).digest());\n}\n\n/** 새 preview token 생성. DB insert 책임은 caller. */\nexport function issuePreviewToken(\n  secret: string,\n  payload: PreviewTokenPayload,\n): string {\n  const exp = payload.expiresAt.getTime();\n  const nonce = base64url(randomBytes(12));\n  const message = `${payload.postId}${DELIMITER}${exp}${DELIMITER}${nonce}`;\n  const sig = sign(secret, message);\n  return `${message}${DELIMITER}${sig}`;\n}\n\n/** Token 검증 (서명 + 만료). DB row consumed_at update 는 caller (atomic). */\nexport function verifyPreviewToken(\n  secret: string,\n  token: string,\n): PreviewTokenPayload | null {\n  const parts = token.split(DELIMITER);\n  if (parts.length !== 4) return null;\n  const [postId, expRaw, nonce, sig] = parts as [string, string, string, string];\n  const exp = Number(expRaw);\n  if (!Number.isFinite(exp)) return null;\n  if (Date.now() > exp) return null;\n\n  const expectedSig = sign(secret, `${postId}${DELIMITER}${exp}${DELIMITER}${nonce}`);\n  const a = fromBase64url(sig);\n  const b = fromBase64url(expectedSig);\n  if (a.length !== b.length) return null;\n  if (!timingSafeEqual(a, b)) return null;\n\n  return { postId, expiresAt: new Date(exp) };\n}\n","import sanitizeHtmlLibrary from \"sanitize-html\";\nimport type sanitizeHtmlTypes from \"sanitize-html\";\n\n// ADR-0034 §7.1.1 test #7 — XSS sanitize helper (allow-list, 2차 방어).\n//\n// Phase 1 = strict CSP (`script-src 'self'`) 가 1차 방어. 본 helper 는 렌더 직전\n// 2차 방어로 위험 태그/속성/URL scheme 을 strip 한다.\n//\n// Tiptap 에디터/renderer 실측 allowlist:\n// - StarterKit: p/h2-h4/ul/ol/li/blockquote/pre/code/hr/br + strong/em/s\n// - editor 추가 mark: u/mark/span(style: color/background) + TextAlign(style)\n// - media/table/custom nodes: img/figure/figcaption/table/thead/tbody/tfoot/tr/th/td/div\n//   (thead/tfoot 는 이관된 원문 HTML 표에 실제로 쓰인다. allowlist 에서 빠져 있으면\n//    sanitize-html 이 그 태그만 벗겨내 `<table><tr>…` 처럼 머리행이 섹션 밖으로\n//    빠져나온 깨진 표가 되므로 안전한 구조 태그로 명시 허용한다.)\n// - FAQ renderer: details/summary + rt-cms-* classes\n// Embed iframe 은 sanitize 경로가 아니라 Tiptap JSON `renderEmbed()` 에서\n// `isAllowedEmbedSrc` 로 검증 후 렌더된다. raw HTML sanitizer 에서는 iframe 전면 strip.\n\n// URL 속성(href/src/…) — deny-list 가 아니라 **scheme allow-list** 로 판정한다.\n// HTML entity / 제어문자 인코딩(`&#106;avascript:`, `java&#9;script:`,\n// `jav&#x0d;ascript:`)은 regex 리터럴 매칭을 우회하므로, 값에서 entity 를 decode\n// 하고 ASCII 공백/제어문자를 제거(브라우저 URL 파서와 동일 정규화)한 뒤 scheme 을\n// 검사한다. sanitize-html parser 가 처리한 속성에도 동일 probe 를 적용한다.\nconst URL_ATTRS = new Set([\"href\", \"src\", \"xlink:href\"]);\n\nconst ALLOWED_URL_SCHEMES = new Set([\"http\", \"https\", \"mailto\", \"tel\"]);\nconst MALFORMED_DANGEROUS_TOKEN_RE =\n  /<\\/?[^>\\s/]*(?:script|style|iframe|object|embed|svg|math|template)[^>\\s/]*(?:\\s[^>]*)?>[\\s\\S]*?<\\/?[^>\\s/]*(?:script|style|iframe|object|embed|svg|math|template)[^>\\s/]*(?:\\s[^>]*)?>/gi;\nconst SLASH_EVENT_ATTR_IN_URL_RE = /\\/on[a-z]+=/i;\nconst ROOTTALE_CLASS_RE = /^rt-[a-z0-9_-]+$/;\nconst HEX_COLOR_RE = /^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/i;\nconst TEXT_ALIGN_RE = /^(left|right|center|justify)$/;\nconst ASPECT_RATIO_RE = /^\\d+(?:\\.\\d+)?\\s*\\/\\s*\\d+(?:\\.\\d+)?$/;\nconst CSS_LENGTH_RE = /^(?:0|\\d+(?:\\.\\d+)?(?:px|em|rem|%))$/i;\nconst CSS_LENGTH_LIST_RE =\n  /^(?:0|\\d+(?:\\.\\d+)?(?:px|em|rem|%))(?:\\s+(?:0|\\d+(?:\\.\\d+)?(?:px|em|rem|%))){0,3}$/i;\nconst CSS_LINE_HEIGHT_RE = /^(?:normal|\\d+(?:\\.\\d+)?(?:px|em|rem|%)?)$/i;\nconst CSS_FONT_WEIGHT_RE = /^(?:normal|bold|[1-9]00)$/i;\nconst CSS_BORDER_RE =\n  /^(?:none|0|(?:\\d(?:\\.\\d+)?|[12]\\d(?:\\.\\d+)?|3[0-2](?:\\.0+)?)px\\s+(?:solid|dashed|dotted)\\s+#[0-9a-f]{3}(?:[0-9a-f]{3})?)$/i;\nconst CSS_TEXT_DECORATION_RE = /^(?:none|underline|line-through)$/i;\n\nconst CSS_MAGNITUDE_LIMITS: Record<string, Record<string, number>> = {\n  \"font-size\": { \"\": 0, px: 96, em: 5, rem: 5, \"%\": 200 },\n  \"line-height\": { \"\": 5, px: 96, em: 5, rem: 5, \"%\": 500 },\n  margin: { \"\": 0, px: 2000, em: 20, rem: 20, \"%\": 200 },\n  \"margin-bottom\": { \"\": 0, px: 2000, em: 20, rem: 20, \"%\": 200 },\n  \"margin-left\": { \"\": 0, px: 2000, em: 20, rem: 20, \"%\": 200 },\n  \"margin-right\": { \"\": 0, px: 2000, em: 20, rem: 20, \"%\": 200 },\n  \"margin-top\": { \"\": 0, px: 2000, em: 20, rem: 20, \"%\": 200 },\n  padding: { \"\": 0, px: 2000, em: 20, rem: 20, \"%\": 200 },\n  \"padding-bottom\": { \"\": 0, px: 2000, em: 20, rem: 20, \"%\": 200 },\n  \"padding-left\": { \"\": 0, px: 2000, em: 20, rem: 20, \"%\": 200 },\n  \"padding-right\": { \"\": 0, px: 2000, em: 20, rem: 20, \"%\": 200 },\n  \"padding-top\": { \"\": 0, px: 2000, em: 20, rem: 20, \"%\": 200 },\n  width: { \"\": 0, px: 4000, em: 100, rem: 100, \"%\": 100 },\n  \"max-width\": { \"\": 0, px: 4000, em: 100, rem: 100, \"%\": 100 },\n  height: { \"\": 0, px: 4000, em: 100, rem: 100, \"%\": 100 },\n};\n\nfunction hasSafeCssMagnitude(property: string, value: string): boolean {\n  const limits = CSS_MAGNITUDE_LIMITS[property];\n  if (!limits) return true;\n  const numericTokens = value.matchAll(/(\\d+(?:\\.\\d+)?)(px|em|rem|%)?/gi);\n  for (const match of numericTokens) {\n    const unit = (match[2] ?? \"\").toLowerCase();\n    const limit = limits[unit];\n    if (limit === undefined || Number(match[1]) > limit) return false;\n  }\n  return true;\n}\n\nfunction filterUnsafeCssMagnitudes(style: string): string {\n  return style\n    .split(\";\")\n    .map((declaration) => declaration.trim())\n    .filter(Boolean)\n    .filter((declaration) => {\n      const colon = declaration.indexOf(\":\");\n      if (colon < 1) return false;\n      const property = declaration.slice(0, colon).trim().toLowerCase();\n      const value = declaration.slice(colon + 1).trim();\n      return hasSafeCssMagnitude(property, value);\n    })\n    .join(\";\");\n}\n\n// scheme 구성에 악용되는 named entity 만 decode (`&colon;`/`&Tab;` 등).\nconst NAMED_ENTITY: Record<string, string> = {\n  colon: \":\",\n  tab: \"\\t\",\n  newline: \"\\n\",\n  sol: \"/\",\n  semi: \";\",\n  num: \"#\",\n  period: \".\",\n  comma: \",\",\n  lpar: \"(\",\n  rpar: \")\",\n};\n\nfunction fromCodePointSafe(n: number): string {\n  if (!Number.isFinite(n) || n < 0 || n > 0x10ffff) return \"\";\n  try {\n    return String.fromCodePoint(n);\n  } catch {\n    return \"\";\n  }\n}\n\n/** URL 값 정규화 — entity decode + 공백/제어문자 제거 + 소문자 (scheme 판정용). */\nfunction decodeUrlProbe(value: string): string {\n  const decoded = value\n    .replace(/&#x([0-9a-f]+);?/gi, (_, h) => fromCodePointSafe(parseInt(h, 16)))\n    .replace(/&#(\\d+);?/g, (_, d) => fromCodePointSafe(parseInt(d, 10)))\n    .replace(/&([a-z]+);/gi, (m, name) => NAMED_ENTITY[name.toLowerCase()] ?? m);\n  // 브라우저는 scheme 판정 시 ASCII 공백/제어문자(탭/개행/CR/NUL 포함)를 제거한다.\n  return [...decoded].filter((c) => { const n = c.codePointAt(0) ?? 0; return n > 0x20 && n !== 0x7f; }).join(\"\").toLowerCase();\n}\n\nfunction stripMalformedDangerousTokens(html: string): string {\n  let out = html;\n  for (let i = 0; i < 3; i++) {\n    const next = out.replace(MALFORMED_DANGEROUS_TOKEN_RE, \"\");\n    if (next === out) return out;\n    out = next;\n  }\n  return out;\n}\n\n/**\n * 안전한 URL 값인지 — 상대경로/허용 scheme, data:image/* 는 img[src] 에서만(SVG 제외).\n */\nfunction isSafeUrlValue(tag: string, attr: string, value: string): boolean {\n  const probe = decodeUrlProbe(value);\n  if (SLASH_EVENT_ATTR_IN_URL_RE.test(probe)) return false;\n  const scheme = /^([a-z][a-z0-9+.-]*):/.exec(probe)?.[1];\n  if (!scheme) return true; // 상대경로 / fragment / query\n  if (ALLOWED_URL_SCHEMES.has(scheme)) return true;\n  if (\n    scheme === \"data\" &&\n    tag === \"img\" &&\n    attr === \"src\" &&\n    probe.startsWith(\"data:image/\")\n  ) {\n    // data:image/svg+xml 은 스크립트 실행 가능 → 차단.\n    return !probe.startsWith(\"data:image/svg\");\n  }\n  return false;\n}\n\nfunction sanitizeUrlAttributes(\n  tagName: string,\n  attribs: sanitizeHtmlTypes.Attributes,\n): sanitizeHtmlTypes.Tag {\n  const next: sanitizeHtmlTypes.Attributes = {};\n  for (const [name, value] of Object.entries(attribs)) {\n    const attr = name.toLowerCase();\n    if (attr === \"style\") {\n      const style = filterUnsafeCssMagnitudes(value);\n      if (style) next[name] = style;\n      continue;\n    }\n    next[name] = URL_ATTRS.has(attr) && !isSafeUrlValue(tagName, attr, value) ? \"#\" : value;\n  }\n  if (tagName === \"a\" && next.target === \"_blank\") {\n    next.rel = \"noopener noreferrer\";\n  }\n  return { tagName, attribs: next };\n}\n\nconst SANITIZE_OPTIONS: sanitizeHtmlTypes.IOptions = {\n  allowedTags: [\n    \"a\",\n    \"blockquote\",\n    \"br\",\n    \"cite\",\n    \"code\",\n    \"details\",\n    \"div\",\n    \"em\",\n    \"figcaption\",\n    \"figure\",\n    \"h2\",\n    \"h3\",\n    \"h4\",\n    \"hr\",\n    \"img\",\n    \"li\",\n    \"mark\",\n    \"ol\",\n    \"p\",\n    \"pre\",\n    \"s\",\n    \"section\",\n    \"small\",\n    \"span\",\n    \"strong\",\n    \"sub\",\n    \"summary\",\n    \"sup\",\n    \"table\",\n    \"tbody\",\n    \"td\",\n    \"tfoot\",\n    \"th\",\n    \"thead\",\n    \"tr\",\n    \"u\",\n    \"ul\",\n  ],\n  allowedAttributes: {\n    \"*\": [\"class\", \"data-*\", \"style\"],\n    a: [\n      \"href\",\n      \"title\",\n      { name: \"rel\", multiple: true, values: [\"noopener\", \"noreferrer\"] },\n      { name: \"target\", values: [\"_blank\"] },\n    ],\n    details: [\"open\", \"class\", \"data-*\"],\n    div: [\"class\", \"data-*\", \"style\"],\n    h2: [\"id\", \"class\", \"data-*\", \"style\"],\n    h3: [\"id\", \"class\", \"data-*\", \"style\"],\n    h4: [\"id\", \"class\", \"data-*\", \"style\"],\n    img: [\"src\", \"alt\", \"title\", \"width\", \"height\", \"loading\", \"class\", \"data-*\"],\n    mark: [\"class\", \"data-*\", \"style\"],\n    p: [\"class\", \"data-*\", \"style\"],\n    span: [\"class\", \"data-*\", \"style\"],\n    td: [\"colspan\", \"rowspan\", \"class\", \"data-*\"],\n    // scope 는 표 머리행의 접근성 의미(스크린리더 열/행 연결) — 값 집합을 고정해 허용.\n    th: [\n      \"colspan\",\n      \"rowspan\",\n      \"class\",\n      \"data-*\",\n      { name: \"scope\", values: [\"col\", \"row\", \"colgroup\", \"rowgroup\"] },\n    ],\n  },\n  allowedClasses: {\n    \"*\": [ROOTTALE_CLASS_RE],\n  },\n  allowedStyles: {\n    img: {\n      display: [/^block$/i],\n      \"margin-left\": [CSS_LENGTH_RE, /^auto$/i],\n      \"margin-right\": [CSS_LENGTH_RE, /^auto$/i],\n    },\n    figure: {\n      display: [/^block$/i],\n      \"margin-left\": [CSS_LENGTH_RE, /^auto$/i],\n      \"margin-right\": [CSS_LENGTH_RE, /^auto$/i],\n    },\n    \"*\": {\n      \"aspect-ratio\": [ASPECT_RATIO_RE],\n      background: [HEX_COLOR_RE],\n      \"background-color\": [HEX_COLOR_RE],\n      border: [CSS_BORDER_RE],\n      \"border-bottom\": [CSS_BORDER_RE],\n      \"border-left\": [CSS_BORDER_RE],\n      \"border-right\": [CSS_BORDER_RE],\n      \"border-top\": [CSS_BORDER_RE],\n      \"border-collapse\": [/^(?:collapse|separate)$/i],\n      color: [HEX_COLOR_RE],\n      \"font-size\": [CSS_LENGTH_RE],\n      \"font-weight\": [CSS_FONT_WEIGHT_RE],\n      height: [CSS_LENGTH_RE, /^auto$/i],\n      \"line-height\": [CSS_LINE_HEIGHT_RE],\n      margin: [CSS_LENGTH_LIST_RE],\n      \"margin-bottom\": [CSS_LENGTH_RE],\n      \"margin-left\": [CSS_LENGTH_RE],\n      \"margin-right\": [CSS_LENGTH_RE],\n      \"margin-top\": [CSS_LENGTH_RE],\n      \"max-width\": [CSS_LENGTH_RE, /^none$/i],\n      padding: [CSS_LENGTH_LIST_RE],\n      \"padding-bottom\": [CSS_LENGTH_RE],\n      \"padding-left\": [CSS_LENGTH_RE],\n      \"padding-right\": [CSS_LENGTH_RE],\n      \"padding-top\": [CSS_LENGTH_RE],\n      \"text-align\": [TEXT_ALIGN_RE],\n      \"text-decoration\": [CSS_TEXT_DECORATION_RE],\n      width: [CSS_LENGTH_RE, /^auto$/i],\n    },\n  },\n  allowedSchemes: [\"http\", \"https\", \"mailto\", \"tel\"],\n  allowedSchemesByTag: {\n    // tag별 목록은 전역 allowedSchemes를 덮어쓴다. data만 쓰면 정상적인\n    // http/https 이미지 src까지 제거되므로 이미지가 사용할 전체 scheme을 명시한다.\n    img: [\"http\", \"https\", \"data\"],\n  },\n  allowedSchemesAppliedToAttributes: [\"href\", \"src\", \"xlink:href\"],\n  allowProtocolRelative: false,\n  nonTextTags: [\n    \"script\",\n    \"style\",\n    \"textarea\",\n    \"option\",\n    \"iframe\",\n    \"object\",\n    \"embed\",\n    \"svg\",\n    \"math\",\n    \"template\",\n  ],\n  transformTags: {\n    \"*\": sanitizeUrlAttributes,\n  },\n};\n\n/**\n * XSS sanitize — Tiptap HTML output 또는 rawHtml 의 안전 정리 (렌더 직전 적용).\n * Phase 1 acceptance: `<script>alert(1)</script>` 등 인라인 벡터가 inert.\n */\nexport function sanitizeHtml(html: string): string {\n  if (!html) return html;\n  return sanitizeHtmlLibrary(stripMalformedDangerousTokens(html), SANITIZE_OPTIONS);\n}\n\n/** 공개 렌더링 원장인 HTML에서 검사·검색용 텍스트를 서버에서도 동일하게 파생한다. */\nexport function htmlToPlainText(html: string): string {\n  const safeHtml = sanitizeHtml(html);\n  const withBlockBoundaries = safeHtml.replace(\n    /<(?:br|hr)\\b[^>]*>|<\\/(?:blockquote|details|div|figcaption|figure|h[2-4]|li|p|pre|section|summary|table|tbody|td|th|tr)>/gi,\n    \" \",\n  );\n  return sanitizeHtmlLibrary(withBlockBoundaries, {\n    allowedTags: [],\n    allowedAttributes: {},\n  })\n    .replace(/\\u00a0/g, \" \")\n    .replace(/\\s+/g, \" \")\n    .trim();\n}\n\n/**\n * 표준 CSP 헤더 값 (Phase 1 acceptance test #7 정합).\n * Astro middleware / Next.js header config 에서 사용.\n */\nexport const PHASE_1_CSP =\n  \"default-src 'self'; \" +\n  \"script-src 'self'; \" +\n  \"style-src 'self' 'unsafe-inline'; \" +\n  \"img-src 'self' data: https://imagedelivery.net https://*.r2.cloudflarestorage.com; \" +\n  \"font-src 'self' data:; \" +\n  \"connect-src 'self'; \" +\n  // core/embed allowlist (embed.ts) 의 영상 iframe — youtube-nocookie / vimeo.\n  \"frame-src https://www.youtube-nocookie.com https://www.youtube.com https://player.vimeo.com; \" +\n  \"frame-ancestors 'none'; \" +\n  \"base-uri 'self'; \" +\n  \"form-action 'self'\";\n","// ADR-0034 §2 P5 — Stable block id (uuidv7 inject)\n//\n// Tiptap 의 BlockId extension 이 본 helper 를 사용해 새 block 에 uuidv7 inject.\n// 기존 block 의 `_id` 는 보존 (revision 간 정합).\n\nimport { uuidv7 } from \"uuidv7\";\n\nimport type { Block } from \"./block\";\n\n/**\n * Block 에 stable id 주입.\n * 이미 `_id` 있는 block 은 그대로, 없으면 uuidv7 생성. recursive (innerBlocks).\n */\nexport function ensureStableIds(block: Omit<Block, \"_id\"> & { _id?: string }): Block {\n  return {\n    ...block,\n    _id: block._id ?? uuidv7(),\n    innerBlocks: (block.innerBlocks ?? []).map((inner) =>\n      ensureStableIds(inner as Omit<Block, \"_id\"> & { _id?: string }),\n    ),\n  };\n}\n\n/**\n * 한 번에 block tree 전체에 stable id 주입.\n * import 또는 새 post 생성 시 entrypoint.\n */\nexport function injectStableIdsTree(blocks: readonly (Omit<Block, \"_id\"> & { _id?: string })[]): Block[] {\n  return blocks.map((b) => ensureStableIds(b));\n}\n","/**\n * 공통 블록(Site Patterns) — 여러 글에 같은 자리로 붙는 재사용 블록의 순수 규칙.\n *\n * 블록 본문은 ROOT-ADMIN 이 `site_patterns` 에 저장하고, \"어느 자리(slot)에 어느\n * 콘텐츠 모델(`model_key`)의 글이면 어떤 블록\"인지는 배치 규칙\n * (`settings.site_pattern_placements`)으로 정한다. 유형의 단위는 콘텐츠 모델이다 —\n * 컬렉션 설정(ADR-0060)은 ADR-0105 로 폐기 중이라 규칙 키로 쓰지 않는다. 공개 API 는 글마다 `pattern_slots`(자리 → 블록 key | null) 를 계산해\n * 내려주므로 FRONT 는 규칙을 몰라도 된다. 이 파일은 api-core(계산)·tenant-admin(편집)·\n * cms-renderer-next(렌더)가 같은 판정을 공유하기 위한 프레임워크 비의존 모듈이다.\n *\n * 규칙 판정:\n * - 자리는 `SITE_PATTERN_SLOTS` 에 선언된 것만 계산한다(미선언 자리는 무시).\n * - 글의 `modelKey` 와 같은 규칙이 있으면 그 규칙(블록 없음 = null 도 명시적 결정).\n * - 없으면 `modelKey: null`(모든 글 기본) 규칙.\n * - 가리키는 블록이 발행 상태가 아니면 null(FRONT 가 미발행 블록을 그리지 않게).\n */\n\n/** 블록을 놓을 수 있는 자리. key 는 공개 API `pattern_slots` 의 키와 같다. */\nexport const SITE_PATTERN_SLOTS = [\n  { key: \"post_footer\", label: \"글 하단\", description: \"글 본문 바로 아래\" },\n] as const;\n\nexport type SitePatternSlotKey = (typeof SITE_PATTERN_SLOTS)[number][\"key\"];\n\nexport const POST_FOOTER_SLOT: SitePatternSlotKey = \"post_footer\";\n\n/** 배치 규칙을 담는 `settings` 키. */\nexport const SITE_PATTERN_PLACEMENTS_KEY = \"site_pattern_placements\";\n\n/** 블록 key 형식 — 소문자·숫자·`-`·`_`, 64자 이하, 영숫자로 시작. */\nexport const SITE_PATTERN_KEY_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/;\n\nexport function isValidSitePatternKey(key: string): boolean {\n  return SITE_PATTERN_KEY_PATTERN.test(key);\n}\n\nexport function isSitePatternSlotKey(value: string): value is SitePatternSlotKey {\n  return SITE_PATTERN_SLOTS.some((slot) => slot.key === value);\n}\n\n/**\n * 배치 규칙 1건. `modelKey: null` = 모든 글의 기본값, 문자열 = 그 콘텐츠 모델의 글만.\n * `patternKey: null` = 이 자리에 블록을 놓지 않는다(유형 규칙이면 기본값 덮어쓰기).\n */\nexport interface SitePatternPlacementRule {\n  readonly slot: SitePatternSlotKey;\n  readonly modelKey: string | null;\n  readonly patternKey: string | null;\n}\n\nexport interface SitePatternPlacements {\n  readonly rules: readonly SitePatternPlacementRule[];\n}\n\nexport const EMPTY_SITE_PATTERN_PLACEMENTS: SitePatternPlacements = { rules: [] };\n\n/** 글 1건의 자리별 블록 key. 선언된 모든 자리를 키로 가진다(없으면 null). */\nexport type PatternSlots = Record<string, string | null>;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n  return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction readNullableKey(value: unknown): string | null | undefined {\n  if (value === null) return null;\n  if (typeof value === \"string\") {\n    const trimmed = value.trim();\n    return trimmed.length > 0 ? trimmed : null;\n  }\n  return undefined;\n}\n\n/**\n * 저장된 값(unknown) → 정규화된 배치 규칙. 형식이 틀린 항목·미선언 자리는 버리고,\n * 같은 (slot, modelKey) 는 마지막 항목이 이긴다. 이중 인코딩된 JSON 문자열도 받는다.\n * 2026-08-19 이전에 저장된 규칙은 `collectionKey` 필드를 썼다 — `modelKey` 가 없으면 그\n * 값을 읽는다(실측상 남은 규칙은 기본값 `null` 뿐이라 의미가 같다). 다시 저장하면\n * `modelKey` 로 쓰인다.\n */\nexport function readSitePatternPlacements(raw: unknown): SitePatternPlacements {\n  let value = raw;\n  if (typeof value === \"string\") {\n    try {\n      value = JSON.parse(value) as unknown;\n    } catch {\n      return EMPTY_SITE_PATTERN_PLACEMENTS;\n    }\n  }\n  if (!isRecord(value) || !Array.isArray(value.rules)) {\n    return EMPTY_SITE_PATTERN_PLACEMENTS;\n  }\n  const byTarget = new Map<string, SitePatternPlacementRule>();\n  for (const item of value.rules) {\n    if (!isRecord(item)) continue;\n    const slot = typeof item.slot === \"string\" ? item.slot : \"\";\n    if (!isSitePatternSlotKey(slot)) continue;\n    const modelKey = readNullableKey(\n      item.modelKey === undefined ? item.collectionKey : item.modelKey,\n    );\n    if (modelKey === undefined) continue;\n    const patternKey = readNullableKey(item.patternKey);\n    if (patternKey === undefined) continue;\n    if (patternKey !== null && !isValidSitePatternKey(patternKey)) continue;\n    byTarget.set(`${slot}\u0000${modelKey ?? \"\"}`, {\n      slot,\n      modelKey,\n      patternKey,\n    });\n  }\n  return { rules: [...byTarget.values()] };\n}\n\n/** (slot, modelKey) 에 해당하는 규칙. 없으면 undefined(= 기본값으로 위임). */\nexport function findSitePatternPlacementRule(\n  placements: SitePatternPlacements,\n  slot: SitePatternSlotKey,\n  modelKey: string | null,\n): SitePatternPlacementRule | undefined {\n  return placements.rules.find((rule) => rule.slot === slot && rule.modelKey === modelKey);\n}\n\n/**\n * 규칙 1건을 갈아 끼운 새 배치 규칙을 돌려준다(불변). `patternKey: undefined` 는\n * \"규칙 삭제 = 기본값으로 위임\"이다 — 유형 규칙에서 \"표시 안 함\"(null)과 구분된다.\n */\nexport function setSitePatternPlacementRule(\n  placements: SitePatternPlacements,\n  target: { slot: SitePatternSlotKey; modelKey: string | null },\n  patternKey: string | null | undefined,\n): SitePatternPlacements {\n  const rest = placements.rules.filter(\n    (rule) => !(rule.slot === target.slot && rule.modelKey === target.modelKey),\n  );\n  if (patternKey === undefined) return { rules: rest };\n  return {\n    rules: [...rest, { slot: target.slot, modelKey: target.modelKey, patternKey }],\n  };\n}\n\n/** 선언된 자리마다 null 인 빈 결과 — 규칙이나 발행 블록이 없을 때의 기본 응답. */\nexport function emptyPatternSlots(): PatternSlots {\n  return Object.fromEntries(SITE_PATTERN_SLOTS.map((slot) => [slot.key, null]));\n}\n\n/**\n * 글 1건의 자리별 블록 key 를 계산한다. 글의 콘텐츠 모델(`modelKey`) 규칙 > 모든 글 기본\n * 규칙. `publishedKeys` 에 없는 블록(미발행·삭제)은 null.\n */\nexport function resolvePatternSlots(\n  post: { readonly modelKey: string | null },\n  placements: SitePatternPlacements,\n  publishedKeys: ReadonlySet<string>,\n): PatternSlots {\n  const slots = emptyPatternSlots();\n  for (const slot of SITE_PATTERN_SLOTS) {\n    const specific =\n      post.modelKey !== null\n        ? findSitePatternPlacementRule(placements, slot.key, post.modelKey)\n        : undefined;\n    const rule =\n      specific ?? findSitePatternPlacementRule(placements, slot.key, null);\n    const key = rule?.patternKey ?? null;\n    slots[slot.key] = key !== null && publishedKeys.has(key) ? key : null;\n  }\n  return slots;\n}\n\n/**\n * 규칙에서 참조하는 블록 key 집합 — 어드민이 \"이 블록은 어디에 쓰이는가\"를 표시하거나\n * 삭제 전 경고할 때 쓴다.\n */\nexport function referencedSitePatternKeys(\n  placements: SitePatternPlacements,\n): ReadonlySet<string> {\n  return new Set(\n    placements.rules\n      .map((rule) => rule.patternKey)\n      .filter((key): key is string => key !== null),\n  );\n}\n\n// ── 표시 형태(presentation) ──────────────────────────────────────────────────\n// 블록의 \"어떻게 보일지\"는 어드민이 정하고 FRONT 는 값을 받아 그리기만 한다 — 사이트 CSS 에\n// 색·모양을 박지 않는다. 값은 작고 범용적인 것만: 카드/본문, 배경색, 링크를 버튼으로, 버튼 색.\n\nexport type SitePatternLayout = \"plain\" | \"card\";\nexport type SitePatternLinkStyle = \"text\" | \"buttons\";\n\nexport interface SitePatternPresentation {\n  /** `plain` = 본문처럼 흐름에 놓임, `card` = 배경색이 있는 카드. */\n  readonly layout: SitePatternLayout;\n  /** 카드 배경색(hex). layout 이 card 일 때만 의미. */\n  readonly background: string;\n  /** 마지막 문단의 링크를 버튼으로 그릴지. */\n  readonly linkStyle: SitePatternLinkStyle;\n  /** 버튼 배경색(hex, 순서대로 1·2·3…; 링크가 더 많으면 마지막 색을 반복). 글자는 흰색. */\n  readonly buttonColors: readonly string[];\n}\n\nexport const DEFAULT_SITE_PATTERN_PRESENTATION: SitePatternPresentation = {\n  layout: \"plain\",\n  background: \"#f8f9fa\",\n  linkStyle: \"text\",\n  buttonColors: [\"#03c75a\", \"#1a1a1a\", \"#666666\"],\n};\n\nexport const SITE_PATTERN_BUTTON_COLORS_MAX = 4;\n\nconst HEX_COLOR_RE = /^#[0-9a-f]{6}$/;\n\nexport function normalizeHexColor(value: unknown): string | null {\n  if (typeof value !== \"string\") return null;\n  let hex = value.trim().toLowerCase();\n  if (/^#[0-9a-f]{3}$/.test(hex)) hex = `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}`;\n  return HEX_COLOR_RE.test(hex) ? hex : null;\n}\n\n/** 저장된 값(unknown) → 표시 형태. 틀린 값은 기본값으로 채운다(절대 throw 하지 않음). */\nexport function readSitePatternPresentation(raw: unknown): SitePatternPresentation {\n  let value = raw;\n  if (typeof value === \"string\") {\n    try {\n      value = JSON.parse(value) as unknown;\n    } catch {\n      value = null;\n    }\n  }\n  const base = DEFAULT_SITE_PATTERN_PRESENTATION;\n  if (!isRecord(value)) return base;\n  const layout: SitePatternLayout = value.layout === \"card\" ? \"card\" : \"plain\";\n  const linkStyle: SitePatternLinkStyle = value.linkStyle === \"buttons\" ? \"buttons\" : \"text\";\n  const background = normalizeHexColor(value.background) ?? base.background;\n  const colors = Array.isArray(value.buttonColors)\n    ? value.buttonColors\n        .map(normalizeHexColor)\n        .filter((c): c is string => c !== null)\n        .slice(0, SITE_PATTERN_BUTTON_COLORS_MAX)\n    : [];\n  return {\n    layout,\n    background,\n    linkStyle,\n    buttonColors: colors.length > 0 ? colors : base.buttonColors,\n  };\n}\n\n/** 버튼 n번째(0부터)의 색 — 링크가 색 개수보다 많으면 마지막 색을 반복. */\nexport function sitePatternButtonColor(presentation: SitePatternPresentation, index: number): string {\n  const colors = presentation.buttonColors;\n  return colors[Math.min(index, colors.length - 1)] ?? DEFAULT_SITE_PATTERN_PRESENTATION.buttonColors[0]!;\n}\n\n/**\n * FRONT 가 래퍼 요소에 얹을 data 속성과 CSS 변수. 사이트 CSS 는 이 변수만 읽는다 —\n * `data-pattern-layout`·`data-pattern-link-style`, `--rt-pattern-bg`, `--rt-pattern-btn-1..n`.\n */\nexport function sitePatternPresentationAttributes(presentation: SitePatternPresentation): {\n  readonly dataset: Readonly<Record<string, string>>;\n  readonly cssVariables: Readonly<Record<string, string>>;\n} {\n  const cssVariables: Record<string, string> = { \"--rt-pattern-bg\": presentation.background };\n  presentation.buttonColors.forEach((color, index) => {\n    cssVariables[`--rt-pattern-btn-${index + 1}`] = color;\n  });\n  return {\n    dataset: {\n      \"data-pattern-layout\": presentation.layout,\n      \"data-pattern-link-style\": presentation.linkStyle,\n    },\n    cssVariables,\n  };\n}\n","/** 편집기와 공개 렌더러가 공유하는 허용 서식. 기본값은 사이트 CSS를 따른다. */\nexport const CMS_FONT_SIZES = [14, 16, 18, 20, 24, 28, 32] as const;\nexport const CMS_LINE_HEIGHTS = [1.2, 1.5, 1.8, 2, 2.4] as const;\nexport const CMS_PARAGRAPH_SPACINGS = [0, 8, 16, 24, 32] as const;\nexport const CMS_IMAGE_WIDTHS = [25, 50, 75, 100] as const;\n\nexport function cmsFontSize(value: unknown): string | null {\n  if (typeof value !== \"string\") return null;\n  return CMS_FONT_SIZES.some((size) => value === `${size}px`) ? value : null;\n}\nexport function cmsLineHeight(value: unknown): number | null {\n  if (typeof value !== \"number\" && typeof value !== \"string\") return null;\n  return CMS_LINE_HEIGHTS.find((size) => String(size) === String(value)) ?? null;\n}\nexport function cmsParagraphSpacing(value: unknown): string | null {\n  if (typeof value !== \"string\") return null;\n  return CMS_PARAGRAPH_SPACINGS.some((size) => value === `${size}px`) ? value : null;\n}\nexport function cmsImageWidth(value: unknown): string | null {\n  if (typeof value !== \"string\") return null;\n  return CMS_IMAGE_WIDTHS.some((size) => value === `${size}%`) ? value : null;\n}\nexport function cmsImageAlign(value: unknown): \"left\" | \"center\" | \"right\" | null {\n  return value === \"left\" || value === \"center\" || value === \"right\" ? value : null;\n}\nexport function cmsImageStyle(attrs: Record<string, unknown>) {\n  const width = cmsImageWidth(attrs.displayWidth);\n  const align = cmsImageAlign(attrs.imageAlign);\n  return {\n    ...(width ? { width, maxWidth: \"100%\", height: \"auto\" } : {}),\n    ...(align ? {\n      display: \"block\",\n      marginLeft: align === \"left\" ? \"0\" : \"auto\",\n      marginRight: align === \"right\" ? \"0\" : \"auto\",\n    } : {}),\n  };\n}\n","export interface SectionLock {\n  move?: boolean;\n  remove?: boolean;\n  structure?: boolean;\n}\n\ninterface SectionNodeSnapshot {\n  id: string;\n  path: string;\n  sectionType: string;\n  attrs: Record<string, unknown>;\n  props: unknown;\n}\n\nexport type SectionLockViolationCode =\n  | \"locked_section_inserted\"\n  | \"locked_section_removed\"\n  | \"locked_section_moved\"\n  | \"locked_section_lock_changed\"\n  | \"locked_section_structure_changed\";\n\nexport interface SectionLockViolation {\n  code: SectionLockViolationCode;\n  sectionId: string;\n  sectionType: string;\n  field: string;\n}\n\nconst CONTENT_PROP_PATHS: Readonly<\n  Record<string, readonly (readonly (string | number)[])[]>\n> = {\n  hero: [\n    [\"eyebrow\"],\n    [\"title\"],\n    [\"description\"],\n    [\"imageSrc\"],\n    [\"imageAlt\"],\n    [\"cta\", \"label\"],\n    [\"cta\", \"href\"],\n  ],\n  \"rich-text\": [[\"heading\"], [\"body\"]],\n  services: [\n    [\"heading\"],\n    [\"description\"],\n    [\"items\", Number.NaN, \"title\"],\n    [\"items\", Number.NaN, \"description\"],\n    [\"items\", Number.NaN, \"href\"],\n  ],\n  faq: [\n    [\"heading\"],\n    [\"items\", Number.NaN, \"question\"],\n    [\"items\", Number.NaN, \"answer\"],\n  ],\n  \"cta-banner\": [[\"title\"], [\"description\"], [\"cta\", \"label\"], [\"cta\", \"href\"]],\n  \"blog-list\": [[\"heading\"], [\"emptyMessage\"]],\n  \"lead-form\": [[\"heading\"], [\"description\"], [\"submitLabel\"]],\n  trust: [\n    [\"heading\"],\n    [\"metrics\", Number.NaN, \"value\"],\n    [\"metrics\", Number.NaN, \"unit\"],\n    [\"metrics\", Number.NaN, \"label\"],\n    [\"metrics\", Number.NaN, \"source\"],\n    [\"credentials\", Number.NaN, \"name\"],\n    [\"credentials\", Number.NaN, \"issuer\"],\n    [\"credentials\", Number.NaN, \"kind\"],\n    [\"credentials\", Number.NaN, \"image\"],\n    [\"logos\", Number.NaN, \"src\"],\n    [\"logos\", Number.NaN, \"alt\"],\n    [\"logos\", Number.NaN, \"href\"],\n    [\"disclaimer\"],\n  ],\n  contact: [\n    [\"heading\"],\n    [\"form\", \"enabled\"],\n    [\"phone\"],\n    [\"address\"],\n    [\"hours\", Number.NaN, \"day\"],\n    [\"hours\", Number.NaN, \"open\"],\n    [\"hours\", Number.NaN, \"close\"],\n    [\"hours\", Number.NaN, \"note\"],\n    [\"mapEmbedUrl\"],\n  ],\n  expertise: [\n    [\"heading\"],\n    [\"description\"],\n    [\"items\", Number.NaN, \"label\"],\n    [\"items\", Number.NaN, \"kicker\"],\n    [\"items\", Number.NaN, \"title\"],\n    [\"items\", Number.NaN, \"body\"],\n    [\"items\", Number.NaN, \"imageSrc\"],\n    [\"items\", Number.NaN, \"imageAlt\"],\n    [\"items\", Number.NaN, \"tags\", Number.NaN],\n    [\"items\", Number.NaN, \"href\"],\n    [\"items\", Number.NaN, \"ctaLabel\"],\n  ],\n  spaces: [\n    [\"heading\"],\n    [\"description\"],\n    [\"spaces\", Number.NaN, \"label\"],\n    [\"spaces\", Number.NaN, \"kicker\"],\n    [\"spaces\", Number.NaN, \"title\"],\n    [\"spaces\", Number.NaN, \"description\"],\n    [\"spaces\", Number.NaN, \"imageSrc\"],\n    [\"spaces\", Number.NaN, \"imageAlt\"],\n    [\"spaces\", Number.NaN, \"tags\", Number.NaN],\n    [\"disclaimer\"],\n  ],\n  concerns: [\n    [\"heading\"],\n    [\"description\"],\n    [\"figureImage\"],\n    [\"figureAlt\"],\n    [\"regions\", Number.NaN, \"label\"],\n    [\"regions\", Number.NaN, \"title\"],\n    [\"regions\", Number.NaN, \"description\"],\n    [\"regions\", Number.NaN, \"items\", Number.NaN],\n    [\"regions\", Number.NaN, \"href\"],\n    [\"regions\", Number.NaN, \"ctaLabel\"],\n    [\"disclaimer\"],\n  ],\n};\n\nfunction valueEquals(left: unknown, right: unknown): boolean {\n  return JSON.stringify(left) === JSON.stringify(right);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n  return Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction collectChangedPaths(\n  before: unknown,\n  after: unknown,\n  prefix: readonly (string | number)[] = [],\n): Array<readonly (string | number)[]> {\n  if (valueEquals(before, after)) return [];\n  if (Array.isArray(before) && Array.isArray(after)) {\n    if (before.length !== after.length) return [prefix];\n    return before.flatMap((item, index) =>\n      collectChangedPaths(item, after[index], [...prefix, index]),\n    );\n  }\n  if ((isRecord(before) || before == null) && (isRecord(after) || after == null)) {\n    const beforeRecord = isRecord(before) ? before : {};\n    const afterRecord = isRecord(after) ? after : {};\n    const keys = new Set([...Object.keys(beforeRecord), ...Object.keys(afterRecord)]);\n    if (keys.size === 0) return [prefix];\n    return [...keys].flatMap((key) =>\n      collectChangedPaths(beforeRecord[key], afterRecord[key], [...prefix, key]),\n    );\n  }\n  return [prefix];\n}\n\nfunction contentPathMatches(\n  pattern: readonly (string | number)[],\n  path: readonly (string | number)[],\n): boolean {\n  if (pattern.length !== path.length) return false;\n  return pattern.every((segment, index) => {\n    const actual = path[index];\n    return Number.isNaN(segment)\n      ? typeof actual === \"number\"\n      : segment === actual;\n  });\n}\n\nexport function isSectionContentPropPath(\n  sectionType: string,\n  path: readonly (string | number)[],\n): boolean {\n  const patterns = CONTENT_PROP_PATHS[sectionType] ?? [];\n  return patterns.some((pattern) => contentPathMatches(pattern, path));\n}\n\nfunction collectSectionSnapshots(doc: unknown): SectionNodeSnapshot[] {\n  const sections: SectionNodeSnapshot[] = [];\n  function visit(node: unknown, path: string): void {\n    if (!isRecord(node)) return;\n    if (node.type === \"section\") {\n      const attrs = isRecord(node.attrs) ? node.attrs : {};\n      sections.push({\n        id: typeof attrs._id === \"string\" ? attrs._id : \"\",\n        path,\n        sectionType: typeof attrs.sectionType === \"string\" ? attrs.sectionType : \"\",\n        attrs,\n        props: attrs.props ?? {},\n      });\n    }\n    if (Array.isArray(node.content)) {\n      node.content.forEach((child, index) => visit(child, `${path}.${index}`));\n    }\n  }\n  visit(doc, \"doc\");\n  return sections;\n}\n\n/** 고객 콘텐츠 저장 경계. 기존 lock 플래그와 무관하게 제작 구조를 보호한다. */\nexport function findSectionLockViolations(\n  beforeDoc: unknown,\n  afterDoc: unknown,\n): SectionLockViolation[] {\n  const before = collectSectionSnapshots(beforeDoc);\n  const after = collectSectionSnapshots(afterDoc);\n  const afterById = new Map(after.map((section) => [section.id, section]));\n  const beforeById = new Map(before.map((section) => [section.id, section]));\n  const violations: SectionLockViolation[] = [];\n\n  // ID 누락·중복은 동일 섹션 비교를 우회할 수 있으므로 실패로 처리한다.\n  for (const sections of [before, after]) {\n    const ids = new Set<string>();\n    for (const section of sections) {\n      if (!section.id || ids.has(section.id)) {\n        violations.push({\n          code: \"locked_section_structure_changed\",\n          sectionId: section.id,\n          sectionType: section.sectionType,\n          field: \"_id\",\n        });\n      }\n      ids.add(section.id);\n    }\n  }\n\n  for (const section of before) {\n    const next = afterById.get(section.id);\n    if (!next) {\n      violations.push({\n        code: \"locked_section_removed\",\n        sectionId: section.id,\n        sectionType: section.sectionType,\n        field: \"(node)\",\n      });\n      continue;\n    }\n\n    if (section.path !== next.path) {\n      violations.push({\n        code: \"locked_section_moved\",\n        sectionId: section.id,\n        sectionType: section.sectionType,\n        field: \"(index)\",\n      });\n    }\n\n    const fields = new Set([\n      ...Object.keys(section.attrs),\n      ...Object.keys(next.attrs),\n    ]);\n    fields.delete(\"props\");\n    for (const field of fields) {\n      if (!valueEquals(section.attrs[field], next.attrs[field])) {\n        violations.push({\n          code: field === \"lock\"\n            ? \"locked_section_lock_changed\"\n            : \"locked_section_structure_changed\",\n          sectionId: section.id,\n          sectionType: section.sectionType,\n          field,\n        });\n      }\n    }\n\n    for (const path of collectChangedPaths(section.props, next.props)) {\n      if (!isSectionContentPropPath(section.sectionType, path)) {\n        violations.push({\n          code: \"locked_section_structure_changed\",\n          sectionId: section.id,\n          sectionType: section.sectionType,\n          field: `props.${path.join(\".\") || \"(root)\"}`,\n        });\n      }\n    }\n  }\n\n  for (const section of after) {\n    if (beforeById.has(section.id)) continue;\n    violations.push({\n      code: \"locked_section_inserted\",\n      sectionId: section.id,\n      sectionType: section.sectionType,\n      field: \"(node)\",\n    });\n  }\n\n  return violations;\n}\n\nexport function formatSectionLockViolations(\n  violations: readonly SectionLockViolation[],\n): string {\n  return violations\n    .map((violation) => {\n      const label = `${violation.sectionType || \"section\"} 섹션(${violation.sectionId})`;\n      if (violation.code === \"locked_section_inserted\") return `${label} 추가`;\n      if (violation.code === \"locked_section_removed\") return `${label} 삭제`;\n      if (violation.code === \"locked_section_moved\") return `${label} 이동`;\n      if (violation.code === \"locked_section_lock_changed\") {\n        return `${label} 잠금 변경`;\n      }\n      return `${label} 구조 변경(${violation.field})`;\n    })\n    .join(\" / \");\n}\n"],"mappings":"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","names":["postcss","sanitizeHtmlLibrary","import_sanitize_html","ALLOWED_URL_SCHEMES","fromCodePointSafe","sanitizeHtmlLibrary","HEX_COLOR_RE","isRecord"]}