Validate before you save
Everyedit_table_blocks write validates before it saves, and a write is atomic: the whole change saves, or none of it does. A refused write saves nothing and says which field caused the refusal. Validation errors block a write; formula warnings do not.
Validation checks:
- expression syntax,
- condition syntax,
- Variable and Dimension references,
- function names and arguments, and
- semantic compatibility.
create with dry_run: true. It checks the new table’s structure, references, and formula syntax, and returns valid, warnings, and model_effects.
- A dry run returns no values, because calculation does not run, and it does not detect circular references.
- Its
model_effectscarry the totals statements the committed receipt would put underlevel_defaults. - With a
tableslist,dry_runapplies to every table in the request. update,reshape, anddeletetake nodry_run. A call that sends one is refused, because edits to saved tables and deletions have no preview. Correct any reported error, then send the intended change.
Read the receipt
Inspect the write’s result before you move on. A table write’s receipt reports:- What it touched. Each row and column touched, with the formula it now carries per range.
formula_warningissues for formulas that need attention. Most follow a formula that saved. Theupdate_calendarcase follows a saved Last close change, and the last case below follows a write that saved nothing. Acreatereports the same cases ascreation_warning, except theupdate_calendarcase, which only that action produces. The cases:- an imported Variable read in Forecast months;
- a constant that matches no Dimension item. A lookup key is judged by the formula
=rule across kinds: a number key selects a text item whose text reads as that number, and a true/false item as1or0, and a text key selects a number or true/false item by its reading. A constant in a condition, which says where a formula applies, is judged within its own kind; - a tie with an existing formula;
- a shared formula the write replaced;
- a read that lands on the generated default, 0, or null for a date or text Variable, because the input lacks one of the read’s coordinates;
- a default saved at a grain coarser than the Model’s base grain;
- an
update_calendarwrite that moves Last close and leaves Variables with no month formula in a month that changed sides. The issue carriescount, the number of such Variables, and names up to 10 of them, those a formula reads at an earlier month first: “Moving Last close from<old>to<new>moved<months>to the other side of Last close.<n>variables have month formulas only in ranges that no longer hold those months, so no formula, value, or source covers those cells; a number there reads 0 and other values read empty:<refs>.” When a formula reads one of them at an earlier month, such as a running balance, it continues: “A formula reads<k>of them at an earlier month, so its later months calculate from those cells:<ref>(read by<reader>).” Nothing in the Model changes; the statement only; - a write in a Scenario other than Main that sets the value the Scenario already inherits. The write stores nothing, and the cell keeps following the Scenario it inherits from: “
<Scenario>already reads this value from<parent>, so the write stored no value of its own: the cell keeps following<parent>and changes when<parent>changes.”
- Dates used as lookup keys. A date used as a lookup key against a Dimension of timestamps is not reported when an item sits at that day’s midnight and every item of the Dimension comes from its source rows, because the engine then selects that item. A Dimension with its own item list or formula keeps exact matching, and the same date in a condition is still reported.
mapping_warningissues, when a mapped Dimension shown as a value column has no lookup whose keys all sit on the table’s axes, so every cell of that column reads the catch-all: “<Field>is mapped from<Key>. A cell of this table is addressed by<axis Dimensions>, and a mapping rule applies only where the cell’s address carries the rule’s keys, so no rule applies here and every<Field>cell reads the catch-all,<value>.” A mapping with no catch-all ends “and the mapping saves no catch-all.”inspect_table_blocksreadreports the same issue on such a table.read_noteentries. One per imported source that the written formulas read, stating where that source’s rows sit on the calendar and which formulas read it. And one for the first Forecast month when the formula reads a prior period, a source, or a Variable with no value there, stating what the formula computed and what each read returned. A note describes the read the formulas asked for; it is not a problem to fix.scenario_warning, naming the Scenario the write used when the call left outscenario. Write in a Scenario quotes it.default_rollup, naming the time rollup each new dated row received, and the statement of the level defaults a breakdown’s save wrote or removed.- Each default formula’s scope: its condition, its range, its Date grain when it is dated, and its reach. How results describe a formula’s reach explains the wording.
- A refusal, when nothing saved. A receipt whose
statusisrejectedorblockedsaved nothing, and carries the sameerrorobject a refusal carries, besideitems[].reason. A write that fails on the calculation service says whether it saved anything:state_changed: "no"when the failure stopped the write before anything saved, for example in a formula or mapping-condition check, and"yes"when the call had already saved a change. A refusal that fits no branch of a union names the one field or shape that was wrong, and a refusal from the calculation service names the formula it was checking. Refusals for a wrong shape gives the wording of shape refusals.
readback.rows and readback.columns. A selector the readback cannot resolve appears in the issues as readback_selector_unresolved, with the nearest coordinates, and the other selections still read.
Deadlines and readbacks
An MCP request runs under the server’s request deadline, a little under two minutes. A table write keeps part of that time, about 10 seconds, to deliver its own account of what happened, so the receipt arrives before the request ends.- Every step after the commit runs under that answer deadline: the readback, the saved-layout echo, and the calendar checks. A write that committed answers with a receipt that says it saved.
- A write that has not committed by the answer deadline is rolled back and saves nothing. It answers: “This write saved nothing. Its transaction had not committed
<n>seconds after it began, when the tool’s answer was due, so it was stopped and rolled back.” - The optional readback stops after at most 30 seconds, or 2 seconds before the answer deadline, whichever comes first.
- A write whose readback did not finish reports the outcome
committed_with_readback_unavailable, with its saved revision and areadback.reason, such as “The readback’s deadline passed while the table was still calculating. The calculation had not failed, and the committed change does not depend on it.” The change is saved: do not send it again. - When the calendar checks did not all finish, the receipt carries one more
creation_warningon a create, orformula_warningon an edit: “The calendar checks of this write’s formulas had finished N of M checks when this result was due. Those checks state where the source rows a formula reads sit on the calendar and which forecast periods a read of a source column leaves empty; no other part of this result depends on them.”
edit_dimension_mappings runs under the same 119-second deadline and takes its answer deadline before saving: a map whose checks have not finished by then saves nothing, and a saved mapping’s receipt always arrives. A table it did not finish calculating carries the note “not calculated: the table calculations ran out of time; inspect_table_blocks compare with before_change_log_id=… shows the change” and no row_membership. A Canvas write takes its answer deadline after the save, so its dataset readback and render check can be cut short while the block stays saved; Canvas blocks for agents quotes those notes.
If your client stops waiting before the answer arrives, wait until two minutes have passed since you sent the call. By then the write has either committed in full or saved nothing. Read the table, or inspect_history changelog, to see which, and only then decide whether to send it again.
Verify the calculated values
After a material change, useinspect_table_blocks on the saved table. Check representative dates, segments, formula origins, and source information.
Every read result carries calculation.source, which says how its values were obtained: cached, promoted, derived, seeded, full, or recalculated.
When a value disagrees with what the user sees, with another read, or with arithmetic you can check, and the source is anything but full or recalculated, read once more with recalculate: true. That read:
- calculates the table from scratch, ignoring every cached or reused result;
- makes the fresh result the cached one, so the page shows it too;
- reports in
recalculatedwhether the cached result differed, and in how many cells.
recalculated compares the fresh values with the one result the cache held for this read’s own calculation request. A read of the same table with other options, such as include_inputs, other columns, or formula_origins, sends a different request, and the cache holds its result separately, so cached: "same" on one read says nothing about what another read showed. To check a value that a read with include_inputs showed, recalculate with the same options.
It needs comparison: "none" and costs a full calculation, so it is not for routine reads.
Examples of checks:
- Compare Gross Profit with Revenue minus Cost of Goods Sold.
- Check a Department formula for two different Departments.
- Check both sides of Last close after a formula-range change.
- Compare the intended Scenario with the Scenario that was written.