Conversation
`add` / `set geometry=` copy an `adj=name:fmla` name straight into the `<a:avLst>` unless the preset is one `CanonicalAdjName` knows (donut, noSmoking) or one `MultiGuidePresetDefaults` covers. So `--prop geometry=rect --prop adj=adj:val 14000` writes `<a:gd name="adj"/>` into a preset that declares no adjustment value at all, and `--prop geometry=roundRect --prop adj=adj1:val 10000` writes the wrong name for the single value roundRect does declare. ECMA-376 leaves an undeclared value unused rather than invalid: the file opens and the schema validator (so `validate`) stays green, while the geometry the author asked for silently never takes effect. `view issues` now reports it as a Warning with the `undeclared_adjust_value` subtype, so the defect is observable. The writer and `validate` are untouched. The `preset -> declared adjustment values` table is generated from the ECMA-376 definitions rather than recalled, and covers all 187 presets: a preset's own `<a:avLst>` in presetShapeDefinitions.xml is its list of authorable values, while the computed guides in its `<gdLst>` are not authorable through `prstGeom`. The 64 definitions that declare none are listed with an empty array, so "declares none" stays distinguishable from "preset unknown to this table" — an unknown token is never judged. Related: iOfficeAI#235 (the lint half only; the write path is unchanged, per the maintainer's 2026-07-19 ruling that `add`/`set` behaviour should not change before the corruption mechanism is pinned down).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
add/set geometry=copy anadj=name:fmlaname straight into a shape's<a:avLst>unless the preset is oneCanonicalAdjNameknows (donut, noSmoking) or oneMultiGuidePresetDefaultscovers. That lets the CLI write an adjustment value the preset never declares:rectdeclares no adjustment value at all, and--prop geometry=roundRect --prop adj=adj1:val 10000writes the wrong name for the single valueroundRectdoes declare. ECMA-376 leaves an undeclared value unused rather than invalid, so the file still opens andvalidatestays green — the geometry the author asked for silently never takes effect.This PR makes that observable:
view issuesnow emits one Warning per offending shape, under the newundeclared_adjust_valuesubtype.add/setandvalidateare unchanged.What a reviewer should see
In JSON the same record with
"subtype": "undeclared_adjust_value","severity": 1,"type": 0;--type format,--type fand--type undeclared_adjust_valueall select it,--type structure/contentdo not.validateis untouched in every respect, including its exit code.Why
view issuesand notvalidateMaking it explicit rather than quietly choosing a different surface:
SKILL.mddraws the line itself —validateis "Validate against OpenXML schema" (line 99),issuesis "Formatting/content/structure problems" (line 108).validate's extra detectors (DetectOutOfGridSheetRefs,DetectMissingDefaultRelsContentType) exist for files that are schema-legal but refused by the app; here the app accepts the file and ignores the value instead.validatehas no non-fatal tier: its list count is the exit code and the delivery gate (CommandBuilder.Check.cs, and the resident path). A lint there would turn a deck that opens fine intorc=1.view issuesalready carries exactly this species of finding withSeverity.Warningand a filterable subtype —low_contrastis the closest precedent.If you would rather have it inside
validate, that needs a non-fatal tier inValidationErrorplus an exit-code decision — a separate atomic change; the table and the detector here would be shared.The table is generated from the definitions, not recalled
A preset's own
<a:avLst>inpresetShapeDefinitions.xmlis its list of adjustment values (adj,adj1…adj8, plus the star/hexagonhf/vf); the computed guides in its<gdLst>are not authorable throughprstGeom, so they are deliberately excluded. The table covers all 187 presets and was generated from the ECMA-376 definitions as vendored verbatim by LibreOffice atoox/source/drawingml/customshapes/presetShapeDefinitions.xml(538972 bytes, sha256eaff19f4405b3be6822428c96ef46cc685499217366fd58e5e63a5a24739f02f), not typed from memory.The 64 definitions that declare no adjustment value (
rect,ellipse,cloud, …) are listed with an empty array rather than omitted, so "declares none" stays distinguishable from "preset unknown to this table" — an unknown preset token is never judged, so the lint fails open. Keyed by theprstGeom@prsttoken, not the SDK enum, so it also covers presets officecli cannot write itself but real files carry.It is the same knowledge the file already hand-codes, not a second opinion:
CanonicalAdjNamehand-codes donut/noSmoking andMultiGuidePresetDefaultshand-codes five shapes, and the generated rows are checked to equal those hand-written rows name for name (tier 1 below).Deliberate limits, stated rather than implied: slides and their top-level shapes only — the same scope as the neighbouring lints in that loop; a subset of a multi-guide preset's declared set is not flagged (that is a completeness concern, not a name problem, and PowerPoint handles those decks); name comparison is ordinal, matching the definitions.
Validation
Rule 2 — how to confirm it works. Before/after with the same three commands (also the reproduction in #235):
view issuesvalidate<a:gd>Found 0 issue(s)Validation passed.(rc 0)<a:gd name="adj" fmla="val 14000"/>insideprst="rect"Found 1 issue(s)([A1],undeclared_adjust_value, Warning)Validation passed.(rc 0)Beyond the single command sequence, a four-tier script (29 assertions, 0 failures), run against two builds of the same commit —
mainunpatched, andmain+ this PR:presetShapeDefinitions.xml(sha256-checked) and requires it to equal the rows in this PR exactly, in order; requires the generated rows to equal the hand-writtenCanonicalAdjName/MultiGuidePresetDefaultsrows name for name.format/f/undeclared_adjust_value(any case) select the finding,structure/contentdo not, the unfiltered text and JSON agree,validatestill returns rc 0, and the pre-fix binary rejects the new subtype name (so registering it is what enables the filter).prstis a token the table does not know produces no finding and does not throw.examples/**/*.pptx, 3621prstGeomshapes: the two binaries report the exact same 1976 findings (including 5 realroundRectadjustment values in the corpus, which are not flagged), i.e. zero false positives and zero regressions on real files.A 15-scenario A/B harness (undeclared names, correct names, multi-value specs, empty avLst, multi-guide presets) shows the same split: the pre-fix binary fails 5 scenarios (writes the value, reports 0 issues), the patched binary passes all 15 — with the
addoutput and the on-disk<a:gd>set identical between the two binaries in every scenario, which is the evidence that only the lint was added.dotnet build src/officecli/officecli.csproj -c Release: 0 errors, and the pre-existing 2 warnings only (no new ones).Rule 1 self-check (one atomic change)
Asked of this diff: can it be decomposed into multiple PRs that could each be merged or reverted independently? No. The table, the detector, the subtype registration and its single consumer are one change with one root cause: the surface cannot report an undeclared value without all four, and no subset of them has standalone value (a table without a consumer is dead code; a consumer without the table cannot tell declared from undeclared). Splitting
add-side rejection out is a separate change, and deliberately not made here.Related
#235 — this is the lint half only. The write path is unchanged, per the 2026-07-19 ruling in that thread that
add/setbehaviour should not change until the corruption mechanism is pinned down. The same table is what a write-side guard would need, and the same split exists for xlsx (whose handler also writesprstGeom) — happy to send either as a separate change if it is wanted.