Repository navigation
declare() mutates the caller's config object — database/schema renamed and columns replaced with protos; publish()/operate()/assert() copy theirs #2280
Description
Summary
#2260 and #2273 fixed the shared-config mutation for tables, incremental tables and views by shallow-cloning the nested bigquery/assertions objects. The remaining gap is one level up: Session.publish, Session.operate and Session.assert all spread the incoming config into a fresh object ({ name, filename, ...queryOrConfig }), but Session.declare passes the caller's object straight through:
// core/session.ts
public declare(config: ... | any): Declaration {
const declaration = new Declaration(this, config, utils.getCallerFile(this.rootDir));Declaration.verifyConfig then rewrites that object in place — unconditionally, since declarations have no type guard:
// core/actions/declaration.ts
if (unverifiedConfig.database) { unverifiedConfig.project = unverifiedConfig.database; delete unverifiedConfig.database; }
if (unverifiedConfig.schema) { unverifiedConfig.dataset = unverifiedConfig.schema; delete unverifiedConfig.schema; }
if (unverifiedConfig.columns) { unverifiedConfig.columns = ColumnDescriptors.mapLegacyObjectToConfigProto(unverifiedConfig.columns); }
if (unverifiedConfig.type) { delete unverifiedConfig.type; }So any JavaScript that keeps a reference to the declared config — a sources list in includes/ that also drives staging views, or code that builds a fully-qualified name from it — sees a different object after declare() runs: database/schema are undefined, and columns is an array of ColumnDescriptor protos instead of the { name: description } map. Feeding that columns value into a later publish() silently produces columns named "0", "1", ….
Reproduction (3.0.68)
workflow_settings.yaml:
defaultProject: acuantia
defaultLocation: US
defaultDataset: dataform
dataformCoreVersion: 3.0.68definitions/declare_mutation.js:
const src = {
database: "acuantia",
schema: "raw",
name: "orders",
columns: { id: "Order id", total: "Order total" }
};
declare(src);
publish("stg_orders", { type: "view", columns: src.columns })
.query(_ => `SELECT '${src.database}.${src.schema}.${src.name}' AS declared_from`);
// Control: operate() copies its config, so the same reuse is safe there.
const opCfg = { type: "operations", schema: "raw", name: "op1" };
operate("op1", opCfg).queries(_ => `SELECT '${opCfg.schema}' AS s`);
publish("after_operate", { type: "view" }).query(_ => `SELECT '${opCfg.schema}' AS s`);dataform compile --json | jq '.tables[] | {name: .target.name, query, columns: .actionDescriptor.columns}'
Actual:
stg_orders: query = SELECT 'undefined.undefined.orders' AS declared_from
columns = [ {path: ["0"], description: "Order id"}, {path: ["1"], description: "Order total"} ]
after_operate: query = SELECT 'raw' AS s <- control, unaffected
Expected: stg_orders query reads 'acuantia.raw.orders' and its columns are id / total, matching the declaration (which itself compiles correctly: {database: "acuantia", schema: "raw", name: "orders"} with columns id/total).
No compilation error is raised, so this fails silently.
Suggested fix
Either spread in Session.declare to match the other entry points:
const declaration = new Declaration(this, { ...config }, utils.getCallerFile(this.rootDir));or shallow-clone at the top of Declaration.verifyConfig so the class is safe regardless of caller. A regression test in the shape of the #2273 "shared config" suite (declare, then read the object back / reuse columns in a publish) would cover it.
Audit notes
I went through the remaining in-place coercions on 3.0.68 main looking for more siblings of #2260/#2273 and found none beyond this one: ColumnDescriptors.mapLegacyObjectToConfigProto builds new objects, resolvableAsActionConfigTarget returns targets without mutating them, verifyObjectMatchesProto does not modify its input, and the iceberg/labels/additionalOptions hoists move references without touching their contents. Notebook, data preparation and property graph configs only arrive from actions.yaml, so no caller object is reused there.
Activity
rafal-hawrylak commentedon Sep 4, 2026
Thank you for reporting that. Let me take a look at this one.
rafal-hawrylak commentedon Sep 5, 2026
The fix is on the way: #2283
Fix Session.declare mutating caller-shared configs (#2280) (#2283)
rafal-hawrylak commentedon Sep 7, 2026
The fix is released in 3.0.69. Feel free to share the feedback.