{
  "body": "## Summary / relationship to #106\r\n\r\n**疑似回归，关联 #106。** Launcher **0.1.29** 对同 Namespace、已登记的同一模型目录再次执行 `models validate --models-dir`，报 `duplicate query model resource in namespace`。重启及显式 clear-existing 仍失败；相同六文件在新 Namespace 首次校验全部通过。\r\n\r\n#106 的已关闭合同明确同 namespace / 同 path 应复用已加载 Bundle 并给出 `BUNDLE_ALREADY_REGISTERED` warning。本 issue 记录新版本的复现条件，不改写旧修复记录，也尚未断言与旧问题是相同代码根因。\r\n\r\n归属：Java Runtime / TM-QM detached validation、Bundle/catalog 候选合并。与 #146 的查询名/输出别名不同；后者在本轮已修复并通过。失败的模型校验没有被绕过后发布。\r\n\r\n## Environment / 发行身份\r\n\r\n- Windows 11 Pro x64；Java 17.0.12；Python 3.11.5；PowerShell 7.6.5；公开 CLI **0.1.24**。\r\n- [Launcher 0.1.29 Release](https://github.com/foggy-projects/foggy-data-mcp-bridge/releases/tag/foggy-runtime-launcher-v0.1.29)，使用该 Release 的 README、原版 PowerShell 启动脚本及 JAR，未从源码构建。Release 标记为 prerelease。\r\n- source commit：`a3693d0a812129f51966ef9c18247b0094a4f7f7`；内部 Java project version：`9.3.0-SNAPSHOT`。\r\n- JAR SHA256：`f02ee6fabced558543b7da11c7b97da0469aaab5b0a32ff23d03a42b1eaf8595`。六项 GitHub asset digest 与 SHA256SUMS 五项全部匹配，实际 JAR/进程命令行/监听 PID 已核对。\r\n- 独立 WorkDir、registry 和数据副本，loopback `http://127.0.0.1:18068`；Namespace `chinook`，named datasource `chinook-sqlite`，`none-dev-test-only`；匿名本地开发查询。\r\n- Runtime API：`foggy-runtime-api/v1`，schema `2026-06-06`，engine `java`。相关 capabilities 声明 supported。\r\n- [公开 Chinook v1.4.5 SQLite](https://github.com/lerocha/chinook-database/releases/download/v1.4.5/Chinook_Sqlite.sqlite)，副本 SHA256：`bdf635be69850bd3be09c9a2dbeef7ddfb80036bd3ef3381383cd03b61e4a61a`。\r\n- Skill 使用本机现有 foggy-ai-analysis / foggy-semantic-query，未宣称某个全新原版 Skill Release；复现依赖下列模型与公开 CLI，不依赖 Skill 推理。Maven 不参与。\r\n- 首轮为默认 lite（Console/FAP 关闭）；重启轮仅显式开启内嵌 Console，FAP 未配置。两轮失败证据均保留。\r\n\r\n## 基础准备\r\n\r\n将发行启动脚本及 JAR 放在同一目录。下载公开 SQLite 为当前目录的 `Chinook.sqlite`，将下文源码按文件名保存到 `./models/model/` 与 `./models/query/`。以下目录均为调用者自己的练习目录。\r\n\r\n```powershell\r\n.\\start-foggy-runtime.ps1 -Port 18068 -WorkDir \"$PWD/work\"\r\nfoggy-runtime --base-url http://127.0.0.1:18068 --namespace chinook wait-ready --timeout-seconds 45\r\nfoggy-runtime --base-url http://127.0.0.1:18068 --namespace chinook capabilities\r\n$dbPath = (Resolve-Path ./Chinook.sqlite).Path -replace '\\\\', '/'\r\nfoggy-runtime --base-url http://127.0.0.1:18068 --namespace chinook datasources add --name chinook-sqlite --type sqlite --jdbc-url \"jdbc:sqlite:$dbPath\"\r\nfoggy-runtime --base-url http://127.0.0.1:18068 --namespace chinook datasources bind --namespace chinook --data-source chinook-sqlite\r\nfoggy-runtime --base-url http://127.0.0.1:18068 --namespace chinook datasources test chinook-sqlite\r\n```\r\n\r\n后续每条 `foggy-runtime` 均在命令名前附加全局参数 `--base-url http://127.0.0.1:18068 --namespace chinook`。\r\n\r\n\r\n## Reproduction (实际顺序)\r\n\r\n1. 准备下方六个模型。首次加载时，明细 QM 的“专辑与艺术家”两项先使用旧展开形式：\r\n\r\n```javascript\r\n{ caption: '专辑与艺术家', items: [\r\n    { ref: line.track.album },\r\n    { ref: line.track.album.artist }\r\n] }\r\n```\r\n\r\n2. 在尚未登记该目录的 `chinook` 校验，实际六文件通过，然后登记一个 Bundle 并刷新：\r\n\r\n```text\r\nfoggy-runtime models validate --models-dir ./models\r\nfoggy-runtime bundles add --name chinook-0129-control --path ./models\r\nfoggy-runtime models refresh\r\nfoggy-runtime models describe ChinookInvoiceLineQueryModel\r\n```\r\n\r\n3. 将明细 QM 的上述两个整维度 ref 改成下文完整文件里的四个具体列 name/alias 项，其余五文件保持。再次对**相同 Namespace、同一物理路径**校验，不新增第二个 Bundle：\r\n\r\n```text\r\nfoggy-runtime models validate --models-dir ./models --include-stack-trace\r\n```\r\n\r\n退出 2。实际 3 个 TM 通过、3 个 QM 失败，均指向相同 duplicate QM 资源。提供的是已编辑明细 QM 的实际场景，不把“完全未编辑目录也失败”冒称本轮单独验证项。\r\n\r\n4. 关闭隔离实例，仍用同 WorkDir、相同注册文件和源码重启，再执行步骤 3；仍失败。重启轮显式启用内嵌 Console，但同样的失败已在默认 lite 观察到。再补测：\r\n\r\n```text\r\nfoggy-runtime models validate --models-dir ./models --clear-existing --include-stack-trace\r\n```\r\n\r\n仍退出 2，同样的重复资源失败。\r\n\r\n5. 不改这六文件，为新的隔离 Namespace 绑定同一 named datasource，再只做首次校验：\r\n\r\n```text\r\nfoggy-runtime datasources bind --namespace chinook-alias-probe --data-source chinook-sqlite\r\nfoggy-runtime --base-url http://127.0.0.1:18068 --namespace chinook-alias-probe models validate --models-dir ./models\r\n```\r\n\r\n实际 **6/6 valid、0 invalid**。随后在这个新 Namespace 首次登记/刷新，实时元数据与第十二章 11 个别名查询通过。这仅排除了明显模型配置错误，**不是原 Namespace 正常更新流程已通过**。\r\n\r\n## Expected\r\n\r\n- 按 #106 合同识别同 namespace / 同 path 的已登记 Bundle，校验当前源码或候选修改时不重复注册同一资源；若支持复用，返回明确的复用诊断。\r\n- 有错误时明确归属文件、行/配置项与调整方式，不能让三个不相关 QM 全部报同一个资源重复。\r\n- 验证过程不能污染现有 catalog；旧模型仍可用，不应要求用户删正式 Bundle 或不断换 Namespace 才能继续修改。\r\n\r\n## Actual — exact CLI diagnostics\r\n\r\n下面是重启后执行 `--include-stack-trace` 的实际 JSON（本机路径已脱敏）；服务并未给出额外可用的 Java cause 栈，`stackTrace` 实际值就是 `Lifecycle operation failed.`，没有补造堆栈：\r\n\r\n```json\r\n{\r\n  \"diagnostics\": {\r\n    \"attributes\": {\r\n      \"invalidFiles\": 3,\r\n      \"validation\": {\r\n        \"cascadingErrors\": 0,\r\n        \"durationMs\": 37,\r\n        \"errors\": [\r\n          {\r\n            \"category\": \"MODEL\",\r\n            \"code\": \"IllegalStateException\",\r\n            \"file\": \"ChinookInvoiceLineQueryModel.qm\",\r\n            \"message\": \"duplicate query model resource in namespace: ChinookInvoiceLineQueryModel\",\r\n            \"severity\": \"ERROR\",\r\n            \"stackTrace\": \"Lifecycle operation failed.\",\r\n            \"type\": \"QM\"\r\n          },\r\n          {\r\n            \"category\": \"MODEL\",\r\n            \"code\": \"IllegalStateException\",\r\n            \"file\": \"ChinookInvoiceQueryModel.qm\",\r\n            \"message\": \"duplicate query model resource in namespace: ChinookInvoiceLineQueryModel\",\r\n            \"severity\": \"ERROR\",\r\n            \"stackTrace\": \"Lifecycle operation failed.\",\r\n            \"type\": \"QM\"\r\n          },\r\n          {\r\n            \"category\": \"MODEL\",\r\n            \"code\": \"IllegalStateException\",\r\n            \"file\": \"ChinookInvoiceTimeQueryModel.qm\",\r\n            \"message\": \"duplicate query model resource in namespace: ChinookInvoiceLineQueryModel\",\r\n            \"severity\": \"ERROR\",\r\n            \"stackTrace\": \"Lifecycle operation failed.\",\r\n            \"type\": \"QM\"\r\n          }\r\n        ],\r\n        \"invalidFiles\": 3,\r\n        \"namespace\": \"chinook\",\r\n        \"path\": \"<redacted-path>\",\r\n        \"totalFiles\": 6,\r\n        \"valid\": false,\r\n        \"validFiles\": 3,\r\n        \"warnings\": []\r\n      }\r\n    },\r\n    \"warnings\": [\r\n      \"Detached model validation rejected the candidate.\"\r\n    ]\r\n  },\r\n  \"engine\": \"java\",\r\n  \"error\": {\r\n    \"code\": \"MODEL_VALIDATE_FAILED\",\r\n    \"lifecycle\": {\r\n      \"affectedBindingGenerations\": [\r\n        {\r\n          \"backendId\": \"runtime-registry:chinook-sqlite\",\r\n          \"bindingKey\": \"runtime:namespace-default:chinook\",\r\n          \"generation\": \"f04995a6-94b0-4c1b-85aa-5f15791e8628:2\"\r\n        }\r\n      ],\r\n      \"beforeCatalogGeneration\": \"catalog:4b33b0e0-56a8-4ca7-bc11-9e69bd687ecc:2\",\r\n      \"catalogState\": \"ACTIVE\",\r\n      \"diagnostics\": [\r\n        {\r\n          \"message\": \"Detached model validation failed (IllegalStateException).\",\r\n          \"phase\": \"validate.qm\",\r\n          \"suggestedNextAction\": \"Fix the model definition and retry validation.\",\r\n          \"target\": \"ChinookInvoiceLineQueryModel\"\r\n        },\r\n        {\r\n          \"message\": \"Detached model validation failed (IllegalStateException).\",\r\n          \"phase\": \"validate.qm\",\r\n          \"suggestedNextAction\": \"Fix the model definition and retry validation.\",\r\n          \"target\": \"ChinookInvoiceQueryModel\"\r\n        },\r\n        {\r\n          \"message\": \"Detached model validation failed (IllegalStateException).\",\r\n          \"phase\": \"validate.qm\",\r\n          \"suggestedNextAction\": \"Fix the model definition and retry validation.\",\r\n          \"target\": \"ChinookInvoiceTimeQueryModel\"\r\n        }\r\n      ],\r\n      \"failedTargets\": [\r\n        \"ChinookInvoiceLineQueryModel\",\r\n        \"ChinookInvoiceQueryModel\",\r\n        \"ChinookInvoiceTimeQueryModel\"\r\n      ],\r\n      \"namespace\": \"chinook\",\r\n      \"sourceRevision\": \"source:103e6b0b-d9de-4d38-a8c4-d1bacc5ab9cc:g0:n2\"\r\n    },\r\n    \"lifecycleCode\": \"CATALOG_VALIDATION_FAILED\",\r\n    \"message\": \"Detached model validation rejected the candidate.\",\r\n    \"model\": \"ChinookInvoiceLineQueryModel\",\r\n    \"phase\": \"models.validate\",\r\n    \"safeToAutoRepair\": false,\r\n    \"suggestedNextAction\": \"Fix the model definition and retry validation.\"\r\n  },\r\n  \"runtimeApiVersion\": \"foggy-runtime-api/v1\",\r\n  \"success\": false\r\n}\r\n```\r\n\r\n显式 `--clear-existing` 的错误摘要：\r\n\r\n```json\r\n{\r\n  \"success\": false,\r\n  \"error\": {\r\n    \"code\": \"MODEL_VALIDATE_FAILED\",\r\n    \"lifecycle\": {\r\n      \"affectedBindingGenerations\": [\r\n        {\r\n          \"backendId\": \"runtime-registry:chinook-sqlite\",\r\n          \"bindingKey\": \"runtime:namespace-default:chinook\",\r\n          \"generation\": \"f04995a6-94b0-4c1b-85aa-5f15791e8628:2\"\r\n        }\r\n      ],\r\n      \"beforeCatalogGeneration\": \"catalog:4b33b0e0-56a8-4ca7-bc11-9e69bd687ecc:2\",\r\n      \"catalogState\": \"ACTIVE\",\r\n      \"diagnostics\": [\r\n        {\r\n          \"message\": \"Detached model validation failed (IllegalStateException).\",\r\n          \"phase\": \"validate.qm\",\r\n          \"suggestedNextAction\": \"Fix the model definition and retry validation.\",\r\n          \"target\": \"ChinookInvoiceLineQueryModel\"\r\n        },\r\n        {\r\n          \"message\": \"Detached model validation failed (IllegalStateException).\",\r\n          \"phase\": \"validate.qm\",\r\n          \"suggestedNextAction\": \"Fix the model definition and retry validation.\",\r\n          \"target\": \"ChinookInvoiceQueryModel\"\r\n        },\r\n        {\r\n          \"message\": \"Detached model validation failed (IllegalStateException).\",\r\n          \"phase\": \"validate.qm\",\r\n          \"suggestedNextAction\": \"Fix the model definition and retry validation.\",\r\n          \"target\": \"ChinookInvoiceTimeQueryModel\"\r\n        }\r\n      ],\r\n      \"failedTargets\": [\r\n        \"ChinookInvoiceLineQueryModel\",\r\n        \"ChinookInvoiceQueryModel\",\r\n        \"ChinookInvoiceTimeQueryModel\"\r\n      ],\r\n      \"namespace\": \"chinook\",\r\n      \"sourceRevision\": \"source:103e6b0b-d9de-4d38-a8c4-d1bacc5ab9cc:g0:n2\"\r\n    },\r\n    \"lifecycleCode\": \"CATALOG_VALIDATION_FAILED\",\r\n    \"message\": \"Detached model validation rejected the candidate.\",\r\n    \"model\": \"ChinookInvoiceLineQueryModel\",\r\n    \"phase\": \"models.validate\",\r\n    \"safeToAutoRepair\": false,\r\n    \"suggestedNextAction\": \"Fix the model definition and retry validation.\"\r\n  }\r\n}\r\n```\r\n\r\n新 Namespace 首次校验摘要：\r\n\r\n```json\r\n{\r\n  \"success\": true,\r\n  \"data\": {\r\n    \"affectedBindingGenerations\": [],\r\n    \"afterCatalogGeneration\": \"catalog:4b33b0e0-56a8-4ca7-bc11-9e69bd687ecc:3\",\r\n    \"beforeCatalogGeneration\": \"catalog:4b33b0e0-56a8-4ca7-bc11-9e69bd687ecc:3\",\r\n    \"cascadingErrors\": 0,\r\n    \"catalogState\": \"ACTIVE\",\r\n    \"durationMs\": 41,\r\n    \"errors\": [],\r\n    \"invalidFiles\": 0,\r\n    \"namespace\": \"chinook-alias-probe\",\r\n    \"path\": \"[lab-root]\\\\releases\\\\launcher-0.1.29\\\\control-models\",\r\n    \"sourceRevision\": \"source:103e6b0b-d9de-4d38-a8c4-d1bacc5ab9cc:g0:n0\",\r\n    \"totalFiles\": 6,\r\n    \"valid\": true,\r\n    \"validFiles\": 6,\r\n    \"warnings\": []\r\n  }\r\n}\r\n```\r\n\r\n## Runtime registration log / registry\r\n\r\n重启后 Runtime stdout 的加载记录（保留原行号，脱敏路径）：\r\n\r\n```text\r\n80: 2026-10-21 10:03:29.686 [main] INFO  c.f.bundle.SystemBundlesContextImpl - 动态添加外部Bundle成功: chinook-0129-control -> [lab-root]\\releases\\launcher-0.1.29\\control-models (namespace: chinook)\r\n83: 2026-10-21 10:03:29.874 [main] INFO  c.f.d.m.e.BundleLifecycleListener - Bundle catalog refresh published: bundleName=chinook-0129-control, namespace=chinook, generation=catalog:4b33b0e0-56a8-4ca7-bc11-9e69bd687ecc:1, pivotOuterCacheRemoved=0\r\n```\r\n\r\n收尾时 `chinook` 的实际注册条目仅一个；watch=false，避免把文件监视重复登记当作已知根因：\r\n\r\n```json\r\n[\r\n  {\r\n    \"name\": \"chinook-0129-control\",\r\n    \"namespace\": \"chinook\",\r\n    \"path\": \"[lab-root]\\\\releases\\\\launcher-0.1.29\\\\control-models\",\r\n    \"watch\": false,\r\n    \"enabled\": true,\r\n    \"createdAt\": \"2026-10-21T02:00:29.550752300Z\",\r\n    \"updatedAt\": \"2026-10-21T02:00:29.550752300Z\",\r\n    \"immutablePublication\": false\r\n  }\r\n]\r\n```\r\n\r\n独立 stdout/stderr、各次 CLI JSON 与命令日志已留档。Runtime 对重复校验失败没有另打印完整 cause 栈；上面的结构化诊断是可取得的关键错误日志。\r\n\r\n## Six-file reproduction fixture (实际编辑后版本)\r\n\r\n下列六文件使用公开 Chinook；首次加载时只把明细 QM 两项换成步骤 1 的旧形式，再按步骤 3 恢复为这里的四个具体列。此处为实际六文件复现，尚未额外缩减成单 TM/QM 对。\r\n\r\n<details>\r\n<summary>model/ChinookInvoiceLineModel.tm</summary>\r\n\r\n```javascript\r\nexport const model = {\r\n    name: 'ChinookInvoiceLineModel',\r\n    caption: 'Chinook 账单明细',\r\n    description: '一行是一条账单明细。分类分析累计交易明细金额，不累计账单总金额，不改用目录单价。',\r\n    tableName: 'InvoiceLine',\r\n    idColumn: 'InvoiceLineId',\r\n    properties: [\r\n        { column: 'InvoiceLineId', name: 'invoiceLineId', caption: '明细 ID', type: 'INTEGER' },\r\n        { column: 'InvoiceId', name: 'invoiceId', caption: '账单 ID', type: 'INTEGER' },\r\n        { column: 'TrackId', name: 'trackId', caption: '曲目或媒体 ID', type: 'INTEGER' },\r\n        { column: 'UnitPrice', name: 'unitPrice', caption: '交易单价', type: 'MONEY',\r\n          description: '账单明细上的交易单价，不是 Track 目录单价。样例原始单位，币种未指定。' }\r\n    ],\r\n    dimensions: [\r\n        {\r\n            name: 'invoice', caption: '账单', tableName: 'Invoice',\r\n            foreignKey: 'InvoiceId', primaryKey: 'InvoiceId', captionColumn: 'InvoiceId',\r\n            properties: [\r\n                { column: 'InvoiceDate', name: 'invoiceDate', caption: '账单日期', type: 'DATETIME',\r\n                  description: '按账单记录日期统计，不自行做时区转换。' },\r\n                { column: 'BillingCountry', name: 'billingCountry', caption: '账单国家', type: 'STRING' },\r\n                { column: 'CustomerId', name: 'customerId', caption: '客户 ID', type: 'INTEGER' }\r\n            ],\r\n            dimensions: [\r\n                {\r\n                    name: 'customer', caption: '客户', tableName: 'Customer',\r\n                    foreignKey: 'CustomerId', primaryKey: 'CustomerId', captionColumn: 'LastName',\r\n                    properties: [\r\n                        { column: 'FirstName', name: 'fullName', caption: '客户姓名', type: 'STRING',\r\n                          description: 'FirstName 与 LastName 拼接；与客户 ID 一起使用。',\r\n                          formulaDef: { builder: (alias) => `${alias}.FirstName || ' ' || ${alias}.LastName` } }\r\n                    ]\r\n                }\r\n            ]\r\n        },\r\n        {\r\n            name: 'track', caption: '曲目或媒体', tableName: 'Track',\r\n            foreignKey: 'TrackId', primaryKey: 'TrackId', captionColumn: 'Name',\r\n            properties: [\r\n                { column: 'Name', name: 'title', caption: '曲目或媒体名称', type: 'STRING' }\r\n            ],\r\n            dimensions: [\r\n                {\r\n                    name: 'genre', caption: '内容分类', tableName: 'Genre',\r\n                    foreignKey: 'GenreId', primaryKey: 'GenreId', captionColumn: 'Name',\r\n                    description: '包含音乐和影视内容；分类缺失不应丢弃交易金额。',\r\n                    properties: [\r\n                        { column: 'GenreId', name: 'genreId', caption: '分类 ID', type: 'INTEGER' },\r\n                        { column: 'Name', name: 'genreName', caption: '内容分类名称', type: 'STRING' }\r\n                    ]\r\n                },\r\n                {\r\n                    name: 'album', caption: '专辑', tableName: 'Album',\r\n                    foreignKey: 'AlbumId', primaryKey: 'AlbumId', captionColumn: 'Title',\r\n                    description: '曲目所属专辑，包含音乐及影视专辑；用 ID 区分同名专辑。',\r\n                    dimensions: [\r\n                        {\r\n                            name: 'artist', caption: '艺术家', tableName: 'Artist',\r\n                            foreignKey: 'ArtistId', primaryKey: 'ArtistId', captionColumn: 'Name',\r\n                            description: '以专辑所属艺术家归属交易金额，不等同于 Track.Composer 作曲者；用 ID 区分同名。'\r\n                        }\r\n                    ]\r\n                }\r\n            ]\r\n        }\r\n    ],\r\n    measures: [\r\n        { column: 'UnitPrice', name: 'lineAmount', caption: '明细金额', type: 'MONEY', aggregation: 'sum',\r\n          description: '交易单价乘数量，沿用样例原始单位，不除以 100，不指定币种。',\r\n          formulaDef: { builder: (alias) => `${alias}.UnitPrice * ${alias}.Quantity` } },\r\n        { column: 'Quantity', name: 'salesQuantity', caption: '销售数量', type: 'INTEGER', aggregation: 'sum',\r\n          description: '累计 Quantity；本样例全部为 1，也不能将数量定义成明细条数。' }\r\n    ]\r\n};\r\n```\r\n\r\n</details>\r\n<details>\r\n<summary>model/ChinookInvoiceModel.tm</summary>\r\n\r\n```javascript\r\nexport const model = {\r\n    name: 'ChinookInvoiceModel',\r\n    caption: 'Chinook 账单',\r\n    description: '一行是一张账单，不关联明细后累计账单金额。金额沿用样例原始单位，币种未指定。',\r\n    tableName: 'Invoice',\r\n    idColumn: 'InvoiceId',\r\n    properties: [\r\n        { column: 'InvoiceId', name: 'invoiceId', caption: '账单 ID', type: 'INTEGER' },\r\n        { column: 'CustomerId', name: 'customerId', caption: '客户 ID', type: 'INTEGER',\r\n          description: '客户识别键，排名时与姓名一起分组，避免同名客户合并。' },\r\n        { column: 'InvoiceDate', name: 'invoiceDate', caption: '账单日期', type: 'DATETIME',\r\n          description: '按账单记录日期统计，不自行做时区转换。' },\r\n        { column: 'BillingCountry', name: 'billingCountry', caption: '账单国家', type: 'STRING',\r\n          description: '账单地址中的国家，不是客户地址国家。' }\r\n    ],\r\n    dimensions: [\r\n        {\r\n            name: 'customer', caption: '客户', tableName: 'Customer',\r\n            foreignKey: 'CustomerId', primaryKey: 'CustomerId',\r\n            captionColumn: 'LastName',\r\n            keyCaption: '客户 ID',\r\n            description: '用客户 ID 区分客户，姓名仅用于展示。',\r\n            properties: [\r\n                { column: 'FirstName', name: 'fullName', caption: '客户姓名', type: 'STRING',\r\n                  description: 'FirstName 与 LastName 拼接；展示时与客户 ID 一起使用。',\r\n                  formulaDef: { builder: (alias) => `${alias}.FirstName || ' ' || ${alias}.LastName` } },\r\n                { column: 'Country', name: 'country', caption: '客户国家', type: 'STRING' }\r\n            ]\r\n        }\r\n    ],\r\n    measures: [\r\n        { column: 'Total', name: 'invoiceAmount', caption: '账单金额', type: 'MONEY', aggregation: 'sum',\r\n          description: '每张账单只累计一次。样例原始单位，不除以 100，不指定币种。' },\r\n        { column: 'InvoiceId', name: 'invoiceCount', caption: '账单数', type: 'INTEGER', aggregation: 'count',\r\n          description: '按账单 ID 计数，只在账单分析中统计。' }\r\n    ]\r\n};\r\n```\r\n\r\n</details>\r\n<details>\r\n<summary>model/ChinookInvoiceTimeModel.tm</summary>\r\n\r\n```javascript\r\nexport const model = {\r\n    name: 'ChinookInvoiceTimeModel',\r\n    caption: 'Chinook 账单时间分析',\r\n    description: '一行是一张账单，按账单记录日期的自然年、自然月统计。金额沿用样例原始单位，币种未指定，不自行做时区转换。',\r\n    tableName: 'Invoice',\r\n    idColumn: 'InvoiceId',\r\n    properties: [\r\n        { column: 'InvoiceId', name: 'invoiceId', caption: '账单 ID', type: 'INTEGER' }\r\n    ],\r\n    dimensions: [\r\n        {\r\n            name: 'invoiceDate', caption: '账单日期',\r\n            foreignKey: 'InvoiceDate', primaryKey: 'InvoiceDate', captionColumn: 'InvoiceDate',\r\n            type: 'DATETIME', timeRole: 'business_date',\r\n            description: '直接使用账单表的 InvoiceDate，不关联额外日期表；按记录日期统计，不自行做时区转换。',\r\n            properties: [\r\n                { column: 'InvoiceDate', name: 'year', caption: '账单年份', type: 'INTEGER',\r\n                  dialectFormulaDef: { sqlite: { builder: (alias) => `CAST(strftime('%Y', ${alias}.InvoiceDate) AS INTEGER)` } } },\r\n                { column: 'InvoiceDate', name: 'month', caption: '账单月份', type: 'INTEGER',\r\n                  description: '月份序号 1—12，跨年分析时应与年份一起使用。',\r\n                  dialectFormulaDef: { sqlite: { builder: (alias) => `CAST(strftime('%m', ${alias}.InvoiceDate) AS INTEGER)` } } },\r\n                { column: 'InvoiceDate', name: 'yearMonth', caption: '账单年月', type: 'STRING',\r\n                  description: 'YYYY-MM，自然月分组键，保留年份。',\r\n                  dialectFormulaDef: { sqlite: { builder: (alias) => `strftime('%Y-%m', ${alias}.InvoiceDate)` } } }\r\n            ]\r\n        }\r\n    ],\r\n    measures: [\r\n        { column: 'Total', name: 'invoiceAmount', caption: '账单金额', type: 'MONEY', aggregation: 'sum',\r\n          description: '每张账单只累计一次，样例原始单位，不指定币种，不代表利润或回款。' },\r\n        { column: 'InvoiceId', name: 'invoiceCount', caption: '账单数', type: 'INTEGER', aggregation: 'count' }\r\n    ]\r\n};\r\n```\r\n\r\n</details>\r\n<details>\r\n<summary>query/ChinookInvoiceLineQueryModel.qm</summary>\r\n\r\n```javascript\r\nconst line = loadTableModel('ChinookInvoiceLineModel');\r\n\r\nexport const queryModel = {\r\n    name: 'ChinookInvoiceLineQueryModel',\r\n    caption: 'Chinook 明细分析',\r\n    description: '按账单日期、客户、曲目或媒体、内容分类、专辑及其所属艺术家分析明细金额和销售数量。每行是一条明细，不提供可累计的账单总金额。艺术家按 Album.ArtistId 归属，不使用 Track.Composer。金额沿用样例原始单位，不指定币种、不做分元换算，不代表利润、回款或退款。',\r\n    loader: 'v2',\r\n    model: line,\r\n    columnGroups: [\r\n        { caption: '交易明细', items: [\r\n            { ref: line.invoiceLineId },\r\n            { ref: line.invoiceId },\r\n            { ref: line.trackId },\r\n            { ref: line.unitPrice },\r\n            { ref: line.invoice$invoiceDate },\r\n            { ref: line.invoice$billingCountry },\r\n            { ref: line.invoice$customerId },\r\n            { ref: line.invoice.customer$fullName },\r\n            { ref: line.track$title },\r\n            { ref: line.track.genre$genreId },\r\n            { ref: line.track.genre$genreName }\r\n        ] },\r\n        { caption: '专辑与艺术家', items: [\r\n            { ref: line.track.album$id, name: 'albumId', alias: 'albumId' },\r\n            { ref: line.track.album$caption, name: 'albumName', alias: 'albumName' },\r\n            { ref: line.track.album.artist$id, name: 'albumArtistId', alias: 'albumArtistId' },\r\n            { ref: line.track.album.artist$caption, name: 'albumArtistName', alias: 'albumArtistName' }\r\n        ] },\r\n        { caption: '明细指标', items: [\r\n            { ref: line.lineAmount },\r\n            { ref: line.salesQuantity }\r\n        ] }\r\n    ],\r\n    accesses: []\r\n};\r\n```\r\n\r\n</details>\r\n<details>\r\n<summary>query/ChinookInvoiceQueryModel.qm</summary>\r\n\r\n```javascript\r\nconst invoice = loadTableModel('ChinookInvoiceModel');\r\n\r\nexport const queryModel = {\r\n    name: 'ChinookInvoiceQueryModel',\r\n    caption: 'Chinook 账单分析',\r\n    description: '按账单日期、客户和账单国家分析账单金额与张数。金额沿用样例原始单位，不指定币种、不做分元换算，不代表利润、回款或退款。分类分析请用明细分析，不按分类分摊账单总额。',\r\n    loader: 'v2',\r\n    model: invoice,\r\n    columnGroups: [\r\n        { caption: '账单与客户', items: [\r\n            { ref: invoice.invoiceId },\r\n            { ref: invoice.invoiceDate },\r\n            { ref: invoice.billingCountry },\r\n            { ref: invoice.customerId },\r\n            { ref: invoice.customer$fullName },\r\n            { ref: invoice.customer$country }\r\n        ] },\r\n        { caption: '账单指标', items: [\r\n            { ref: invoice.invoiceAmount },\r\n            { ref: invoice.invoiceCount },\r\n            {\r\n                name: 'averageAmount',\r\n                caption: '平均账单金额',\r\n                formula: 'sum(invoiceAmount) / nullif(count(invoiceId), 0)',\r\n                type: 'MONEY',\r\n                description: '当前筛选与分组范围的账单总金额除以账单数；无账单时为空，不累计或简单平均分组均额。样例原始单位，币种未指定。'\r\n            }\r\n        ] }\r\n    ],\r\n    accesses: []\r\n};\r\n```\r\n\r\n</details>\r\n<details>\r\n<summary>query/ChinookInvoiceTimeQueryModel.qm</summary>\r\n\r\n```javascript\r\nconst invoice = loadTableModel('ChinookInvoiceTimeModel');\r\n\r\nexport const queryModel = {\r\n    name: 'ChinookInvoiceTimeQueryModel',\r\n    caption: 'Chinook 账单时间分析',\r\n    description: '按账单日期、自然年和自然月分析账单金额与张数。月份序号需与年份结合，年月字段为 YYYY-MM；金额沿用样例原始单位，币种未指定。',\r\n    loader: 'v2',\r\n    model: invoice,\r\n    columnGroups: [\r\n        { caption: '账单与时间', items: [\r\n            { ref: invoice.invoiceId },\r\n            { ref: invoice.invoiceDate },\r\n            { ref: invoice.invoiceDate$year },\r\n            { ref: invoice.invoiceDate$month },\r\n            { ref: invoice.invoiceDate$yearMonth }\r\n        ] },\r\n        { caption: '账单指标', items: [\r\n            { ref: invoice.invoiceAmount },\r\n            { ref: invoice.invoiceCount }\r\n        ] }\r\n    ],\r\n    accesses: []\r\n};\r\n```\r\n\r\n</details>\r\n\r\n## Scope / regression requests\r\n\r\n建议覆盖同 path 重复校验、真实 QM 修改后重复校验、clear-existing、重启恢复、Windows 路径规范化、旧 validation Bundle 与已有 Bundle 合并，以及失败前后 catalog 可用性；同时检查新 Namespace 首次校验这个对照。\r\n\r\n目前只证明上述正式发行版、SQLite/Chinook 与明确命令顺序；具体代码根因、所有数据库/宿主的影响未验证。未删除已有 Bundle 作为修复，未改产品代码，无生产环境操作。\r\n",
  "number": 149,
  "state": "OPEN",
  "title": "[Regression] 已登记目录再次 models validate 报重复 QM 资源（Launcher 0.1.29，关联 #106）",
  "url": "https://github.com/foggy-projects/foggy-data-mcp-bridge/issues/149"
}