Skip to content

Commit f120661

Browse files
docs(spec): say what the optional master did to the cbp-master-detail-required-defaulted notes (#31055)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} --> _Requested by **Tesla** · [project thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoSzxEPRq7jQs3PC2g7nYmoh)_ Clause-②: no Metadata-protocol: none Fixes #30977 Claim: 6697140710 · Branch: `claude/project-thread-kujw5c` ## 维护者速读 - 改了什么:只改注释。协议 18 的转换 `cbp-master-detail-required-defaulted` 和构建器 `forceCbpMasterDetailRequired` 的说明里,还写着「显式 `required: false` 的主表会被构建器和解析拒绝」,现在改成按 #30940 之后的实际规则来写。 - 为什么改:#30940(#30905 裁决 A)落地后,`controlled_by_parent` 对象唯一的 `master_detail` 写 `required: false` 是合法的可选主表;旧注释会让下一个改这个转换的人按已经不存在的规则推理。 - 对作者的影响:没有。转换行为不变:省略 `required` 仍补成 `true`,显式 `required: false` 仍不转换。 - 风险与回滚:零运行时风险,无行为改动;`apply` 与 fixture 形状一字未动。回滚即撤本 PR。 - 不在范围:文档和技能里的同类说法归 #30907(卡 3/3),本 PR 不碰。 - 发布:注释不进发布内容,打 `skip-changeset`。 ## Stale sentences found, and how each was resolved | # | Where (on `origin/main` 81deb54) | Stale claim | Resolution | |---|---|---|---| | 1 | `conversions/entries/18/cbp-master-detail-required-defaulted.ts:56-58` | "An explicit `required: false` … The builder refuses it loudly … an author the builder would have stopped" | Rewritten: on the only `master_detail` it is the optional master (ADR-0055 §1 as amended, #30905 ruling A), kept by the builder and admitted by the parse, so the stored pass keeps it too; beside another `master_detail` the builder still refuses it. | | 2 | same file `:66-68` | "a live author still meets the parse's refusal and its prescription" | Rewritten: a live author meets the builder's force through `ObjectSchema.create()` and `os lint`'s `relationship/master-detail-required` at `error`; a raw parse reads the omitted key as the optional master (the field default writes `false` first). | | 3 | same file `:110` (fixture comment) | "refused by the builder, not converted here" | Now "the optional master, kept as written; not converted". Fixture shape untouched. | | 4 | same file `:23-26` | "Ruling B made the parse refuse … without exactly one `master_detail` declared `required: true`" | True as history; kept, with one parenthesis noting ruling A's amendment and that the omitted key this entry converts is unchanged by it. | | 5 | `data/object.zod.ts` `forceCbpMasterDetailRequired` docblock, closing paragraph | "the RULE — exactly one required `master_detail` … so a raw parse of a `master_detail` without `required: true` is refused rather than forced" | Rewritten: the rule includes the only `master_detail` as the optional master, and a raw parse is never forced (it reads an omitted `required` after its `false` default). The paragraphs above it already carry #30940's "⚠️ Amended" note and were left. | Read and left as is: - `summary` string (`:79-82`): every clause is still true ("an explicit `required: false` … not converted"), so it did not change, and no printed text moved. - `migrations/entries/semantic/18.cbp-master-relation-tiers-retired.ts` (`:50-51`, `:69-70`, `:76`): its text on an explicit `required: false` is outdated, but #30940 added `18.cbp-optional-master-admitted`, whose `reason` says in so many words that it "supersedes what `cbp-master-relation-tiers-retired` and `cbp-master-detail-required-forced` say of it … everything else they say stands". The D3 record is superseded by a later entry, not edited, and its strings are printed to authors; left for that reason. - `data/controlled-by-parent-master.ts` and `conversions/refused-shapes.test.ts`: already updated by #30940; nothing stale. ## Verification - `dispatch-gates --commands`: 68 derived, 68 run, all exit 0. `dispatch-gates --ran`: 68 accounted, 0 NOT-MEASURED. 11 of them first exited 3 (not measured) on a fresh worktree; they went green after `pnpm build`, and `check-plugin-teardown-shape --self-test` after fetching its pinned fixture commit on this shallow clone. - `vitest run src/conversions src/data/controlled-by-parent-master.test.ts` in `packages/spec`: 24 files, 842 tests passed. - Open PRs touching these files: none found (#31044, the one search hit, touches only skills). - Diff: 2 files, +24 / −15, comments only. ## Acceptance notes - No behaviour change: `apply`, `summary`, `surface` and the fixture objects are byte-identical. - No changeset: comments only, nothing published moves (`skip-changeset`). - Docs and skills untouched (#30907). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01L9qibzpwEPquNhPcgrrjew Co-authored-by: Claude <noreply@anthropic.com>
1 parent 022b4e7 commit f120661

2 files changed

Lines changed: 24 additions & 15 deletions

File tree

‎packages/spec/src/conversions/entries/18/cbp-master-detail-required-defaulted.ts‎

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@ import { type Dict, isDict } from '../shared.js';
2323
* Ruling B made the parse refuse a `controlled_by_parent` object without
2424
* exactly one `master_detail` declared `required: true`, and retired the
2525
* security runtime's second tier, which derived the master from ANY
26-
* `master_detail`. One shape inside that refusal has a producer at rest:
27-
* objectui's Studio adds a field as `{ type, label }` and its Required toggle
26+
* `master_detail`. (Ruling A of #30905 later amended ADR-0055 §1 to admit an
27+
* only `master_detail` declared `required: false` as an optional master; the
28+
* omitted key this entry converts is unchanged by it.) One shape inside that
29+
* refusal has a producer at rest: objectui's Studio adds a field as
30+
* `{ type, label }` and its Required toggle
2831
* writes `required: true` or nothing, and the object's Settings tab offers
2932
* `controlled_by_parent`. So a Studio-authored detail can be stored with its
3033
* one master reference carrying no `required` key at all. Before protocol 18
@@ -53,9 +56,11 @@ import { type Dict, isDict } from '../shared.js';
5356
* authored object.
5457
*
5558
* **Deliberately narrow — exactly one shape, nothing else.**
56-
* - An explicit `required: false` is NOT converted. The builder refuses it
57-
* loudly rather than forcing it (an authored contradiction, ADR-0032), so the
58-
* stored pass does not overrule an author the builder would have stopped.
59+
* - An explicit `required: false` is NOT converted. On the object's only
60+
* `master_detail` it is the OPTIONAL master (ADR-0055 §1 as amended, #30905
61+
* ruling A): the builder keeps it as written and the parse admits it, so the
62+
* stored pass keeps it too. Beside another `master_detail` there is no one
63+
* master to default (below), and the builder refuses it there.
5964
* - An object with two or more `master_detail` fields is NOT converted. Choosing
6065
* which reference becomes the master is exactly the pick ADR-0055 §1 refuses.
6166
* - A required `lookup` (the retired third tier) is NOT converted. Turning it
@@ -64,9 +69,12 @@ import { type Dict, isDict } from '../shared.js';
6469
* rule only.
6570
*
6671
* **Retired from the load path, from day one.** The authoring funnel does not
67-
* replay it, so a live author still meets the parse's refusal and its
68-
* prescription. A stored row, a built artifact inside its declared-floor window
69-
* and `os migrate meta --from 17` all replay it. Idempotent by construction: a
72+
* replay it: a live author meets the builder's own force through
73+
* `ObjectSchema.create()` and `os lint`'s `relationship/master-detail-required`
74+
* at `error`. A raw parse cannot see the omission (the field default writes
75+
* `false` first), so there the omitted key reads as the optional master. A
76+
* stored row, a built artifact inside its declared-floor window and
77+
* `os migrate meta --from 17` all replay it. Idempotent by construction: a
7078
* converted field carries `required`, which this transform never touches again.
7179
*/
7280
const cbpMasterDetailRequiredDefaulted: MetadataConversion = {
@@ -107,7 +115,7 @@ const cbpMasterDetailRequiredDefaulted: MetadataConversion = {
107115
},
108116
},
109117
{
110-
// An explicit `required: false` — refused by the builder, not converted here.
118+
// An explicit `required: false` — the optional master, kept as written; not converted.
111119
name: 'cbpd_explicit',
112120
label: 'Explicit',
113121
sharingModel: 'controlled_by_parent',

‎packages/spec/src/data/object.zod.ts‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4492,12 +4492,13 @@ function assertReferenceViaSiblingDeclared(objectName: unknown, fields: unknown)
44924492
* {@link assertSystemDataIsWritable}, and deliberately NOT in raw
44934493
* `.parse()`/`.safeParse()`: a parse must not rewrite what it reads. The force
44944494
* is the builder's convenience, not the rule: the RULE — exactly one required
4495-
* `master_detail` under this sharing model — is judged on the parse itself by
4496-
* {@link refuseControlledByParentWithoutOneMaster} (#23100, maintainer ruling
4497-
* B), so a raw parse of a `master_detail` without `required: true` is refused
4498-
* rather than forced, and the security gate's fallbacks this paragraph used to
4499-
* call "runtime tolerance" (any `master_detail`, then a required `lookup`) are
4500-
* retired.
4495+
* `master_detail` under this sharing model, or (ADR-0055 §1 as amended,
4496+
* #30905 ruling A) its only `master_detail` as the optional master — is judged
4497+
* on the parse itself by {@link refuseControlledByParentWithoutOneMaster}
4498+
* (#23100, maintainer ruling B), so a raw parse is never forced (it reads an
4499+
* omitted `required` after its `false` default), and the security gate's
4500+
* fallbacks this paragraph used to call "runtime tolerance" (any
4501+
* `master_detail`, then a required `lookup`) are retired.
45014502
*
45024503
* Alias spellings (`isRequired` / `mandatory` / `notNull`) need no handling
45034504
* here: `FieldSchema` is a `strictObject`, so those keys are refused at parse

0 commit comments

Comments
 (0)