Skip to content

refactor(spec)!: 按 ADR-0049 摘除 IStorageService.list(prefix) —— 零消费方、双适配器语义分叉 (#5540) - #5983

Merged
qq9340100 merged 2 commits into
mainfrom
claude/issue-5540-retire-istorageservice-list
Aug 6, 2026
Merged

refactor(spec)!: 按 ADR-0049 摘除 IStorageService.list(prefix) —— 零消费方、双适配器语义分叉 (#5540)#5983
qq9340100 merged 2 commits into
mainfrom
claude/issue-5540-retire-istorageservice-list

Conversation

@qq9340100

@qq9340100 qq9340100 commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Fixes #5540

按 ADR-0049 enforce-or-remove 摘除 IStorageService.list(prefix),并原子地删除它在仓内的唯一调用方(SwappableStorageService 透传)。维护者 2026-08-05 在 #5266 批准方案 2(摘除);落地方式经 PM 2026-08-06 裁决为「合并为一个 PR」(见下)。

为什么摘

一个契约方法,两个自带适配器两种语义,而且双双静默给不完整答案:

  • LocalStorageAdapter.list 是单层 readdir —— 嵌套 key a/b/clist('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 裁决评论 扩为原子落地。

实测证据:契约成员一摘,SwappableStorageServiceIStorageService 类型读 this.inner.list(那是调用方,不是「类上多余的方法」)立刻不再 type-check:

src/swappable-storage-service.ts(75,27): error TS2339: Property 'list' does not exist on type 'IStorageService'.
src/swappable-storage-service.ts(78,23): error TS2339: Property 'list' does not exist on type 'IStorageService'.

而这让 CI 红 —— 不经由 typecheck script(@objectstack/service-storage 确无该 script,挂在 check-type-check-coverage.mjs 的 DEBT 账本上),而是经由 tsup 的 DTS 生成步骤(它跑 tsc 产 .d.ts),TypeScript Type Check job 先 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-retirement playbook 主要覆盖 authorable property(Zod schema 里的 key)。IStorageService 不是 —— 它是纯 TypeScript 契约,由代码 implements,没有任何地方 .parse() 过一个 storage adapter。所以:

  • 不加 retiredKey() tombstone(playbook §2 第三行「nothing parses it → neither」):处方没有接收者就是噪声,唯一能承载处方的通道是 tsc,它报在调用点 —— 本次 CI 恰好把这条演示了一遍;
  • 不做 ADR-0087 D2 conversion:adapter 是代码,永不是 stack metadata,链上没有源可重写;
  • 做 ADR-0087 D3 semantic migration:新增 storage-service-list-retired(protocol-17 step 的 semantic[]),带 reason + acceptanceCriteria,于是 spec-changes.json、生成的 upgrade guide、spec_changes MCP 工具都携带它。

仓内有逐条对应的先例: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:generated10/10 全绿

反向验证(先定方向,再跑)

预测方向且量级明确:把 list?() 放回接口 → 两条 @ts-expect-error 变 unused(TS2578 x2)→ check:test-typecheck 这张精确 ratchet 该文件从 1 涨到 3。实测:

check:test-typecheck: 1 problem(s)
  - src/contracts/storage-service.test.ts: 3 type error(s), ledger records 1 — the debt GREW. Fix the 2 new one(s)

方向与量级都对上,pin 是活的(不是「因为什么都没产出所以绿」的空 pin)。已还原。

文档

两页手写文档同步(它们原样在教 list?(prefix) 怎么调,含可复制示例;契约摘掉而文档不动正是 Prime Directive #10 的「advertise a capability the runtime doesn't deliver」):content/docs/kernel/contracts/storage-service.mdxcontent/docs/kernel/runtime-services/storage-service.mdx。两页都不是生成物,content/docs/releases/ 一个字没碰

验证

pnpm --filter @objectstack/service-storage... build       # DTS ⚡️ Build success(此前正是这里红的)
pnpm --filter @objectstack/service-storage test           # 21 files / 282 tests 全过
pnpm --filter @objectstack/spec typecheck                 # 绿(tsc --noEmit + check:test-typecheck)
pnpm --filter @objectstack/spec test                      # 323 files / 8268 tests 全过
pnpm --filter @objectstack/spec check:generated           # All 10 generated artifacts are up to date.
npx tsc --noEmit(service-storage)                        # 无任何 list 相关错误残留
npx eslint packages/services/service-storage/src packages/spec/src/{contracts,migrations}   # exit 0
node scripts/check-nul-bytes.mjs                          # OK(5727 files,无裸控制字节)

一条读数值得记:service-storage 手跑 tsc --noEmit 现为 41 条(全部先于本 PR 存在)。我早先记过 72 —— 那次是在依赖未构建时测的,@objectstack/types / @objectstack/observability 未解析导致 TS7006 噪声级联(AGENTS.md 点名的 NodeNext 陷阱)。归因之所以没被这 31 条噪声带偏,是因为我按名字('list')grep 而不是信总数。

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
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 6, 2026 2:45pm

Request Review

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/service-storage, @objectstack/spec.

113 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/api/plugin-endpoints.mdx (via @objectstack/service-storage)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/service-storage, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/service-storage, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/service-storage, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

…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
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 31114891451 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Console Pin Gate — 失败步骤: Set up job(日志不可读,点进 job 看)

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 13 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec: 按 ADR-0049 摘除 IStorageService.list(prefix) 契约成员(零消费方,双适配器语义分叉 —— #5266 方案 2,维护者已批)

2 participants