Skip to content

Author-time expression validator is blind to registry-injected system columns — record.owner_id / created_at / organization_id are rejected as unknown fields #5378

Description

@yinlianghui

Found while migrating an app onto the platform's ownership column (objectstack-ai/hotcrm#548). Filed unassigned by the hotcrm PM loop; evidence gathered on @objectstack/* 17.0.0-rc.2.

The defect

packages/lint/src/validate-expressions.tsbuildFieldIndex resolves CEL field references against the authored fields map only. Every registry-injected system column is therefore invisible to it, and any expression referencing one is rejected at author time:

expression-invalid: unknown field owner_id on crm_contact

The same applies to record.created_at, record.organization_id — columns the platform itself injects into every object and documents as available.

A second surface has the same blind spot: highlightFields entries naming an injected column produce

highlightFields entry "owner_id" is not a field on this object — it is silently skipped by every consumer

Why it matters

The platform's own contract says these columns exist on every object — ownership: 'user' is documented as "injects reassignable owner_id", and the record-level machinery (plugin-sharing / plugin-security, OWNER_FIELD = 'owner_id') reads exactly that column. So an app that correctly targets the platform ownership model is told by the platform's own linter that the column does not exist.

The practical consequence is that apps are pushed into re-declaring system columns to get author-time validation and UI surfacing to work at all. hotcrm#548 had to declare owner_id explicitly on all 12 business objects (carrying system: true so the clone path still strips it) purely to satisfy these two author-time checks — injection-only was unworkable:

  • 6 objects emitted the highlightFields … silently skipped warning;
  • the contact_welcome trigger's has(record.owner_id) was hard-rejected.

That is a workaround for a platform gap, and it is exactly the shape this project treats as a defect: a capability that is declared (the column is injected and documented) but not honoured by the enforcement layer (the validator that decides what an author may reference).

Suggested fix

Have buildFieldIndex union the authored fields with the columns the registry injects for that object's ownership / system-field configuration, so injected columns resolve like any other field — in expression validation and in the highlightFields existence check alike. If re-declaration is intended to be the supported authoring style, then the documentation and the injection behaviour should say so, and the injected-but-undeclared case should stop being silently半-supported.

Acceptance

  • has(record.owner_id) validates on an object that does not declare owner_id;
  • highlightFields: ['owner_id'] does not warn on such an object;
  • a test pins both against an object whose only ownership column comes from injection.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions