Repository navigation
docs(spec): put the forceCbpMasterDetailRequired "only the security gate" sentence in the past tense - #24310
Conversation
…ate" sentence in the past tense The docblock said that today only the security gate closes the null-master-FK shape. That held before the builder's force landed; since protocol 18 the parse refuses the declaration itself (refuseControlledByParentWithoutOneMaster). Comment text and a patch changeset only. Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK Co-authored-by: Claude <noreply@anthropic.com>
…ssue-24291-cbp-docblock-tense
📓 Docs Drift Check
What this run could not see
Coarse fallback — 143 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
席位核对 (seat check) · PR #24310 ·
|
| item | reading |
|---|---|
| Shape | Draft, base main. The body's line 1 is Fixes #24291, line 2 is Clause-②: no; no other closing keyword. Assignee = the card's. 2 files, +21/-4. |
| Comment-only | The object.zod.ts hunk changes only * docblock lines in the "Why" paragraph of forceCbpMasterDetailRequired. No key, export, accepted value or message text moves. |
The present half, read by the seat on origin/main |
refuseControlledByParentWithoutOneMaster (object.zod.ts:2213) adds an issue whenever controlledByParentMasterRefusal (data/controlled-by-parent-master.ts) answers. That function answers undefined only when exactly one field passes isControlledByParentMasterField, so zero required master_detail fields is refused. "refuses a controlled_by_parent object none of whose master_detail fields declares required: true" is true, and it claims only that one of its two cases, which the docblock does not overstate. |
| The past half | "Before commit 75b7c24 only the security gate … closed that shape" rests on the dev's reading of security-plugin.ts at 75b7c240a and on that commit's own changeset. The contract review re-reads it. |
| Changeset | '@objectstack/spec': patch, Clause-②: no. Its three sentences match the diff: "no longer says that today only the security gate …"; "now in the past tense"; "names what has refused the declaration at parse since protocol 18 …". |
| Accepted deviations | One main merge before the PR opened (the incoming commits touch no file here). Base moved to ef6564684 before the worktree was cut. Three packages built for two derived gates. check:dual-build-cjs-loads NOT MEASURED locally, which CI's Lint & Repo Gates runs. |
| Out of scope | Both Acceptance notes. (1) The same docblock's later "a raw parse of a master_detail without required: true is refused" holds for an object's one master. The second-master_detail case parses and create() refuses it, so it is imprecise, not false; the contract review may weigh it. (2) packages/lint/src/data-model-rules.ts:304 ("the security gate's fallbacks stay") is a present-tense comment that ships in no tarball (0 hits in lint dist, positive control present). |
| Landing | After the PASS on this head and a fully green head: pr_ready then automerge_enable, through the relay. |
Generated by Claude Code
Contract reviewServed-tier: Inputs: card #24291 (body and all three comments: triage 6248485488, Claim 6248580616, os-dev-report 6249053516), PR #24310 (body, file list, net diff against merge base 21070a2), the check-runs on the head. Read-only: the head was fetched into a ref of my own ( ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
|
Contract review adopted ·
Generated by Claude Code |
Fixes #24291
Clause-②: no
What changes
This PR rewrites one sentence in the
forceCbpMasterDetailRequireddocblock and adds a prose-onlypatchchangeset. Nothing else changes. The sentence is in the "Why" paragraph ofpackages/spec/src/data/object.zod.ts, now:3483–:3490.Before:
After:
.changeset/24291-cbp-docblock-tense.md:'@objectstack/spec': patch, prose only, withClause-②: no.No key, schema, export, accepted value or message text changes. All 12 changed lines in
packages/specare*comment lines.What each claim rests on
The past half: before commit 75b7c24, only the security gate closed that shape, by refusing the insert.
75b7c240a3eb(read through the REST API) adds the builder force and this docblock. Its changeset says: "Acontrolled_by_parentobject may declare its master reference withoutrequired, so the master-access guard is the only thing preventing an unreachable orphan detail row #8772 measured that only the security gate closed that shape while the declaration surface accepted it." So the old "Today" meant the tree before that commit, and "records" stays true.packages/plugins/plugin-security/src/security-plugin.tsat75b7c240athrough the contents API. That commit does not touch this file, so its parent has the same content.resolveCbpRelationpicks a requiredmaster_detail, then anymaster_detail(:4949), then a requiredlookup.omissionRefusedByValidationfalse (:4969).:5164). A null id throwsMasterReferenceMissingError(:5251), because the stand-down at:5250does not apply.required(PR docs(spec): name the mechanism each of two docblocks describes #24290's contract review, D7). So at runtime, the gate's insert refusal was the only one.The present half: since protocol 18, the parse refuses the declaration itself.
refuseControlledByParentWithoutOneMaster(object.zod.ts:2213) runs insideObjectSchemaBase's.superRefine(:3143, called at:3167).controlledByParentMasterRefusal(src/data/controlled-by-parent-master.ts). That function counts a field as a master only whentype === 'master_detail'andrequired === true, and refuses unless exactly one field counts.src/migrations/entries/semantic/18.cbp-master-relation-tiers-retired.ts.dist/data/index.mjs. No test file was added or kept. Each object below declaressharingModel: 'controlled_by_parent':master_detailfieldsObjectSchema.safeParseObjectSchema.createrequiredomitted["sharingModel"], "but no requiredmaster_detailfield"required: false["sharingModel"]required: truerequiredomitted["fields"](two required after the force)The fourth row is why the new sentence states the exact predicate ("none of whose
master_detailfields declaresrequired: true"). It does not say "amaster_detailwithoutrequired: true", which would be wrong for that row.Which door the sentence claims. It claims only the
ObjectSchemaparse. That means.parse(),.safeParse()andcreate(), which runs the force (:3677) and thenObjectSchemaBase.parse(withDefaults)(:3687).The sentence says nothing about
defineStack,os validate, the metadata save door or stored rows. The refinement's own docblock says stored rows still register, under a[metadata_spec_invalid]diagnostic. I did not read those doors for this PR, so the sentence does not name them.The sentence also drops "only", and it does not say whether the gate still closes the shape. I read what the gate does today: it derives a master from exactly one required
master_detail(security-plugin.ts:7923), and on any write to an object without one it throwsMasterDetailRelationMissingError(:8585). That is not this sentence's subject, so the sentence makes no claim about it.The symbol reference. The new text cites the symbol as
{@link refuseControlledByParentWithoutOneMaster}. That is the form the same docblock already uses one paragraph below the list. It is not apath#symbolanchor, socheck-spec-docblock-symbol-anchorsdoes not judge it. That gate counts12 symbol (12 declaration, 0 literal)on this head, the same as on PR #24290. The name has exactly one declaration in the file:git grep -c "^function refuseControlledByParentWithoutOneMaster("gives 1.Changeset
@objectstack/spec'sfilesarray shipssrc/**/*.zod.ts, so the docblock goes into the tarball as written. It does not reachdist. After the build, 0distfiles carry the docblock's unchanged phrase "arms the worst measured failure shape", and 0 carry the new "closed that shape, by refusing the". The function's name is indist, which is the positive control. So the published surface is the source file, and the changeset is apatchwithClause-②: no. This follows PR #24290 and PR #24072.Verification
The final head is
5d9d774b1, the merge ofmainat21070a2f0. The two commits that merge brought in touch onlypackages/lint,docs/qaand one changeset. Build and test ran throughscripts/pm/os-verify-lock.sh, and every exit code was captured before any pipe.pnpm --filter @objectstack/spec build && pnpm --filter @objectstack/spec check:generated:VERDICT command-exit 0,All 14 generated artifacts are up to date. Nothing was regenerated.pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2:Test Files 694 passed (694),Tests 21040 passed | 1 todo (21041).pnpm --filter @objectstack/spec typecheck:VERDICT command-exit 0, endingcheck:test-typecheck: OK.6abb5d815. The merge brought nopackages/specchange.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon5d9d774b1derived 81 commands from 2 paths against merge base21070a2f0. 80 exit 0. The--ranreconciliation reads81 derived, 80 run, 1 NOT-MEASURED, 0 UNRUN.check-spec-docblock-symbol-anchors:4972 anchors across 2121 spec sources resolve.check:nul-bytes: OK.check-adr-0087-registration:this PR adds no declared-breaking changeset.check-empty-changeset: green.check:doc-formula-expressionsandcheck:lean-entry-closureread built output. Both exit 0, after I built@objectstack/formula,@objectstack/lintand@objectstack/objectql.check:dual-build-cjs-loads(exit 3,PREREQUISITE NOT MET). It reads every workspace package'sdist, and 69 package entries had none. I did not run a whole-repo build locally; that is a declared narrowing. CI'sLint & Repo Gatesruns this gate.git diff -U0ofpackages/specagainst the base has 12 changed lines, and each one starts with*. Neither changed file contains a control byte.git grep "Today only the security gate" -- packages: 0 hits on this head, 1 on the baseef6564684.test:repo(Test Core),pnpm lint, the type-check lanes, and the families outside the derived set.Acceptance notes
:3498–:3507) says "so a raw parse of amaster_detailwithoutrequired: trueis refused rather than forced". The probe's fourth row reads otherwise: next to a requiredmaster_detail, a second one withoutrequiredparses green. (create()refuses that object, because the force makes both required.) Read as "the object's onemaster_detail", the sentence holds. This is an observation, not a defect. Carrier: none.packages/lint/src/data-model-rules.ts:304, a code comment. It says "the security gate's fallbacks stay, so metadata already at rest keeps loading and is still guarded". Since protocol 18, the gate has no fallback:security-plugin.ts:7923, and the gate's own docblock says both tiers retired. This is the same family, a comment that names a retired mechanism in the present tense. It is not published:@objectstack/lint'sfilesisdist,README.mdandCHANGELOG.md. 0distfiles carry the phrase, while the neighbouring code string "readable by nobody" is indist. Carrier: none.Deviations
mainwas merged once before this PR opened (5d9d774b1), as AGENTS.md section 10 asks.75b7c240atwice through the REST API: the commit itself, andsecurity-plugin.tsat that ref. Nothing was fetched into the shared object store.Generated by Claude Code