The two Minutes notes (permission apply --policy int typing and the skill
reference updates) landed in the released 1.0.58-beta.2 section after the
branch merged main. That rewrites published release notes and would drop
both notes from the next release generated out of Unreleased. Move them
verbatim into a Changed subsection under Unreleased; the beta.2 section is
byte-identical to main again.
Auto-CR (P1) correctly flagged that the carve-out's "nothing else changed"
guard was keyed on len(otherFailures) == 0, which only observes changes the
gate already judges incompatible. Several parameter contract changes are
individually compatible and so produce no failure at all: relaxing required or
cli_required, clearing required_when, widening enum, clearing interface_type,
and clearing property through a reviewed mapping exclusion. Any of those could
have ridden along with a reviewed type migration, leaving the exemption wider
than both its documentation and what the entry actually reviewed.
Replace the failure-list heuristic with a real equality check over every
published field except Type. Comparing the struct also means a field added to
parameterSchema later is covered automatically, instead of silently widening
every existing entry. The type check moves back ahead of the field loop because
it no longer needs to observe the other findings.
Add a rejection case for each individually-compatible direction. Each case first
asserts that the drift alone really is compatible, so it keeps exercising the
equality guard instead of quietly duplicating one of the incompatible-bundle
cases.
Verified against the previous implementation: with the old guard all six new
cases fail while the nine incompatible-bundle cases still pass, which is exactly
the gap that was reported.
schema-compatibility is the third check in the same Interface Integrity job,
after the two CLI interface gates. It also rejected every published parameter
type change outright. Because the earlier gates failed first and `set -e`
stopped the step from ever running, this one never surfaced in CI, so the
previous exemption only covered two thirds of the problem.
checkParameterCompatibility now consults a precise allowlist: the tool path,
parameter name and both type values must match exactly, making it
direction-sensitive by construction, and it applies only when nothing else the
gate checks about the parameter moved (default, interface_default, format,
property, interface_type, required, cli_required, required_when, enum). The type
check moved to the end of the function so the carve-out can see those findings;
ordering is unobservable because the result is sorted.
The only entry is "minutes/minutes.apply_minutes_permission" parameter "policy"
migrating from "string" to "integer" (for #912). That type is projected from the
Cobra flag type (provenance cobra_flag_type), so it describes how the CLI accepts
a value. Consumers build a command line from it, and "--policy 4" is the same
argv under either declaration — a quoted "--policy \"4\"" still reaches pflag as
4 — while RunE keeps enforcing the same [2,4] domain. The parameter maps to
property "policyId", which the command has always sent as a number, so "integer"
is closer to the actual request than "string" was.
Table values must be the canonical form schemaType emits: the JSON encoding of
the type keyword, so `"string"` with its quotes rather than a bare string. The
guard test recomputes both through schemaType and checks the decoded name
against the closed JSON Schema type set — reviewedInterfaceRefRedirect was
silently disabled twice by exactly this class of spelling mistake.
The authoritative interface baseline and command-compatibility gates
rejected every flag type change on a historical command, with no review
channel — even when the new type only moves the same validation from RunE
to flag parsing. Both now consult a precise allowlist.
An entry must match command path, flag name and both type names exactly,
so it is direction-sensitive by construction, and it applies only when
nothing else about the flag moved (shorthand, required, hidden, no-opt,
scope). A bundled regression re-reports the type change.
The first and only entry is "dws minutes permission apply --policy" moving
from string to int (for #912): the old RunE parsed with
strconv.ParseInt(v, 10, 64) and enforced [2,4], the new one lets pflag
parse with base 0 and still enforces [2,4], so the historical set of
successful invocations is a subset of the new one. Base 0 additionally
accepts spellings like "0x3", which widens rather than narrows. Defaults
are excluded from the guard because the migration necessarily changes one.
In the snapshot gate the exemption resolves against the canonical
Command.Path, never the alias-expanded accepted path: an aliased command is
compared once per accepted spelling, so keying on that would let every
alias bypass the table.
The table is duplicated because check-authoritative-interface-baselines.sh
copies the whole scripts/policy/interface-baseline directory into a
worktree checked out at a historical revision and builds it there, so that
copy cannot import a package this branch adds. A guard test fails if the
two copies drift.
--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>
Both capabilities were added as flags on an existing leaf, and in both cases
the leaf's published interface stopped describing what the command did:
- `sheet create --values/--sheets/--styles` orchestrates create → probe →
resolve default worksheet → write → read back → optional styles, yet the
leaf still published `interface_mode: mcp` + `create_workspace_sheet`.
- `sheet export --export-format csv` reads `get_range_as_csv` and never
invokes `submit_export_job`, yet the leaf published `submit_export_job`.
Each moves to its own command, declaring the interface it actually uses:
`sheet create-with-data` is `composite` with a reviewed reason and no
`interface_ref`; `sheet export-csv` is `mcp` + `get_range_as_csv`. Both are
pinned in the interface-disposition contract test.
`sheet create` and `sheet export` are restored byte-for-byte to main, so the
compatibility gates see two `command_added` additions instead of four
locked-field changes. The split also removes a user-visible trap: `--range`
without `--export-format csv` used to be silently discarded and the whole
workbook exported; the cross-format flags no longer exist, pinned by
TestSheetExportAndExportCsvFlagsDoNotLeak.
Drops the 8 now-stale mapping-ledger exclusions that covered the flags on
`sheet.create_workspace_sheet` / `sheet.submit_export_job`, shrinking this
branch's exclusion surface. Skill references and CHANGELOG follow the split.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
parseBorderStyles read only style and a string color, so every other key and
any non-string color was silently dropped:
{"top":{"style":"solid","colour":"#f00"}} succeeded and drew a border with no
colour, and color: 123 did the same. That contradicts the unknown-key
rejection this PR applies to --sheets and --styles — a partially applied
style reported as success is harder to notice than an error.
Each edge now accepts only style/color, rejects near-miss spellings with the
canonical key, and fails when style or color is present with the wrong type
or empty. All three entry points (set-style --border-styles-json,
batch-set-style --ranges/--batch, and create --styles border_styles) share
parseBorderStyles, so one fix covers them; tests assert the rejection happens
before any MCP call on every path.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
sheet create promises that every structural check happens before the first
MCP request, but each sheet spec was only checked for object type and name:
columns/data/dtypes/formats/startCell were left to table_put, so a bad type
created and renamed the remote document first and failed at write time,
leaving behind a document the user never successfully asked for. Validate
every provided field against table_put's input contract up front, and reject
the malformed {"sheets":"bad"} wrapper instead of treating it as one spec.
Also fixed while auditing the same flow:
- unknown/misspelled keys are now rejected in both --sheets and --styles.
The server DTOs are fixed beans, so a stray "datas" was silently dropped
and the read-back probe landed on the header row: full data loss reported
as success. Near-miss spellings get the canonical key in the message.
- columns is required (the server requires it), non-blank and trim-unique;
dtypes/formats keys must resolve to a column, since the server looks them
up by trimmed name and silently ignores the rest.
- sheetId inside a spec is rejected: the document does not exist yet.
- the read-back probe now honours header:false, mode:append (a fresh sheet
appends at row 1, the startCell row is ignored) and $-absolute/lowercase
startCell refs, which the server accepts after uppercasing.
- --values cells must be scalars; a map used to be written as "map[a:1]".
- the 30000-cell and 2000000-char write limits are enforced locally.
Docs: the --styles top level only accepts snake_case (camelCase aliases are
inner-field only), and the read-back probe is not pinned to A1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The --export-format csv branch reads get_range_as_csv, but the sheet export leaf
still declares interface_ref: submit_export_job, so discovering the csv
capability through Schema yields the wrong backing interface. Audit metadata
only — interface_ref is not read at runtime and routing is unaffected. Recorded
here so the limitation reaches release notes rather than living only in a code
comment; accurate attribution is tracked as follow-up.
Three review P1s.
1. CHANGELOG no longer asserts a breaking Schema change this PR does not ship.
sheet export / sheet create deliver main's mcp + interface_ref and
schema-compatibility reports ok (0 changed fields), so the "declared as
composite (breaking)" entry was false and is removed; the set-style entry
drops the "breaking" framing (accepted as compatible by the reviewed
mapping-exclusion carve-out); the duplicate ### Changed heading is merged.
2. firstNonEmptySheetSpecCell reads the start cell via
pickStr(spec, "startCell", "start_cell"). Sheet specs are forwarded verbatim
to table_put, whose wire fields are camelCase in this repo. The prior
snake_case-only read meant a user passing the real startCell would have data
written at the offset while the probe read A1 — a false "写入未生效" on a
successful write. camelCase preferred, snake_case kept for tolerance.
3. planStyleOps now parses cell_merges range with parseA1Range, matching the
cell_styles branch. planStyleOps is dry-run once before create_workspace_sheet
as the up-front structural gate, so an invalid range like "not-a-range" is now
rejected before any RPC instead of failing only at the final merge_cells call
and leaving an unrollbackable partially-completed document. merge_cells' range
contract is A1:B3-style, which parseA1Range covers (and it strips a Sheet1!
prefix).
Tests: cell-merges-invalid-range added to TestSheetCreateValidatesBeforeCreating
Document (asserts calls == 0); TestFirstNonEmptySheetSpecCell covers both
startCell and start_cell. Changed-code coverage stays at 100%; targeted sheet
suites pass; CHANGELOG has a single Changed section with no false breaking claim.
Adds four capabilities and, for the style surface, moves to the interface that
can actually express them.
Added:
- sheet create --values / --sheets / --styles: create a workbook and populate it
in one command. --values takes a 2D array into the default sheet, --sheets
takes typed tables across several sheets, --styles carries cell_styles /
row_sizes / col_sizes / cell_merges. Every structure and enum is validated
before the document is created, so an invalid config never leaves an orphan
empty document behind.
- sheet export --export-format csv: synchronous single-sheet RFC4180 export with
--sheet-id, --range and --value-render-option. --output writes to a file (a
directory gets sheet-export.csv), otherwise the CSV goes to stdout while the
truncation warning goes to stderr, keeping stdout pipeable.
- sheet update-dimension --size-type: pixel / standard (restore the default row
height or column width) / auto (fit row height to content, ROWS only).
- sheet replace --match-formula: search and replace inside formula text.
- sheet range set-style --font-style / --font-line / --font-family /
--border-styles-json.
- sheet range batch-set-style --ranges: stamp one style across several
sheet-qualified ranges.
Changed (breaking Schema change, no CLI break):
- sheet range set-style moves from update_range to set_cell_range. The
update_range style channel exposes exactly eight properties
(backgroundColors, fontSizes, horizontalAlignments, verticalAlignments,
fontColors, fontWeights, wordWrap, numberFormat) and has no slot for italic,
underline/line-through, font family or borders, so the four new dimensions are
not expressible there. interface_ref becomes set_cell_range and the twelve
style flags stop publishing a flat property, because the value now lands in
cells[i][j].cellStyles.* with no single top-level field to name.
- sheet range batch-set-style submits one atomic batch_update instead of looping
update_range, so a partial failure no longer leaves half the ranges stamped.
--continue-on-error becomes a server passthrough. Caps the fan-out at 100
ranges and 200000 cells in aggregate.
- sheet export and sheet create declare interface_mode=composite: both route
across several tools depending on the flags, so a single mcp ref was wrong.
Schema hygiene:
- Nineteen parameters that previously resolved through flag_name_inference into
property names present in no request (bgColor, exportFormat, values, ...) are
now reviewed mapping exclusions with a stated reason, so Schema omits the
property instead of inventing one.
schema-compatibility reports 17 changed fields: 13 on sheet.range_set_style
(interface_ref plus twelve property mappings) and 2 each on
sheet.submit_export_job and sheet.create_workspace_sheet (interface_mode plus
interface_ref). No CLI flag is removed and no command path changes; the same
invocation runs on both the old and the new binary. Landing this needs a
decision on the Schema contract break.
Tests: targeted sheet suites in internal/helpers and the interface disposition
contract tests in internal/app pass; make build and gofmt clean;
check-schema-catalog, check-generated-drift, check-command-surface,
check-skill-commands and check-runtime-confirmation-truth all pass.