{
  "body": "## 问题摘要与归属\r\n\r\n公开文档第五节推荐的 `dsl({model: prevPlan, ...})` 派生写法，在 Launcher 0.1.39 的 Runtime 受限 Compose 中触发 Java ClassCastException。基础计划 BaseModelPlan 与 JOIN 计划 JoinPlan 均复现；相同投影改用 `prevPlan.query({...})` 可以通过验证、预览并执行。\r\n\r\n归属：Java Runtime Compose 的 dsl 宿主桥接与公开文档合同一致性，不是数据库 SQL 执行错误。校验错误还应明确输入类型 / 参数位置，相关诊断方向见 #160。\r\n\r\n## 环境与版本核验\r\n\r\n- 实测日期：2026-10-24，Windows、Java 17.0.12、Python 3.11.5。\r\n- 采用已发布的 [Launcher 0.1.39](https://github.com/foggy-projects/foggy-data-mcp-bridge/releases/tag/foggy-runtime-launcher-v0.1.39)，按发行包原始 PowerShell 脚本启动；当前监听 PID 133260 的 JAR 路径已核对为 foggy-runtime-launcher-0.1.39.jar。\r\n- manifest source commit：`7b6d06987e3ec90f05143a0552791f80381dcc4b`，Java projectVersion：`9.3.0-SNAPSHOT`；不是本地源码编译结果。\r\n- JAR SHA256：`8228d31c104d58b1b2f1d1c5bc856a25531e4d5af06e1e9eefed2be576f20bf9`。\r\n- 项目固定 CLI：`foggy-runtime 0.1.25`；CLI 子进程使用 UTF-8 输出。\r\n- Runtime：`http://127.0.0.1:18067`；Namespace：`chinook-composition`；命名数据源：`chinook-composition-sqlite`（SQLite，公开 Chinook v1.4.5 副本）。\r\n- `securityMode=none-dev-test-only`，匿名本地教学环境，Console/FAP 未启用；未使用生产数据、身份、私有接口或 SQL 直通。\r\n\r\ncapabilities 实际响应节选：\r\n```json\r\n{\r\n  \"engine\": \"java\",\r\n  \"runtimeApiVersion\": \"foggy-runtime-api/v1\",\r\n  \"schemaVersion\": \"2026-06-06\",\r\n  \"securityMode\": \"none-dev-test-only\",\r\n  \"capabilities\": {\r\n    \"compose.validate\": \"supported\",\r\n    \"compose.preview\": \"supported\",\r\n    \"compose.execute\": \"supported\",\r\n    \"models.describe\": \"supported\"\r\n  }\r\n}\r\n```\r\n\r\n## 模型前提（已实际 describe 核对）\r\n\r\n`ChinookInvoiceQueryModel` 对应 Chinook 的 `Invoice` 表，必要公开字段如下：\r\n\r\n| 字段 | 实际物理映射 / 角色 |\r\n| --- | --- |\r\n| customerId | Invoice.CustomerId，客户键 |\r\n| invoiceDate | Invoice.InvoiceDate，账单日期 |\r\n| invoiceAmount | Invoice.Total，MONEY、sum；沿用样例原单位 |\r\n\r\n下面只引用已公开字段和前一计划的输出别名。年度基线为 47 位有账单客户、83 张账单、477.53；原始数据与模型在复现前后均未修改。\r\n\r\n## 文档依据\r\n\r\n已远端读取核对 [DSL 手册第五节](https://github.com/foggy-projects/foggy-data-mcp-docs/blob/main/zh/dataset-model/compose-query/dsl-manual.md)，该节标题为“派生查询：dsl({ model: prevPlan, ... })”，并明确 model 可传上一个 QueryPlan。本轮读取文件的 Git blob SHA 为 `60ec0347b097cd2887cbcd3c9008c270b700054a`。\r\n\r\n## 复现步骤与最小实际请求\r\n\r\n1. 启动上述 0.1.39，加载并 describe 账单 QM。\r\n2. 保存如下完整脚本为 `02-dsl-plan-model.fsscript`。它只把上一计划 bills 作为 dsl 的 model，没有 calculatedFields，也没有 JOIN：\r\n\r\n```javascript\r\nconst bills = dsl({\r\n    model: 'ChinookInvoiceQueryModel',\r\n    columns: ['customerId as customer_id', 'sum(invoiceAmount) as bill_amount'],\r\n    slice: [{field: 'invoiceDate', op: '[)', value: ['2012-01-01', '2013-01-01']}],\r\n    groupBy: ['customerId']\r\n});\r\nreturn {plans: dsl({\r\n    model: bills,\r\n    columns: ['customer_id', 'bill_amount'],\r\n    orderBy: ['customer_id'], limit: 100\r\n})};\r\n```\r\n\r\n3. 校验：\r\n\r\n```powershell\r\nfoggy-runtime --output json --base-url http://127.0.0.1:18067 --namespace chinook-composition compose validate --script ./02-dsl-plan-model.fsscript\r\n```\r\n\r\n## 预期\r\n\r\n按公开文档将 bills 视作计划输入，对其已输出的 customer_id、bill_amount 做投影，校验应通过。\r\n\r\n如果标准 Runtime 的受限桥接有意不支持该公开形态，至少应在输入校验中返回明确的 model 类型 / 能力限制及路径，并给出 `bills.query({...})` 替代写法，同时对齐文档的宿主适用范围；不应暴露内部 Java 强制类型转换异常。\r\n\r\n## 实际响应\r\n\r\nCLI exitCode **2**，`success=false`，`error.code=COMPOSE_SCRIPT_INVALID`、`phase=compose.validate`、`safeToAutoRepair=false`。完整实际响应：\r\n```json\r\n{\r\n  \"diagnostics\": {\r\n    \"attributes\": {\r\n      \"datasourceType\": \"sqlite\",\r\n      \"defaultDialect\": \"sqlite\",\r\n      \"dialectSource\": \"namespace-datasource-type\",\r\n      \"namespace\": \"chinook-composition\",\r\n      \"namespaceDatasourceId\": \"chinook-composition-sqlite\",\r\n      \"namespaceDatasourceStatus\": \"active\",\r\n      \"resolvedDialect\": \"sqlite\"\r\n    },\r\n    \"warnings\": []\r\n  },\r\n  \"engine\": \"java\",\r\n  \"error\": {\r\n    \"code\": \"COMPOSE_SCRIPT_INVALID\",\r\n    \"message\": \"class com.foggyframework.dataset.model.engine.compose.plan.BaseModelPlan cannot be cast to class java.lang.String (com.foggyframework.dataset.model.engine.compose.plan.BaseModelPlan is in unnamed module of loader org.springframework.boot.loader.launch.LaunchedClassLoader @7106e68e; java.lang.String is in module java.base of loader 'bootstrap')\",\r\n    \"phase\": \"compose.validate\",\r\n    \"safeToAutoRepair\": false,\r\n    \"suggestedNextAction\": \"Inspect diagnostics and runtime logs, then retry.\"\r\n  },\r\n  \"runtimeApiVersion\": \"foggy-runtime-api/v1\",\r\n  \"success\": false\r\n}\r\n```\r\n\r\n失败发生在脚本宿主构造计划时，尚未执行数据库查询；没有调用 execute。\r\n\r\n## 关键服务端日志\r\n\r\n以下为本次复现的 runtime.out.log 增量节选：\r\n```text\r\njava.lang.ClassCastException: class com.foggyframework.dataset.model.engine.compose.plan.BaseModelPlan cannot be cast to class java.lang.String (com.foggyframework.dataset.model.engine.compose.plan.BaseModelPlan is in unnamed module of loader org.springframework.boot.loader.launch.LaunchedClassLoader @7106e68e; java.lang.String is in module java.base of loader 'bootstrap')\r\n\r\n\tat com.foggyframework.dataset.model.engine.compose.runtime.ScriptRuntime.lambda$doRunScript$0(ScriptRuntime.java:276)\r\n\r\njava.lang.ClassCastException: class com.foggyframework.dataset.model.engine.compose.plan.JoinPlan cannot be cast to class java.lang.String (com.foggyframework.dataset.model.engine.compose.plan.JoinPlan is in unnamed module of loader org.springframework.boot.loader.launch.LaunchedClassLoader @7106e68e; java.lang.String is in module java.base of loader 'bootstrap')\r\n\r\n\tat com.foggyframework.dataset.model.engine.compose.runtime.ScriptRuntime.lambda$doRunScript$0(ScriptRuntime.java:276)\r\n```\r\n\r\n## 正常对照：只改为计划对象的 query 方法\r\n\r\n实际完整请求：\r\n```javascript\r\nconst bills = dsl({\r\n    model: 'ChinookInvoiceQueryModel',\r\n    columns: ['customerId as customer_id', 'sum(invoiceAmount) as bill_amount'],\r\n    slice: [{field: 'invoiceDate', op: '[)', value: ['2012-01-01', '2013-01-01']}],\r\n    groupBy: ['customerId']\r\n});\r\nreturn {plans: bills.query({\r\n    columns: ['customer_id', 'bill_amount'],\r\n    orderBy: ['customer_id'], limit: 100\r\n})};\r\n```\r\n\r\n这个请求 validate、preview、execute 退出码均为 0，实际得到 **47 位客户 / 477.53**；没有更换模型、权限、数据源或日期条件。\r\n\r\n## JOIN 计划也复现（不带 calculatedFields）\r\n\r\n为避免与另一单计算字段问题混淆，本轮另用纯列内表达式的 JOIN 请求复测 model: joined。完整实际请求与响应：\r\n\r\n<details><summary>JOIN 计划请求及响应</summary>\r\n\r\n```javascript\r\nconst bills = dsl({\r\n    model: 'ChinookInvoiceQueryModel',\r\n    columns: ['customerId as bill_customer_id', 'sum(invoiceAmount) as bill_amount'],\r\n    slice: [{ field: 'invoiceDate', op: '[)', value: ['2012-01-01', '2013-01-01'] }],\r\n    groupBy: ['customerId']\r\n});\r\n\r\nconst lines = dsl({\r\n    model: 'ChinookInvoiceLineQueryModel',\r\n    columns: ['invoice$customerId as line_customer_id', 'sum(lineAmount) as line_amount'],\r\n    slice: [{ field: 'invoice$invoiceDate', op: '[)', value: ['2012-01-01', '2013-01-01'] }],\r\n    groupBy: ['invoice$customerId']\r\n});\r\n\r\nconst joined = bills.join(lines, 'left', [\r\n    { left: 'bill_customer_id', op: '=', right: 'line_customer_id' }\r\n]);\r\n\r\nconst compared = dsl({\r\n    model: joined,\r\n    columns: ['bill_customer_id', 'line_customer_id', 'bill_amount', 'line_amount',\r\n        'ROUND(bill_amount - line_amount, 2) as amount_difference'],\r\n    orderBy: ['bill_customer_id'], limit: 100\r\n});\r\n\r\nreturn { plans: compared };\r\n```\r\n```json\r\n{\r\n  \"diagnostics\": {\r\n    \"attributes\": {\r\n      \"datasourceType\": \"sqlite\",\r\n      \"defaultDialect\": \"sqlite\",\r\n      \"dialectSource\": \"namespace-datasource-type\",\r\n      \"namespace\": \"chinook-composition\",\r\n      \"namespaceDatasourceId\": \"chinook-composition-sqlite\",\r\n      \"namespaceDatasourceStatus\": \"active\",\r\n      \"resolvedDialect\": \"sqlite\"\r\n    },\r\n    \"warnings\": []\r\n  },\r\n  \"engine\": \"java\",\r\n  \"error\": {\r\n    \"code\": \"COMPOSE_SCRIPT_INVALID\",\r\n    \"message\": \"class com.foggyframework.dataset.model.engine.compose.plan.JoinPlan cannot be cast to class java.lang.String (com.foggyframework.dataset.model.engine.compose.plan.JoinPlan is in unnamed module of loader org.springframework.boot.loader.launch.LaunchedClassLoader @7106e68e; java.lang.String is in module java.base of loader 'bootstrap')\",\r\n    \"phase\": \"compose.validate\",\r\n    \"safeToAutoRepair\": false,\r\n    \"suggestedNextAction\": \"Inspect diagnostics and runtime logs, then retry.\"\r\n  },\r\n  \"runtimeApiVersion\": \"foggy-runtime-api/v1\",\r\n  \"success\": false\r\n}\r\n```\r\n\r\n</details>\r\n\r\n## 建议验收\r\n\r\n- 根据公开合同，测试 model 接受 BaseModelPlan、DerivedPlan、JoinPlan 等计划对象的合法形态；当前已实测 BaseModelPlan / JoinPlan 失败，其余形态未冒充已测。\r\n- 若此入口只接受 QM 名称，应返回结构化 MODEL_TYPE_INVALID / 明确能力边界及 model 路径，给 AI 可操作的替代方式，并同步文档。\r\n- 无论是否支持，都不应给使用者内部 ClassCastException 或引导其反复重试同一参数。\r\n\r\n上述失败请求均止于 validate。当前教学主线使用已通过的 plan.query(...)，没有用私有 API 或手写 SQL 绕过语义治理。\r\n",
  "labels": [
    {
      "id": "LA_kwDOQqKzuc8AAAACSrKNdg",
      "name": "bug",
      "description": "Something isn't working",
      "color": "d73a4a"
    },
    {
      "id": "LA_kwDOQqKzuc8AAAACSrKNew",
      "name": "documentation",
      "description": "Improvements or additions to documentation",
      "color": "0075ca"
    }
  ],
  "number": 164,
  "state": "OPEN",
  "title": "[Bug][0.1.39] 文档派生写法 dsl({model: prevPlan}) 在 Runtime Compose 触发 ClassCastException",
  "url": "https://github.com/foggy-projects/foggy-data-mcp-bridge/issues/164"
}
