feat(spec)!: view 面收紧 —— #4001 最后一批 - #4534
Merged
Merged
Conversation
Views are the surface an author iterates on visually, which is why a dropped key
hides here: the view still renders, just not the way it was described.
`FormFieldBase` / `FormSection` / `FormButtonConfig` closed years ago under
ADR-0089 D3a; the other forty-odd shapes kept the posture those three were
rescued from.
`defineView`'s guard was another one-door workaround — its own comment says
"`ViewSchema` strips unknown top-level keys, so a flat list view would parse to
an empty container". Fifth bespoke guard this campaign has found built around
silent stripping, and like all of them it covered exactly one door. The
rejection now lives in the parse, reaches both, and carries the wrap
instruction. The guard stays for what strict cannot see: `defineView({})`.
Three shapes deliberately left open, each with its reason in the file rather
than a silent skip: `FormSectionSchema` (already closed with its own error map
and a transform), `UserFiltersSchema` (deliberately strips page-only keys, with
a test asserting it — changing that needs its own verification), and the
flattened Studio overlay in `ViewMetadataSchema`, which must stay open.
That last one is the `.extend()` trap the ledger warned about: closing
`ListViewSchema`/`FormViewSchema` silently closed the overlay too, turning a
shape the platform itself writes into a 422. Both `.strip()` back. It was caught
by a test whose name is the whole contract — someone wrote "preserves auxiliary
Studio round-trip keys without a strict-mode 422" before this campaign existed.
The container's `name`, `label` and `object` were tombstoned in the first draft
and should not have been: the platform writes all three. Fourth false guidance
claim in three batches. What fixed it was not more care but a different method —
scan every real payload of the shape in the repo and keep only the entries no
real payload contradicts. Six of nine survived. A tombstone asserts "nothing
legitimately writes this key", which is an empirical claim, and the codebase can
be asked.
`view` is the end state, not the last item of debt: its registered schema is a
union of three runtime shapes and a union is only as closed as its most open
member — the Studio overlay, a wire shape wearing the same type name. So the
campaign finishes at 24 of 25 closed with the 25th a documented permanent
exception, recorded beside the reverse pin so nobody closes it by force.
Verified: full monorepo suite 132/132 tasks green, spec 7241 tests, tsc clean,
8 generated artifacts current, all 10 spec gates green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY
os-zhuang
marked this pull request as ready for review
August 1, 2026 21:34
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
os-zhuang
enabled auto-merge
August 1, 2026 21:34
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This was referenced Aug 2, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Aug 17, 2026
…fact, and eleven drifted rows are reconciled (objectstack-ai#7377) (objectstack-ai#7455) Closes objectstack-ai#7377. Two stages, in order — the reconciliation first, because doing the mechanism first would have made CI red with no path to green. STAGE 1 — per-row reconciliation. The table declares its own counting method (`check-liveness.mts --json`, fixed in objectstack-ai#4488) and says the count columns are never hand-edited. Nobody re-ran the snippet: 9 of 30 rows disagreed with the gate when objectstack-ai#7377 was filed, and PR objectstack-ai#7425 added two more. Every delta is measured and explained rather than absorbed: - field/action/hook/page/seed/webhook — the ADR-0010 protection envelope entered each schema's walked shape as the objectstack-ai#4001 campaign closed it (objectstack-ai#4514/objectstack-ai#4530/objectstack-ai#4531/objectstack-ai#4533/objectstack-ai#4974); the gate auto-classifies those keys `live`. - flow — +1 dead: errorHandling.retryDelayMs, tombstoned by objectstack-ai#4964's rename to `backoffMs`. The Note said "4 tombstone entries"; it says 5. - action — +1 live beyond the envelope: `description` (objectstack-ai#7367 / PR objectstack-ai#7430). - view — objectstack-ai#4001 batch 6e (objectstack-ai#4534) declared three container-level keys the Note never mentioned: `object` live, `name`/`label` dead. The hand-enumerated dead set was 4 against a real 6. - app — dead 9 still reconciles exactly; +1 live `_unpublished` (objectstack-ai#4829 / PR objectstack-ai#6942) and the type's first `planned`, `navigation.runAction` (objectstack-ai#4848 / PR objectstack-ai#7253). - job — zero dead under objectstack-ai#7425. First row in the table where that holds WITH the ADR-0033 exemption still in force; the Note says so. - translation — zero dead under objectstack-ai#7425. Its cell had published `dead 2` beside a sentence naming one key, and that key had already been removed in objectstack-ai#4667: the number was right and the prose false, in the same cell, on the day it was written. Recorded as measured history. The other 24 Notes are byte-identical. Five rows carried a SEVENTH cell against a six-column header, which GFM drops — that prose rendered nowhere, and the 2-column table folds it back into the Notes cell losslessly. STAGE 2 — the mechanism, on objectstack-ai#5107's precedent. The numbers move to `packages/spec/liveness/state-counts.md` (`merge=os-regen`, registered in `scripts/regen-artifacts.mjs` and `.gitattributes`); the Notes prose stays hand-written, because regenerating a Note manufactures a verdict. `gen:liveness-counts` spawns the gate rather than re-implementing its walk, and keeps objectstack-ai#7257's skeleton row. `check:liveness` gains three legs in `readme-table.mts`: artifact freshness by byte comparison, README-vs-artifact row sets in both directions, and a count column reappearing in the README — which the other two cannot see. Reverse verification (predicted, then run against a mutated `--ledger-root` copy): artifact deleted => MISSING, red; `view` live skewed 80->81 => STALE naming that line, red; a count column re-added to the `object` row => red naming the line; the `qa` row deleted => red on both the objectstack-ai#7257 heading and the new row-set heading. Verbatim copy green. Gates: liveness script tests 9 files / 186 tests; check-generated ledger tests 8/8; `check:liveness` green; `check:generated --reconcile-only` green; `git-merge-regen.mjs --self-test` green (12 paths); scripts typecheck clean; check-nul-bytes OK. Claude-Session: https://claude.ai/code/session_01PJn5BD9LY9ZCECPs25oiDb Co-authored-by: Claude <noreply@anthropic.com>
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.
#4001 最后一批(清单)。批 1–6d = #4514 → #4533,均已合并。
为什么 view 最会藏
视图是作者用眼睛迭代的那个面——所以一个被丢弃的键在这里最隐蔽:视图照样渲染,只是不是他描述的那个样子。
FormFieldBase/FormSection/FormButtonConfig早在 ADR-0089 D3a 就关了。文件里另外四十多个形状,保持的正是那三个当年被救出来的姿态。defineView的守卫,是第五个「只守一扇门」的补丁它的注释自己写着理由:
这是本战役找到的第五个为绕开静默剥离而建的 bespoke 守卫,而且和前四个一样——它只守了
defineView这一扇门。走元数据门(Studio / API / agent)的,拿到的是一个静默的空容器。现在拒绝在解析层,两扇门都覆盖,而且带上「该怎么包起来」的指令。守卫留着,管 strict 看不见的那个 case:
defineView({})——没有未知键,照样零视图注册。三处刻意不转,每处都把理由写进文件
FormSectionSchemastrictVisibilityError和归一化.transform()。转换 = 重写可用且有测试的行为UserFiltersSchematabs/showAllRecords,有测试钉着。改成拒绝需要单独验证——这批没验证就不动,点名留在文件里isPinned/sortOrder等往返键,saveMetaItem原样持久化第三处正是账本预告过的
.extend()陷阱:.extend()继承 strictness,关闭ListViewSchema让这个 overlay 静默变严——对平台自己写入的形状 422。抓到它的是一个测试的名字:
有人在这场战役存在之前,就精确命名了未来某次收紧会造成的失败。
action.permissions「没有权限键」requiredPermissions有,403 强制——照做会删掉一个正在工作的门action.location→locationsview.name/view.label@objectstack/objectqlview.objectgetViewsByObject()读它@objectstack/cli没有一次是靠写 schema 时发现的。
最后管用的不是「更小心」,是换方法:把仓库里每一个真实的 container 载荷扫一遍,只保留没有任何真实载荷反驳的 guidance 条目。九条活六条,杀三条——一条命令,而且能在第一次 CI 红之前抓到全部四个。
推论两条,已写进账本:拒绝信息里的散文是行为,不是文档(一条自信地写错的比没有更糟,因为作者没有理由怀疑它);关闭一个注册类型的影响半径是每一个解析那个类型的包。
🏁
view是终点,不是最后一笔欠债注册的
view是三个运行时形状的 union,union 的严格程度取决于它最开放的成员——那个成员就是上面那个 Studio overlay,一个穿着同一个类型名的 wire 形状。所以战役停在 24 / 25 关闭,第 25 个是有据可查的永久例外。这段写在反向钉旁边,免得后来者「为了凑齐」硬把它关上。
关掉的是作者写的全部;留开的,正是账本开头那条分类规则要划的线——在最后一步以答案的身份出现,而不是以例外的身份。
战役结账
一个为「警告 strip 模式元数据」而建的层,几乎没有东西可警告了——那是棘轮走到头。 它的测试在原地写着:把下限改成 0 并刻意断言空集,永远不要删掉这个测试——因为一个没人选择的空结果,和一个坏掉的推导,长得一模一样。
验证
@objectstack/spec:7241 用例,tsc --noEmit干净check:*全绿object= live 附消费者;name/label= dead 的 body 键,行上的列才是活的)授权影响:这些形状没声明的键从「静默丢弃」变成「拒绝」——本来就已经被忽略。
🤖 Generated with Claude Code
https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY
Generated by Claude Code