返回正文目录

问题摘要与归属

Runtime Compose 的派生 .query() 中声明 calculatedFields,再在 columns 选择该计算列,校验却把它当作“来源未输出字段”。同一基础字段与同一算式改成列内表达式即可验证、预览并执行。

本单先请求明确能力合同并改善诊断:当前 Skill 明确列出基础 dsl 的 calculatedFields,但没有明确承诺派生层等价支持。因此不把“必须支持此能力”预设成已确认合同 BUG;如果派生层本来不支持,也应指出不支持的参数,而非引导 AI 去源 QM 找该计算字段。

归属:Java Runtime 的 compose.validate / 派生 schema 推导与可修复诊断。关联 #160 的结构化诊断方向,但这里是一个独立的 calculatedFields 入口差异场景。

环境与版本核验

capabilities 实际响应节选:

{
  "engine": "java",
  "runtimeApiVersion": "foggy-runtime-api/v1",
  "schemaVersion": "2026-06-06",
  "securityMode": "none-dev-test-only",
  "capabilities": {
    "compose.validate": "supported",
    "compose.preview": "supported",
    "compose.execute": "supported",
    "models.describe": "supported"
  }
}

模型前提(已实际 describe 核对)

ChinookInvoiceQueryModel 对应 Chinook 的 Invoice 表,必要公开字段如下:

字段 实际物理映射 / 角色
customerId Invoice.CustomerId,客户键
invoiceDate Invoice.InvoiceDate,账单日期
invoiceAmount Invoice.Total,MONEY、sum;沿用样例原单位

下面只引用已公开字段和前一计划的输出别名。年度基线为 47 位有账单客户、83 张账单、477.53;原始数据与模型在复现前后均未修改。

复现步骤与实际请求

  1. 启动上述 0.1.39,绑定 SQLite,并加载 / describe 上述账单 QM。
  2. 将下列完整脚本保存为 01-derived-calculated-fields.fsscript。
  3. 使用项目固定 CLI 校验:
foggy-runtime --output json --base-url http://127.0.0.1:18067 --namespace chinook-composition compose validate --script ./01-derived-calculated-fields.fsscript

实际请求脚本:

const bills = dsl({
    model: 'ChinookInvoiceQueryModel',
    columns: ['customerId as customer_id', 'sum(invoiceAmount) as bill_amount'],
    slice: [{field: 'invoiceDate', op: '[)', value: ['2012-01-01', '2013-01-01']}],
    groupBy: ['customerId']
});
return {plans: bills.query({
    columns: ['customer_id', 'bill_amount', 'double_amount'],
    calculatedFields: [{name: 'double_amount', expression: 'bill_amount * 2'}],
    orderBy: ['customer_id'], limit: 100
})};

预期

实际响应与关键诊断

CLI exitCode 2;success=false、field=double_amount、phase=compose.validate、safeToAutoRepair=true。完整实际响应:

{
  "diagnostics": {
    "attributes": {
      "composeErrorCode": "compose-schema-error/derived-query/unknown-field",
      "composeErrorPhase": "schema-derive",
      "datasourceType": "sqlite",
      "defaultDialect": "sqlite",
      "dialectSource": "namespace-datasource-type",
      "namespace": "chinook-composition",
      "namespaceDatasourceId": "chinook-composition-sqlite",
      "namespaceDatasourceStatus": "active",
      "resolvedDialect": "sqlite"
    },
    "warnings": []
  },
  "engine": "java",
  "error": {
    "code": "COMPOSE_SCRIPT_INVALID",
    "field": "double_amount",
    "message": "derived query references unknown field 'double_amount' not present in source output schema",
    "phase": "compose.validate",
    "safeToAutoRepair": true,
    "suggestedNextAction": "Inspect compose fields/schema and retry."
  },
  "runtimeApiVersion": "foggy-runtime-api/v1",
  "success": false
}

实际 CLI / 诊断记录:

exitCode=2, success=false
error.code=COMPOSE_SCRIPT_INVALID
error.field=double_amount
error.phase=compose.validate
diagnostics.attributes.composeErrorCode=compose-schema-error/derived-query/unknown-field
diagnostics.attributes.composeErrorPhase=schema-derive
safeToAutoRepair=true
message=derived query references unknown field 'double_amount' not present in source output schema

本例是结构化校验拒绝,本轮服务端增量日志未见对应的独立 Java 异常堆栈;没有为它拼接另一个请求的 ClassCastException 日志。失败请求没有 execute。

正常对照(同一模型、期间、字段与算式)

仅改为列内表达式,实际完整请求:

const bills = dsl({
    model: 'ChinookInvoiceQueryModel',
    columns: ['customerId as customer_id', 'sum(invoiceAmount) as bill_amount'],
    slice: [{field: 'invoiceDate', op: '[)', value: ['2012-01-01', '2013-01-01']}],
    groupBy: ['customerId']
});
return {plans: bills.query({
    columns: ['customer_id', 'bill_amount', 'bill_amount * 2 as double_amount'],
    orderBy: ['customer_id'], limit: 100
})};

该请求依次 compose validate → preview → execute,退出码均为 0,实际返回 47 行,每行 double_amount 等于 bill_amount × 2。执行响应节选(items 对应 Compose 的 data.value.plans):

{
  "success": true,
  "data": {
    "valid": true,
    "value": {
      "plans": [
        {
          "bill_amount": 15.84,
          "customer_id": 1,
          "double_amount": 31.68
        },
        {
          "bill_amount": 0.99,
          "customer_id": 2,
          "double_amount": 1.98
        }
      ]
    }
  }
}

原始账单合计仍为 477.53,独立 Decimal 核对通过。这说明不是基础字段缺失或乘法本身不可用。

最初发现的 JOIN 形状

问题最初发生在两个 QM 按客户聚合并 JOIN 后,以 ROUND(bill_amount - line_amount, 2) 生成 amount_difference。加入本次单 QM 最小复现,是为了排除 JOIN 和小数函数对定位的干扰。原请求及实际响应如下,明细 QM 同样为公开 Chinook 的 InvoiceLine,lineAmount=UnitPrice×Quantity,通过 Invoice 取客户及日期。

最初的完整 JOIN 请求与实际错误
const bills = dsl({
    model: 'ChinookInvoiceQueryModel',
    columns: ['customerId as bill_customer_id', 'sum(invoiceAmount) as bill_amount'],
    slice: [{ field: 'invoiceDate', op: '[)', value: ['2012-01-01', '2013-01-01'] }],
    groupBy: ['customerId']
});

const lines = dsl({
    model: 'ChinookInvoiceLineQueryModel',
    columns: ['invoice$customerId as line_customer_id', 'sum(lineAmount) as line_amount'],
    slice: [{ field: 'invoice$invoiceDate', op: '[)', value: ['2012-01-01', '2013-01-01'] }],
    groupBy: ['invoice$customerId']
});

const joined = bills.join(lines, 'left', [
    { left: 'bill_customer_id', op: '=', right: 'line_customer_id' }
]);

const compared = joined.query({
    columns: ['bill_customer_id', 'line_customer_id', 'bill_amount', 'line_amount', 'amount_difference'],
    calculatedFields: [{ name: 'amount_difference', expression: 'ROUND(bill_amount - line_amount, 2)' }],
    orderBy: ['bill_customer_id'], limit: 100
});

return { plans: compared };
{
  "diagnostics": {
    "attributes": {
      "composeErrorCode": "compose-schema-error/derived-query/unknown-field",
      "composeErrorPhase": "schema-derive",
      "datasourceType": "sqlite",
      "defaultDialect": "sqlite",
      "dialectSource": "namespace-datasource-type",
      "namespace": "chinook-composition",
      "namespaceDatasourceId": "chinook-composition-sqlite",
      "namespaceDatasourceStatus": "active",
      "resolvedDialect": "sqlite"
    },
    "warnings": []
  },
  "engine": "java",
  "error": {
    "code": "COMPOSE_SCRIPT_INVALID",
    "field": "amount_difference",
    "message": "derived query references unknown field 'amount_difference' not present in source output schema",
    "phase": "compose.validate",
    "safeToAutoRepair": true,
    "suggestedNextAction": "Inspect compose fields/schema and retry."
  },
  "runtimeApiVersion": "foggy-runtime-api/v1",
  "success": false
}

建议验收

明确派生 query 的支持矩阵;对上述一种支持实现或明确拒绝实现增加测试。错误应能让 AI 知道是参数能力边界,还是引用了真正未输出的字段;若支持计算列,覆盖投影与后续筛选对该列的引用。保留现有字段治理边界。