Skip to content

[finding] Three data-model index rules put a positional config path in the finding's human-readable where slot, so the CLI renders the location as objects[44].indexes[1] #9600

Description

@os-sam

Recorded while taking the false-positive-budget measurement on #4716. Not fixed there — that dispatch changes no production file. Filed unassigned for triage.

The deviation

AuthoringFinding declares two distinct slots (packages/lint/src/authoring-rules.ts):

  • where"Human-readable location, e.g. object "leave_request""
  • path"Config path, e.g. objects[3].sharingModel"

Three registry adapters set both from the config path:

ruleregistry entryadapter line
lintUnscopedDeclaredIndexesauthoring-rules.tswhere: f.path
lintUniqueDeclarationsauthoring-rules.tswhere: f.path
lintLegacyOrganizationCompositesauthoring-rules.tswhere: f.path

All three implement in packages/lint/src/data-model-rules.ts and all three are tier: 'advisory'.

Measured

origin/main @ 1c6da6eaf, over the 45 object declarations @objectstack/platform-objects (44) and @objectstack/metadata-core (1) ship. 36 findings from these rules render their location as a bare index:

unique/unscoped-declared-index where = objects[44].indexes[1]
unique/legacy-organization-composite where = objects[44].indexes[0]

The object's name is not in where at any point. It is in the message ("sys_account" declares index [provider_id, account_id] with bare unique: true …), so no information is lost — but the location column is unreadable on its own, and every other object-scoped rule in the table spells it object "sys_account".

Why this reads differently than when it was written

Two things changed under it:

  1. Advisories now reach a human.SaveMetaItemResponseSchema.advisories ships ([P2/P3] 运行时授权门的 advisory findings 要进响应并被 Studio 渲染 + docs「the gate」补第四列(#4463 收尾) #4717) and objectui PR sys_migration 由 service-storage 持有,但它是平台级账本 —— 第二个消费者出现后这层耦合该解开 #4236 renders it. These strings are no longer CLI-only output beside a config file the author is looking at.
  2. All three carry surfaceReason: RUNTIME_OBJECT_WRITES_P2 — they are inside the group [P2] 运行时授权门扩到 object 写入 + 全量 runtime-safe 规则快照(#4463 P1 之后) #4716's first bullet proposes to put on the object write path. At that surface the index is an index into a per-write snapshot, not into the author's stack: their own object is objects[0] (or the last slot) no matter where it sits in their tenant. The number would be not merely unhelpful but wrong-looking.

A second, measured consequence

Any consumer that diffs findings across two stack shapes sees one finding under two identities, because the index moves. Taking the #4716 budget, comparing the per-write gate's added findings against the whole-stack verdict reported 36 phantom findings; normalising [digits] out of the fingerprint flipped all 36 to exact matches. They were the same findings at a shifted index.

⚠️ To be precise about blast radius: the runtime gate's own differential is not affected.buildRuntimeWriteSnapshots derives baseline and candidate from the same filtered array, so every non-written item keeps its index across both passes and its findings cancel correctly. This bites downstream consumers and anyone measuring the gate, not the gate.

Suggested shape

Give the three adapters the where the rest of the table builds (object "name", derivable from the same walk that produces f.path), and leave path exactly as it is. Low risk: where is display text, and the only in-repo consumer that keys on it is the gate's fingerprint, which reads whereandpath together and would be unchanged in behaviour by making where more specific rather than less.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions