Repository navigation
🔀 feat: Save Config Edits as One Change Batch Against the Loaded Revision - #175
Conversation
…nd Accept Dotted Keys Adds a gating layer that reads the admin config contract (GET /api/admin/config/meta) with bundled fallbacks for older servers, accepts dotted record keys where the server keeps them, and fixes a set of smaller configuration issues.
…eading Dotted Keys as Secrets
…ld Titles from the Config Contract Shows what a save skipped or ignored and why, maps issues by segments and params, edits profile priority and creates profiles create-only, makes the pre-auth Base fields editable, and resolves titles per field — each gated on the server contract with today's behaviour as the fallback.
…aths in Profile Merges, Count Partial Reports Apart
…sion On servers with change batches and revisions, saves each principal's pending edits as one POST /changes batch against the loaded revision, with a changed-elsewhere flow on conflict; older servers keep the field-by-field save path.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5c7ccaabd0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…nd Import the Rest
…Imported Record
…by Whole Sections Also merges the #173 fix that leaves reserved keys out of a whole imported record.
|
Codex Review: Didn't find any major issues. Another round soon, please! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Pull Request Template
Summary
Builds on the config contract PRs, and includes the latest of #174. A LibreChat server that reports both
changesBatchandexpectedRevision(GET /api/admin/config/meta) now gets each save as one atomicPOST /api/admin/config/:principalType/:principalId/changesbatch, sent with the revision the edits were made against. A server without either flag, or without the meta route, keeps the separate field writes with rollback and read-back. That path has no revision check, so "Changed elsewhere" needs a server with change batches. Both kinds of server were checked live. The older one does not prune emptied parents, so some saves leave a different stored document there (see Testing).One batch per save
unsetper path).limits.maxChangeOps(1000 on currentdev) is split into several batches, only between config sections. All changes to one section (a hide and the values that replace it, an entry and the settings that depend on it) go in the same batch, so no batch leaves a section half-changed.priority: 0, as Base PATCHes did.Revisions and "Changed elsewhere"
revision. A Base with no document yet usesabsent.CONFIG_VERSION_CONFLICTfrom the batch. In both cases nothing is written.Write reports
ignoredcovers the whole stored document, and an entry list is sent as the collection's whole override, which can carry reported parts of entries the save did not change. So, as with 🧾 feat: Write Reports, Profile Priority, Pre-Auth Sign-In and Per-Field Titles from the Config Contract #174's field writes, a dropped credential (secret_rebinding), or a skipped or ignored part, is listed only when the save changed what it belongs to: its custom endpoint, its MCP server, the Azure groups, or the object that holds it (OCR, a speech provider). A later save of another setting, or of another entry in the same list, does not repeat it.awaitedInvalidation).opIndex) is shown on the field it addresses. Validation issues keep the existing segment and parameter mapping.Validation of the end state
Emptied parents (
prunesEmptyParents)Hiding or showing an entry with edits inside it (both kinds of server, with one difference in what is stored)
prunesEmptyParents, the hide still leaves the emptied parent beside the tombstone (mcpServers: {}when the profile stored only that server). The server stays hidden.prunesEmptyParents, a reset now widens over a stored tombstone that the same save unhides. Switching the memory agent back to the librechat.yaml agent ID no longer leaves an emptymemory.agentobject.Not in this PR:
upsertEntry,removeEntry,rename). Entries are still sent as the collection's whole override, merged by identity before sending.dryRunvalidation.Change Type
Testing
Unit and component tests:
prunesEmptyParents, a reset is not widened.absentwhen there is no document.unsetwithout widening.opIndex, with nothing rolled back.maxChangeOps, a partial failure, and a 409 on a later batch reported as a partial write.expectedRevisionwithoutchangesBatch.absentwithout a document). Older servers get none.Live, with Playwright and MongoDB checks:
dev:configVersion+1).{}, without an emptyinterfaceobject.USERprofile:USERhides a Base MCP server. Clicking Unhide and changing its Server URL stored the URL with no tombstone, and the toast said "Changes saved". Changing the URL and then hiding the server stored only the tombstone.dev, a batch that hides the librechat.yaml memory agent ID alone is accepted, and so is a following batch that sets only its provider. That leavesmemory.agent: { provider }stored without a model, the kind of intermediate state a split by position could send. The split by sections was checked in unit tests only, sincedevtakes 1000 changes per batch.USER:endpoints.customoverride, GPT's entry included. A dry-run batch afterwards showed that the server still reports GPT's dropped key for that document.{}, because the reset was widened to the emptiedinterfaceobject.USERhides a Base MCP server. Unhide with its Server URL changed stored the URL with no tombstone, and the toast said "Changes saved", as ondev.mcpServersobject. Ondevthe document had nomcpServersobject. The server stayed hidden on both.memoryoverride and no tombstone, as ondev.Test Configuration:
dev, and an older LibreChat (f7a56e8, #17204) withoutGET /api/admin/config/meta.librechat.yaml.npx tsc --noEmit,npx eslint src/ e2e/,npx prettier --checkon the changed files,bun run test:lint-rules,npx vitest run(2707 tests).Checklist