Resolve docs/followups: scaffold validation, targeted upstream fixes, connect, View/Map writers, bulk labels - #133
Merged
Conversation
Closes the two issue #70 scaffold-validation follow-ups and the seven "targeted fixes" catalogued in the 2026-08 upstream port pass. Scaffold validation (issue #70): - ScaffoldFileWriter.EnsureValueShapes runs from both Write overloads: requires the XMLSchema-instance declaration on AxEnum/AxTable/AxEdt* roots, and rejects anything but true/false/1/0 in CLR-bool elements (IsExtensible), which is exactly what XmlConvert.ToBoolean accepts. - New --verify flag on GenerateSettings reads an artefact back through the Metadata API after writing. Skipped (never fails) when the runtime is absent, so offline generation keeps working; only --install-to is verifiable because the provider resolves objects by name. Targeted upstream fixes: - XrefRepository.ResolveTargetPaths recognises label targets (@WAX2194, @LabelFile:LabelId, /Labels/@...) and emits the exact /Labels/@<ref> path instead of a bare name that matched nothing. - ExtensionOfRx gains IgnoreCase: X++ casing varies freely on the Str intrinsic, so class extensions were silently missed by CoC indexing. - All 16 object-level Search* methods order by m.IsCustom DESC first, so custom/ISV objects are not buried under Microsoft's — and, since the ordering precedes LIMIT, no longer truncated away entirely. - generate table gains --configuration-key and --form-ref; both elements are omitted when unset so the AOT defaults still apply. - GetClassDetails walks the ExtendsName chain (recursive CTE, bounded at 32 hops) and reports inherited methods in a separate list, each stamped with its DeclaringClass. - GetSecurityCoverage reports row-level (XDS) coverage with an explicit state instead of omitting it: Constrained / NotConstrained / Unknown / NotApplicable. Unknown covers "the index holds no policies at all", so zero matches is never mistaken for an assurance. Resolved items are removed from docs/followups; the issue-70 doc held only these two and is deleted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FfAAnxQDnPHRB1ne5H724n
…faf38)
`label create` could only write one key per invocation (fanned out across
locales). The MCP `labels action=create` tool already accepted a
`labels:[{key,value}]` batch with per-item error isolation; the CLI had no
equivalent, so scripted label seeding meant one process per key.
Adds a repeatable `--entry <KEY>=<VALUE>`, splitting on the first '=' so
values may contain one. The positional <KEY> <VALUE> form becomes optional
and keeps its exact previous behaviour, including the hard KEY_EXISTS
failure that scripts rely on.
Batch semantics mirror the MCP handler's payload: each key is written
independently, so a key that already exists (or a file that will not take
it) costs only itself. Partial success stays ok:true with the failures
repeated as warnings — the writes that landed are real and journaled, and
callers gate on `data.failed == 0`. When nothing at all could be written
the command fails with BATCH_FAILED so CI still sees a non-zero exit.
Duplicate keys and malformed entries are rejected up front, before any
write, rather than letting the last value silently win.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FfAAnxQDnPHRB1ne5H724n
Follow-on to the #114 HTTP transport. With a server deployed there was still no CLI-side way to point an MCP client at it; the documented procedure was "write this JSON by hand", which is exactly how the other MCP entries in a shared .mcp.json get clobbered. d365fo connect https://d365fo-mcp.example.com # .mcp.json d365fo connect https://d365fo-mcp.example.com --editor vscode # .vscode/mcp.json Probes GET /health before writing, so a mistyped URL reports SERVER_UNREACHABLE instead of producing a config that yields no tools -- the two failures are indistinguishable from inside an editor. --force writes anyway (a cold-starting instance may answer later), --no-probe skips it. The resolved MCP_SERVER_MODE is echoed back, with a warning when it is not "full", so connecting to a read-only instance and then hunting for generate_object is a one-line answer. Merge semantics, tested directly because this is the part that can destroy work: only the named entry is written, every other server and top-level key survives, JSONC comments and trailing commas are tolerated (VS Code writes them), a config that will not parse is refused rather than overwritten, and an existing entry needs --force. --name adds a second entry so prod and local can coexist. Base-URL normalisation accepts the /mcp endpoint people actually paste. --api-key is written in plain text and says so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FfAAnxQDnPHRB1ne5H724n
The CLI could read and index views and maps but had no writer for either, so the upstream view/map fixes catalogued in the 2026-08 follow-up had nothing to land on. Adds ViewScaffolder / MapScaffolder and `generate view` / `generate map`. Both shapes are ground-truthed against shipped standard-model files on the local AOS rather than guessed, which settled two things a reasonable guess gets wrong: - bound view fields are always i:type="AxViewFieldBound" (21 846 of them on the reference AOT, none with a primitive suffix); only computed fields carry a type, as AxViewFieldComputed<Type> plus a <ViewMethod>. - map fields are <AxMapBaseField i:type="AxMapField<Suffix>"> over the same primitive vocabulary as AxTableField, so both now share XppScaffolder.ConcreteFieldSuffix and cannot disagree on one EDT. Honesty constraints upstream had to retrofit are built in: no <Ranges> and no invented <Title*> on views (ranges belong on the backing query); a computed field without an explicit type is rejected rather than guessed; a mapping that connects a field the map does not declare is rejected rather than written to compile into something that silently never matches. AxView and AxMap join the writer's xmlns:i-required set — their fields are polymorphic, so the declaration is load-bearing. Both commands write straight to disk: the bridge's createObject only accepts class|table|edt|enum|form, and routing through it would report a misleading "Metadata API rejected" warning on every install (`generate query` already has this shape). Also fixes a real pre-existing bug the round-trip surfaced: ParseView searched Descendants() for <Fields> and matched the empty one nested in the first <AxTableFieldGroup>, which the AOT emits BEFORE the view's real <Fields>. Every shipped view therefore indexed with zero fields. Now reads direct children like every other parser here — the same fix already applied table-side. Verified end to end: generate → index extract → get view returns all three fields, and the map indexes and resolves through find usages. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FfAAnxQDnPHRB1ne5H724n
Main has moved since these were written: #112/#113 (modify method + modification journal) landed, and this branch adds `connect`, the View/Map writers and CLI bulk label creation. Removes the items that are now done and rewrites the two that only partly were, so the docs list open work and nothing else: - 2026-08 gains a short "resolved so far" note in the intro and drops the connect, View/Map and bulk-label entries; the modify item is rewritten around what actually remains (add-field, add-control, modify-property, extension writers — the bridge still takes only class|table|edt|enum|form). - 2026-07 drops View/Map and bulk labels, marks modify as partially resolved, and points at the 2026-08 doc as the current view of each remaining topic. Three items are left across both docs: the rest of the modify surface, the knowledge-base subsystem, and the TRUDUtils generators / form auto-repair. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FfAAnxQDnPHRB1ne5H724n
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.
Works through
docs/followups, resolving everything in it except three items that are whole subsystems rather than fixes (listed at the bottom). Resolved entries are removed from the follow-up docs as they land; the two docs now list open work and nothing else.Rebased on current
main, so #112 (modify method), #113 (journal/undo) and the #114 HTTP transport are already accounted for rather than duplicated. Two things were found to be partly done and were not rebuilt: the MCPlabelstool already had bulk create (only the CLI side was missing), and themodifycommand family now exists (only the wider write surface is left).Scaffold validation — issue #70 follow-ups
ScaffoldFileWriter.EnsureValueShapesruns from bothWriteoverloads: requires the XMLSchema-instance declaration on the roots whose fields are polymorphic (AxEnum,AxTable,AxView,AxMap,AxEdt*), and rejects anything outsidetrue/false/1/0in CLR-bool elements (IsExtensible) — exactly whatXmlConvert.ToBooleanaccepts, so both the NoYes spelling and garbage are caught. Deliberately not a blanket rule:AxClass/AxMenuItem/AxQueryare written without the declaration today and read back fine.--verifyonGenerateSettingsreads an artefact back through the Metadata API after writing. Skipped (never fails) when the runtime is absent, so offline generation keeps working; only--install-tois verifiable, because the provider resolves objects by name inside the packages paths and an arbitrary--outpath would either be missed or matched against a different same-named object.Targeted upstream fixes
ResolveTargetPathsrecognises@WAX2194,@LabelFile:LabelId,/Labels/@…and emits the exact/Labels/@<ref>path instead of a bare name matching nothing[ExtensionOf]casingExtensionOfRxgainsIgnoreCase— X++ casing varies freely on theStrintrinsic, so class extensions were silently missed by CoC indexingSearch*methods order bym.IsCustom DESCfirst, so custom/ISV objects are neither buried nor truncated away byLIMITgenerate table--configuration-key/--form-ref; omitted when unset so AOT defaults still applyGetClassDetailswalks theExtendsNamechain (recursive CTE, bounded at 32 hops) and reports inherited methods in a separate list, each stamped with itsDeclaringClassGetSecurityCoveragereports row-level (XDS) state explicitly:Constrained/NotConstrained/Unknown/NotApplicableThe OLS item was flagged "verify, do not assume" — it turned out to be a stronger gap than upstream's. The CLI indexes XDS policies (
SecurityPolicies.ConstrainedTable) butGetSecurityCoveragenever read them, soget security --type Table Xreturned a roles-only answer that reads as "no row-level security" when the CLI simply never looked.Unknowncovers the case where the index holds no policies at all, so zero matches is never mistaken for an assurance.New features from the deferred list
d365fo connect <url>— probesGET /healthfirst, so a mistyped URL reportsSERVER_UNREACHABLEinstead of producing a config that yields no tools (indistinguishable from inside an editor). Echoes the resolvedMCP_SERVER_MODEback, warning when it is notfull. Only the named entry is written: other servers and top-level keys survive, JSONC is tolerated, an unparseable config is refused rather than overwritten, and replacing an entry needs--force.generate view/generate map—ViewScaffolderandMapScaffolder. Honesty constraints upstream had to retrofit are built in: no<Ranges>and no invented<Title*>on views, a computed field without an explicit type is rejected rather than guessed, and a mapping connecting a field the map does not declare is rejected rather than compiled into something that silently never matches.label create --entry KEY=VALUE— per-key error isolation mirroring the MCP handler's payload. Partial success staysok:true(the writes that landed are real and journaled; gate ondata.failed == 0);BATCH_FAILEDwhen nothing could be written. The positional form keeps its exact previous behaviour, including the hardKEY_EXISTSscripts rely on.Grounded against a real AOT, not guessed
View and Map shapes were verified against shipped standard-model files on the local AOS (196 models) rather than inferred. That settled two things a reasonable guess gets wrong: bound view fields are always
AxViewFieldBoundwith no primitive suffix (21 846 occurrences, no exceptions) while only computed fields carry a type; and map fields use the same suffix vocabulary asAxTableField, so both now shareXppScaffolder.ConcreteFieldSuffixand cannot disagree on one EDT.A round-trip through the CLI's own index surfaced a real pre-existing bug.
ParseViewsearchedDescendants()for<Fields>and matched the empty one nested in the first<AxTableFieldGroup>, which the AOT emits before the view's real<Fields>. Every shipped view therefore indexed with zero fields. Fixed to read direct children like every other parser here — the same fix already applied table-side.Verification
647 tests pass in Release (
262CLI +385Core), 60 of them new. Beyond unit tests:connectwas exercised against a lived365fo-mcp --httpinstance including both failure paths, andgenerate view/generate mapwere driven end to end throughindex extract→get view/find usages. The[ExtensionOf]regression test was confirmed to fail without its fix.Two gaps worth knowing: the label where-used fix has no unit test —
D365FO.Bridgetargets net48 and no test project references it, so it is build-verified only; andget mapstill has no CLI command (the repository method exists), which is a separate read-surface gap I did not widen into.Still open
Three items remain across both docs, all multi-day subsystems rather than fixes: the rest of the modify surface (add-field, add-control, modify-property, extension writers), the knowledge-base subsystem, and the TRUDUtils generators / form auto-repair.
🤖 Generated with Claude Code
https://claude.ai/code/session_01FfAAnxQDnPHRB1ne5H724n