[Pimcore Agent] Propose import configurations through Change Control - #693
Draft
markus-moser wants to merge 74 commits into
Draft
markus-moser wants to merge 74 commits into
markus-moser wants to merge 74 commits into
Conversation
…iven The detail view both fetched the configuration and rendered it, so the tab tree could only ever show what the detail endpoint returned. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
transformBackendToForm() flattens general.* and the mapping list is addressed by mappingId when stored but by index in the form, so a dotted address into the stored document does not name the same field in the editor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ritten A configuration change an agent makes lands immediately, so nobody sees the mapping it drops until an import runs with it. Registered only when the Change Control bundle is installed: the handler and the hydrator implement its interfaces, and the review surface resolves its registry by id, so an installation without it is unaffected. Slot addresses are document paths, not form paths - the merge takes them back as exclude paths. mappingConfig rides one address, whole: a positional address into a list stops being stable the moment a row is inserted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… diff A review payload carries only the changed subset of the state. Mounted on that alone the editor rendered every untouched select empty, so the proposed leaves now lay over the configuration the importer's own endpoint returns. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The review offered a run button, import logs, and Add/Autofill/Delete on the mapping list - none of which a reader can act on. It also disabled the Advanced button, which is the one control a reader needs. Runtime sections come off through showRuntime; the mapping affordances ask a new read-only context, since a disabled form greys out inputs but leaves plain buttons live. The change rail groups by the section the editor shows the field in, folds when the change set is large, and drops the enumeration entirely for a create - listing every field of a new configuration says only "all of it" at length. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…f it A row now names a place in the editor and carries the reader there: the tab switches, the Data Setup step switches, the pane scrolls and the field is marked. Grouped tab -> section, with counts, a state filter and a field search. The editor composes ContentLayout/Content/FormKit/Tabs itself rather than calling data-hub's BaseDetailView. That wrapper holds no logic - it passes defaultActiveKey, which keeps the active tab as its private state and puts it out of reach of anything that wants to navigate to a field. The scroll waits two frames: a Data Setup step is hidden with display:none, so a field in an inactive step has no box to measure until the step switches and the browser lays it out again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three MCP tools: list_import_configs, get_import_config and propose_import_config, the last one opening a change set instead of writing. The tool folds the agent's document over the stored one (lists replaced whole, never merged by index), unwraps a wrapped or index-keyed mapping list, refuses invented sections, and strips the volatile keys the subject strips — a document the editor cannot render never reaches the review. A skill tells the agent to read before it proposes. The subject gets its own merge shape: the engine's structural default reads any keyed array holding `type` or `path` as an element reference, which made every section one leaf and an untick in the review a no-op. The review surface diffs against the recorded base once the change set is resolved, drops its checkboxes there, and speaks the bundle's translations. A non-list mapping config degrades instead of taking the surface down. PHPStan excludes the Change Control contract implementations and ignores the optional Agent/Studio-MCP symbols; the tests that need those bundles skip without them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…rry their own marks The rail loses its box, pills, filters and search: a caption, the tabs in editor order, one row per changed field with its checkbox and mark, the mapping rows underneath. It sits flush with the modal's own gutters, which the surface was doubling. The mapping step now marks its rows the way the form marks its fields: an added row, a changed one with the target it had, and a dropped row left in place, struck through. The editor shows the list in review order. A configuration that does not exist yet reviews too: its draft is recorded whole instead of filtered against a tree that is not there, and the editor's steps, which read a configuration by name, find the proposed one in the cache instead of a 404. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The propose tool now checks every adapter and strategy type — loader, interpreter, the resolver strategies, cleanup, data targets, transformation operators — against the service tags the factories are built from, and refuses with the allowed list. The editor's selects cannot hold anything else; a proposal must not either. The review rail decides nothing any more: no checkboxes, no include state, no "was" hints — a map only. Sections in editor order, each field named the way the editor names it (Publish Strategy, not "type"), the mark beside it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A name nothing is stored under creates one: the document is the whole configuration, checked for what the importer cannot run without (loader, format, target class, the resolver strategies) and for a name a file and a YAML key accept. It starts inactive unless the proposal says otherwise — a pipeline nobody switched on must not run because it was reviewed. The skill tells the agent to copy the closest existing configuration rather than write one from memory. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ewed field stays visible Cleanup strategy is only read while cleanup is on, delta check and cleanup only with an ID field. A proposal that changes one of them while its switch stays off would put a mark on a field the editor folds away and the import ignores; the tool refuses it and names the switch. Where a change set does carry such a field, the processing step keeps it on screen for the review. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…l rows read as controls Studio's own Content spinner while the change set loads, no count reported before it has been read, and rail rows bordered and filled so they read as something to press rather than as text on the page. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…them is a listing Each section headline — at body size — carries one arrow that opens that tab and step. The fields beneath are no longer controls: a plain listing of name and mark, one hairline apart. The per-field scroll-to-anchor goes with it; the section is where the reader lands. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This was referenced Sep 11, 2026
…s read as a list The arrow is an ornament on a headline that is the button; the fields under it are a bulleted, hairline-separated list. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ays it is new The create marks its proposal isNew, so the card and the review title say "new" instead of "changed". The rail no longer replaces the map with a summary for a create: the same sections and fields, every one added, with the subject's own name, type and path left out — those are identity, not a change anyone weighs. The list drops its bullets and indent. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The card was written against a 14px base and Studio's is 12, so sizes derived as fontSizeSM minus one or two rendered at 9px and 8px — the section labels and the notes under each step were below anything the scale contains. They take fontSizeSM now, the name takes fontSizeLG, and the state pill takes fontSize so it reads as one of the tags beside it. Letter-spacing stays a literal: the scale has no token for it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three things a review round turned up. A failed request reached the user as "Something went wrong": Studio's ApiError reads `message`, `error` or `errorKey`, and a Symfony HTTP exception serialises as problem+json with its sentence in `detail`, so a missing source asset never said so. Every place the importer surfaces a request failure now passes the sentence through, wherever it landed in the body. The rail takes the modal's own 24px gutter, so its content lines up with the title above it instead of sitting 4px inside. And two changed fields in one section no longer stack their marks edge to edge. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adopted from the Change Control bundle, which settled this on 2026-09-11: one line carrying the state with its icon, when it was resolved, and the read-only notice folded into a hint rather than spelt out underneath. The header also leaves the rail and runs the width of the modal body, which is where the title above it sits. Its translation key was already on the branch, swept into an earlier commit of mine, so this is the half that was missing. The horizontal inset on that row is mine: the modal body reaches the modal's edges, so the row needs the gutter the title keeps. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The rail for a changed configuration and the brief for a new one were this bundle's own card, copied once already into Webhooks. Both now map onto the SDK's summary - sections of rows for the changes, one stop per section for the brief, and its foot for what is waiting in the editor - and the card, its styles and the status tag are deleted here. The mapping step's mark is the SDK's too. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The surface registered through a locally declared registry interface with a register() the lane has not had since it moved onto Studio's dynamic types, so the guard was silently false and no importer review surface was ever registered. Typing the seam against @pimcore/change-control-bundle is what catches that. mode followed: the lane resolves the stance and sends compareAgainst, never mode, so history was unreachable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
It moved there with #96, so the importer no longer reaches into the Data Hub for a review component. StatusTag folded into the lane's own ChangeTag. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three refusals the propose tool returned inline now throw one refusal type, which takes the branching out of execute; unknownValues judges one leaf at a time; the vocabulary takes its tagged iterators as one map keyed by the family it prints. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
FLATTENED is only ever asked whether it holds a segment. A dataTarget language that is not a string cannot key a mapping row, so it keys as absent rather than stringifying to [object Object]. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s not have An invented leaf was recorded verbatim: only top-level sections and typed values were checked, so processingConfig.doDeltaCheckCheck reached a change set, reviewed as a change and applied as nothing. propose_import_config now refuses a leaf the document does not name and the installation does not already store; `settings` subtrees and the mapping list stay open, their keys belonging to the type they configure. The review surface marks a change it cannot bind to a field, which also catches the residue in configurations already written. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The warning rode the row's hint, which the summary renders only as a title attribute. It is the row's note now, which the summary draws under the label. The rail also reaches into the modal body's gutter: the summary's spine markers hang to the left of their text, and the overflow rule was clipping them away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Giving the scroller the gutter was half of it: the rail itself had no left padding, so what the scroller reached into fell outside the rail and the markers were still clipped. Both boxes borrow it from the modal body now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The summary draws its own card now, so the rail's divider and the gutter it borrowed for the hanging spine both go. The nine brief* rules go with them: they drew the card this replaces and nothing has referenced them since the summary moved into the SDK. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The new-configuration rail showed four pipeline stops while the change rail showed the editor's sections, so the two read as different things. It now walks the same sections in the same order and opens the same steps. A section that is all new has nothing to mark field by field, so it says what it is set to and how much of it the line did not get to — "Asset · JSON / /Import Test/car-export.json · 2 more settings". The tail counts only leaves the line did not already name. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tings" General counted one setting and said so twice over: the name, description and on/off state it holds are the identity header directly above the sections, and the only thing left is the group. It now appears when a group is set and is the header otherwise. Counts read as counts: a key for one and a key for the rest, the way the summary already picks between "1 change" and "%s changes". The aggregate foot goes — each section carries its own count now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three mapping columns want room; the shell should not be the one knowing that. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
permissions was always an accepted section, but nothing said so: the propose tool listed seven sections and the skill never mentioned it, so an agent asked for permissions inferred a prohibition and invented a security rationale for it. Both now describe the section and its shape. list_config_principals gives the names to put in it — an entry is matched by name against the logged-in user's own, so a name this installation does not have grants nothing while reading to a steward as though it does. Admins are left out; they pass every check anyway. The review guard learns the section too, or it would mark a permissions change as naming a field the configuration does not have. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
HistoryHead was this bundle's copy of the change-control review header — its own comment said so. The header now carries that design and ships from the SDK, so the copy goes, along with its four styles and the six strings per locale that only it used. The band draws its own gutter and rule, so the surface wraps it in the gap="small" Flex its bottom margin is cut to cancel. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The registration declares a layout; the published package only gained that type this morning. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… tracks canary-2026.x The modal body already gives the surface its gutter; the outcome band's own put its tag one gutter in from the title and the rail. The surface cancels it. The SDK dependency goes back to the canary-2026.x tag, which now carries the surface layout the registration declares, so the exact pin is no longer needed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The change rail puts its unbound-field warning in a row's note; the summary in the previously locked canary never drew it in the changes variant. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
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.



Lets an agent propose an import configuration as a Change Control change set instead of writing it. A person approves or rejects it in the review lane.
list_import_configs,get_import_config,list_config_principalsandpropose_import_config, with thedata-importer-configurationskill. The propose tool folds the agent's document over the stored one, and refuses invented sections and fields, adapter or strategy types this installation does not register, and a change behind a switch that is off. A name nothing is stored under creates a configuration, inactive unless told otherwise.permissionscan be proposed; an entry is matched by name, so the names come fromlist_config_principals.DataImporterConfigEditorrenders from a configuration it is given, so it can show a change set as well as the detail API.@pimcore/change-control-bundletracks thecanary-2026.xtag for its SDK types and needs to move to a release before merge. PHPStan skips the Change Control contract classes insrc/ChangeControl/and ignores the optional symbols insrc/Mcp/*, because Change Control can't be installed in CI. Overlaps with #529 inPimcoreDataImporterExtensionandcomposer.json; whichever merges second rebases.🤖 Generated with Claude Code