Skip to content

docs(spec): the optional-master entry and docblock name the raw-parse door's reading of an omitted required - #31523

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-2rp7qk
Nov 8, 2026
Merged

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

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by 田騏 · project thread

Fixes #31470

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么: 只改文字和一个测试,不改任何行为。语义迁移条目 cbp-optional-master-admitted 的 replacement 和 acceptanceCriteria、以及 controlledByParentMasterOf 的 docblock,补上「原始解析入口」的读法;另加一个测试把三个入口并排钉住。
  • 为什么改: 子对象唯一的 master_detail 不写 required 时,ObjectSchema.create() 读成必填主字段,而 ObjectSchema.safeParse / defineStack / 保存入口读成可选主字段。这个分歧是你在 controlled_by_parent admits an optional master (1/3): spec, lint and the read owner branch: a null-master detail row is readable by its owner only #30905 裁定的(ruling record 6687830530,选 A「放行为可选主字段」),但条目和 docblock 只写了 create() 和存储行,读者会以为省略永远等于必填。座位据此对本卡选 B(只改文字),不是 A(A 会推翻现行裁决)。
  • 风险与回滚: 没有运行时风险:无 schema、类型、导出或运行时答案变化。条目文字随 @objectstack/spec 的 ./migrations 发布(os migrate 会读),所以带 patch changeset。回滚即还原这三个文件和 changeset。
  • 先看: packages/spec/src/migrations/entries/semantic/18.cbp-optional-master-admitted.ts 里 replacement 末句和 acceptanceCriteria 末句。
  • 两分钟验证: pnpm --filter @objectstack/spec exec vitest run --project local src/data/controlled-by-parent-master.test.ts,看新测试「the three doors on an omitted required」:create() 给必填主字段,safeParse 与 defineStack 给可选主字段。
  • 需要你做的: 无,座位做契约复核后落地。

What changes

Card #31470 asked to pick A (refuse an omitted required at the raw parse and in defineStack) or B (keep the reading and say so). The seat picked B: maintainer ruling A on #30905 (ruling record 6687830530) already decided that at the raw parse doors (an object written directly in defineStack, the save doors) an omitted required on a controlled_by_parent object's only master_detail is read as the optional master, and it explicitly did not take "refuse the omission at every door". Option A on this card would reverse that ruling. What was wrong is the prose, which named only the doors that see the omission.

  • 18.cbp-optional-master-admitted.ts (semantic migration entry).
    • replacement: after "an omitted required keeps meaning required through ObjectSchema.create() and on stored rows", it now says that through a raw parse (an object written directly in defineStack, or saved through a save door) required defaults to false before the object is judged, so the omission is read as the optional master, as the maintainer ruled rather than refusing it at every door. os lint still reports the omission at error.
    • acceptanceCriteria: the last sentence gains the same clause: through ObjectSchema.safeParse, defineStack or a save door, the omission is admitted as the optional master, its field parsed as required: false.
    • The header comment names the ruling record. id, surface and reason are unchanged.
  • controlledByParentMasterOf (JSDoc). "One whose required is not written names no master" holds only on a map read as written (an authored or stored body no parse has touched). On a parsed map the omission has already defaulted to false, so through a raw parse that field is the optional master; the builder forces it to true first. "Reads required as written" is kept.

Tests

One new it in packages/spec/src/data/controlled-by-parent-master.test.ts, citing the ruling record, pins the three doors on one omitted required:

  • ObjectSchema.create(): required: true, controlledByParentMasterOf gives { field: 'order', optional: false };
  • ObjectSchema.safeParse: success, required: false, { field: 'order', optional: true };
  • defineStack: required: false, { field: 'order', optional: true }.

Measured first: no existing test pinned all three in one place. controlled-by-parent-master.test.ts (the omitted-required case) pins create() and safeParse; defineStack is pinned separately in conversions/cbp-master-detail-required-defaulted.test.ts and data/object.test.ts.

Acceptance notes

  • Changeset, not skip-changeset: the entry ships. @objectstack/spec exports ./migrations from dist, and os migrate prints the entry's replacement. Patch changeset .changeset/31470-cbp-omitted-required-raw-parse-door.md.
  • The D2 conversion docblock the ruling called stale under A (conversions/entries/18/cbp-master-detail-required-defaulted.ts) was already fixed on main by docs(spec): say what the optional master did to the cbp-master-detail-required-defaulted notes #31055 (f1206610a9): it says a raw parse reads the omitted key as the optional master. Nothing owed here.
  • ADR-0055 already states the raw-parse reading (its "An omitted required keeps meaning required where the omission is visible" bullet); not touched.
  • "Save door" wording is taken from ruling record 6687830530, which names the save doors among the raw-parse doors. This PR did not re-measure the save door's conversion order.

Verification

Before the merge of origin/main (head 425cef8315):

  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 85 commands for this 4-file diff; all 85 ran and exit 0.
  • NODE_OPTIONS=--max-old-space-size=6144 pnpm --filter @objectstack/spec run typecheck :: exit 0 (the default heap OOMs in check:test-typecheck).
  • pnpm --filter @objectstack/spec exec eslint on the three touched source/test files :: exit 0.
  • vitest run --project local on controlled-by-parent-master.test.ts, cbp-master-detail-required-defaulted.test.ts and src/migrations :: 7 files, 252 tests passed.

After git merge origin/main (merge commit 5e9d2802a6, the PR head):

  • spec gen:schema && gen:docs :: exit 0, no tree change. pnpm --filter @objectstack/spec run check:generated :: exit 0 (14 artifacts current, after a spec rebuild; the first run read a pre-merge dist).
  • dispatch-gates --commands derived the same 85 commands; after a packages/* build all 85 ran and exit 0.
  • dispatch-gates --ran: 85 derived famil(ies) accounted for — 85 run, 0 NOT-MEASURED, exit 0.

🤖 Generated with Claude Code

https://claude.ai/code/session_017ihL2diS4EEKVj8TfNEQAs

… door's reading of an omitted required

The semantic entry cbp-optional-master-admitted and the
controlledByParentMasterOf docblock described an omitted required on the
only master_detail of a controlled_by_parent object as the required
master, naming only ObjectSchema.create() and stored rows. Through a raw
parse (ObjectSchema.safeParse, defineStack over a plain literal, a save
door) the field default writes false first, so the omission is the
optional master, as the maintainer ruled on the optional master. Name
that door in both texts and pin the three doors side by side in one test.
No behaviour change.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ihL2diS4EEKVj8TfNEQAs
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation protocol:data tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 8, 2026
@github-actions

github-actions Bot commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️ 1 changed file(s) yielded no anchor (packages/spec/src/data/controlled-by-parent-master.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/data/controlled-by-parent-master.ts) — pages documenting those are invisible to this run
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 64 of 227 client-bound route-ledger rows — the other 163 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 163: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 59 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 104 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 171 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 f4b1f9ee0865544d0a404a2ceb0b02b4d77cb69c → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 6e84da946833cbf35fcff6cf94e0aeb5e32d5ad6 — the merge of head 5e9d2802a6cb2c07c1cda4a707e4fef82afdf3bb into base f4b1f9ee0865544d0a404a2ceb0b02b4d77cb69c, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 6e84da946833cbf35fcff6cf94e0aeb5e32d5ad6 && git checkout 6e84da946833cbf35fcff6cf94e0aeb5e32d5ad6
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin f4b1f9ee0865544d0a404a2ceb0b02b4d77cb69c 5e9d2802a6cb2c07c1cda4a707e4fef82afdf3bb && git checkout -B drift-repro f4b1f9ee0865544d0a404a2ceb0b02b4d77cb69c && git merge --no-ff 5e9d2802a6cb2c07c1cda4a707e4fef82afdf3bb

node scripts/docs-audit/affected-docs.mjs --json f4b1f9ee0865544d0a404a2ceb0b02b4d77cb69c

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#4 · marchtian · session_017ihL2eAG5J8aqAyPZif4BJ

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 5e9d2802a6cb2c07c1cda4a707e4fef82afdf3bb
Local-runs: probe — vitest run --project local src/data/controlled-by-parent-master.test.ts on the head worktree: 47/47 passed; -t "three doors" 1 passed. Nothing else built or run.
Scope: packages/spec/src/data/controlled-by-parent-master.ts, packages/spec/src/migrations/entries/semantic/18.cbp-optional-master-admitted.ts + packages/spec/src/data/controlled-by-parent-master.test.ts (pins the three doors the two prose hunks describe). Prose face (.changeset/31470-cbp-omitted-required-raw-parse-door.md) checked sentence by sentence for the seat's ACCEPT.

① Derived judgments

The dev's open question (do the save doors really read the omission as optional?) was MEASURED, not taken from the ruling record.

Save door trace (true on head). The object save door is saveMetaItem (packages/metadata-protocol/src/protocol-save.ts); its spec gate at :1424-1429 runs resolveOverlaySchema(...) → schema.safeParse(request.item), and resolveOverlaySchema (protocol-meta-type-schema.ts:556-562) returns ObjectSchema (kernel/metadata-type-schemas.ts:93) — the raw parse, not create(). The only conversion replays in that file are the flow one (:1316) and stored-row convertStoredItemDetailed (:2507); no request.item rewrite before the gate touches required. The D2 conversion cbp-master-detail-required-defaulted is retiredFromLoadPath: true, and applyConversions skips retired entries unless includeRetired (conversions/apply.ts:159), which only stored-row rehydration pins (apply.ts:44-47, protocol-stored-item-conversion.ts:93). The registry write-through registers the body as written and validates with ObjectSchema.parse (objectql/src/registry.ts:7728, 7912). Studio reaches the same door (rest/src/meta-item-write-routes.ts:364). So "a save door reads an omitted required as the optional master" is true of the code. Once stored, read-back replays the retired conversion and the row reads required: true — consistent with the entry's "keeps meaning required … on stored rows".

Sentence by sentence:

  • Entry header comment (18.cbp-optional-master-admitted.ts:14-19): true (trace above; defineStack pinned by the new test and conversions/cbp-master-detail-required-defaulted.test.ts:117); the ruling citation matches record 6687830530 verbatim.
  • replacement (:44-48): true; "os lint reports the omission at error" true (lint/src/data-model-rules.ts:417-418, 447-448, pinned at data-model-rules.master-detail-required.test.ts:110).
  • acceptanceCriteria (:70-72): true (probe + trace).
  • Docblock (controlled-by-parent-master.ts:160-166): true; builder force forceCbpMasterDetailRequired at object.zod.ts:4509, applied :4679; "Reads required as written" kept (:170).
  • Order item 4 (report only): the D2 docblock is already fixed on main by f1206610a (docs(spec): say what the optional master did to the cbp-master-detail-required-defaulted notes #31055). Nothing owed.
  • nit: the STEP18_RATIONALE fragment for cbp-optional-master-admitted (packages/spec/src/migrations/registry.ts.template:1322-1335) still names only the builder's reading. True as far as it goes, outside the order's two named texts.

② Semver level

Clause-②: no — correct: only a JSDoc and string-literal content of a SemanticMigration entry change. Metadata-protocol: none — correct. A patch changeset (not skip-changeset) is right: the entry ships — gen:migration-registry (scripts/build-migration-registry.ts:145) → MIGRATIONS_BY_MAJOR (src/migrations/index.ts:26-32) → the ./migrations export; os migrate meta prints judge.replacement (cli/src/commands/migrate/meta.ts:841,851); the upgrade guide renders the same fields (spec/scripts/build-upgrade-guide.ts:153-155).

③ Boundary flags

  • Four files, all inside the order's surface. No behaviour change: the non-test product hunks are comment text and string-literal content only. The merge commit 5e9d2802a6 brings only docs/adr/0143-… from origin/main.
  • Author objectstack-fleet[bot]; no login note.

④ Design judgment

Implemented-by: claude/project-thread-2rp7qk
Reviewed-by: session_017ihL2eAG5J8aqAyPZif4BJ

VERDICT: PASS

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 protocol:data size/s tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

2 participants