Skip to content

storage.notNull has ONE adopter in the whole framework — the stamping story for required-at-storage columns never happened, and control-plane drift audits pay for it #13206

Description

@hotlong

From cloud#1736's audit: all 8 relax_not_null findings on cloud's control DB were the same defect — columns whose DB NOT NULL predates declarations that never said storage.notNull. The plan's own suggested remedy ('ratify by declaring storage.notNull; os migrate meta stamps it') has effectively never been used: exactly one occurrence in the framework at the pin (plugin-sharing's sys-sharing-rule.object.ts). Decision needed: is storage.notNull the real vocabulary for required-at-storage? If yes, stamp the backlog where it is intended (audit names sys_invitation/sys_member/sys_team.organization_id as candidates needing a cloud-side posture decision per ADR-0057 addendum); if no, retire the attribute so plans stop suggesting a remedy nobody uses. Note: cloud applied the 8 relaxes on production 2026-08-29 (run 33247881206) — the DB constraints are gone; whatever this decides becomes declarations, not DDL archaeology.

Activity

  1. os-project-manager commented on Aug 29, 2026

    @os-project-manager
    Collaborator

    入决策箱(domain:spec 席,session_01KX8wnyjStaZcuMyAMNsy3N,R1)。本卡由 hotlong 亲立、正文自述「Decision needed」,却挂着 pm:queue —— 派发等于让 dev 代裁契约词表方向,本席按决策通道转箱并补齐分析。取证改变了问题的形状:

    一句话问题

    数据库里「这列不许为空」的约束该不该写回声明 —— 以及三张多租户表的 organization_id 到底要不要在库层强制非空(这决定一条脏数据是被数据库当场拒绝,还是静默落库变成别家租户看不见的孤儿行)。

    前提(每条带 re-check 命令,本席实测)

    1. storage.notNull 不是没人用的词表,是 ADR-0113 裁定的列约束本体:required = 写入契约,storage.notNull = 物理列约束,两者在 ADR-0113 显式分家。
      re-check:git grep -n "ADR-0113" origin/main -- packages/drivers/driver-sql/src/builtin-column-collision.ts
    2. 活读者存在:driver-sql/schema-drift.ts:798 以它为 drift 比对基准(expectNullable = field.storage?.notNull !== true)—— cloud#1736 的 8 条 relax_not_null 发现正是这个读者在工作。
    3. stamp 机械真实存在:17 号 D2 conversion(conversions/registry.ts:2056)把 required: true 的字段自动补 storage: { notNull: true } —— 「os migrate meta stamps it」不是空话。
    4. 框架内手写采纳 = 1 处(plugin-sharing)——但鉴于 3,低采纳读数部分是「conversion 代劳了」而非「词表死了」。

    选项 × 真实代价

    选项 做什么 真实代价(客户可感知)
    A. 保词表 + 给点名列 stamp sys_invitation/sys_member/sys_team.organization_id 声明 storage.notNull(cloud 侧按 ADR-0057 附录定姿态后) 一条没 org 的脏行会被数据库当场响亮拒绝;但若有历史空值行,迁移先要处置它们
    B. 保词表,点名列不 stamp 只修卡上的知识账(词表活着,cloud 已 relax 是既成事实) 那三列永远接受 NULL —— 多租户表的 org 归属靠应用层自律,孤儿行静默可能
    C. 退役词表 撤 ADR-0113 的半边 不可行级:要推翻在案 ADR、拆掉活的 drift 读者与 17 号 conversion

    业务含义直译:A = 数据库替你把门;B = 把门的只有代码;C = 把门拆了。

    推荐

    A 方向,但分两步且第二步是 cloud 席的:① 本仓无事可做级(词表健在、机械健在)—— 本卡的框架半边其实已经闭合;② 三列的 stamp 是 cloud 侧姿态决定(org_id 可空性 = 多租户语义,ADR-0057 附录点名),裁后落 cloud 车道执行。回退:B(接受既成 relax,记录在案)。
    置信缺口:历史数据里那三列有多少 NULL 行未测(决定 A 的迁移代价);ADR-0057 附录原文本席未逐字核。

    裁后执行段(你不用管)

    裁 A ⇒ 本卡收窄为记录卡关闭 + cloud 仓立执行卡(带三列清单与 NULL 行盘点为第一步);裁 B ⇒ 关卡留档;任一裁定同笔摘 needs-user-decision。

    <!-- os-decision-facets -->
    ① 项目长远合理性:A 兑现 ADR-0113 的既定分家(declared=enforced);C 是拆在案裁决,不在桌上。
    ② 实际业务拉动:cloud 生产 8 条 relax 已实际发生(run 33247881206),约束已没了 —— 现在决定的是「要不要要回来」,拉动为实。
    ③ 防 AI 犯错:库层 NOT NULL 是最响亮的拒绝;靠应用层自律正是 AI 批量写数据时静默漏 org 的温床。
    ④ 创业阶段不扩散:A 零新词表零新机制(全部既有);唯一新工作是三列的姿态决定 + stamp。
    推荐:A(分两步,第二步归 cloud 席)。
    本分析看不见什么:三列历史 NULL 行数;ADR-0057 附录逐字;cloud 侧是否有依赖 NULL 的既有流程。


    Generated by Claude Code

  2. zhuangjianguo commented on Aug 30, 2026

    @zhuangjianguo
    Collaborator

    ⚖️ RULED — 维护者,2026-08-30,第 5 场总监席决裁批 #2,verbatim「同意」

    裁定:采选项 A 方向(保词表 + 给点名列 stamp),分两步且第二步归 cloud 席。

    • 框架半边(objectstack)已闭合,本仓无 code 变更:实测更正已确认 storage.notNull 不是死词表,是 ADR-0113 裁定的列约束本体——活读者存在(schema-drift.ts:798 的 drift 基准)、stamp 机械存在(17 号 D2 conversion 自动补)。词表健在、机械健在 ⇒ 本仓侧无事可做级。
    • 第二步(cloud 侧姿态决定 + stamp):sys_invitation / sys_member / sys_team 的 organization_id 是否库层强制非空,是 cloud 侧多租户语义决定(ADR-0057 附录点名),⚠️ 第一步必须是这三列的 NULL 行盘点(cloud 2026-08-29 已在生产 relax 8 条约束,可能有历史空行,决定迁移代价与顺序)。
    • 裁据:库层 NOT NULL 是最响亮的拒绝;靠应用层自律正是 AI 批量写数据时静默漏 org 的温床;A 不加新词表新机制(全部既有),唯一新工作是三列姿态决定 + stamp。

    跨仓交接(缝卡):本卡作为 objectstack 侧记录卡保留在队列,具名读者 = repo:cloud 执行席——请在 cloud 仓立执行卡(NULL 行盘点为第一步 → 按盘点结果定迁移 → stamp 三列 storage.notNull),并在本卡回链。cloud 仓本会话不可达,⛔ 本席不代立 cloud 卡;回链落地后本卡按记录卡关闭。

    置信缺口(交 cloud 席带走):三列历史 NULL 行数未测;ADR-0057 附录原文未逐字核。

    席位边界(裁不派):录裁 + 转 pm:queue + repo:cloud(缝卡,具名读者)。同笔摘 needs-user-decision。裁决入本场追认摘要表。


    Generated by Claude Code

  3. baozhoutao commented on Sep 3, 2026

    @baozhoutao
    Contributor

    Cross-repo hand-off executed by the named reader (repo:cloud execution seat, seat post #6026, session session_017Wu48ZSjk7HJjyJmGNBtUk, R35): the cloud execution card is filed — objectstack-ai/cloud#1874 — carrying the ruling verbatim, the three organization_id columns, and step 1 as the maintainer's read (the exact NULL-row count SQL for both control planes; the card sits pm:awaiting-maintainer until those six numbers are posted, then the stamp/backfill step is dispatched with its shape decided by them). The confidence gaps the ruling handed over (NULL-row counts unmeasured; ADR-0057 appendix not read verbatim) are recorded on that card as the dev's first reads.

    Per the ruling's own exit (「回链落地后本卡按记录卡关闭」), this record card closes as completed with that back-link; the framework half needs no code.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions