refactor(lint): one reference-integrity entry point + the HotCRM residual audit (#3583 D5/§6.4) - #3818
Merged
Merged
Conversation
D5) Six rules answering the same question — "does this name resolve to anything?" — were wired by hand into three CLI commands. `validate`, `lint` and `compile` each carried their own import list and their own call site, so landing a rule meant remembering three places, and forgetting one meant the same stack got a different verdict depending on which command the author ran. The assessment (§2.2) named that drift as the enemy and (§5 D5) asked for this entry point once Phase 2 landed; six rules in, it has cost three hand-edits per rule. `validateReferenceIntegrity(stack)` runs `REFERENCE_INTEGRITY_RULES` in order and returns the concatenated findings. Adding a rule is now a one-line edit to that list — the commands do not change. `lint.ts` loses five near-identical for-loops; `validate.ts`/`compile.ts` lose their six-way spreads. Membership is deliberately a written-out list, mirrored by a test, so a rule joins the suite by review rather than by accident. What belongs: rules that resolve a NAME against what a stack declares. What does not: shape checks (view containers, responsive styles, seed replay/state) — a different question with its own call sites. The suite test runs one live instance per member and asserts a finding from each. A suite that silently stops invoking a member is indistinguishable from a clean stack — exactly how the dead quick-actions check in #3684 passed its own tests. `os doctor` is NOT converted: it runs only `validateWidgetBindings` and is an environment health check, not an authoring gate, so adopting the suite there is a product decision about what `doctor` is for. Named in the module doc so the remaining asymmetry stays visible. Behaviour-preserving: identical findings on all three example apps (zero) and on the HotCRM corpus (24, unchanged per rule). Refs #3583 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015r31hzuSN19iK8ATRPcCpS
… (§6.4) §6.4 asked for a re-run of the audit after the rules landed, so the issue could close with an explicit accepted-gap statement instead of silence. This is that re-run, against the same corpus the original audit read (hotcrm @ 8b28fa2, v2.2.2, ~18k lines). Results: 23 findings, no false positives, mapping category-for-category onto the issue's list — `optionsFrom.object: 'user'` on four dashboards, the three undefined bulk actions plus two row actions, four report axes naming raw fields, the `sys_approval_process` navigation, two nav-exposed objects granted by none of the six permission sets, and six orphan translation keys. Three things worth recording beyond the counts: - Categories 4 and 5a report zero, and the zero is real: injecting a bogus field into HotCRM's `record:highlights` components moves the page rule from 0 to 3, so the walk reaches the pages and the corpus is simply clean there. - The corpus caught a false-positive class before release (~40 `_views` keys), because a view record is a container whose object binding lives inside `list.data.object`. The examples missed it since none of their bundles carry `_views` keys at all — so §6.1's false-positive floor has to be read per branch, not per rule. - HotCRM no longer parses against the dev spec at all (24 Zod errors: `enable.trash`/`enable.mru`, `body` on non-script actions). Pre-existing drift, unrelated to this work, but it is why the lint path taking raw config matters: an app pinned to a published spec can be un-`validate`-able against main. Accepted gaps are named with their evidence: hook body write sets (D4, documented at the authoring surfaces), knowledge indexes (D1 — no definition site, 2 refs unresolvable by construction), skill hand-off (free-form prompt text, §7 non-goal), and R8's option-value literals (Tier-B, needs a verification note first). The one buildable gap left is R7: HotCRM declares 8 agent→skill and 16 skill→tool references against a stack with zero tools — every one dead, as the original audit reported. It needs D1 and D2 decided first, which are spec questions rather than lint ones, so it belongs in its own issue. §5 D5 is also marked done, pointing at the suite entry point. Refs #3583 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015r31hzuSN19iK8ATRPcCpS
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 19 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
The suite adds public API to @objectstack/lint (validateReferenceIntegrity, REFERENCE_INTEGRITY_RULES and its types), so it releases as a minor there and a patch on the CLI, whose behaviour is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015r31hzuSN19iK8ATRPcCpS
os-zhuang
marked this pull request as ready for review
July 28, 2026 07:20
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#3583 收口的两件事:方案 §5 D5(接线漂移)和 §6.4(残留审计)。没有新规则。
1. D5 —— 一个入口,终结接线漂移
六条回答同一个问题(「这个名字解析得到东西吗?」)的规则,此前是手工接进三个命令的:
validate/lint/compile各自维护一份 import 和一处调用点。落一条规则要记三个地方,漏一个就意味着同一个 stack 因为作者跑了哪个命令而得到不同结论 —— 方案 §2.2 点名的正是这个。六条规则下来,每条都付了三次手工编辑的代价。validateReferenceIntegrity(stack)按序跑REFERENCE_INTEGRITY_RULES并合并结果。加规则从此是这个列表里的一行,命令侧不动。lint.ts少了五段几乎一样的 for 循环,validate.ts/compile.ts少了六路 spread。两处刻意的设计:
os doctor刻意不改:它只跑validateWidgetBindings,本质是环境体检(node 版本、配置存在性、循环 lookup),不是作者侧门禁;给它接套件是「doctor 是干什么的」这个产品决策,不是接线清理。这条残留不对称写进了模块文档,免得被重新发现一遍。行为不变:三个示例应用(0)与 HotCRM 语料(逐规则计数不变)结果完全一致。
2. §6.4 —— HotCRM 残留审计
对着原审计读的同一份语料(hotcrm @
8b28fa2,v2.2.2,~18k 行)重跑,23 条 finding,未见误报,逐类对上 issue 原文:object-reference-unknownoptionsFrom.object: 'user'(平台对象是sys_user)action-name-undefinedmass_update/mass_delete/assign_owner+edit/deletechart-measure-unknownyAxis写amount/case_number而非 measuretranslation-target-unknownapps.crm_enterprise.navigation.group_products/.group_analytics× 3 localeobject-reference-unregistered-platformsys_approval_processnav-object-ungrantedcrm_knowledge_article、crm_forecast六个权限集都没授予三件值得单独记的事:
record:highlights组件注入一个假字段,页面规则从 0 变 3 —— 遍历确实走到了页面,语料在这两类上现在就是干净的(HotCRM 在审计之后已经修过)。_views键被误报,因为 view record 是容器,对象绑定在list.data.object里而不是记录根上。示例应用漏掉它,是因为它们的 bundle 一条_views键都没有 —— 所以 §6.1 的误报地板得按分支读,不能按规则读。enable.trash/enable.mru、非 script action 带body)。既有漂移、与本次无关,但它说明了 lint 走原始配置这件事的价值:一个钉在已发布 spec 上的应用,可能对着 main 根本validate不了。明确接受的缺口(各自带证据):hook body 写入集(D4,已在作者界面标注)、knowledge indexes(D1 —— stack 里没有定义位,2 条引用天然不可解析)、skill 交接(自由文本,§7 非目标)、R8 选项值字面量(Tier-B,需先出验证说明)。
唯一还能建的缺口是 R7:HotCRM 声明了 8 条 agent→skill、16 条 skill→tool 引用,而这个 stack 声明的 tool 数是 0 —— 16 条全是死的,和原审计说的一模一样。但它得先定 D1(spec 加定义位,还是标
[EXPERIMENTAL])和 D2(内核运行时注册的 skill/tool 怎么静态看待),这两个是 spec 决策不是 lint 决策,所以应当独立成 issue 而不是把 #3583 一直挂着。验证
eslint、check:nul-bytes、check:doc-authoring干净;lint / cli build(含 DTS)通过。Refs #3583 · #3806(R6)· #3698(R5)· #3684(Phase 2)· #3657(Phase 1)· #3640(方案)
🤖 Generated with Claude Code
https://claude.ai/code/session_015r31hzuSN19iK8ATRPcCpS
Generated by Claude Code