Split out of objectstack-ai/objectstack#5077 as an out-of-scope finding — filed unassigned. Found by measurement while fixing the dropped readonly key; not fixed in that PR because it changes the meaning of an authored type across two components.
What was measured
Probe against origin/main (532cf8b), rendering RecordHighlightsRenderer inside an InlineEditProvider canEdit, reading the DOM for the inline-edit affordance (cursor-pointer target + hover pencil):
| object field |
authored entry |
chip inline-editable? |
|
supply_share: { type: 'number' } |
'supply_share' |
yes |
correct |
supply_share: { type: 'number' } |
{ name, type: 'formula' } |
no |
correct |
score: { type: 'formula' } |
{ name: 'score' } |
no |
correct |
score: { type: 'formula' } |
{ name: 'score', type: 'number' } |
yes |
❌ wrong |
The last row is the problem: the object declares the column computed, the author writes a display type to fix formatting, and the chip becomes writable.
Why it happens
HeaderHighlight.tsx:
const resolvedType = field.type || objectDefField?.type;
const isComputed = TEXTUAL_REF_FALLBACK_TYPES.has(resolvedType as string);
TEXTUAL_REF_FALLBACK_TYPES is formula | summary | rollup | auto_number. Because the authored type replaces the object's type rather than adding to it, an authored non-computed type erases the object's computed declaration from the gate's view.
DetailSection.tsx has the identical shape — inlineEditType = enrichedField.type || field.type, and enrichDetailField only fills type from the object when the view field left it undefined. So the details body has the same hole. Any fix must land in both or the two surfaces drift, which is the drift fieldEnrichment.ts was created to stop.
Why it is not academic
This is the real configuration in the app that reported objectstack-ai/objectstack#5077. They ship
properties: { fields: [ /* … */ { name: 'supply_share', type: 'number' }, /* … */ ] }
as a workaround for objectstack-ai/objectstack#5066 (display formatting), and supply_share is a hook-maintained rollup. The measured corruption there — a rollup overwritten by hand, staying wrong until an unrelated child-row touch re-fired it — goes through this path, not through the missing readonly alone. Fixing readonly gives them a way out; it does not close the trap for the next app that writes a display override.
The decision
What does an authored type on a highlight / detail-view field mean?
Option A — narrow-only. Treat the gate's type set as a union: non-editable if the authored type or the object type is computed. An authored type can then only ever lock a field, never unlock one. Display-renderer selection keeps using the authored type exactly as today, so nothing visual changes.
- Long-term soundness: the object schema is the source of truth for whether a column is machine-computed; a presentation override has no business granting write access. Monotonic-in-the-safe-direction is the property you want in a gate that guards data integrity. Nobody can be legitimately relying on inline-editing a formula column — the write either bounces server-side or, worse, lands and corrupts.
- AI-authoring safety: this is the structural fix. An authoring model writing
type: 'number' to fix formatting cannot accidentally hand users write access to a rollup, because the two concerns stop sharing one key's authority. No new vocabulary, no way to get it wrong.
- Cost: behaviour change for any page that today relies on a
type override to make a computed field editable. I could not construct a case where that is desirable rather than a latent bug.
Option B — separate the concerns properly: keep type as display-only and add an explicit editable/readonly declaration for the gate.
Option C — leave as-is, documenting that an authored type fully overrides including editability.
- Cheapest, and honest about current behaviour. Long-term cost: a documented footgun in a data-integrity gate, on the exact key authors are told to reach for when a chip renders wrong. Documentation does not stop an AI author who never reads it.
Recommendation: A, with B's readonly key as the explicit-intent escape hatch on top (that half is already in flight). A is the option where the mistake becomes unrepresentable rather than merely avoidable, and it is the one that keeps the object schema authoritative about what is machine-owned. Land it in HeaderHighlight and DetailSection together.
Split out of objectstack-ai/objectstack#5077 as an out-of-scope finding — filed unassigned. Found by measurement while fixing the dropped
readonlykey; not fixed in that PR because it changes the meaning of an authoredtypeacross two components.What was measured
Probe against
origin/main(532cf8b), renderingRecordHighlightsRendererinside anInlineEditProvider canEdit, reading the DOM for the inline-edit affordance (cursor-pointertarget + hover pencil):supply_share: { type: 'number' }'supply_share'supply_share: { type: 'number' }{ name, type: 'formula' }score: { type: 'formula' }{ name: 'score' }score: { type: 'formula' }{ name: 'score', type: 'number' }The last row is the problem: the object declares the column computed, the author writes a display type to fix formatting, and the chip becomes writable.
Why it happens
HeaderHighlight.tsx:TEXTUAL_REF_FALLBACK_TYPESisformula | summary | rollup | auto_number. Because the authoredtypereplaces the object's type rather than adding to it, an authored non-computed type erases the object's computed declaration from the gate's view.DetailSection.tsxhas the identical shape —inlineEditType = enrichedField.type || field.type, andenrichDetailFieldonly fillstypefrom the object when the view field left it undefined. So the details body has the same hole. Any fix must land in both or the two surfaces drift, which is the driftfieldEnrichment.tswas created to stop.Why it is not academic
This is the real configuration in the app that reported objectstack-ai/objectstack#5077. They ship
as a workaround for objectstack-ai/objectstack#5066 (display formatting), and
supply_shareis a hook-maintained rollup. The measured corruption there — a rollup overwritten by hand, staying wrong until an unrelated child-row touch re-fired it — goes through this path, not through the missingreadonlyalone. Fixingreadonlygives them a way out; it does not close the trap for the next app that writes a display override.The decision
What does an authored
typeon a highlight / detail-view field mean?Option A — narrow-only. Treat the gate's type set as a union: non-editable if the authored type or the object type is computed. An authored
typecan then only ever lock a field, never unlock one. Display-renderer selection keeps using the authored type exactly as today, so nothing visual changes.type: 'number'to fix formatting cannot accidentally hand users write access to a rollup, because the two concerns stop sharing one key's authority. No new vocabulary, no way to get it wrong.typeoverride to make a computed field editable. I could not construct a case where that is desirable rather than a latent bug.Option B — separate the concerns properly: keep
typeas display-only and add an expliciteditable/readonlydeclaration for the gate.readonlyon the entry (highlightFieldscannot declare read-only, so the detail page's header chips let users overwrite hook-maintained columns objectstack#5077) is already half of it. But it leaves the trap open by default — the author must know to write the extra key — which is precisely the failure mode that produced the corruption. Safe defaults beat available knobs.Option C — leave as-is, documenting that an authored
typefully overrides including editability.Recommendation: A, with B's
readonlykey as the explicit-intent escape hatch on top (that half is already in flight). A is the option where the mistake becomes unrepresentable rather than merely avoidable, and it is the one that keeps the object schema authoritative about what is machine-owned. Land it inHeaderHighlightandDetailSectiontogether.