Found while implementing #15552 (the stack.objects reader sweep); filed as a finding only, not claimed.
#15552 closes the class for ONE collection. stack.objects is now read everywhere in @objectstack/lint through the guarded recordsOf, and a family test pins that no AUTHORING_RULES entry throws or invents a finding on a non-record member of it. Every OTHER stack collection is still read through a hand-copied helper that does not filter.
Measured
On e7cede2ca (the #15552 branch, all sixteen stack.objects seams re-pointed), 23 files in packages/lint/src still carry the undefended body verbatim:
function asArray(v: unknown): AnyRec[] {
if (Array.isArray(v)) return v as AnyRec[]; // unchecked
if (v && typeof v === 'object') {
return Object.entries(v as AnyRec).map(([name, def]) => ({ name, ...(def as AnyRec) }));
}
return [];
}
build-access-matrix.ts, lint-flow-patterns.ts, lint-liveness-properties.ts, validate-action-locations.ts, validate-action-name-refs.ts, validate-approval-approvers.ts, validate-capability-references.ts, validate-chart-bindings.ts, validate-component-props.ts, validate-component-types.ts, validate-dashboard-action-refs.ts, validate-dataset-references.ts, validate-flow-template-paths.ts, validate-flow-trigger-readiness.ts, validate-list-view-field-refs.ts, validate-nav-access.ts, validate-object-field-refs.ts, validate-readonly-flow-writes.ts, validate-readonly-hook-writes.ts, validate-responsive-styles.ts, validate-rls-predicate-enforceability.ts, validate-semantic-roles.ts, validate-translation-references.ts.
Between them they read flows, pages, dashboards, datasets, agents, apps, navigation, permissions, positions, books, data and the per-object sub-collections. A null member of any of those reaches the same dereference stack.objects did, for the same reason: the rules are pure (stack) = Finding[] and run on the RAW lint path, so nothing upstream has judged the entry's shape, and an empty YAML list item deserialises to null.
Not re-measured per collection here — #15552's sweep drives objects only, so the throw count for the other collections is unknown rather than zero. lint-liveness-properties.test.ts already pins translations: [null, …], agents: [null, …] and fields: [null, …] cases, which suggests some are guarded at the call site and some are not.
Suggested shape
The same edit #15552 made, widened: delete the remaining copies and re-point them onto recordsOf (packages/lint/src/object-graph.ts), whose docblock already carries the skip argument. Worth pairing with a source-text gate asserting the package holds exactly ONE such coercion, so the copy count cannot grow back — the copies are what made #15552 a fourteen-site repair instead of a one-line one.
The family test to widen alongside it is packages/lint/src/non-record-object-entry.test.ts: it sweeps AUTHORING_RULES over five non-record shapes for objects, and the same sweep parameterised by collection key would cover the rest.
Context
Generated by Claude Code
Found while implementing #15552 (the
stack.objectsreader sweep); filed as a finding only, not claimed.#15552 closes the class for ONE collection.
stack.objectsis now read everywhere in@objectstack/lintthrough the guardedrecordsOf, and a family test pins that noAUTHORING_RULESentry throws or invents a finding on a non-record member of it. Every OTHER stack collection is still read through a hand-copied helper that does not filter.Measured
On
e7cede2ca(the #15552 branch, all sixteenstack.objectsseams re-pointed), 23 files inpackages/lint/srcstill carry the undefended body verbatim:build-access-matrix.ts,lint-flow-patterns.ts,lint-liveness-properties.ts,validate-action-locations.ts,validate-action-name-refs.ts,validate-approval-approvers.ts,validate-capability-references.ts,validate-chart-bindings.ts,validate-component-props.ts,validate-component-types.ts,validate-dashboard-action-refs.ts,validate-dataset-references.ts,validate-flow-template-paths.ts,validate-flow-trigger-readiness.ts,validate-list-view-field-refs.ts,validate-nav-access.ts,validate-object-field-refs.ts,validate-readonly-flow-writes.ts,validate-readonly-hook-writes.ts,validate-responsive-styles.ts,validate-rls-predicate-enforceability.ts,validate-semantic-roles.ts,validate-translation-references.ts.Between them they read
flows,pages,dashboards,datasets,agents,apps,navigation,permissions,positions,books,dataand the per-object sub-collections. Anullmember of any of those reaches the same dereferencestack.objectsdid, for the same reason: the rules are pure(stack) =Finding[]and run on the RAWlintpath, so nothing upstream has judged the entry's shape, and an empty YAML list item deserialises tonull.Not re-measured per collection here — #15552's sweep drives
objectsonly, so the throw count for the other collections is unknown rather than zero.lint-liveness-properties.test.tsalready pinstranslations: [null, …],agents: [null, …]andfields: [null, …]cases, which suggests some are guarded at the call site and some are not.Suggested shape
The same edit #15552 made, widened: delete the remaining copies and re-point them onto
recordsOf(packages/lint/src/object-graph.ts), whose docblock already carries the skip argument. Worth pairing with a source-text gate asserting the package holds exactly ONE such coercion, so the copy count cannot grow back — the copies are what made #15552 a fourteen-site repair instead of a one-line one.The family test to widen alongside it is
packages/lint/src/non-record-object-entry.test.ts: it sweepsAUTHORING_RULESover five non-record shapes forobjects, and the same sweep parameterised by collection key would cover the rest.Context
stack.objectsandpackages/lint/src/object-graph.ts'srecordsOf. Nothing above is in that PR.Generated by Claude Code