Skip to content

Steel row data and validation

Steel does not provide a separate computed-row store. Scripted targets are ordinary table columns whose values are submitted by the client and verified by the server.

PostTableData converts the incoming protobuf values to a row snapshot, loads all scripts for the table, and executes every script before inserting.

For each scripted target:

  1. The target must exist in data.
  2. The server prepares current-row, linked-row, and aggregate inputs.
  3. The stored transformed script runs in a request-scoped Steel context.
  4. The final result is converted for the target type.
  5. The submitted value is compared with the result.
  6. A mismatch rejects the complete insert.

The server never trusts a computed value merely because it came from the client’s Steel runtime.

Target Comparison
Decimal Parsed as exact decimals and compared numerically
Money Parsed exactly, checked in the target currency, then compared with configured rounding
Integer Parsed as integers and compared numerically
Boolean Parsed as booleans
Other supported targets Compared as returned strings

An empty script result matches only an empty submitted value.

PutTableData merges the supplied fields over the stored row and evaluates all scripts against that proposed final snapshot.

There are two cases for each scripted target:

Target in data? Server behavior
Yes The submitted target must equal the newly calculated value
No The newly calculated value must equal the currently stored target

This design prevents an update from making a computed field stale without silently adding fields the caller did not request.

For example, changing quantity from 2 to 3 changes line_total. A request containing only quantity is rejected. Send both the changed input and the newly calculated line_total.

GetTableData and GetTableDataByPosition return the values already stored in the row. They do not run scripts and do not recalculate stale data.

A client preparing Steel for an editable row generally performs:

  1. GetTableData to load the persisted current-row values.
  2. GetTableScripts to load the table’s current scripts.
  3. HydrateScriptDependencies to load external inputs.
  4. Local Steel execution whenever form data changes.
  5. PutTableData with the changed inputs and changed computed targets.

When a submitted computed value differs from the Steel result, the server returns:

Detail Value
gRPC code FAILED_PRECONDITION
Metadata komp-ac-error-reason: computed-value-mismatch

Clients should branch on this metadata reason instead of parsing the human-readable comparison message. Script parsing, type, missing-input, and runtime failures are reported separately, usually as INVALID_ARGUMENT or FAILED_PRECONDITION according to the Steel failure.

Each PostTableDataBulk row uses the same Steel execution, computed-value comparison, and quantity-effect path as a single insert. The RPC stops at the first row that fails Steel validation and identifies its row index.

quantity-add and quantity-subtract are the only controlled effects a script may emit. They require:

  • a link from the source row to the target row;
  • a target column marked as quantity-ledger backed;
  • a positive numeric or money amount;
  • compatible quantity type, unit, and currency metadata.
(quantity-subtract
"product"
"stock"
(steel_get_column "sale_line" "quantity"))

The function records an effect while also returning its amount as the expression value. After the source row is inserted or updated, the server reconciles effects by (source row, script, effect index):

  • a new effect applies its signed amount;
  • a changed amount applies only the difference;
  • a changed target reverses the old contribution and applies the new one;
  • a removed effect reverses its previous contribution;
  • deleting the source row reverses all of its recorded contributions.

This avoids repeatedly adding the full amount every time the source row is edited.

Circular relationships in dependency metadata are allowed. Saving one row does not recursively execute scripts on every table in the graph. Each data request executes the scripts belonging to the row being written, using a bounded snapshot of declared inputs.

Consequently, callers remain responsible for submitting any directly affected target columns in the row they update. Steel is not a general cross-table trigger engine.