Measured while implementing #8340 (the four filter/page-binding read sites). #8340's card enumerates four SYSTEM_FIELDS read sites and its file surface stops there; validate-searchable-fields.ts is a fifth with the same structure, and it was not in scope.
The gap
packages/lint/src/validate-searchable-fields.ts judges a declared searchableFields entry against the object-independent blanket union:
:345 — if (!known.has(name) && !SYSTEM_FIELDS.has(name)) gates searchable-field-unknown;
:239 — resolveAllowedSet goes further and manufactures a stub meta for exactly these entries (systemDeclared = declared, not in authored fields, in SYSTEM_FIELDS) so they survive the resolution's existence filter "exactly as [they do] at runtime".
Both are correct about EXISTENCE and structurally blind to PROVENANCE, the same way the four #8340 sites were. On an ADR-0015 external object the platform registers the injected anchors (owner_id, organization_id, the audit family, owning_business_unit_id) and provisions no storage behind them (#7865 / #8116), so:
searchableFields: ['name', 'owner_id'] // external object
lints clean, the stub keeps the entry in the resolved allow-list, and the view's $searchFields narrowing then searches a column that is empty on every record. The degradation is #4830's own failure mode reached by a different route — a narrower search than declared, silently — and it is exactly the class the searchable-field-unsearchable half of this rule exists to catch.
Why this is not part of #8340
#8340's ruling scoped it to the four measured read sites (validate-widget-bindings, validate-react-page-props, validate-page-field-bindings, validate-flow-template-paths) and its file surface excludes this module. The wiring is now cheap — #8340 landed the shared per-object index and message builders (indexUnprovisionedAnchors, unprovisionedAnchorCause, unprovisionedAnchorHint in packages/lint/src/system-fields.ts) on top of #8116's unprovisionedInjectedColumnsFor — but this site needs its own false-positive analysis, because unlike the four it does not merely skip the name: the stub it fabricates at :239 feeds the resolution, so the fix has to decide whether an unprovisioned anchor should still receive a stub (it is addressable, so probably yes) in addition to whether it warns.
Pointers
Blocked-by: none — #8340 has landed the prerequisite helpers on its branch.
Measured while implementing #8340 (the four filter/page-binding read sites). #8340's card enumerates four
SYSTEM_FIELDSread sites and its file surface stops there;validate-searchable-fields.tsis a fifth with the same structure, and it was not in scope.The gap
packages/lint/src/validate-searchable-fields.tsjudges a declaredsearchableFieldsentry against the object-independent blanket union::345—if (!known.has(name) && !SYSTEM_FIELDS.has(name))gatessearchable-field-unknown;:239—resolveAllowedSetgoes further and manufactures a stub meta for exactly these entries (systemDeclared= declared, not in authoredfields, inSYSTEM_FIELDS) so they survive the resolution's existence filter "exactly as [they do] at runtime".Both are correct about EXISTENCE and structurally blind to PROVENANCE, the same way the four #8340 sites were. On an ADR-0015
externalobject the platform registers the injected anchors (owner_id,organization_id, the audit family,owning_business_unit_id) and provisions no storage behind them (#7865 / #8116), so:lints clean, the stub keeps the entry in the resolved allow-list, and the view's
$searchFieldsnarrowing then searches a column that is empty on every record. The degradation is #4830's own failure mode reached by a different route — a narrower search than declared, silently — and it is exactly the class thesearchable-field-unsearchablehalf of this rule exists to catch.Why this is not part of #8340
#8340's ruling scoped it to the four measured read sites (
validate-widget-bindings,validate-react-page-props,validate-page-field-bindings,validate-flow-template-paths) and its file surface excludes this module. The wiring is now cheap — #8340 landed the shared per-object index and message builders (indexUnprovisionedAnchors,unprovisionedAnchorCause,unprovisionedAnchorHintinpackages/lint/src/system-fields.ts) on top of #8116'sunprovisionedInjectedColumnsFor— but this site needs its own false-positive analysis, because unlike the four it does not merely skip the name: the stub it fabricates at:239feeds the resolution, so the fix has to decide whether an unprovisioned anchor should still receive a stub (it is addressable, so probably yes) in addition to whether it warns.Pointers
packages/lint/src/system-fields.ts(finding: author-time expression validation resolves injected anchors on external objects but cannot warn they are unprovisioned — the #7865 provenance marker is unreachable from @objectstack/lint #8116, lint: view-filter / page-binding field checks resolve against the blanket SYSTEM_FIELDS union, so the #8116 unprovisioned-anchor warning cannot reach filter surfaces #8340).warnUnprovisionedAnchorsinvalidate-expressions.ts— WARN, never gating (lint: five hand-copiedSYSTEM_FIELDSlists in packages/lint should derive from the spec's declarations #4330's cost asymmetry).<ListView searchableFields>on a React page is not checked against the object's fields #4329, lint: five hand-copiedSYSTEM_FIELDSlists in packages/lint should derive from the spec's declarations #4330, compiler/validate: view searchableFields not validated against searchable field types — a declaration typo 400s the whole list at runtime #4830, [Decision]applySystemFieldsinjects platform anchors intoexternalobjects the platform provisions no storage for — three consumers have now independently re-derived "that column is not really there" #7865, finding: author-time expression validation resolves injected anchors on external objects but cannot warn they are unprovisioned — the #7865 provenance marker is unreachable from @objectstack/lint #8116, lint: view-filter / page-binding field checks resolve against the blanket SYSTEM_FIELDS union, so the #8116 unprovisioned-anchor warning cannot reach filter surfaces #8340.Blocked-by: none — #8340 has landed the prerequisite helpers on its branch.