Skip to content

meta-field-write-inert: an accepted field PUT never reaches the object — a runtime-created field is stored valid=true and is absent from fields forever #7893

Description

@huangyiirene

Summary

Split out of #7743, which recorded two entangled defects. #7743's PR closes the first (an artifact-backed field override is now refused 403 NOT_OVERRIDABLE). This is the second, and it is independent: a field write that is entirely legitimate under the registry declaration is accepted, persisted, reported valid — and read by nothing.

The registry declares field with allowRuntimeCreate: true, so authoring a brand-new field through PUT /api/v1/meta/field/<object>.<name> is a supported capability. It just does not work. declared ≠ enforced, Prime Directive #10.

Why this is its own card, not part of #7743

#7743's dispatch predicted that refusing the override would destroy the evidence for the inertness. Measured: it does not. The inertness has two halves and only one of them is behind the new refusal:

So this card has a live repro, not archived evidence.

Measured (booted showcase, admin bearer)

Before #7743's fix — both halves:

PUT /api/v1/meta/field/showcase_task.title {name:'title', label:'Tampered', type:'text'}
→ 200 {"success":true,"state":"active","message":"Saved field 'showcase_task.title' (env-wide…)"}
GET /api/v1/meta/field/showcase_task.title
→ {"item":{"name":"title","label":"Tampered","type":"text","_diagnostics":{"valid":true}}}
GET /api/v1/meta/object/showcase_task
→ title.label === 'Title' ← unchanged. 24 fields.

After #7743's fix — the create half, unchanged:

PUT /api/v1/meta/field/showcase_task.zz_new_probe {name:'zz_new_probe', label:'Probe', type:'text'}
→ 200 {"success":true,"state":"active","message":"Saved field 'showcase_task.zz_new_probe' (env-wide…)"}
GET /api/v1/meta/field/showcase_task.zz_new_probe
→ {"item":{…,"_diagnostics":{"valid":true}}}
GET /api/v1/meta/object/showcase_task
→ fields count === 24, 'zz_new_probe' in fields === false ← the field does not exist

The row answers success twice over — accepted, and stored valid — for a document no consumer reads.

Where to look

A field write lands in sys_metadata as a standalone row type='field', name='<object>.<field>'. Nothing merges standalone field rows into an object's effective schema: the object's fields come from the object artifact plus its own overlay row, and applyRegistryWriteThrough routes only type === 'object' into applyObjectRegistryMutation. A field row takes the generic hydrateOverlayIntoRegistry branch, and field is supportsOverlay: false, so there is no overlay merge for it either.

Consistent with field having no standalone artifact loader at all (filePatterns: ['**/*.field.ts'] matches nothing in any app — fields are authored inside ObjectSchema.fields), which is the same structural fact #7743's root cause turned on.

The decision this needs

Three coherent outcomes; this is a maintainer call, not a mechanical fix:

  1. Implement it — a field write composes into the parent object (registry write-through + read-path merge + column sync), making allowRuntimeCreate: true true.
  2. Retire the write channel — ADR-0049 enforce-or-remove: if a field is only ever authored as part of its object, field should not carry allowRuntimeCreate: true, and the route should refuse with a sentence pointing at PUT /meta/object/<name>.
  3. Keep the row, document it as inert — the weakest, and it is the shape ADR-0049 forbids.

⚠️ If (1) is ever chosen, note that #7743's overlay refusal must stay: making field overrides legal is a separate decision from making field creates work, and #7743 pins the override as refused in both topologies.

Source

Extracted from #7743 (itself from the QA run #7695). Measured on origin/main at bcea363 plus #7743's fix, on the showcase with writable runtime packages.

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions