Skip to content

test(dogfood): pin five more records-forms checklist items at the REST door (slice 3) - #26865

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-jcuzjj
Oct 27, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-jcuzjj

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Warren · project thread

Refs #26573 (slice 3, items: field-type-matrix, field-unique-enforcement, import-wizard-encoding-and-hints, master-detail-atomic-save, object-hook-lifecycle)

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:records-forms 区按卡片顺序的下 5 个清单项,新增 6 个 dogfood 测试文件(object-hook-lifecycle 分 showcase 与 scratch 两个),在真实启动的栈上经 REST 门(以及服务端日志)断言它们的 api/log 条款,5 项 automated.ref 指向新钉测,revision 各加 1,history 引本卡。
  • 为什么改:18.2.0 Tier 1 扫描把这些条款判为 PARTIAL 或无钉测;钉在 oracle 层之后每次发版可直接复跑。
  • 风险与回滚:只加测试和清单文字,不动产品代码、不加 CI 车道;新文件落在 dogfood 现有的 isolated 项目。回滚即 revert 本 PR。
  • 先看:field-unique-rest-refusal.dogfood.test.ts(实测:global 单列索引时面向用户的 error 用 API 键名 gsku,developerMessage 用标签,见 Acceptance notes);object-hook-lifecycle-*.dogfood.test.ts(日志经 stdout/stderr 截取读回)。
  • 两分钟验证:先建依赖闭包,再 pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2 test/field-type-matrix-server-refusals.dogfood.test.ts test/field-unique-rest-refusal.dogfood.test.ts test/import-lands-readable-rows.dogfood.test.ts test/master-detail-batch-atomic.dogfood.test.ts test/object-hook-lifecycle-showcase.dogfood.test.ts test/object-hook-lifecycle-scratch.dogfood.test.ts,预期 6 个文件 23 个用例全绿;node scripts/check-platform-checklist.mjs 预期 OK。
  • 需要你做的:无,席位落地。

What changes

Six new dogfood files drive the REST doors of a booted stack (the showcase as shipped, or a scratch app where the item prescribes one), and two read the server log off stdout/stderr for the item's log oracles:

Item (rev → new) Clauses now pinned Pin
field-type-matrix (6 → 7) acceptance[2], acceptance[5] refusal half, acceptance[6], negative[0]–[1] field-type-matrix-server-refusals.dogfood.test.ts: zoo refusals (invalid_option, required, reference_not_found on f_lookup / f_master_detail / f_tree) with the row count read back; invoice totals vs their lines; EXP-2001's two roll-ups
field-unique-enforcement (10 → 11, kind unit → dogfood) acceptance[0]–[3], negative[0]–[2] field-unique-rest-refusal.dogfood.test.ts: the item's scratch object on the SQL family
import-wizard-encoding-and-hints (7 → 8) acceptance[3] server half, negative[1] import-lands-readable-rows.dogfood.test.ts: POST /data/showcase_contact/import, created count vs a filtered GET, CJK values byte-identical
master-detail-atomic-save (8 → 9) acceptance[1], [2], [3], acceptance[5] persisted half, negative[0] master-detail-batch-atomic.dogfood.test.ts: the cross-object POST /batch with {$ref: 0} children, server amount and total roll-up across add/delete, the whole-batch abort
object-hook-lifecycle (10 → 11) acceptance[0]–[9], negative[0]–[4] object-hook-lifecycle-showcase.dogfood.test.ts (the seeded hooks: stamp, trim, transition audit, over-budget warn) + object-hook-lifecycle-scratch.dogfood.test.ts (abort, async log, fail-closed condition, priority order)

Each item's automated.ref keeps the pins it already named (as evidence of their own layer where they sit below the oracle) and adds the new ones with what they score. One history row per item, citing #26573. No clause text, fixture, step or knownGaps line was edited (the claim's file surface).

Skipped, by the slice-3 order:

  • record-discussion-mentions: waits on qa(checklist): records-forms — provisioning recipes for a walled two-organization mention leg, a packaged action specimen, and the app-crm import specs #26625.
  • form-dirty-guard: next in card order, but its only api clause (acceptance[1]) reads the row after a browser exit (discard or save through the guard), so every clause needs a browser.
  • Not taken yet, in card order: related-list-server-pagination, v18-semantic-defaults, view-type-gallery. v18-semantic-defaults has browser clauses only (a1–a3 dom/screenshot, a0 the spec pins).
  • Browser legs inside the five items, left unpinned: field-type-matrix acceptance[1]; import-wizard-encoding-and-hints the wizard's decode/preview (negative[0]); master-detail-atomic-save acceptance[0], [4], negative[1] and acceptance[5]'s live recompute. field-unique-enforcement acceptance[4]–[5] and negative[3] stay the driver pins', as the item says.

Why

The 18.2.0 release-candidate Tier 1 run scored these clauses PARTIAL (pins below the oracle layer: driver-level, in-process engine, or objectui unit) or unpinned. The triage ruling allows ~5-item slices and no new CI lane; the new files fall under the dogfood package's existing isolated vitest project and run where the dogfood legs run today.

Tests

Measured answers the pins now hold (all first read live, then pinned exactly):

  • zoo: out-of-set f_select → 400 VALIDATION_FAILED [[f_select, invalid_option]]; no name → [[name, required]]; a fabricated id in each relational field → [[<field>, reference_not_found]]; row count unchanged each time.
  • unique, bare unique: true (tenant-scoped, composite index) → 409 {error: 'A record with this value already exists', code: UNIQUE_VIOLATION, developerMessage: "Duplicate record refused on 'Unique Item': a unique constraint already holds these values. No record was written.", object}; unique: 'global' (single column) → error: 'A record with this gsku already exists', field: 'gsku', and the developerMessage names 'Global SKU'. No [driver-, no UNIQUE constraint failed, no bound value.
  • unique, atomic updateMany stamping one sku on two rows → 200 {success: false, total: 2, succeeded: 0, failed: 2}, results ROLLED_BACK then UNIQUE_VIOLATION; both rows re-read unchanged.
  • import: 3 CJK rows → {total: 3, ok: 3, errors: 0, created: 3}; the filtered GET finds those 3, values identical.
  • batch: invoice + 2 lines → 200; a client-sent amount: 1 stores as 2 × 29.99; total tracks the line sum after create, add and delete. A line with no product → 400 VALIDATION_FAILED [[product, required]], and neither the invoice nor the valid sibling exists.
  • hooks: the showcase logs task completed: <title> once per transition and project over budget: <name> (1100 / 1000) on the spent-only PATCH. Scratch: abort → 400 with the hook's sentence; async throw → PATCH kept plus [hook] handler failed (onError=log; suppressing) naming the hook; int(record.name) > 0 on a non-numeric name → 400 VALIDATION_ERROR "Hook 'ohl_unevaluable_guard' could not evaluate its condition … operation aborted", no source echoed, no [REST] Unhandled error; priority 10 logs before 90 although declared after it.
Runs
  • Dependency closure on 3c2187f4 (the restarted branch) under os-verify-lock.sh, then the six files: Tests 21 passed | 2 failed (23), both in the unique file. The global index's error names the field (A record with this gsku already exists), and the atomic updateMany answers 200 with a per-row refusal report, not a 4xx. Both re-pinned to the measured bodies. Then the six files → Test Files 6 passed (6), Tests 23 passed (23), plus the dogfood typecheck, VERDICT command-exit 0.
  • Final head ea357b54 (after merging origin/main e9e344a9, which brought an objectql engine refactor and metadata-protocol changes): the closure rebuilt (Tasks: 67 successful, 67 total), then the six files → Test Files 6 passed (6), Tests 23 passed (23), then pnpm typecheck in packages/qa/dogfood, one locked command, VERDICT command-exit 0.

No ablation was run: this diff adds tests and changes no behaviour they pin. Each refusal assertion has a same-door admitted control beside it, and the two ordering/transition log pins are built so the wrong semantics would fail them (the priority hooks are declared in the opposite order; a bare state condition would log a second audit line).

Acceptance notes

  • field-unique-enforcement acceptance[0] says the user-facing error "stays generic". On the tenant-scoped (composite) index it does; on the single-column global index the wire error names the field by its API key (A record with this gsku already exists) while developerMessage names it by its label ('Global SKU'). The pin holds both bodies as measured. That is the opposite split from the engine envelope's own rule (labels in the user-facing message, API names for diagnostics, objectql: a unique refusal's user-facing sentence names the object by its API name ("Duplicate record refused on 'crm_account': …"), which a screen-flow user and a record-save user both read verbatim #24951), so it is noted here as a possible finding, not filed and not changed: product code is outside this card.
  • field-unique-enforcement acceptance[3]: REST has no predicate-update door. The REST door that stamps one value onto two rows in one request is POST /data/:object/updateMany with options.atomic, so the pin drives that, and the engine's predicate updateMany stays the memory driver pin's (named in the ref).
  • master-detail-atomic-save acceptance[1]: the product pick's auto-fill is client-side (the grid copies description / unit_price from the chosen product). The pin sends the catalog values and asserts they persist; the copy itself stays a browser leg.
  • import-wizard-encoding-and-hints acceptance[3]: the console decodes the file and posts JSON rows to /data/:object/import; the pin sends the decoded strings, so it scores the server half only.

Verification

  • node scripts/check-platform-checklist.mjs → exit 0, OK — 16 areas, 329 items (324 active, 1 planned), on the commit e945fc15 and on the final head.
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack → 55 commands on e945fc15, each run and reconciled with --ran: 55 derived famil(ies) accounted for — 55 run, 0 NOT-MEASURED, all exit 0.
  • CI families are read from this PR's check-runs, not re-run locally. Dogfood Regression Gate does not run on pull_request, so the local runs above are this PR's evidence for the new pins.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (not touched)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes. (CI-owned run)
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset — nothing publishes here (@objectstack/dogfood is private; docs/qa/** ships in no package), so skip-changeset; both declaration lines are filled in above.
  • 6. packages/spec not touched.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English (the maintainer brief above is Chinese by the repo's communication rule).

Generated by Claude Code

…T door (slice 3)

Items field-type-matrix, field-unique-enforcement,
import-wizard-encoding-and-hints, master-detail-atomic-save and
object-hook-lifecycle.

Six new dogfood pins score the api and log clauses the 18.2.0 sweep left
PARTIAL: the field zoo's server refusals and the summary carriers, the
unique refusal envelope on the SQL family, an import that reads back, the
atomic cross-object batch with its server roll-up, and the hook lifecycle
on the showcase hooks and on scratch hooks with the server log read back.
Each item's automated.ref names its pins, revision +1, one history row.

Claude-Session: https://claude.ai/code/session_01JQwxbZ3oTCkvGGTMNd5XYU
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json e9e344a9dd3ac55c5cfdc3ff29eaaa5c0ad30b00 → packageMentionDocs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读

  • 改了什么:records-forms 区第三批 5 条清单项(字段类型矩阵、字段唯一约束、导入向导编码与提示、主从原子保存、对象钩子生命周期),新增 6 个独立启动的 dogfood 测试文件,在接口层或日志层钉住它们的条款;5 条清单项的 automated.ref、修订号和历史行已更新。
  • 为什么改:18.2.0 Tier 1 扫描把这些条款判为 PARTIAL,以前只有单元测试或只能现场手跑。
  • 风险与回滚:只加测试和清单文字,不改产品代码,不加 CI 通道。回滚 = revert 本 PR。
  • 先看:field-unique-rest-refusal.dogfood.test.ts(全局唯一索引的报错给用户看的是字段 API 名,按现状钉住,另开 types(data-error-classification): a unique refusal on a single-column index tells the user "A record with this gsku already exists" — the field by its API key, while developerMessage carries its label #26867 跟进),再看 object-hook-lifecycle-scratch.dogfood.test.ts(钩子条件无法求值时拒绝写入)。
  • 两分钟验证:node scripts/check-platform-checklist.mjs,退出 0。
  • 需要你做的:无,席位落地。余下 6 条里 2 条有接口条款,由最后一片派发。
  • 席位意见:每个拒绝都复读确认没写入,原子批量失败后父记录和兄弟明细都不存在。

席位核对

ACCEPT · domain:devx#1 · session_01JQwxbaFpnMwgpCgneKe2qS · head ea357b5430 · 2026-10-27T19:28Z

  1. Shape: 7 files, +836/−11: docs/qa/platform-checklist/areas/records-forms.json and six new packages/qa/dogfood/test/*.dogfood.test.ts. Not governed (check-governed-merges --pr 26865, 847 lines ≤ 3000). File surface as in claim 6445199361; Refs #26573 (slice 3).
  2. Read by the seat: all six files. Each refusal re-reads the store (row counts, the dangling-reference names, the aborted batch leaving neither invoice nor sibling line, both rows unchanged after the atomic updateMany). The roll-up cases check total against line sums after create, add and delete, and the client-sent amount is shown not to stand. The hook files capture stdout/stderr with spies restored in finally, and the fail-closed case checks the condition source never reaches the caller. The unique and scratch-hook files boot their own inline stacks; none of the six is in SHARED_SHOWCASE.
  3. Gates: head check-runs on ea357b5430: 35 success, 16 skipped, 0 failing; check-expected-skips --pr 26865: OK, all 16 in main's roster; mergeable clean. Thread's local run: 23/23 on the final head after merging main e9e344a9 and rebuilding the closure; dispatch-gates 55/55 exit 0, 0 NOT-MEASURED; check-platform-checklist OK. Report 6451084300.
  4. Accepted deviations: the global-unique refusal is pinned exactly as measured, with the user-facing error naming the field by its API key (gsku) while developerMessage uses the label; the seat filed that as types(data-error-classification): a unique refusal on a single-column index tells the user "A record with this gsku already exists" — the field by its API key, while developerMessage carries its label #26867, whose fix updates this pin. acceptance[3] of field-unique-enforcement is pinned on the atomic updateMany door, since REST has no predicate update. The dogfood battery does not run on pull_request or merge_group, so these pins first run in CI on main's next full battery; a red there is main-red and this seat stanches it.
  5. Out of scope: record-discussion-mentions waits on qa(checklist): records-forms — provisioning recipes for a walled two-organization mention leg, a packaged action specimen, and the app-crm import specs #26625; form-dirty-guard's only api clause is the re-read after a browser exit. Of the 6 items left, related-list-server-pagination and view-type-gallery carry api clauses for one last slice. Skills-docs: not owed.
  6. Landing: not governed, CI green on this head, mergeable clean ⇒ the seat readies it and arms auto-merge through the relay. The card stays pm:dispatched for the last slice.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 27, 2026 19:32
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 27, 2026 19:32
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 27, 2026
Merged via the queue into main with commit e78d705 Oct 27, 2026
52 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-jcuzjj branch October 27, 2026 19:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants