feat(eval): Phase 4 — CI gate, triage, K∧E∧T coverage, an L3 build oracle, and the three defects it found - #159
Conversation
…L3 build oracle Phase 4 of docs/KNOWLEDGE_AUDIT_PLAN.md (audit finding R7). 4.1 CI replays the whole catalog (`eval run --all`, 51/51) and gates `eval/COVERAGE.md` on drift. 4.2 `eval verify-build` is the L3 oracle: it provisions every reviewed golden into a throwaway model, compiles it with xppc, and attributes each diagnostic back to the case whose golden produced the named object. Four assumptions were wrong until it actually ran on an installation — a descriptor good enough for this repo's extractor kills the compiler (no <ModelModule>, Layer is an ordinal, the string collections live in the arrays namespace); referencing only ApplicationSuite leaves the standard Name EDT unresolvable; the fixture has to share the goldens' module or its tables stay invisible; and XppcDiagnostics knew five severity prefixes while the compiler emitted three more, so a run that logged "Errors: 8" parsed as zero and every golden was called clean. 48 of 51 goldens compile. The three that do not are real defects the offline loop could not see — every generated AxQuery crashes the metadata reader, and a generated event handler's method is written where the provider cannot find its name. They are recorded as TOOL_DEFECT clusters rather than fixed here: the scaffolder fix is the improver's next PR, which is the loop working. Same reason the fixture gained an EDT and an enum but not a query. 4.4 EvalTriage derives a hypothesis for replay runs, where no model is in the loop, and refuses to for agent runs, where the same scorecard is equally consistent with a tool defect and the agent's own mistake. `eval clusters` carries run ids and failing dimensions; `eval knowledge` turns KNOWLEDGE_GAP / MODEL_ERROR runs into skills/_source proposals naming the literal that links each topic to the case (finding K4). 4.5 eval/COVERAGE.md is derived from ObjectTypeRegistry, a new GenerateSurface, the case catalog and the embedded corpus: 42 of 70 leaves complete. GenerateSurface is gated in both directions against the CLI's own help output so it cannot become the fifth drifting registry. 4.3 (L4 runtime oracle) is NOT built — it needs a SysTestRunner result parser and per-run provisioning in a live model. One prerequisite defect was fixed: `test run --suite X` passed "--suite X" as a single argv element. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…n scripts compile The three defects the L3 build oracle found on its first runs. None was visible to a golden diff or to any offline validator — the XML was structurally perfect in every case, which is the whole argument for a compiler-backed tier. **generate query** — the metadata reader died with a bare KeyNotFoundException on every generated query, before compiling anything. Ground-truthed against the installation: 300 of 300 shipped ApplicationSuite queries carry a `<SourceCode><Methods><Method><Name>classDeclaration` method, and it is mandatory. Each data source also needs `<DynamicFields>Yes</DynamicFields>` (contract position: after ConcurrencyModel, before Table) — a scaffolded data source selects no fields, and the compiler rejects that combination outright. **generate event-handler** — the handler method was written inline in `<Declaration>`, so the provider found a method in the source with no name in the XML. It is now an XML `<Method>` with its own `<Name>`, which is what CocExtension and every other class scaffolder here already emitted. **generate migration-script** — surfaced only after the first two were fixed, because the metadata crashes had been aborting the compile before IL generation: the loop counter was named `count`, an X++ keyword, so its declaration was rejected and the rest of the method mis-parsed. Also: `known-reference-gap` cases are now exempt from build classification and clustering. Their X++ names a sibling artifact the case's single golden does not include, or a standard object the fixture cannot contain, and the compiler rejects those names for exactly the reason `validate references` does — filing them as TOOL_DEFECT would park three permanent false clusters at the top of the improver's queue. Fixture: the query goes back in now that `generate query` can produce a readable one, plus an `OnInitialized` delegate on FmVehicleService — delegateStr() is compile-time checked, so without it the event-handler case could not compile however correct the scaffolder was. Baseline: 48 of 51 goldens compile clean; the remaining 3 are the known-reference-gap cases. `eval run --all` 51/51, `dotnet test` 1007 passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Follow-up commit: the scaffolder defects the oracle found are now fixed in this PR rather than deferred.
Also in this commit:
Baseline is unchanged at 48/51 clean, but it means something different now: the 3 failures are the documented known-reference-gap cases, not defects.
|
… the oracle under-reporting
**generate business-event.** The attribute was wrong in three ways at once: it
named the event class where the contract belongs, passed a second classStr where
the display name belongs, and a plain string where ModuleAxapta::<module> belongs
("Cannot implicitly convert from type 'str' to type 'Extensible
Enumeration(ModuleAxapta)'"). Shape read off shipped events
(BenefitAnnualSalaryChangeBusinessEvent, BackgroundOperationCancelledBusinessEvent):
classStr(<Contract>), name, description, ModuleAxapta::<module>. `--category` now
means that enum and is validated against its 40 real values, defaulting to
NotApplicable — the honest default for a skeleton.
The generated factory also called parmId(), which exists on no business event:
BusinessEventsBase declares only parmUserId. It now keeps the source record the way
shipped events do — a private member behind a generated parm method.
**The oracle was under-reporting, which is worse than a red build.** Two bugs, both
in code this PR added:
- the metadata validator reports in its own shape (`Metadata Error:
AxForm/<object>/Design/Controls/…/DataGroup: …`) — no dynamics:// URL, no source
location, and for a form the member path is the whole control tree. XppcDiagnostics
did not recognise it, so those lines fell through to the message-only fallback with
no object to blame.
- the attribution key was the golden's file stem, which truncates `NoYes.Extension`
at the dot. ProvisionedArtifact now carries the declared <Name>.
Baseline: **36 of 51 clean, zero unattributed diagnostics** (was a flattering 48/3).
The number went down because the oracle got more honest, not because the tool got
worse — the same failure mode as a validator that passes because it never ran.
The 12 actionable clusters this leaves are the real generation-depth queue: reports
missing every mandatory AX_* framework parameter and a page size, public data
entities with no key, form patterns binding DataGroup to field groups the table does
not declare and one nesting an AxFormActionPaneTabControl under a tab page, a
workflow and a security policy with an empty mandatory property. Phase 2-shaped work,
for the improver's next round.
dotnet test 1014 passed · eval run --all 51/51 · coverage/knowledge/skills gates clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Fourth defect fixed — and it exposed that the oracle itself was under-reporting.
|
…and the entity key
Two more defects the L3 build oracle found, both making every generated artifact of
their kind invalid.
**generate report.** Three problems, all grounded against a shipped auto-design
report (QMSDispBatchProdReport):
- `DefaultParameterGroup` was emitted only when the caller passed `--parameter`, so
a plain report had neither the group ("Report default parameter group is not
found") nor the five mandatory framework parameters the SSRS runtime injects —
AX_PartitionKey, AX_CompanyName, AX_UserContext, AX_RenderingCulture,
AX_ReportContext, one validator error each. It is emitted always now, with the
caller's parameters appended.
- The auto design carried no PageSize/InteractiveSize/Margin, so its height and
width were zero: "Invalid page size. The page height and the page width cannot be
less than or equal to 0." Portrait Letter with half-inch margins, in contract
order after DataRegions.
- `--extra-dataset Name:DPClass` produced a dataset with no fields, an empty temp
table and a table data region with no data fields — three errors from one option
that could never produce anything valid. Fields are now given inline
(`Name:DPClass:F1,F2`) or inherited from `--field`, so the dataset, its temp
table, the DP getter and the design region describe the same columns.
**generate entity.** `IsPublic` was hard-coded Yes while `Keys` stayed optional and
nothing ever passed keys, so every generated entity failed "There must be a key
defined for a public data entity" and both PrimaryKey checks. The command now
derives the EntityKey from the table's alternate-key index (census-backed: that is
what a data entity key mirrors), or takes `--key`, or falls back to explicitly
mandatory fields — and asks instead of guessing when none of those work. The
scaffolder keeps IsPublic and Keys in step so the two cannot disagree again.
Baseline: 39 of 51 goldens compile clean (was 36), zero unattributed diagnostics.
The 9 actionable clusters left are forms binding DataGroup to field groups the
table does not declare, one nesting an AxFormActionPaneTabControl under a tab page,
a workflow and a security policy with an empty mandatory property, a custom service
naming an ungenerated class, and a case that extends the non-extensible NoYes.
dotnet test 1016 passed · eval run --all 51/51 · coverage/knowledge/skills clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Reports and the data entity fixed — both were producing invalid artifacts on every invocation.
|
| Problem | What the validator said |
|---|---|
DefaultParameterGroup emitted only when --parameter was passed |
"Report default parameter group is not found", plus one error per missing framework parameter — AX_PartitionKey, AX_CompanyName, AX_UserContext, AX_RenderingCulture, AX_ReportContext |
auto design had no PageSize / InteractiveSize / Margin |
"Invalid page size. The page height and the page width cannot be less than or equal to 0." |
--extra-dataset Name:DPClass produced a dataset with no fields |
"The dataset X has no fields specified" + "Table2: Missing Data fields" + an empty temp table |
The parameter group is now always emitted (framework parameters first, caller's appended), the design carries portrait-Letter geometry in contract order after DataRegions, and extra datasets take fields inline (Name:DPClass:F1,F2) or inherit --field — so the dataset, its temp table, the DP getter and the design region all describe the same columns.
generate entity
IsPublic was hard-coded Yes while Keys stayed optional and nothing ever passed keys, so every generated entity failed "There must be a key defined for a public data entity" and both PrimaryKey checks.
The command now derives the EntityKey from the table's alternate-key index — that's what a data entity key mirrors — or takes a new --key, or falls back to explicitly mandatory fields, and asks instead of guessing when none of those work. The scaffolder keeps IsPublic and Keys in step so they can't disagree again.
Baseline
39 of 51 clean (was 36), zero unattributed diagnostics. Six defects found and fixed by this oracle so far: generate query, generate event-handler, generate migration-script, generate business-event, generate report, generate entity.
The 9 actionable clusters left:
- forms (3 pattern cases +
L2-datasource-method-basic) —DataGroupbound to field groups the table doesn't declare; one nests anAxFormActionPaneTabControlunder a tab page L1-workflow-basic,L2-security-policy-basic,L2-security-duty-extension— empty mandatory propertiesL1-custom-service-basic— names a service class the command doesn't generateL2-enum-extension— extendsNoYes, not extensible on this platform; a case-authoring bug
dotnet test 1016 passed · eval run --all 51/51 · coverage / knowledge-audit / skills-drift clean.
…den the parser **The R5 trap, confirmed.** `<DataGroup>` names a field group on a table, so it is meaningless without the sibling `<DataSource>` saying which table — the reader reports "Field group 'Overview' does not exist" even when the table defines it. The templates emitted DataGroup alone, and *before* `<Controls>` rather than after, where the contract declares it and every shipped form puts it. Fixed in DetailsMaster, DetailsTransaction and SimpleListDetails; the LinesGrid also had DataSource before DataGroup. **AxFormActionPaneTabControl under a tab page.** "Child form control of type 'AxFormActionPaneTabControl' is not allowed for parent control of type 'AxFormTabPageControl'". Shipped ToolbarList tab pages nest TabPage > ActionPane > ActionPaneTab > ButtonGroup; the template skipped the ActionPane. **Fixture.** The mini-AOT tables now carry the field groups `generate table` produces. Overview/General are not part of the AOT default scaffold — they exist because every form pattern this CLI generates binds to them, a contract `generate form`'s preflight check already warns about. The fixture was hand-written without them, so the form goldens could never have loaded. **The parser was still hiding findings.** `FormPatternValidation Error`, `FormPatternValidation Fatal Error`, `Metadata Warning`, `ExternalReference Warning` and `Generation Information` were not in the severity list, so 24 diagnostics vanished. They are counted now. Baseline: 35 of 51 clean, zero unattributed. It went 43 → 35 on that last change: the count drops whenever the parser learns a prefix it was discarding and rises when a scaffolder is fixed, so the signal to watch is the unattributed count, not the clean count. That leaves one project as the biggest slice of the queue: **AOT form-pattern compliance**. Seven form cases fail FormPatternValidation — a different validator from this repo's FP001–FP010, run by the AOS against its pattern registry. Five design <Pattern> values name patterns that registry does not have (DetailsMaster 1.1, ListPage 1.1, Lookup 1.2, DetailsTransaction 1.1, Workspace 1.0, plus SidePanel 1.0 on a container); the rest are missing required children (TOC Tabs, Details Tabs) and required property values (ColumnsMode=Fill, WidthMode/HeightMode=SizeToAvailable, ArrangeMethod=HorizontalRight, ViewEditMode=Edit). Reconciling FormPatternCatalog with that registry, and teaching FP001–FP010 the same rules so the offline gate catches them, is its own piece of work. dotnet test 1018 passed · eval run --all 51/51 · coverage/knowledge/skills clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Forms fixed — and the parser was still hiding a whole class of findings. What was actually wrongThe R5 trap, confirmed. The audit listed "the
Fixture. The mini-AOT tables now carry the field groups The parser was still under-reporting
Baseline went 43 → 35 of 51 clean on that change alone. To repeat the rule from the last comment: the count drops whenever the parser learns a prefix it was discarding, and rises when a scaffolder is fixed. The signal to watch is the unattributed count (zero), not the clean count. What that exposed: AOT form-pattern complianceThis is now the single biggest slice of the queue, and it's a project rather than a fix. Seven form cases fail
Reconciling
|
…tes against it `generate form --pattern Dialog` is fixed and verified; the other five patterns are turned from a VM-only surprise into an offline test failure with the real answer attached. **Dialog.** The AOS requires ColumnsMode=Fill on the FieldsFieldGroups body, WidthMode/HeightMode=SizeToAvailable on body and button group, and ArrangeMethod=HorizontalRight on the button group, per pattern 'Dialog - Basic'. L1-form-dialog now compiles clean. **The registry is no longer a mystery.** scripts/emit-form-patterns.ps1 extracts all 102 pattern definitions — name, version, alias, required parts with cardinality, and the property values each part must carry — from Microsoft.Dynamics.AX.Metadata.Patterns.dll into src/D365FO.Core/FormPatterns/form-patterns.json, the same way emit-metadata-contracts.ps1 derives the DataContract catalog from the metadata DLL. FormPatternRegistry serves it with no installation present, and FormTemplatePatternRegistryTests pins every template's design pattern against it. Five templates name a pattern that does not exist on any AOS. They are on that test's KnownWrong list with the real one named, so the list is the work queue and adding to it is a regression: DetailsMaster 1.1 -> DetailsMaster 1.4 DetailsTransaction 1.1 -> DetailsTransaction 1.4 ListPage 1.1 -> ListPage UX7 1.0 Lookup 1.2 -> LookupGridOnly 1.1 / LookupTab 1.0 (no plain 'Lookup') Workspace 1.0 -> WorkspaceOperational 1.1 / TabbedWorkspace 1.0 **Why they are not fixed here.** FormPatternCatalog — this repo's own model of the patterns, which FP001-FP010 enforce — disagrees with the registry, so a template rewritten to satisfy the AOS is rejected by our own FP003 before it can be written. Verified by doing it: restructuring DetailsMaster to the shipped 1.4 shape (SidePanel navigation list, Details page with a nested FastTabs tab, Overview page with the main grid) satisfied the AOS and failed FP003 on the quick-filter sub-pattern. That restructure is reverted rather than left half-applied. The catalog, the templates, the validator, the nine form goldens and ~90 form-pattern tests have to move together. Baseline: 36 of 51 clean (was 35), zero unattributed diagnostics. dotnet test 1029 passed · eval run --all 51/51 · coverage/knowledge/skills clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Form patterns: one fixed, five turned from a VM-only surprise into an offline test failure with the real answer attached — and I stopped short of the rest deliberately. Fixed:
|
| Template says | Registry has |
|---|---|
DetailsMaster 1.1 |
DetailsMaster 1.4 |
DetailsTransaction 1.1 |
DetailsTransaction 1.4 |
ListPage 1.1 |
ListPage UX7 1.0 |
Lookup 1.2 |
LookupGridOnly 1.1 / LookupTab 1.0 — there is no plain Lookup |
Workspace 1.0 |
WorkspaceOperational 1.1 / TabbedWorkspace 1.0 |
Shrinking that list is the work queue; adding to it is a regression.
Why I didn't just fix them
FormPatternCatalog — this repo's own model, which FP001–FP010 enforce — disagrees with the registry. A template rewritten to satisfy the AOS is rejected by our own FP003 before it can be written.
I verified that by doing it: I restructured DetailsMaster to the shipped 1.4 shape (SidePanel navigation list, Details page with a nested FastTabs tab, Overview page with the main grid, from QMSCAPACaseDetail). The AOS accepted the structure and FP003 blocked generation on the quick-filter sub-pattern. That restructure is reverted rather than left half-applied — a repo where the templates satisfy the AOS and the offline validator rejects them is worse than either state.
The catalog, the templates, the validator, the nine form goldens and ~90 form-pattern tests have to move together. That's a coherent next PR, and it's now mechanical rather than speculative because the required parts and properties are derived data.
Baseline: 36 of 51 clean (was 35), zero unattributed diagnostics.
dotnet test 1029 passed · eval run --all 51/51 · coverage / knowledge-audit / skills-drift clean.
…, migrate SimpleList FormPatternCatalog now takes the structural half of a pattern — versions, design properties, the required control tree, and what else may sit at the design root — from the AOT registry instead of a hand-written model of it, for everything on its RegistryDerived list. The editorial half stays hand-written, because the registry does not know it: purpose, when to use it, reference forms, lifecycle guidance, sub-pattern hints. **SimpleList is migrated end to end.** Its spec is derived, its template carries the layout properties the AOS requires (design ArrangeMethod/Columns/ColumnsMode, the action pane's and grid's sizing and Style, all in contract order), its goldens are re-captured, and its form plus the two form-method cases that build on it compile clean. Without the properties the repairer rewrote every generated form, which is what "conforming" was quietly failing to mean. Two corrections came out of doing it, both encoded in RegistrySpecFactory: - A registry part that declares no children **delegates** its contents to whatever sub-pattern the container declares. Reading the absence as "nothing allowed" rejected a quick filter inside a CustomFilterGroup — the one thing that belongs there. - Versions come in two lineages: plain numbers and an older series whose version string is literally "UX7 1.0", which sorts above "1.4". Ranking the lineages is what keeps DetailsMaster's newest at 1.4 rather than UX7 1.2. It also corrected the previous commit's reading of the registry: ListPage is a real pattern name whose only version is the string "UX7 1.0", so that template needs a version change and not a rename. The KnownWrong notes now say what the registry has rather than what I first assumed. Two consequences worth naming, both of which are the AOS being stricter than we were: the custom-filter group is a required part of SimpleList, not optional, and a second root Group is legitimate (the registry's optional Footer slot) — so the fixtures that encoded the old model moved with the spec. The remaining eight patterns migrate one at a time: derive the spec, restructure the template to the required parts, re-capture goldens, fix the fixtures. Each changes what FP003/FP004 accept, which is why the spec and the template have to move together. FormPatternSpec became a record so a derived spec can be built from the hand-written one; Patterns is lazy because static initialisers run in textual order. Baseline: 36 of 51 clean, zero unattributed diagnostics. dotnet test 1031 passed · eval run --all 51/51 · coverage/knowledge/skills clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
The mechanism
What doing it taughtTwo corrections, both now encoded in
It also corrected my previous comment: And two places where the AOS is simply stricter than we were, so the fixtures moved with the spec: the custom-filter group is a required part of SimpleList, and a second root Group is legitimate (the registry's optional What's leftEight patterns, one at a time — derive the spec, restructure the template to the required parts, re-capture goldens, fix the fixtures that encoded the old model. Each migration changes what FP003/FP004 accept, which is exactly why the spec and the template have to move together rather than in separate passes. Baseline: 36 of 51 clean, zero unattributed diagnostics.
(Note: while running the suite repeatedly I saw three different tests flake once each — |
Second pattern off the hand-written model. TableOfContents 1.1 exists in the
registry, so this was structure rather than naming — and the structure was wrong in
five ways the AOS names precisely:
- the design has exactly one part, the vertical tab, and allows nothing beside it,
so the ActionPane had to go ("has child 'ActionPane' which is not allowed at its
current location")
- every page carries a title group (TOCPageTitleGroup, Style=TOCTitleContainer,
Skip=Yes) holding a MainInstruction static text
- the fields belong in a sibling content group, not in the title group
- that content group is required even when empty — a page with only its title is
"missing a child required by pattern 'Table of Contents'"
- the page must NOT declare FieldsFieldGroups. The TOC pattern already governs it,
and that sub-pattern forbids the static text the TOC pattern requires. Stamping it
is also where "ColumnsMode must have value 'Fill' per pattern 'Fields and Field
Groups'" came from.
One correction to the derivation, and it is the more important half of this commit:
**a part's declared children are the ones that must be present, not the only ones
that may be.** TableOfContents never writes Children="*" anywhere, yet the AOS
accepts field controls on a TOC page while rejecting an ActionPane at the design
root. So the closed set is the design root; inside a part the spec is permissive.
Reading it the other way had rejected a quick filter inside a CustomFilterGroup —
the one thing that belongs there.
Also: ToOccurrence now understands the bare "*" cardinality.
Baseline: 37 of 51 clean (was 36), zero unattributed diagnostics. Seven patterns
left, each its own increment.
dotnet test 1031 passed · eval run --all 51/51 · coverage/knowledge/skills clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…Of and SubPattern ListPage was a version problem, not a naming one: the registry has exactly one version of it and that version is the string "UX7 1.0". With that, the design's layout properties and the grid's AllowEdit=No, it compiles clean. **The extraction was dropping parts.** Two element kinds the script ignored: - <OneOf> — a choice slot where exactly one of the listed controls appears. LookupGridOnly's entire list control (Grid | Tree | ListView) lives inside one, so the derived spec had no grid at all. NodeSpec already models "one of these types", so a choice maps onto a single slot with the alternatives as ControlTypes and no property requirements: demanding the Grid's AllowEdit of a Tree would be nonsense. - <SubPattern Name="X" /> — the container must declare that sub-pattern. Now feeds RequiresSubPattern/AllowedSubPatterns. Wiring sub-patterns through surfaced the next layer of the same drift: **four of our sub-pattern names are wrong** — ToolbarAndList for the registry's ToolbarList, ToolbarAndFields for ToolbarFields, ToolbarAndListDouble for ToolbarListDouble, and HorizontalFieldsButtonGroup for HorizontalFieldsButtonsGroup — and the shipped-form census backs the registry (real forms declare ToolbarList 1.2). Two more the catalog never covered. RegistrySpecFactory drops names the catalog cannot resolve rather than claiming FP007 checks them, and the list is pinned in a test so it stays visible. Baseline: 38 of 51 clean (was 37). Three patterns migrated, five to go. dotnet test 1032 passed · eval run --all 51/51 · coverage/knowledge/skills clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Three patterns migrated now — TableOfContentsStructure, not naming — and wrong in five ways the AOS named precisely: the design has exactly one part (the vertical tab) and allows nothing beside it, so the ActionPane had to go; every page needs its title group with a That also produced the more important correction of the two commits: a part's declared children are the ones that must be present, not the only ones that may be. TableOfContents never writes ListPageA version problem, not a naming one — the registry has exactly one version and it is the string The extraction was dropping partsTwo element kinds the script ignored:
Wiring sub-patterns through surfaced the next layer of the same drift: four of our sub-pattern names are wrong.
The shipped-form census backs the registry — real forms declare LeftFive patterns:
|
…o the registry
Both declared version 1.1, which exists on no installation, and both had a shape the
real pattern rejects. Rebuilt from the registry's 1.4 definitions:
Design
ApplicationBar (Style=Standard)
NavigationList (SidePanel group: quick filter + read-only list grid)
PanelTab (ShowTabs=No)
DetailsPanel - title group with the required HeaderTitle, then
DetailsMaster: a FastTabs tab
DetailsTransaction: HeaderLinePanels > Lines/Header panels,
the Lines panel carrying its own FastTab with a toolbar and
the lines grid
GridPanel - custom-and-quick-filter group, main grid, default action
Four things the compiler had to tell us, each fixed where it belongs:
- the title group requires a HeaderTitle control (Skip=Yes, ShowLabel=No,
WidthMode=SizeToAvailable) — bound to the first requested field, since that is the
best guess a scaffold can make;
- MainGrid's DefaultAction cannot be empty; it points at MainGridDefaultAction;
- a form may not repeat a control name, and both patterns now have two quick filters
("Duplicate value 'QuickFilterControl' detected"). The outer name is what must be
unique; the *extension's* name is what identifies the control type, so that one
stays QuickFilterControl — our own FP003 caught the first attempt at renaming both;
- DetailsTransaction's Lines and Header panels each require their own tab
(LineViewTab, HeaderDetailsTab), which is where the header fields now live.
Two harness fixes that came with them:
- provisioned goldens are copied with the read-only attribute cleared. The compiler
rewrites a form's metadata header while validating it and otherwise reports
"Failed to write metadata back to the file … often due to the file being
read-only" — a property of the copy, scored against the case.
- `Generation Error` joins the severity list. It was the prefix on exactly that
message, so the harness was not counting it.
- NodeSpec.NameHint is never null now: the repairer names a control it adds from the
hint, and registry parts without a Part attribute made it throw.
Baseline: 40 of 51 clean (was 38). Five patterns migrated, three to go —
SimpleListDetails, Lookup (→ LookupGridOnly 1.1), Workspace (→ WorkspaceOperational 1.1).
dotnet test 1032 passed · eval run --all 51/51 · coverage/knowledge/skills clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Both declared version Four things the compiler had to tell us
Three harness fixes that came with them
Left
|
…s closed SimpleListDetails, Lookup and Workspace complete the migration. All nine patterns now name a pattern the AOS has, every form eval case compiles clean, and FormTemplatePatternRegistryTests' KnownWrong list is empty. **Lookup → LookupGridOnly 1.1.** There is no pattern named "Lookup" at all; the grid-only lookup is LookupGridOnly, alongside LookupTab and LookupPreview. Its filter group takes the CustomFilters sub-pattern, not CustomAndQuickFilters, and its grid is never editable. **Workspace → WorkspaceOperational 1.1.** "Workspace" exists only as an inactive 2.0. The live pattern's sections are FastTab pages — summary tiles, tabbed list, related links — not a Panorama. The important finding: **an operational workspace's lists are not inline.** A tabbed-list page must hold a FormPartControl pointing at a *separate* form whose own design pattern is FormPartSectionList. Placing fields there produces a form the AOS rejects, and pointing a FormPart at a menu item that does not exist is the dangling reference this repo refuses to emit — so `generate form --pattern Workspace` now declines --field/--section and says what to do instead, rather than dropping them silently or writing something unloadable. The eval case moved with it. **SimpleListDetails.** "SidePanel" was declared as a sub-pattern; it is a Style, and the AOS went looking for a pattern it has no record of. The navigation list, details header and FastTabs tab are now the registry's three parts. **The extraction was still incomplete**: sub-patterns live in their own resource namespace and were not being read at all, so the registry knew nothing about their versions. It does now — 162 definitions, up from 102 — which is how the section sub-patterns' 1.0 versions turned out to be inactive with 1.1 current. That also exposed the last of the name drift. Six sub-patterns the catalog had invented — Workspace_Tiles, Workspace_Links, ToolbarAndList, ToolbarAndFields, ToolbarAndListDouble, HorizontalFieldsButtonGroup — are really SectionTiles, SectionRelatedLinks, ToolbarList, ToolbarFields, ToolbarListDouble, HorizontalFieldsButtonsGroup. The catalog's own note said the names were "to be confirmed by mining"; the registry confirmed the alias had been the real one all along, so XmlName and alias are swapped. Baseline: 43 of 51 clean (was 40), and no form case among the remaining 8 — those are a workflow and two security objects with an empty mandatory property, a custom service naming an ungenerated class, a case extending the non-extensible NoYes, and the three known-reference-gap cases. dotnet test 1030 passed · eval run --all 51/51 · coverage/knowledge/skills clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
AOT form-pattern compliance is closed. All nine patterns migrated, every form eval case compiles clean, The last three
The finding that mattered: an operational workspace's lists are not inline. A tabbed-list page must hold a
The extraction was still incompleteSub-patterns live in their own resource namespace and weren't being read at all — the registry knew nothing about their versions. It does now: 162 definitions, up from 102, which is how the section sub-patterns' That exposed the last of the name drift. Six sub-patterns the catalog had invented:
The catalog's own note said these were "to be confirmed by mining". The registry confirmed the alias had been the real name all along — so XmlName and alias are swapped. Where the queue stands43 of 51 clean, and not one form case among the remaining 8: a workflow and two security objects with an empty mandatory property, a custom service naming a class the command doesn't generate,
|
Five of the eight were the oracle's own blind spot. A case scores exactly one
artifact — EvalScorer rejects a golden directory holding more, and rightly so —
but one generate command usually ships several. Compiling the scored file alone
left the siblings dangling and reported five cases red for objects the tool does
in fact generate, which buried the real defects underneath. Siblings now live in
a _companions subfolder of the golden directory: invisible to the scorer, which
enumerates the case directory non-recursively, and compiled by the oracle. They
are captured from the same command run as the golden, so a companion that drifts
fails the build exactly like the golden does.
With that out of the way, six shipping defects surfaced:
- AxService carried no ExternalName, which the metadata provider rejects
outright ("Property cannot be empty").
- generate workflow treated AxWorkflowTemplate.Category as a UI nicety and
warned that the workflow "cannot be configured" — it did not build at all.
There is no defensible default, so --category is now required.
- The WorkflowDocument's getQueryName() named a query the command never
created. The driving table is already known, so it scaffolds the AxQuery too
(--no-query opts out when the query already exists).
- The canSubmitToWorkflow CoC class was named <Table>_WorkflowExtension, which
ends with "WorkflowExtension", not the literal "_Extension" the compiler
checks for — and it restated a default parameter value CoC forbids.
- The business event contract declared "implements BusinessEventsContract".
That is a class, not an interface, and the buildContract() return then failed
to convert as well.
- generate number-sequence extended NumberSeqApplicationModule_<Module>, a name
assembled from the abstract base class and present in no module. The
convention is NumberSeqModule<Module>, and the wrapped loadModule is
protected, not public.
Four cases were themselves mis-authored and are corrected: extending a NoYes
that is not extensible, adding a privilege the target duty already granted,
naming a policy query nothing generates, and hanging a number sequence off a
module that does not exist.
Not fixed: generate custom-service still names its class <NAME>Service, so
"ConFmVehicleQueryService" yields "ConFmVehicleQueryServiceService". Dropping
the redundant suffix makes the class and the service collide on one path under
a flat --out, and that trade is worse than the stutter. Recorded in the code.
Baseline: 51 of 51 goldens compile clean, zero unattributed diagnostics.
dotnet test 1038 passing; eval run --all 51/51; coverage and knowledge gates clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0142bEYAssRgRHZnN3G9rLbw
Phase 4 of
docs/KNOWLEDGE_AUDIT_PLAN.md— the eval loop to predecessor parity (audit finding R7).What landed
4.1 — CI gate. An
evaljob replays the whole catalog (eval run --all, 51/51 green) and runseval coverage --checkon every PR.4.2 — L3 build oracle.
d365fo eval verify-buildprovisions every reviewed golden into a throwaway model, compiles it withxppc.exe, attributes each diagnostic to the case whose golden produced the named object, and persistseval/golden-build-verification.json.EvalScoreCardgainedBuildClean/BuildErrors,nullwherever no compiler ran.Four assumptions were wrong until it ran against a real installation:
<ModelModule>,Layeris an ordinal, string collections live in the DataContract arrays namespace);ApplicationSuitealone leaves the standardNameEDT andModuleAxaptaunresolvable;FmVehicle;XppcDiagnosticsknew five severity prefixes and the compiler emits three more, so a run loggingErrors: 8parsed as zero diagnostics and the harness called every golden clean.48 of 51 goldens compile clean. The three that don't are real shipping defects no offline gate could see, now ranked as
TOOL_DEFECTclusters with corpus provenance:L1-query-basic,L1-query-joinAxQuerycrashes the metadata reader —<SourceCode><Methods><Method><Name>classDeclarationis mandatory, and each data source needs<DynamicFields>Yes</DynamicFields>in contract positionL2-event-handler-basic<Declaration>; the provider needs it as an XML<Method>with a<Name>Fixing them is scaffolder work and belongs to the improver's next PR — which is the loop behaving as designed. Same reason the mini-AOT fixture gained an EDT and an enum but not a query: a generated one would make the fixture module itself uncompilable and block the oracle.
4.4 — Triage, clusters, knowledge feedback.
EvalTriagederives a hypothesis for replay runs (no model in the loop, soMODEL_ERRORis impossible by construction) and deliberately refuses to for agent runs, where the same scorecard is equally consistent with a tool defect and the agent's own mistake.eval clustersgained--actionable/--top, carries run ids and failing dimensions, and exemptsknown-reference-gapcases.d365fo eval knowledgeturnsKNOWLEDGE_GAP/MODEL_ERRORruns intoskills/_sourceproposals naming the literal that links each topic to the case (finding K4).4.5 — K ∧ E ∧ T coverage.
eval/COVERAGE.mdis derived fromObjectTypeRegistry, a newGenerateSurface, the case catalog and the embedded corpus: 42 of 70 leaves complete, with the real gaps listed (no topic namesAxMap,AxViewor the extension families).GenerateSurfaceis gated in both directions against the CLI's own help output so it cannot become the fifth drifting registry.What did not land
4.3 — the L4 runtime oracle. It needs a
SysTestRunnerresult parser (test runreturns a raw output tail) and per-run fixture provisioning inside a live model. One prerequisite defect was fixed on the way:test run --suite Xpassed"--suite X"as a single argv element, so the flag could never have worked.Verification
dotnet test— 1004 passed, 0 failedd365fo eval run --all— 51/51d365fo eval coverage --check,d365fo knowledge audit --verify,scripts/emit-skills.pydrift — all cleand365fo eval verify-buildon a D365FO installation — 48 clean / 3 errors / 0 skipped🤖 Generated with Claude Code