Skip to content

test(dogfood): 字段文件回收的真实启动端到端证明 — ADR-0104 PR-5b 全周期 + 两条否决路径 (#3459) - #4285

Merged
os-zhuang merged 1 commit into
mainfrom
claude/adr-0104-d3-wave2-eval-2v9jsy
Jul 31, 2026
Merged

test(dogfood): 字段文件回收的真实启动端到端证明 — ADR-0104 PR-5b 全周期 + 两条否决路径 (#3459)#4285
os-zhuang merged 1 commit into
mainfrom
claude/adr-0104-d3-wave2-eval-2v9jsy

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

GA 前对全平台唯一会删用户字节的代码做的最后一道确认。做成常驻 dogfood 证明而不是一次性手工演练——手工确认的信心会随每次重构衰减。

缺口

附件血统从 #2755 起就有端到端证明(attachments-permission-matrix.dogfood.test.ts)。字段文件血统一条都没有——而它走的是完全不同的路线:独占所有权、由写路径释放、按部署门禁。此前它的全部保障只有 fake 之上的单元测试。

五条断言,全部在真实 kernel 上(真实存储适配器、真实字节落盘、真实 lifecycle.sweep()

断言 内容
PRECONDITION 本次启动创建的库已完成出生认证——所以门禁是开的,后面四条不会真空通过。同时它是 #3438 出生认证的活体确认:以前只有 fake 覆盖
CYCLE 释放即打墓碑 → 窗内 sweep 不动它 → 过期后行与字节双双消失
REVIVAL 窗内重新引用则复活,且不再被 sweep 视为候选
VETO(所有权) 绕过 hook 直接改驱动重新认领 → guard 扫描时复核发现有主,un-tombstone 并否决,字节完好。这正是 #3459 锁死的"两半必须同一个 change"里的那半
VETO(门禁) 标记回退(模拟一次失败的 --apply)→ 已过期的墓碑仍被保留、字节保留、无需重启;恢复标记后同一行立刻被回收

最后一条是 PR-5b 里那个设计决定的实证:guard 每次 sweep 都新读标记,而不是复用 release 路径的 memoized 读取——所以数据漂移的部署能立即自行关门。这条此前完全没有真实环境覆盖。

fixture 显式引入 PlatformObjectsPlugin

sys_migration#4243 从 storage 的关切变成了平台基础设施,不引它就读不到门禁。同时补上 dogfood 缺失的 workspace 依赖声明。

过程中的一个诊断修正(值得记)

新测试单跑通过、混在整套里却全挂,我起初判断为"测试隔离问题"——这个判断是错的。真实原因是我的验证被过期的构建产物骗了:单跑那次用的还是我早先构建的 service-storage dist,那个版本仍然自己注册 sys_migration。重装依赖、按当前源码重建后,fixture 缺插件的问题才浮出来。教训是本地 dist 与当前 main 不同步时,绿色可能是假的。

验证

  • 新测试单跑 5/5
  • 整个 dogfood 套件 430 通过 / 3 跳过 / 零失败(确认不是隔离侥幸)
  • 全量 pnpm test 132/132 · nul-bytes ✅ · lint ✅
  • 空 changeset(test-only,不发版)

🤖 Generated with Claude Code

https://claude.ai/code/session_01KM46HedAWLbkuZyMBXn5wJ


Generated by Claude Code

…3459, #3438)

The released-file collection path is the only code on the platform that deletes
a user's bytes, and its only proof was unit tests over fakes. Its sibling
lineage — attachments — has had an end-to-end proof since #2755; field files,
which reach collection by a different route (exclusive ownership, released by
the write path, gated per deployment), had none.

Five properties, on a real kernel with a real storage adapter, real bytes on
disk and a real lifecycle.sweep():

  PRECONDITION  a store created by this boot has already attested its file
                migration — so the gate is open and the rest cannot pass
                vacuously. Doubles as a live check of #3438's creation-time
                attestation.
  CYCLE         release tombstones; a fresh tombstone survives a sweep; past
                the declared window the row AND the bytes are reclaimed.
  REVIVAL       re-referencing inside the window brings the file back.
  VETO (owner)  a tombstone that regained an owner behind the hooks' back is
                un-tombstoned, never reaped — the half of PR-5b that had to
                ship with the tombstone itself.
  VETO (gate)   a regressed migration flag stops collection immediately,
                tombstones included, without a restart; re-verifying makes the
                same row collectable again. This is what the guard's fresh
                sweep-time flag read buys, and nothing else covered it.

The fixture boots PlatformObjectsPlugin explicitly: sys_migration became
platform infrastructure rather than a storage concern in #4243, and the gate
cannot be read without it. Adds the matching workspace dependency.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KM46HedAWLbkuZyMBXn5wJ
@vercel

vercel Bot commented Jul 31, 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 Jul 31, 2026 2:55am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file tests tooling size/m labels Jul 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/dogfood.

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

  • content/docs/permissions/authorization.mdx (via packages/qa/dogfood)
  • content/docs/permissions/delegated-administration.mdx (via packages/qa/dogfood)

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.

@os-zhuang
os-zhuang marked this pull request as ready for review July 31, 2026 03:18
@os-zhuang
os-zhuang merged commit 1b04fd2 into main Jul 31, 2026
19 checks passed
@os-zhuang
os-zhuang deleted the claude/adr-0104-d3-wave2-eval-2v9jsy branch July 31, 2026 03:19
os-zhuang added a commit that referenced this pull request Jul 31, 2026
#4298)

`packages/qa/dogfood/test/field-file-collection.dogfood.test.ts` declares
`@proof: adr0104-field-file-collection`, but the ADR-0054 registry never
learned about it — so the liveness gate has been warning on every run since
#4285 landed the test:

    ⚠ 1 unregistered dogfood proof tag(s) — add to proof-registry.mts

That warning is the registry's reverse-integrity leg doing exactly its job:
an orphan tag means a proof was written but never wired into the high-risk
class list, so nothing connects the test to the surface it guards.

Registered as **honestly unbound**, not bound. The collection contract the
proof exercises is the `lifecycle` ttl declared on the SYSTEM object
`sys_file` (service-storage), not an authorable per-type property — and
`object.lifecycle` is already bound to `data-lifecycle`, which carries one
`proof` ref. The rest of the route (exclusive ownership, release-on-write,
the per-deployment migration gate) is service code with no authorable
surface to govern, so there is no other entry to bind instead. Faking a
binding would misrepresent both what the proof covers and what that entry
is proven by; `blockedReason` records the reason instead, the same shape as
`flow-runas-userless` and `scope-depth-cli-fallback`.

Enforcement is therefore unchanged: `BOUND_PROOF_PATHS` skips unbound
classes, so the bound-class list and the pinned path map are byte-identical.
The only behavioural change is that the gate now runs clean.

Verification: `check:liveness` green with the warning gone; liveness script
suite 98/98; spec suite 277 files / 7128 tests; `tsc --noEmit` clean.
Gate-only change under `packages/spec/scripts/`, which is not in the
package's published `files` — empty changeset.

Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants