--ranges validated the position of "!" in the raw string and then returned the
trimmed halves, so " !A1:B2" was accepted and produced a set_cell_range /
clear_range operation carrying sheetId: "". Depending on how the server treats
an empty sheetId, the whole batch_update fails, or — worse — the operation lands
on the default worksheet instead of the one the user named, while the command
reports success.
Both halves must now be non-empty *after* trimming. batch-clear grew the same
hole independently (it duplicated the split inline); it now shares
splitSheetPrefixedRange, so the invariant holds by construction rather than by
being repeated correctly in two places.
batch-set-style --batch had the same gap at the JSON level: it only rejected
sheetId == "", so " " passed. It now judges the trimmed value but still sends
the raw one — sheetId may be a worksheet *name*, and names may legitimately
carry leading or trailing spaces, so trimming on the user's behalf would target
a different sheet. The --ranges form cannot express such a name anyway, which is
what --batch is for.
TestBlankSheetIdentifierIsRejectedBeforeAnyRemoteCall covers all three entry
points with calls == 0; TestBatchStyleSheetIDIsSentVerbatimNotTrimmed pins the
no-normalisation half.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The ContractFinal assembly path dropped every non-declared parameter fact,
breaking the published Schema against the reviewed merge-base contract:
- merge pinned MCP parameter metadata and the reviewed in-code runtime hints
back into contract_final parameter resolution (318 interface_type losses,
calendar recurrence required/required_when regressions)
- restore devapp write risk to high and publish back to write/high; the
migration silently downgraded 14 dev write tools and re-classified publish
as destructive
- keep dev at-least-one checks as Validate hooks with the shipped wording
instead of publishing new typed constraints; constraint publication is a
contract change that belongs to its own reviewed PR (aitable annotations
reverted for the same reason)
- align RunE escape hatch, BoolFlag shadowing, guard-first ConfirmFirst
declaration, and Sheet target preflight with behavioral tests; drop the
retired gen_schema_decls.py helper and fix corecmd naming in docs/CHANGELOG
check-authoritative-schema-compatibility vs origin/main: ok.
PreRunE Validate was skipped by direct RunE / proxy calls. Run both hooks
in one wrapper (Validate first), add pat chmod Validate, let Sheet outer
guards call ContractValidate first, and assert declare user_required
leaves expose Validate, required flags, or CallTool-defer confirm.
Co-authored-by: Cursor <cursoragent@cursor.com>
DeclareLeafMetadata user_required wraps were confirming before RunE-local
checks, so illegal calls got confirmation_required instead of real errors.
Allow Validate on PreRunE, migrate event stop and drive publish checks, and
lock the Sheet dual-gate transitional state.
Co-authored-by: Cursor <cursoragent@cursor.com>