Summary
lintLivenessProperties' docblock states the contract: "Advisory only — returns findings, never throws." The flat TYPE_COLLECTIONS loop does not hold it — it reads item.name off every element of every registered collection without checking the element is an object:
for(constitemofasArray(stack[key])){constname=typeofitem.name==='string' ? item.name : …Measurement (built @objectstack/lint dist, main @ b863f01 plus PR #11383)
lintLivenessProperties({ agents: [null] })
-> THROWS: TypeError: Cannot read properties of null (reading 'name')
Reproduces for any registered collection, not just agents.
Reachability — why this is a finding and not a bug report
Latent, as far as measured. The rule is declared input: 'parsed' in authoring-rules.ts, so the stack it receives has already been through Zod, and a null element does not survive that parse. The throw is reachable only by a caller handing lintLivenessProperties an unparsed or hand-built stack — which the exported signature (stack: AnyRec) permits and the docblock invites, since a function that promises never to throw reads as safe to call on anything.
What makes it worth recording rather than ignoring: this rule is the advisory tier, whose whole design premise is that it can never fail a build. A throw from an advisory rule is not a warning that is wrong, it is the linter aborting — a strictly worse failure than the one the tier exists to avoid.
Suggested shape
One guard in the flat loop, mirroring what the bespoke translation walk added in #11383 (if (!isRecord(item)) continue; — the helper already exists in the file after that PR). Whether the object/field walk above it wants the same guard is the same one-line question.
Provenance
Found while implementing #11288 (PR #11383): a regression test there authors a deliberately malformed bundle, and on the pre-fix code that fixture failed with this TypeError rather than with the assertion it was written for. The PR's own bespoke walk guards its two new levels; the flat loop is untouched by it and out of that card's scope. No fix attempted here.
Summary
lintLivenessProperties' docblock states the contract: "Advisory only — returns findings, never throws." The flatTYPE_COLLECTIONSloop does not hold it — it readsitem.nameoff every element of every registered collection without checking the element is an object:Measurement (built
@objectstack/lintdist,main@b863f01plus PR #11383)Reproduces for any registered collection, not just
agents.Reachability — why this is a finding and not a bug report
Latent, as far as measured. The rule is declared
input: 'parsed'inauthoring-rules.ts, so the stack it receives has already been through Zod, and anullelement does not survive that parse. The throw is reachable only by a caller handinglintLivenessPropertiesan unparsed or hand-built stack — which the exported signature (stack: AnyRec) permits and the docblock invites, since a function that promises never to throw reads as safe to call on anything.What makes it worth recording rather than ignoring: this rule is the advisory tier, whose whole design premise is that it can never fail a build. A throw from an advisory rule is not a warning that is wrong, it is the linter aborting — a strictly worse failure than the one the tier exists to avoid.
Suggested shape
One guard in the flat loop, mirroring what the bespoke translation walk added in #11383 (
if (!isRecord(item)) continue;— the helper already exists in the file after that PR). Whether theobject/fieldwalk above it wants the same guard is the same one-line question.Provenance
Found while implementing #11288 (PR #11383): a regression test there authors a deliberately malformed bundle, and on the pre-fix code that fixture failed with this TypeError rather than with the assertion it was written for. The PR's own bespoke walk guards its two new levels; the flat loop is untouched by it and out of that card's scope. No fix attempted here.