Skip to content
🚧 Early alpha — building the foundation. See the roadmap →

Transformation has two shapes — Challenge 46 framing synthesis

Created Updated

The decision synthesizes the transformation-language prior-art survey and the transformation-placement survey.

QuestionAdoptedRejected
Where transformation belongsSplit by operation shape. Value-shaping stays in templates. Collection-shaping extends source through optional source.where and source.joins.One general transform stage; templates as the universal transform engine; producer-only handling before the bundled engine covers a complete single-workbook path.
Which languageJSONata, narrowly, only for the new source declaration sites.jq as a second schema language; a new Crosswalker expression language; continued growth of the template filter grammar.

The load-bearing principle is that transformation is not one thing, so it does not get one home.

2. Why the split is structural, not stylistic

Section titled “2. Why the split is structural, not stylistic”

The placement survey found the decisive implementation fact:

ParsedData holds one collection, and generation renders and discards one row at a time.

By template evaluation, sibling sheets and sibling arrays are already gone. A cross-sheet join is therefore impossible at template time. More filters can reshape values inside the current row, but they cannot recover a second collection discarded before render(). Option A, transformation only in templates, could never close all five measured gaps.

That produces two different operation classes:

Operation classMeasured gapsPermanent home
Value-shaping (reshape one value of one row)Literal keys, per-item transformation over a cell collection, substring extractionTemplates. Render-time, per-row, deterministic, and streaming-friendly.
Collection-shaping (change which rows exist or what a row is)Row subset, cross-collection joinThe source contract. These operations must run while the necessary collection context still exists.

The tactical literal-key and list-algebra work is therefore not debt. It is the permanent value-shaping layer.

3. Verdict 1: extend source; do not create a transform stage

Section titled “3. Verdict 1: extend source; do not create a transform stage”

Recipes gain two optional declarations:

  • source.where: a row predicate. A row for which it evaluates to false never becomes a note.
  • source.joins: keyed lookup-enrichment only. Named secondary collections, such as other sheets in the same workbook or sibling arrays in the same document, are indexed by a key expression. Matched fields become addressable on the primary row.

source.joins is not relational algebra. The bounded model is one primary stream plus N lookup indexes. Filtering preserves streaming. A lookup join uses memory proportional to the secondary collection while the primary collection continues to stream.

A general transform stage was rejected because it would introduce the fourth-language anti-pattern structurally: first a source model, then an execution model, then eventually a transform IDE. Closed source declarations preserve the Ch 22 closed-grammar principle: membership grows additively through named capabilities, not through an open-ended language stage.

The schema-as-primitive pillar keeps external producers first-class, but it does not excuse the bundled engine from serving the launch user with one workbook and no Python. The boundary is:

Single-file import with row selection, value shaping (including lists), and keyed enrichment across sheets of the same workbook, deterministic and streaming at the measured scales (1,468×369 proven).

The plugin deliberately does not owe cross-file joins, import-time aggregation or pivoting, or recursive graph expansion. Those remain producer-side normalization into valid Tier 1 or Tier 2 query-time work.

Source declarations execute before identity, CURIE minting, hashing, and render. They define what a row is. They also participate in the recipe hash because they change which notes exist.

Both declarations are optional. Omission preserves today’s behavior byte-for-byte. That is the architectural additive-evolution construction; concrete schema syntax and the full non-breaking-extension proof remain open implementation deliverables.

JSONata is already the recipe schema’s only permitted string-typed expression language. jq would add a second expression language to one schema before its approximately 1.06 MB WASM payload and asset-loading complexity disqualify it for the mobile-first bundled engine.

JSONata powers only the new declaration sites:

  • the source.where predicate;
  • join key expressions within source.joins.

The template filter grammar is frozen at its current five-form list algebra. Templates remain a small, closed addressing-and-formatting grammar. Computation belongs at a JSONata declaration site. Work beyond one workbook belongs to a producer. This gives each expressiveness tier a stated ceiling and avoids inventing a Crosswalker language by filter accretion.

JSONata’s silent missing-path behavior is unsafe for imports. This codebase had three bugs in one week from the same failure shape: cache lag was interpreted as absence, including a real-vault evidence modal failure recorded in the evidence closeout and a re-import identity failure corrected by the import-set ownership fix. A typo or unavailable path must never silently become “skip this row.”

Every implementation is bound by this four-point contract:

  1. A source.where expression MUST evaluate to a boolean. undefined or any non-boolean is a RenderError naming the row and the expression — never “false, skip the row.” Silently dropping rows on a typo’d column name is this codebase’s signature bug, pre-committed.
  2. A join key that evaluates to undefined fails loud the same way; a key that finds no match is a legitimate empty result (absence of a relation is data; absence of the key is a defect).
  3. Crosswalker owns a conformance fixture suite for the JSONata subset it uses, run against both the JS engine and any port a producer relies on. The runtime-agnostic commitment survives only if parity is tested, not assumed.
  4. Guardrails per the survey’s security notes: JSONata ≥ 2.2.1, evaluation timeout, depth limits.
StatusItem
LockedValue-shaping remains in templates.
LockedCollection-shaping extends source, not a general transform stage.
LockedOptional source.where and keyed lookup-only source.joins.
LockedSource declarations run before identity, CURIE minting, hashing, and render, and participate in the recipe hash.
LockedJSONata only at the new source declaration sites.
LockedCurrent template list algebra is frozen.
LockedFour-point fail-loud and runtime-conformance contract.
OpenConcrete JSON Schema syntax and semantics for both declarations.
OpenMigration path for bundled and unknown user recipes.
OpenWorked examples against the measured gaps, including the CPRT sibling-array case.
OpenFull proof that this implementation admits the next source shape without breaking earlier recipes.

If implementation discovers a fact that contradicts a verdict, such as a lookup join failing to preserve streaming in the real parser, the work returns to an architect decision rather than bending the boundary in code.

7. Reconciliation with settled architecture

Section titled “7. Reconciliation with settled architecture”
Settled commitmentReconciliation
Schema-as-primitiveExternal producers still handle shapes beyond the single-workbook boundary and may emit valid Tier 1 directly.
Closed target-structure grammarThe new capability is expressed as closed optional source declarations, not an open transform stage or target-language expansion.
Runtime-agnostic recipe schemaJSONata is already the sole permitted expression language; conformance fixtures make cross-runtime parity a tested contract.
Mobile-first TypeScript engineJSONata’s small pure-JS implementation fits the bundled engine; jq-WASM does not. Lookup indexes are bounded to secondary collections.
Additive evolutionExisting recipes omit both declarations and preserve current behavior. Future source capabilities extend the same optional declaration seam.