Measured on the pinned @objectstack/lint@17.3.0 + @objectstack/spec@17.3.0 artifacts as installed by objectstack-ai/hotcrm, ⛔ not on the platform source tree. Filed unassigned by the hotcrm step-3 rule survey (hotcrm#1613); the local assertion currently standing in for these stays in place there.
The gap
lintLivenessProperties only considers a ledger row when shouldWarn accepts it:
function shouldWarn(entry) {
if (!entry) return false;
return entry.authorWarn === true || entry.status === "experimental";
}
describe(entry) then maps status: 'dead' to LIVENESS_DEAD_PROPERTY and status: 'live-elsewhere' to LIVENESS_LIVE_ELSEWHERE_PROPERTY. Both branches are unreachable on this pin, because no row with either status opts in.
Enumerating every props entry (children included) across all 36 files in @objectstack/spec/liveness/:
status histogram: live 636 · dead 90 · planned 14 · experimental 5 · live-elsewhere 1 · (unset) 34
every row shouldWarn accepts:
action.json operation planned authorWarn: true
action.json patch planned authorWarn: true
field.json relatedListFilter planned authorWarn: true
object.json externalSharingModel planned authorWarn: true
translation.json flows planned authorWarn: true
agent.json lifecycle experimental
agent.json memory experimental
agent.json guardrails experimental
agent.json structuredOutput experimental
tool.json outputSchema experimental
Ten rows, all planned or experimental. Zero dead, zero live-elsewhere. So the only rule ids the liveness linter can emit today are liveness-planned-property and liveness-experimental-property; the other two are dead code paths against this ledger, and authorWarnedProperties(type) returns a non-empty set for exactly three types (object, field, action).
Measurement, with a working control
One committed tree (hotcrm at a0362a37), one injection at a time, restored by blob hash. Clean-tree baseline: 0 error(s), 17 warning(s), 12 suggestion(s), exit 0.
| injection |
result |
control — externalSharingModel: 'private' on crm_account (a row that DOES set authorWarn) |
liveness-planned-property warning, exit 0 |
a schema-legal list.tabs: [{ name, label }, …] on crm_account's list view |
no liveness-* finding — 17 warnings, exit 0, byte-identical to baseline |
The control fires in the same harness on the same command, so the second row is a reading about the ledger, not about the linter being off.
The second half, and it points the other way
hotcrm carries test/view-tab-label-inert.test.ts, which asserts that no list / listViews block declares tabs at all. Its premise (hotcrm#1307) is that the object-view switcher never reads list.tabs, so authoring one is inert metadata. The pinned ledger disagrees:
view.json list.tabs status: live
So even if the dead rows were opted in, this particular key would be judged live and the rule still would not fire on it. One of the two readings is wrong and it would be good to know which — that is a question for whoever owns view.json, not something this survey can settle. (Note list.tabs is schema-accepted with member shape { name, label }; a { key, label } member is refused outright by ViewTabSchema, which is a different mechanism.)
Suggested direction, not a prescription
Two separable asks:
- Decide whether the
dead and live-elsewhere verdicts should imply authorWarn rather than requiring a per-row opt-in. Ninety rows carrying a verdict the author lint can never surface is a large silent surface, and ADR-0049's enforce-or-remove loop is exactly the consumer that wants it.
- Re-derive
view.json's list.tabs verdict against the shipped switcher.
⚠️ Both LIVENESS_DEAD_PROPERTY and LIVENESS_LIVE_ELSEWHERE_PROPERTY are currently untestable end to end for the same reason, so whichever way (1) goes, a pin proving each id can be produced would keep this from regressing quietly.
Related, and why this is not a duplicate
Refs: hotcrm#1613 · hotcrm#1582 (the family card) · hotcrm#1307 (the list.tabs premise)
Measured on the pinned
@objectstack/lint@17.3.0+@objectstack/spec@17.3.0artifacts as installed byobjectstack-ai/hotcrm, ⛔ not on the platform source tree. Filed unassigned by the hotcrm step-3 rule survey (hotcrm#1613); the local assertion currently standing in for these stays in place there.The gap
lintLivenessPropertiesonly considers a ledger row whenshouldWarnaccepts it:describe(entry)then mapsstatus: 'dead'toLIVENESS_DEAD_PROPERTYandstatus: 'live-elsewhere'toLIVENESS_LIVE_ELSEWHERE_PROPERTY. Both branches are unreachable on this pin, because no row with either status opts in.Enumerating every
propsentry (children included) across all 36 files in@objectstack/spec/liveness/:Ten rows, all
plannedorexperimental. Zerodead, zerolive-elsewhere. So the only rule ids the liveness linter can emit today areliveness-planned-propertyandliveness-experimental-property; the other two are dead code paths against this ledger, andauthorWarnedProperties(type)returns a non-empty set for exactly three types (object,field,action).Measurement, with a working control
One committed tree (hotcrm at
a0362a37), one injection at a time, restored by blob hash. Clean-tree baseline:0 error(s), 17 warning(s), 12 suggestion(s), exit 0.externalSharingModel: 'private'oncrm_account(a row that DOES setauthorWarn)liveness-planned-propertywarning, exit 0list.tabs: [{ name, label }, …]oncrm_account's list viewliveness-*finding — 17 warnings, exit 0, byte-identical to baselineThe control fires in the same harness on the same command, so the second row is a reading about the ledger, not about the linter being off.
The second half, and it points the other way
hotcrmcarriestest/view-tab-label-inert.test.ts, which asserts that nolist/listViewsblock declarestabsat all. Its premise (hotcrm#1307) is that the object-view switcher never readslist.tabs, so authoring one is inert metadata. The pinned ledger disagrees:So even if the
deadrows were opted in, this particular key would be judged live and the rule still would not fire on it. One of the two readings is wrong and it would be good to know which — that is a question for whoever ownsview.json, not something this survey can settle. (Notelist.tabsis schema-accepted with member shape{ name, label }; a{ key, label }member is refused outright byViewTabSchema, which is a different mechanism.)Suggested direction, not a prescription
Two separable asks:
deadandlive-elsewhereverdicts should implyauthorWarnrather than requiring a per-row opt-in. Ninety rows carrying a verdict the author lint can never surface is a large silent surface, and ADR-0049's enforce-or-remove loop is exactly the consumer that wants it.view.json'slist.tabsverdict against the shipped switcher.LIVENESS_DEAD_PROPERTYandLIVENESS_LIVE_ELSEWHERE_PROPERTYare currently untestable end to end for the same reason, so whichever way (1) goes, a pin proving each id can be produced would keep this from regressing quietly.Related, and why this is not a duplicate
lint-liveness-properties.tsdescribe() has not been taught thelive-elsewhereverdict — throws by design the day such a row opts intoauthorWarn#14057 (closed) —describe()had not been taught thelive-elsewhereverdict and would throw "the day such a row opts intoauthorWarn". That card anticipated this exact state from the other side; it fixed the throw, not the reachability. On this pin no row has opted in, so the branch is still never taken.ActionSchema.operation/patchfromplannedtoliveonce the runtime executor lands, evidence anchored on the runtime reads (follow-up of #14092) #15080 (open) — flippingActionSchema.operation/patchfromplannedtolive. Different rows, different direction.getNested) has no warned subject left, so it is now untested #7079 (closed) — the array fan-out having "no warned subject left" is the same shrinking-opt-in-set symptom, one helper down.Refs: hotcrm#1613 · hotcrm#1582 (the family card) · hotcrm#1307 (the
list.tabspremise)