Observation-class finding, spotted while implementing objectui#6375 (the subsumed CONCURRENT_UPDATE guard in packages/data-objectstack). Not touched by that PR — its scope was the normaliseClientError guard and the isConcurrentUpdateError doc in data-objectstack only, so this is recorded rather than edited.
What
packages/plugin-detail/src/ConcurrentUpdateDialog.tsx exports a type predicate whose narrowed type is unsound for exactly the limb it was written to accept:
exportfunctionisConcurrentUpdateError(err: unknown): err is {code: 'CONCURRENT_UPDATE';currentVersion?: string;currentRecord?: Record<string,unknown>|null;message?: string;}{if(!err||typeoferr!=='object')returnfalse;conste=erras{code?: string;name?: string};returne.code==='CONCURRENT_UPDATE'||e.name==='ConcurrentUpdateError';}The name === 'ConcurrentUpdateError' limb accepts an object that carries nocode. The narrowed type asserts code: 'CONCURRENT_UPDATE' as a required literal. So for the cross-realm case the limb exists for — a host that bundles the adapter twice, where instanceof fails and the class name is the only discriminator left — the predicate hands the caller a type that claims a property the value does not have.
Why it is worth a card rather than a silent fix
There is no symptom today, and that is the whole reason to record it rather than patch it under an unrelated PR. The only consumer is packages/plugin-detail/src/InlineEditSaveBar.tsx:183:
if(isConcurrentUpdateError(err)&&canAtomic){setConflict(buildConflict(draft,err));}buildConflict reads only the two optional fields (currentVersion / currentRecord), never err.code. So nothing reads the unsound part of the narrowing. The next reader who does — err.code === 'CONCURRENT_UPDATE' type-checks and is silently undefined on a name-only error — gets no compiler help, which is what makes this worth writing down before that happens rather than after.
Declaring code?: 'CONCURRENT_UPDATE' (optional) would make the narrowed type honest at zero runtime cost. Whether that is worth a patch at all is triage's call; the two-limb runtime check itself is deliberate and should stay (see the rationale recorded above isConcurrentUpdateError in packages/data-objectstack/src/index.ts and, in full, above isViewConfigPermissionDeniedError in the same file).
Context: three independent copies of this predicate
Deliberate, documented, and not itself the finding — noted so triage sees the blast radius of any decision here:
packages/data-objectstack/src/index.ts — error is ConcurrentUpdateError (narrows to the class; duck-typing to the class is the intended cross-realm behaviour).packages/plugin-form/src/occSave.tsx:78 — returns plain boolean, so it has no narrowing to be unsound about. Its own test pins the name limb (occSave.test.tsx:86).packages/plugin-detail/src/ConcurrentUpdateDialog.tsx:223 — the one above.
Each copy is deliberate: the two plugins document that they duck-type in order not to depend on the adapter. Only the third declares a narrowed object type.
Severity
Low, filed flat rather than pre-graded — no user-visible symptom, no test moves either way, and the fix (if any) is a one-character ?.
Where
packages/plugin-detail/src/ConcurrentUpdateDialog.tsx — the exported isConcurrentUpdateError return typepackages/plugin-detail/src/InlineEditSaveBar.tsx:183 — its only consumer
Found from: objectui#6375.
Generated by Claude Code
Observation-class finding, spotted while implementing objectui#6375 (the subsumed
CONCURRENT_UPDATEguard inpackages/data-objectstack). Not touched by that PR — its scope was thenormaliseClientErrorguard and theisConcurrentUpdateErrordoc indata-objectstackonly, so this is recorded rather than edited.What
packages/plugin-detail/src/ConcurrentUpdateDialog.tsxexports a type predicate whose narrowed type is unsound for exactly the limb it was written to accept:The
name === 'ConcurrentUpdateError'limb accepts an object that carries nocode. The narrowed type assertscode: 'CONCURRENT_UPDATE'as a required literal. So for the cross-realm case the limb exists for — a host that bundles the adapter twice, whereinstanceoffails and the classnameis the only discriminator left — the predicate hands the caller a type that claims a property the value does not have.Why it is worth a card rather than a silent fix
There is no symptom today, and that is the whole reason to record it rather than patch it under an unrelated PR. The only consumer is
packages/plugin-detail/src/InlineEditSaveBar.tsx:183:buildConflictreads only the two optional fields (currentVersion/currentRecord), nevererr.code. So nothing reads the unsound part of the narrowing. The next reader who does —err.code === 'CONCURRENT_UPDATE'type-checks and is silentlyundefinedon a name-only error — gets no compiler help, which is what makes this worth writing down before that happens rather than after.Declaring
code?: 'CONCURRENT_UPDATE'(optional) would make the narrowed type honest at zero runtime cost. Whether that is worth a patch at all is triage's call; the two-limb runtime check itself is deliberate and should stay (see the rationale recorded aboveisConcurrentUpdateErrorinpackages/data-objectstack/src/index.tsand, in full, aboveisViewConfigPermissionDeniedErrorin the same file).Context: three independent copies of this predicate
Deliberate, documented, and not itself the finding — noted so triage sees the blast radius of any decision here:
packages/data-objectstack/src/index.ts—error is ConcurrentUpdateError(narrows to the class; duck-typing to the class is the intended cross-realm behaviour).packages/plugin-form/src/occSave.tsx:78— returns plainboolean, so it has no narrowing to be unsound about. Its own test pins thenamelimb (occSave.test.tsx:86).packages/plugin-detail/src/ConcurrentUpdateDialog.tsx:223— the one above.Each copy is deliberate: the two plugins document that they duck-type in order not to depend on the adapter. Only the third declares a narrowed object type.
Severity
Low, filed flat rather than pre-graded — no user-visible symptom, no test moves either way, and the fix (if any) is a one-character
?.Where
packages/plugin-detail/src/ConcurrentUpdateDialog.tsx— the exportedisConcurrentUpdateErrorreturn typepackages/plugin-detail/src/InlineEditSaveBar.tsx:183— its only consumerFound from: objectui#6375.
Generated by Claude Code