refactor(spec)!: 按 ADR-0049 摘除 IStorageService.list(prefix) —— 零消费方、双适配器语义分叉 (#5540) - #5983
Merged
Merged
Conversation
One contract method, two adapter dialects, both silently incomplete, and no caller. ADR-0049 enforce-or-remove; maintainer ruling 2026-08-05 on #5266. Same disposition as IDataDriver.findStream (#4484): a TS/API contract that code IMPLEMENTS and nothing ever .parse()s, so there is no tombstone and no D2 source rewrite -- tsc is the channel and it reports at the call site. The retirement is registered as the ADR-0087 D3 semantic entry `storage-service-list-retired` in the protocol-17 chain step. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 113 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This was referenced Aug 6, 2026
Closed
Open
…through (#5540) The contract member it forwarded to is gone, so `this.inner.list` no longer type-checks. That break is CI-visible through tsup's DTS step (which runs tsc), not through a `typecheck` script -- `@objectstack/service-storage#build` failed and took Build Core / Test Core / Dogfood with it. PM ruling on #5540: land the contract removal and its only caller's deletion atomically, so `main` is never red. Scope is the proxy passthrough plus its two test sites only; the adapters' own `list` implementations stay for #5541. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
Contributor
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31114891451 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
This was referenced Aug 6, 2026
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.
Fixes #5540
按 ADR-0049 enforce-or-remove 摘除
IStorageService.list(prefix),并原子地删除它在仓内的唯一调用方(SwappableStorageService透传)。维护者 2026-08-05 在 #5266 批准方案 2(摘除);落地方式经 PM 2026-08-06 裁决为「合并为一个 PR」(见下)。为什么摘
一个契约方法,两个自带适配器两种语义,而且双双静默给不完整答案:
LocalStorageAdapter.list是单层readdir—— 嵌套 keya/b/c在list('a')下看不见(只回a/b),而且stat成功的子目录被当成文件推进结果,产出一个size是目录 inode、download()根本取不到的StorageFileInfo;S3StorageAdapter.list是递归的(ListObjectsV2按整串 key 前缀匹配),且不读IsTruncated/ContinuationToken—— 超过 1000 个对象时调用方拿到的「全部文件」其实是第一页,没有任何信号。同一个调用在一种部署上是「单层 + 混入目录」、在另一种上是「递归 + 静默截断」,第一个真正需要枚举前缀的功能(备份 / 孤儿清理 / 迁移校验)会拿到两个不同的错误答案,两边都不报错。#5172 差点就是那个功能:原计划用
list(EMAIL_ATTACHMENT_KEY_PREFIX)驱动附件回收,发现本地适配器连一层都看不下去,改走队列延迟任务。原子落地依据(PM 裁决,2026-08-06)
本 PR 最初只做 spec 面。CI 实测证明那样不可能绿,于是按 #5540 上的 PM 裁决评论 扩为原子落地。
实测证据:契约成员一摘,
SwappableStorageService按IStorageService类型读this.inner.list(那是调用方,不是「类上多余的方法」)立刻不再 type-check:而这会让 CI 红 —— 不经由
typecheckscript(@objectstack/service-storage确无该 script,挂在check-type-check-coverage.mjs的 DEBT 账本上),而是经由 tsup 的 DTS 生成步骤(它跑 tsc 产.d.ts),TypeScript Type Checkjob 先pnpm build就炸在@objectstack/service-storage#build,并连带Build Core/Test Core (1-3/3)/Dogfood (1-3/3)全红 —— 同一根因,11 个红 job。上一版 commit(521b6fb)的实测读数就是如此。顺便订正 issue 正文一句:「TS 层面…故本单可独立先行」前半句成立(适配器类上多余方法不是错误),后半句不成立(代理是调用方)。也因此 #5266 原定的
#5541 Blocked-by #5540箭头是反的:list?()在契约上是 optional,先删代理透传是绿的,先摘契约成员反而不行。本 PR 的 services 面范围严格限定为 PM 裁决申报的那部分:
swappable-storage-service.ts—— 删除list()透传(留注说明去向);swappable-storage-service.test.ts—— 删掉forwards list()用例,并从「rejects optional methods」用例里去掉proxy.list那一行(其余三个 optional 方法仍在测);local-storage-adapter.test.ts—— 去掉expect(typeof storage.list)(它断言的正是被退役的「契约宣称 list」)。⛔ 双适配器(local / S3)的
list实现没有动,测试替身FakeAdapter.list也没动 —— 类上多一个方法合法且可编译,那是 #5541 重定价后的剩余范围(#5541 已留注)。前提重验
「零消费方」在本仓成立。 对
origin/main用带引号精确名逐条排除:IStorageService的全部命中,除packages/spec声明与其自身测试外,全部落在packages/services/service-storage内部;REST / CLI / storage-routes / 插件均无调用点;skills/、docs-import-surface.baseline.json零命中。零命中做了反查证伪「扫描器坏了」:同一命令查邻近成员getInfo返回 13 个文件。兄弟仓:⚠️
objectui实测干净(查storage作反查返回大量命中)。cloud本会话无法核验:add_repo报无访问权限,GitHub code search 对该仓连必然存在的对照词(objectstack/defineStack)都返回 0,所以它的 0 不构成证据 —— 如实标注,不冒充已验。退役路线:为什么没有 tombstone,为什么是 D3 而不是 D2
spec-property-retirementplaybook 主要覆盖 authorable property(Zod schema 里的 key)。IStorageService不是 —— 它是纯 TypeScript 契约,由代码 implements,没有任何地方.parse()过一个 storage adapter。所以:retiredKey()tombstone(playbook §2 第三行「nothing parses it → neither」):处方没有接收者就是噪声,唯一能承载处方的通道是tsc,它报在调用点 —— 本次 CI 恰好把这条演示了一遍;storage-service-list-retired(protocol-17 step 的semantic[]),带reason+acceptanceCriteria,于是spec-changes.json、生成的 upgrade guide、spec_changesMCP 工具都携带它。仓内有逐条对应的先例:
IDataDriver.findStream(#4484)—— 同样 TS 契约成员摘除、同样零调用方、同样「no tombstone / no source rewrite / tsc 是通道」,登记为同一 step 的data-driver-find-stream-retired。本条reason显式点了这层同构。四张 ratchet 零变化 —— 路线决定的预期读数,不是漏做
先定路线再看读数:本次既不是枚举值收窄也不是整 def 删除,而是一个 TS 接口成员的摘除。
api-surface.json记的是导出面(IStorageService (interface)仍在,打印的是类型引用不是展开形状),authorable-surface.json/json-schema.manifest.json记 Zod 声明面,api-surface-signatures.json只覆盖defineX函数 —— 四张对「接口少一个成员」结构上不可见。实测吻合:12 张 gate 逐条捕获退出码,只有check:spec-changes/check:upgrade-guide因新增 D3 条目变红,重生成后check:generated报 10/10 全绿。反向验证(先定方向,再跑)
预测方向红且量级明确:把
list?()放回接口 → 两条@ts-expect-error变 unused(TS2578 x2)→check:test-typecheck这张精确 ratchet 该文件从 1 涨到 3。实测:方向与量级都对上,pin 是活的(不是「因为什么都没产出所以绿」的空 pin)。已还原。
文档
两页手写文档同步(它们原样在教
list?(prefix)怎么调,含可复制示例;契约摘掉而文档不动正是 Prime Directive #10 的「advertise a capability the runtime doesn't deliver」):content/docs/kernel/contracts/storage-service.mdx、content/docs/kernel/runtime-services/storage-service.mdx。两页都不是生成物,content/docs/releases/一个字没碰。验证
一条读数值得记:service-storage 手跑
tsc --noEmit现为 41 条(全部先于本 PR 存在)。我早先记过 72 —— 那次是在依赖未构建时测的,@objectstack/types/@objectstack/observability未解析导致 TS7006 噪声级联(AGENTS.md 点名的 NodeNext 陷阱)。归因之所以没被这 31 条噪声带偏,是因为我按名字('list')grep 而不是信总数。