Skip to content

Declarative "at most one row per parent where X" (conditional unique) — summary-based guards miss rows in the same bulk insert #25477

Closed as duplicate

Description

@marchtian

Ruled: 6377847361 · batch #9 item 4 · letter C · 2026-10-24T00:43:45Z (proposal 3: batch-aware summary, in-memory per batch; proposals 1 and 2 not now)

Context

hotsrm needs "each supplier has at most one primary contact" (srm_supplier_contact.is_primary). It must hold for UI edits and for the spreadsheet import, metadata only.

What we tried

  • indexes[].partial is retired, and a unique index on ['supplier', 'is_primary'] would also forbid two non-primary contacts.
  • A summary on the parent (primary_contact_count, count with filter: { is_primary: true }) plus a script rule on the child: record.is_primary == true && record.supplier.primary_contact_count > 0. This catches a second primary when the first already exists, but not two primary rows created in the same import batch: the import runner batches creates through createManyData / insertManyData, and the parent summary is not recomputed between the rows of one batch, so both rows pass.

Reproduction: import 供应商,姓名,邮箱,主联系人 with two rows for a new supplier, both 是 → both created, primary_contact_count = 2.

Proposal

One of:

  1. Conditional unique index: { fields: ['supplier'], unique: 'organization', where: { is_primary: true } } (partial index on SQL drivers, emulated on memory). (not now — re-opens the key retired in ADR-0049 enforce-or-remove:IndexSchema.type 与 IndexSchema.partial 没有任何 DDL 消费者 #5248; its entry stays ADR-0049 enforce-or-remove:IndexSchema.type 与 IndexSchema.partial 没有任何 DDL 消费者 #5248's enforce-first condition, on a second app or a real incident; ruling 6377847361)
  2. A declarative cross-row rule type (type: 'unique_where' or similar) that is judged per row inside bulk writes. (not chosen; ruling 6377847361)
  3. At minimum: evaluate child script rules that read a parent summary against a summary that includes earlier rows of the same batch, or document that they do not.

Workaround in hotsrm (temporary)

The summary-based guard stays (it covers single writes and later imports), and a list view "主联系人重复" (primary_contact_count > 1) surfaces the batch case for manual repair during review.

Linked: objectstack-ai/hotsrm#10

Activity

  1. added
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    domain:specDomain lane — seat card indexed by label:pm:seat
    enhancementNew feature or request
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Oct 23, 2026
  2. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    Contributor

    Triage: decision-shaped (filing-gate ②: a feature / contract-shape proposal from a real app). Lands in packages/spec (object indexes or validation rules) and the engine's bulk-write path (createManyData / insertManyData). Marked needs-user-decision; not dispatchable until ruled.
    Path: records · 写入规则在服务端兑现 | records-forms.field-unique-enforcement (P1, does not cover conditional uniqueness) — 缺项 | step ②
    Type: Feature

    维护者速读

    "每个供应商最多一个主联系人"这类规则,单条编辑能挡住,但一次批量导入里的两条主联系人会同时通过,因为批内不重算父级汇总。

    • A:唯一索引加 where 条件(SQL 部分索引,内存驱动模拟)。注意:indexes[].partial 在协议 17 被退役(ADR-0049 enforce-or-remove:IndexSchema.type 与 IndexSchema.partial 没有任何 DDL 消费者 #5248),当时的裁决是"真有需求时以 enforce-first 回来",而 MySQL 没有部分索引。
    • B:新增一种跨行规则类型(如 unique_where),批量写入里逐行判定。
    • C:不加新键,修现有语义:批量写入里的校验规则读父级汇总时,看得见同批前面的行(批量等于逐条)。

    推荐 C:不加新契约,让批量和逐条行为一致,hotsrm 现有的规则直接生效;A 受 MySQL 限制且要重开已退役的键。置信缺口:C 对大批量导入的性能代价未测,也不解决并发写入。问:A / B / C?

    os-decision-facets

    • ① 项目长远合理性:C 消除"批量与逐条结果不同"这个特例;A 重开已退役的键且方言不齐;B 新增一类规则。
    • ② 实际业务拉动:hotsrm 实测(两行同批都标主联系人都通过);目前只有这一个应用在要。
    • ③ 防 AI 犯错:C 下 AI 写的普通校验规则在批量导入里也成立,不会被静默绕过;A/B 要 AI 学新键。
    • ④ 创业阶段不扩散:C 零新增键;A 重开一个键;B 新增一种规则类型。

    Governing text: packages/spec/src/data/object.zod.ts docblock "type / partial were RETIRED at protocol 17 (#5248, #4943, ADR-0049)".
    Prior rulings read: "partial index" → #5248 (maintainer chose remove over enforce, 2026-08-06; "comes back enforce-first"); ADR-0049, ADR-0078; thread: none.

    Batch note: one of five import / automation proposals from hotsrm's trial import filed on 2026-10-23 (#25475, #25476, #25477, #25481, #25493); the earlier hotsrm platform evaluation is the decision card #25496. They can be ruled as one batch.

    Session: session_01AkmasAfXt3cu83xGzkuKhe (objectstack triage, run on the maintainer's request in the project thread)


    Generated by Claude Code

  3. os-elon-musk commented on Oct 24, 2026

    @os-elon-musk
    Collaborator

    Ruling: batch #9 item 4 · letter C · maintainer 「#9 同意」 2026-10-24T00:43:45Z

    C = 不加键:批量写入里,子表规则读父级汇总时把同批前面的行算进去(批量等于逐条)。实现须在内存里按批累计汇总(S),⛔ 不得每行回库重算;并发写不在范围,文档写明「批量等于逐条,并发不保证」。验收:本卡复现表的两行只建成一行,另一行报规则错误。A(条件唯一索引 where)的入口仍是 #5248 的 enforce-first 条件(每个驱动真执行才能上),等第二个应用或真实事故再开,记在卡上。工作量 engine S–M(server 车道)。

    标签 needs-user-decision → pm:queue;裁不派。简报:https://claude.ai/artifact/XKBVwFLq2V5nBTNy3enkbz(卡 4)。


    Generated by Claude Code

  4. added
    pm:queueReady for the PM dispatch loop
    and removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Oct 24, 2026
  5. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    Contributor

    Closed 2026-10-24T01:38Z. Superseded by #25579 — the same card ("no declarative "at most one row per parent where X" (conditional unique)"), re-filed as objectstack-fleet[bot] with this body and these labels. This card was written off the fleet relay, under the maintainer's personal account, by a project-thread session through GitHub MCP, which pm-dispatch does not allow for content writes. The thread above stays here for reference; the queue continues on #25579.

    Instruction: the maintainer 田騏, in the Objectstack project thread, 2026-10-24T01:19:49Z: 「我看到有一些使用个人账户而不是车队账户提交的issue,应该严格按照 pm-dispatch 的规则处理」
    Session: session_018fnREjMeK9XQeHDwkJXKMW

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:recordsBusiness objects, records, the views that show data, usable forms, searchdomain:specDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpriority:p3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions