Skip to content

feat(config): explicit rebase provenance for deletion vs unseen keys (#1478) - #2613

Merged
lidge-jun merged 2 commits into
devfrom
codex/1478-provenance-merged
Aug 25, 2026
Merged

lidge-jun merged 2 commits into
devfrom
codex/1478-provenance-merged

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Aug 25, 2026 •

Copy link
Copy Markdown
Owner

Summary

Closes #1478. src/config.ts stored snapshot baselines only and inferred intent from key presence, so "the live writer deleted this key" and "this key was never in the live config" were the same observation. The rebase path had to guess, and both wrong guesses were separately pinned by existing tests.

Deletion intent is now explicit. deleteConfigTopLevelKey() records the writer's intent alongside the delete, and a versioned configRebaseProvenance: { version: 1, deletedTopLevelKeys: [...] } carries it across a stale whole-config rebase. Every top-level deletion writer routes through it — 10 modules, 27 named deletion paths plus one generic keyed path, enumerated in the design note rather than trusted to the issue's estimate.

Compatibility is lossless in both directions, and each direction has a test:

  • A config with no provenance behaves exactly as it does today. Absence grants no authority, so an install that never writes provenance sees zero change.
  • Future provenance versions are preserved byte-for-value and grant no deletion authority. Only version 1 is authoritative, so an older binary reading a newer config neither loses the metadata nor acts on something it does not understand.
  • Assigning a deleted key clears its tombstone, so intent cannot outlive itself.

One subtlety worth naming: provider preservation reads symbol-keyed live-owner state that structuredClone drops, so ownership is resolved before the JSON provenance projection rather than after. Getting that order wrong silently loses disk-only provider rows.

Verification

bun x tsc --noEmit                                    exit 0
bun test tests/config-user-edits.test.ts \
         tests/config-rebase-provenance-writers.test.ts  45 pass / 0 fail
bun test <the above + convergence + alias + policy>      76 pass / 0 fail

Falsified independently on the merge with current dev: dropping the provenance-aware key set from the rebase turns "provenance distinguishes an unseen disk key from an explicit deletion" red while the other 42 stay green — which is what pins that the change discriminates rather than just widening the rebase. Restored, all green. The subagent additionally falsified the account-pause writer by reverting it to a raw delete.

One pinned test changed: the Grok deletion case now expresses deletion intent explicitly instead of relying on a bare delete. That assertion was pinning the ambiguity this issue exists to remove. The pinned Claude hand-edit assertion is untouched.

Merge note

Two conflicts with dev, both the same shape: #2463 and #2464 added top-level config fields at the position this branch also extends. Both sides kept — they are independent additions, not competing ones.

Checklist

  • Targets dev
  • Design note and writer inventory recorded at devlog/_plan/260826_config_rebase_provenance/010_design.md
  • Migration tested in both directions, including opaque future versions
  • No credential, auth, workflow or release-automation surface touched

Summary by CodeRabbit

  • New Features
    • Configuration deletions are now preserved during rebases, preventing removed settings from unexpectedly returning.
    • Configuration metadata tracks explicit top-level setting removals across saves and synchronization.
  • Bug Fixes
    • Improved handling of concurrent configuration edits and deleted settings.
    • Deleted settings are cleared from tracking when they are intentionally restored.
  • Tests
    • Added coverage for deletion tracking, rebasing, legacy configurations, metadata compatibility, and configuration writers.

Replace the pinned grokExcludedModels raw-delete setup with the provenance helper so its deletion assertion now proves explicit intent. The unrelated claudeCode hand-edit assertion remains unchanged because provenance supersedes only disk-only top-level key inference.
Two conflicts, both in the same shape: #2463 and #2464 added top-level config
fields at the position this branch also extends. Both sides kept - the schema
entry and the type field are independent additions, not competing ones.
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner August 25, 2026 19:59
@lidge-jun
lidge-jun merged commit aacd652 into dev Aug 25, 2026
6 checks passed
@lidge-jun
lidge-jun deleted the codex/1478-provenance-merged branch August 25, 2026 19:59
@github-actions

Copy link
Copy Markdown
Contributor

✅ Deterministic PR hygiene checks passed.

@coderabbitai

coderabbitai Bot commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 34f18e03-2b3f-45f6-80f3-759540782f45

📥 Commits

Reviewing files that changed from the base of the PR and between 82dbb1a and 4a3a682.

📒 Files selected for processing (17)
  • devlog/_plan/260826_config_rebase_provenance/010_design.md
  • src/cli/v2.ts
  • src/codex/account-pause.ts
  • src/codex/account-priority.ts
  • src/codex/desired-state.ts
  • src/config.ts
  • src/config/rebase-provenance.ts
  • src/providers/context-cap.ts
  • src/providers/provider-id-rewrite.ts
  • src/server/management/agent-settings-routes.ts
  • src/server/management/combo-routes.ts
  • src/server/management/config-routes.ts
  • src/server/management/routing-profile-routes.ts
  • src/types.ts
  • src/types/config.ts
  • tests/config-rebase-provenance-writers.test.ts
  • tests/config-user-edits.test.ts

📝 Walkthrough

Walkthrough

The change adds version-1 config deletion provenance, applies it during config save and rebase operations, migrates top-level deletion writers to a shared helper, and adds tests for deletion, legacy, future-version, round-trip, and reassignment behavior.

Changes

Config provenance and deletion tracking

Layer / File(s) Summary
Provenance contract and helper
src/types/config.ts, src/types.ts, src/config/rebase-provenance.ts, devlog/_plan/.../010_design.md
Defines OcxConfigRebaseProvenance and the optional configRebaseProvenance field. The helper validates metadata, tracks explicit deletions, projects normalized tombstones, and clears pending deletions.
Persistence and rebase integration
src/config.ts
Config save paths project provenance before serialization, preserve supported metadata, track a live baseline, reconcile persisted keys, reapply tracked deletions, and clear pending deletion state after persistence.
Config writer migration
src/cli/v2.ts, src/codex/*.ts, src/providers/*.ts, src/server/management/*.ts
Top-level configuration removals now use deleteConfigTopLevelKey across CLI, Codex, provider, desired-state, and management route writers.
Writer and rebase validation
tests/config-rebase-provenance-writers.test.ts, tests/config-user-edits.test.ts
Tests enforce writer coverage and reject direct top-level deletes. Rebase tests cover explicit deletions, absent provenance, version-1 round trips, opaque future metadata, and reassignment of tombstoned keys.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: ingwannu

Sequence Diagram(s)

sequenceDiagram
  participant ConfigWriter
  participant deleteConfigTopLevelKey
  participant saveConfigPreservingClaudeCode
  participant DiskConfig
  participant LiveConfig
  ConfigWriter->>deleteConfigTopLevelKey: delete a top-level config key
  deleteConfigTopLevelKey->>LiveConfig: remove key and record pending deletion
  ConfigWriter->>saveConfigPreservingClaudeCode: save live config
  saveConfigPreservingClaudeCode->>DiskConfig: read persisted config
  saveConfigPreservingClaudeCode->>LiveConfig: reconcile disk keys and tracked deletions
  saveConfigPreservingClaudeCode->>DiskConfig: persist config and provenance
Loading
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/1478-provenance-merged

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4a3a682aa4

ℹ️ 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".

Comment thread src/config.ts
Comment on lines +3025 to +3026
if (provenanceProjection.configRebaseProvenance === undefined) delete projectedConfig.configRebaseProvenance;
else projectedConfig.configRebaseProvenance = provenanceProjection.configRebaseProvenance;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the newest opaque provenance during guarded saves

When a long-lived server loaded an unknown/future provenance record and a newer process subsequently updates that record on disk, reconciliation deliberately skips this field, and these lines unconditionally restore the stale in-memory projection. The next unrelated saveConfigPreservingClaudeCode therefore erases metadata the current binary cannot interpret, defeating the promised forward-compatible passthrough. Derive opaque provenance from the authoritative onDisk value while combining only understood version-1 candidate tombstones.

AGENTS.md reference: src/AGENTS.md:L7-L11

Useful? React with 👍 / 👎.

} catch (error) {
if (previousSettings.hasCodexAutoStart) config.codexAutoStart = previousSettings.codexAutoStart;
else delete config.codexAutoStart;
else deleteConfigTopLevelKey(config, "codexAutoStart");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid recording deletion intent while rolling back a failed save

When /api/settings attempts to set a previously absent option and the injected or real save throws (for example, a full disk), this rollback calls the provenance helper for every setting that was previously absent. Although the visible values are restored, the WeakMap now contains tombstones for actions the user never requested; a later successful save can persist those tombstones and delete concurrent values for these keys. Restore the provenance bookkeeping captured before the attempt, or use non-intent-recording deletes for rollback.

AGENTS.md reference: src/AGENTS.md:L7-L11

Useful? React with 👍 / 👎.

// Drop the key entirely once nothing is left in it, so enabling twice does
// not leave `"clientIntegrations": {}` behind in the user's file.
if (Object.keys(integrations).length === 0) delete config.clientIntegrations;
if (Object.keys(integrations).length === 0) deleteConfigTopLevelKey(config, "clientIntegrations");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Carry desired-state tombstones through the mutation projection

When enabling the final disabled integration removes clientIntegrations, this helper stores the tombstone against confirmedConfig, but mutatePersistedConfig then passes a new object returned by projectCustomModelCatalogMigration to persistConfigUnlocked. Because pending provenance is identity-keyed in a WeakMap, the projected object has no tombstone and the resulting file contains no configRebaseProvenance; after restart, the legacy rebase path can still discard an unrelated disk-only key. Project or transfer the provenance before changing object identity.

AGENTS.md reference: src/AGENTS.md:L7-L11

Useful? React with 👍 / 👎.

tarunravi pushed a commit to tarunravi/opencodex that referenced this pull request Sep 14, 2026
Replace the pinned grokExcludedModels raw-delete setup with the provenance helper so its deletion assertion now proves explicit intent. The unrelated claudeCode hand-edit assertion remains unchanged because provenance supersedes only disk-only top-level key inference.
agentHits pushed a commit to agentHits/opencodex that referenced this pull request Sep 17, 2026
Replace the pinned grokExcludedModels raw-delete setup with the provenance helper so its deletion assertion now proves explicit intent. The unrelated claudeCode hand-edit assertion remains unchanged because provenance supersedes only disk-only top-level key inference.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant