Skip to content

docs(adr): ADR-0104 附录 —— #3438 剩余两个 strict 翻转的证据来源方案(v17 姿态) - #4188

Merged
os-zhuang merged 1 commit into
mainfrom
claude/d1-d2-strict-default-flip-vbly5z
Jul 30, 2026
Merged

docs(adr): ADR-0104 附录 —— #3438 剩余两个 strict 翻转的证据来源方案(v17 姿态)#4188
os-zhuang merged 1 commit into
mainfrom
claude/d1-d2-strict-default-flip-vbly5z

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

方案(针对 #3438,准备 v17 大版本发布)

#3438 的三块中,第 1 块(D1 媒体类型)已由 #3681 按部署翻转落地;本 PR 以 ADR-0104 附录(2026-07-30)的形式,为剩下两块提出方案。纯设计文档,不改任何运行时行为,不发布任何包(changeset 为空 frontmatter,沿用 adr-0104-design-doc-only 先例)。

先立一个时间线事实

warn-first 本身是随 17.0 首发的——D1/D2 的 changeset 都还在本次 pre-mode 发版窗口里,没有任何稳定版部署经历过哪怕一天的告警窗口。所以无论采用哪种门禁,17.0 都不能按版本号翻转任何默认值——那会直接吞掉 R2/R3 承诺的 warn 窗口,这一点独立于「按部署生效」的论证而成立。

D1 引用 + 结构化 JSON:证据可以存在,所以把它建出来

要证明的事实是「本部署存量数据中没有会被 valueSchemaFor(field, 'stored') 拒绝的值」。与调用方行为不同,存量数据是可枚举的——全量扫描即为完备证据,与 file-as-reference 的对账在认识论上同级。因此复用 #3617 的门禁形状(去掉回填步骤):

os migrate value-shapes        # dry run:只报告,不写任何东西
os migrate value-shapes --apply  # 零阻断项 → 记录 adr-0104-value-shapes 标记
  • 没有可转换的东西:畸形 location、存成展开对象的 lookup 只能由应用作者修,命令负责报告+处方,修完重跑到绿。--apply 唯一的写入就是标记行,保住 平台形态的迁移门禁:带自检的数据迁移 + 部署级标记 —— file-as-reference 回收与 strict 翻转都依赖它 #3617 的不变量(dry run 零写入;posture 是否改变从不取决于扫描结果)。机械归一化器(如展开对象折回 id)刻意不先建,等真实报告证明该形态足够普遍再说。
  • 扫描器与校验器共用同一个谓词(同类集合、同 valueSchemaFor、同 skip 规则),import 而非重推导——手抄值知识正是本 ADR 要治的病。
  • 门禁消费与媒体半边逐字对齐:引擎 memoized 读取、无相关字段的对象零开销、失败重跑清除 verified_at。新增逃生阀 OS_ALLOW_LAX_VALUE_SHAPES(PD#9 的 OS_ALLOW_* 形态),优先级同 mediaStrictEffective

新建数据库在创建时直接记账(两个标记都记)

现在只有迁移运行会记标记,导致 ≥17 新生部署永远从 lax 起步、warn 体制永不消亡。方案:创建空数据库的引导路径在创建时刻记录两个标记行。这不是变相按版本判断——被记录的事实(「此处不存在遗留值」)是被观察到的(创建时刻库确实为空),与 GC 门禁的 observed-transition 纪律同源。升级上来的老库非空、不记账、仍走扫描取证。附带收益:showcase/CRM 的 fresh 启动天生 strict,平台自己的 dogfood 成为 R2(契约比现实更严)的常驻金丝雀。

D2 动作参数:证据在原理上不可能存在,诚实的门禁是「声明过的大版本」

要证明的是「本部署没有调用方还依赖宽松契约」——调用方不可枚举(每月才跑的集成、无人记得的 cron、每次现构造调用的 AI agent)。存量数据可以扫完,未来的调用不能。任何「静默 N 天」标记都在断言它不可能拥有的完备性——这正是 2026-07-27 附录清除的「无处安放的门禁」错误在下一层的复现。我们拒绝建一个表演性的门禁。

剩下的就是 wave 2 已经用过的失败模式演算:D2 拒绝是响亮的(400 点名违规参数和声明清单)、面向调用方的(开发者/AI,不是被表单卡死的最终用户)、可恢复且可回退的(逃生阀)。不搁浅数据、不删字节——这正是 wave 2 第 4 步(写入切换)被允许骑大版本、而第 6 步(GC)必须等证据的那一类。

所以明确认账:D2 是按版本翻转,但在 warn 窗口之后的那个大版本(18.0),不是 17.0。17.0 首发 warn-first 并在发版说明中预告;17→18 的间隔就是每个部署按自己节奏经历的 warn 窗口(告警行已点名 OS_ACTION_PARAMS_STRICT_ENABLED=1,即「今天就选进明天默认值」的部署级动作)。18.0 翻转默认值,新增 OS_ALLOW_LAX_ACTION_PARAMS 逃生阀;OS_ACTION_PARAMS_STRICT_ENABLED 语义不变故无需改名(与媒体半边同一结论);dogfood 对偶用例翻转为「默认即严格」。

各版本承载什么

载体 内容
17.0 warn-first 首发;os migrate value-shapes + 门禁接线;新库创建时记账;发版说明预告 18.0 的 D2 默认值。无任何按版本号的默认值翻转
各部署 ≥17.0 os migrate value-shapes --apply(或生而记账)→ 自己的 D1 非媒体类翻 strict
18.0 D2 默认 strict + OS_ALLOW_LAX_ACTION_PARAMS

后续工作项(本 PR 合并后从 #3438 拆出)

  1. os migrate value-shapes 命令 + 引擎门禁接线 + 创建时记账(17.0 车次)
  2. 17.0 发版说明补 D2 预告段
  3. 18.0 车次:D2 翻转 + dogfood 对偶

Closes nothing — #3438 保持打开作为两个工作项的跟踪。

🤖 Generated with Claude Code

https://claude.ai/code/session_016zgA8CQMJeJbFEjnbib1Uv


Generated by Claude Code

…t flips (#3438)

The 2026-07-27 amendment split #3438 into three and settled only the media
half. This addendum decides the other two, differently, because their facts
have different epistemic reach:

- D1 references + structured JSON: the evidence CAN exist — data at rest is
  enumerable — so it gets the #3617 gate shape minus the backfill:
  `os migrate value-shapes` scans the covered classes with the validator's
  own predicate and, at zero blocking findings, records the deployment-level
  `adr-0104-value-shapes` flag that strict enforcement reads. New escape
  hatch `OS_ALLOW_LAX_VALUE_SHAPES`; precedence mirrors the media half.
- Fresh datastores attest BOTH flags at creation: emptiness is observed, not
  version-inferred, and without it every new deployment would re-enter the
  warn regime forever.
- D2 action params: the evidence CANNOT exist — callers are not enumerable —
  so the honest gate is a declared major with a served warn window: 17.0
  ships warn-first and the announcement, 18.0 flips the default with
  `OS_ALLOW_LAX_ACTION_PARAMS` as the opt-out. Owned explicitly as a
  version flip, justified by the same failure-mode calculus that let wave
  2's write cutover ride v17 while GC-enable waits on evidence.

Also notes the framing fact: warn-first itself first reaches stable
deployments in 17.0, so 17.0 could not flip anything version-wide without
deleting the warn window R2/R3 promised.

Design doc only — releases nothing.

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

vercel Bot commented Jul 30, 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 30, 2026 2:33pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tooling size/m labels Jul 30, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review July 30, 2026 14:40
@os-zhuang
os-zhuang merged commit d7851cf into main Jul 30, 2026
15 checks passed
@os-zhuang
os-zhuang deleted the claude/d1-d2-strict-default-flip-vbly5z branch July 30, 2026 14:40
os-zhuang added a commit that referenced this pull request Jul 30, 2026
…tays per-deployment, wired into the upgrade path (#3438) (#4199)

Revises the 2026-07-30 addendum per maintainer review of #4188: the
amortisation argument (every 16.x deployment performs the 16→17 ceremony
anyway) stands, so each half is asked what an extra major actually protects.

- D2 action params: nothing — no data at risk, rejection is a loud 400 to
  the party who can fix it, the dangerous half of R3 is closed by
  ACTION_PARAM_BUILTIN_KEYS, and v17 already ships harsher zero-window
  flips. Default flips in 17.0; OS_ACTION_PARAMS_STRICT_ENABLED (RC-only,
  never on latest) is deleted before GA; OS_ALLOW_LAX_ACTION_PARAMS is the
  one escape hatch.
- D1 references + structured JSON: records, not callers — read-modify-write
  re-submits ancient malformed values, and part of that data is our own
  fault (the dead LocationCoordinatesSchema declared {latitude, longitude}
  while storage is {lat, lng}). Stays per-deployment; os migrate meta and
  boot logs now surface the scan so clean deployments get one-ceremony
  strictness.

Design doc only — releases nothing.
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 tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants