{
  "body": "## 问题摘要与归属\r\n\r\nRuntime Compose 的派生 `.query()` 中声明 `calculatedFields`，再在 `columns` 选择该计算列，校验却把它当作“来源未输出字段”。同一基础字段与同一算式改成列内表达式即可验证、预览并执行。\r\n\r\n本单先请求明确能力合同并改善诊断：当前 Skill 明确列出基础 dsl 的 calculatedFields，但没有明确承诺派生层等价支持。因此不把“必须支持此能力”预设成已确认合同 BUG；如果派生层本来不支持，也应指出不支持的参数，而非引导 AI 去源 QM 找该计算字段。\r\n\r\n归属：Java Runtime 的 compose.validate / 派生 schema 推导与可修复诊断。关联 #160 的结构化诊断方向，但这里是一个独立的 calculatedFields 入口差异场景。\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\n1. 启动上述 0.1.39，绑定 SQLite，并加载 / describe 上述账单 QM。\r\n2. 将下列完整脚本保存为 `01-derived-calculated-fields.fsscript`。\r\n3. 使用项目固定 CLI 校验：\r\n\r\n```powershell\r\nfoggy-runtime --output json --base-url http://127.0.0.1:18067 --namespace chinook-composition compose validate --script ./01-derived-calculated-fields.fsscript\r\n```\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', 'double_amount'],\r\n    calculatedFields: [{name: 'double_amount', expression: 'bill_amount * 2'}],\r\n    orderBy: ['customer_id'], limit: 100\r\n})};\r\n```\r\n\r\n## 预期\r\n\r\n- 若派生 query 承诺支持 calculatedFields，应从来源输出的 bill_amount 推导 double_amount，校验通过并正确计算。\r\n- 若该派生入口不支持，应在校验阶段明确指出 `calculatedFields` 不适用 / 未支持，给出参数路径及建议写法（例如列内表达式）；不要仅报新声明的计算列“不在 source schema”。\r\n- 不要求放宽字段权限或执行被拒绝的请求。\r\n\r\n## 实际响应与关键诊断\r\n\r\nCLI exitCode **2**；`success=false`、`field=double_amount`、`phase=compose.validate`、`safeToAutoRepair=true`。完整实际响应：\r\n```json\r\n{\r\n  \"diagnostics\": {\r\n    \"attributes\": {\r\n      \"composeErrorCode\": \"compose-schema-error/derived-query/unknown-field\",\r\n      \"composeErrorPhase\": \"schema-derive\",\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    \"field\": \"double_amount\",\r\n    \"message\": \"derived query references unknown field 'double_amount' not present in source output schema\",\r\n    \"phase\": \"compose.validate\",\r\n    \"safeToAutoRepair\": true,\r\n    \"suggestedNextAction\": \"Inspect compose fields/schema and retry.\"\r\n  },\r\n  \"runtimeApiVersion\": \"foggy-runtime-api/v1\",\r\n  \"success\": false\r\n}\r\n```\r\n\r\n实际 CLI / 诊断记录：\r\n```text\r\nexitCode=2, success=false\r\nerror.code=COMPOSE_SCRIPT_INVALID\r\nerror.field=double_amount\r\nerror.phase=compose.validate\r\ndiagnostics.attributes.composeErrorCode=compose-schema-error/derived-query/unknown-field\r\ndiagnostics.attributes.composeErrorPhase=schema-derive\r\nsafeToAutoRepair=true\r\nmessage=derived query references unknown field 'double_amount' not present in source output schema\r\n```\r\n\r\n本例是结构化校验拒绝，本轮服务端增量日志未见对应的独立 Java 异常堆栈；没有为它拼接另一个请求的 ClassCastException 日志。失败请求没有 execute。\r\n\r\n## 正常对照（同一模型、期间、字段与算式）\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', 'bill_amount * 2 as double_amount'],\r\n    orderBy: ['customer_id'], limit: 100\r\n})};\r\n```\r\n\r\n该请求依次 compose validate → preview → execute，退出码均为 0，实际返回 **47 行**，每行 double_amount 等于 bill_amount × 2。执行响应节选（items 对应 Compose 的 `data.value.plans`）：\r\n```json\r\n{\r\n  \"success\": true,\r\n  \"data\": {\r\n    \"valid\": true,\r\n    \"value\": {\r\n      \"plans\": [\r\n        {\r\n          \"bill_amount\": 15.84,\r\n          \"customer_id\": 1,\r\n          \"double_amount\": 31.68\r\n        },\r\n        {\r\n          \"bill_amount\": 0.99,\r\n          \"customer_id\": 2,\r\n          \"double_amount\": 1.98\r\n        }\r\n      ]\r\n    }\r\n  }\r\n}\r\n```\r\n\r\n原始账单合计仍为 477.53，独立 Decimal 核对通过。这说明不是基础字段缺失或乘法本身不可用。\r\n\r\n## 最初发现的 JOIN 形状\r\n\r\n问题最初发生在两个 QM 按客户聚合并 JOIN 后，以 `ROUND(bill_amount - line_amount, 2)` 生成 amount_difference。加入本次单 QM 最小复现，是为了排除 JOIN 和小数函数对定位的干扰。原请求及实际响应如下，明细 QM 同样为公开 Chinook 的 InvoiceLine，lineAmount=UnitPrice×Quantity，通过 Invoice 取客户及日期。\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 = joined.query({\r\n    columns: ['bill_customer_id', 'line_customer_id', 'bill_amount', 'line_amount', 'amount_difference'],\r\n    calculatedFields: [{ name: 'amount_difference', expression: 'ROUND(bill_amount - line_amount, 2)' }],\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      \"composeErrorCode\": \"compose-schema-error/derived-query/unknown-field\",\r\n      \"composeErrorPhase\": \"schema-derive\",\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    \"field\": \"amount_difference\",\r\n    \"message\": \"derived query references unknown field 'amount_difference' not present in source output schema\",\r\n    \"phase\": \"compose.validate\",\r\n    \"safeToAutoRepair\": true,\r\n    \"suggestedNextAction\": \"Inspect compose fields/schema and 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明确派生 query 的支持矩阵；对上述一种支持实现或明确拒绝实现增加测试。错误应能让 AI 知道是参数能力边界，还是引用了真正未输出的字段；若支持计算列，覆盖投影与后续筛选对该列的引用。保留现有字段治理边界。\r\n",
  "labels": [
    {
      "id": "LA_kwDOQqKzuc8AAAACSrKNhg",
      "name": "enhancement",
      "description": "New feature or request",
      "color": "a2eeef"
    }
  ],
  "number": 163,
  "state": "OPEN",
  "title": "[Enhancement][0.1.39] 明确派生 query 的 calculatedFields 支持范围并改进未知字段诊断",
  "url": "https://github.com/foggy-projects/foggy-data-mcp-bridge/issues/163"
}
