Repository navigation
[Architecture]: define config rebase provenance for deletion vs unseen keys #1478
Description
Client or integration
OpenCodex Dashboard / management API
Area
Configuration persistence / provider and model management
Summary
The rebase-on-save landed for #1273 cannot distinguish two situations that look
identical from the merge's point of view:
- a key is present on disk and absent in the live config because this writer**
**deleted it; - a key is present on disk and absent in the live config because this writer**
**never held it — another writer added it to the file after our config was
loaded.
Absence alone does not say which. Both readings are defensible and each breaks
the other's scenario:
| Scenario | Deletion reading | Ignorance reading |
|---|---|---|
live deletes a field, disk still has it (PUT /api/grok/selection with []) |
correct: field is cleared | wrong: field comes back |
| another writer adds a key after we loaded, we save something unrelated | wrong: key is dropped | correct: key survives |
Two independent fixes for #1462 landed on opposite sides of this. d6e4545b2 on dev restricts the rebase to keys the baseline knew about, which fixes the Grok
case; the contributor's later head d51bd2856 arms provenance on every config
load, which preserves disk-added keys but leaves the Grok case failing (9 pass /
1 fail on tests/grok-management-api.test.ts). Reimplementing the first rule
inside the second design breaks its own regression, an independently loaded config rebases a non-server save.
Both scenarios currently pass on dev, so this is not an active user-facing
defect — it is an unresolved rule that the next change in this area will hit.
Reproduction
Deletion case, against d51bd2856:
const config = loadConfig(); config.grokExcludedModels = ["a"]; saveConfig(config);PUT /api/grok/selectionwith{"excluded": []}on a server whose live
config was loaded before step 1.- Expected
grokExcludedModelsto be gone; it is still["a"].
Ignorance case, against a rebase restricted to baseline-known keys:
const stale = loadConfig();then, from a secondloadConfig(), setdisabledModelsandsaveConfig().stale.contextCapValue = 240_000; saveConfig(stale);- Expected
disabledModelsto survive; it is dropped.
Version
2.12.x (dev at 62be62222)
Operating system
macOS 15 (arm64); not platform-specific
Provider and model
Not provider-specific
Logs or error output
deletion case: Expected: undefined Received: [ "a" ]
ignorance case: Expected: ["disk/model"] Received: undefined
Anything else
Two directions look viable, and the choice belongs to whoever owns this
subsystem rather than to whichever patch merges last:
- Record deletion intent where the deletion happens. There are eight or more
delete config.<field>sites across the management routes; each would mark
the removal so the merge can tell intent from absence. - Refresh the baseline on every cooperating write rather than arming it once
at server startup, so a key added by another writer is no longer invisible to
this instance.
Context and the full two-scenario analysis: devlog/_plan/260811_bug_pair_landing/080_wp10_publication_plan.md.
Activity
[-][Bug]: config rebase cannot distinguish a deleted key from one this writer never held[/-][+][Architecture]: define config rebase provenance for deletion vs unseen keys[/+]Wibias commentedon Aug 12, 2026
Triage classification update: this is not currently an active user-facing bug. The issue itself records that both conflicting scenarios pass on
dev; the open question is the provenance rule we want future config writers to follow.I removed the
buglabel and marked this as roadmap/architecture work. Keep it open as the canonical design decision so the next config-rebase change does not accidentally choose one semantic by patch order.lidge-jun commentedon Aug 14, 2026
Closing: this is an architecture decision record (ADR) task — defining config rebase provenance is a design exercise, not a bug fix. Track under roadmap/architecture.
lidge-jun commentedon Aug 14, 2026
Reopened — reclassification does not justify closing. Keeping open as a valid architecture decision item.
lidge-jun commentedon Aug 19, 2026
리뷰 · 우선순위 22 / 80
rebase-on-save가 삭제한 키와, 이 작성자가 본 적 없는 디스크 신규 키를 구분하지 못한다는 아키텍처 이슈입니다. 둘 다 라이브에는 없고 디스크에는 있어 보입니다. 이슈 말대로 지금 사용자 대면 결함은 아니고, 다음 변경이 어느 쪽을 깨뜨릴지 규칙이 없다는 로드맵입니다.
현재 dev의 src/config.ts는 liveConfigBaseline WeakMap과 armClaudeCodeBaseline()으로 서버 시작 시 베이스라인을 겁니다. 주석은 두 번째 loadConfig가 장기 서버 베이스라인을 갱신하면 안 된다고 합니다. reconcile 경로는 baseline/live/persisted를 비교합니다. 삭제 의도를 표시하는 provenance 필드는 이 심볼들에 없습니다.
Grok 선택 비우기처럼 라이브가 지운 필드는 사라져야 하고, 다른 작성자가 넣은 disabledModels 같은 키는 unrelated save에 지워지면 안 됩니다. 두 수정이 반대편을 깨뜨린 역사가 이슈에 적혀 있습니다. 지금 양쪽 테스트가 통과해도, 규칙이 없으면 다음 패치가 한쪽을 다시 깨뜨립니다.
해결방안: 삭제가 일어나는 관리 라우트에서 삭제 의도를 기록하거나, 협력 쓰기마다 베이스라인을 갱신하는 두 방향이 이슈에 있습니다. 어느 쪽을 제품 규칙으로 고를지는 이 서브시스템 소유자가 정해야 합니다. 지금 어느 한 쪽만 다시 넣으면 다른 회귀가 돌아옵니다. 패치는 추측하지 않음.
이 댓글은 grok-bot이 작성했습니다
lidge-jun commentedon Aug 23, 2026
Backlog review 2026-08-23 — retained as roadmap, still accurate against
dev.The gap is in the merge's inputs, not its code: rebase-on-save cannot tell "this writer deleted the key" from "this writer never saw the key", and no amount of care in the merge function recovers information the writer never recorded. Closing it would be pretending the ambiguity resolved itself.
Fixing it means adding provenance to persisted config — a schema change with a migration and a compatibility story for every existing install. That earns its own cycle rather than a slot in a backlog sweep.
lidge-jun commentedon Aug 25, 2026
Closed on
devby #2613 (squashaacd6528d8fbb97dcf022d1420d262652a42f0ff).Deletion intent is explicit now.
deleteConfigTopLevelKey()records the writer's intent alongside the delete, and a versionedconfigRebaseProvenancecarries it across a stale whole-config rebase, so "the live writer deleted this" and "this key was never in the live config" are no longer the same observation. Ten modules, 27 named deletion paths plus one generic keyed path — enumerated in the design note rather than trusted to the estimate in the issue.Compatibility holds in both directions, each with its own test: absent provenance behaves exactly as today and grants no authority; future versions are preserved byte-for-value and grant no deletion authority either; assigning a deleted key clears its tombstone so intent cannot outlive itself.
One pinned test changed — the Grok deletion case now expresses intent explicitly instead of relying on a bare
delete, because that assertion was pinning the ambiguity this issue exists to remove. The Claude hand-edit assertion is untouched.Falsified by dropping the provenance-aware key set from the rebase, which reddens only the discrimination test while the other 42 stay green — that is what pins it as discrimination rather than a wider rebase.
GitHub only auto-closes on merges into
main; PRs here targetdev, so closing manually.fix: repair three regressions from the #2463/#1478 landings (#2614)
lidge-jun commentedon Aug 25, 2026
Landed via #2613 at aacd652
fix: repair three regressions from the lidge-jun#2463/lidge-jun#1478 …
fix: repair three regressions from the lidge-jun#2463/lidge-jun#1478 …