From 3fcf4cb7dcf803bbe2955b5c1970075071df72d9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Oct 2026 07:59:46 +0000 Subject: [PATCH 1/2] docs(spec): name the mechanism each of two docblocks describes The ActionParam options docblock's visibleWhen bullet quoted objectui's inline param branch as `options: param.options`; at the console pin it reads `options: authoredOptions` (resolveOptionLabels over the authored list). The cbp-master-detail-required-defaulted conversion's docblock said an update nulling the master was refused before by the security gate; that gate reads the stored row's FK on a by-id update, and the refusal is the record validator's ADR-0113 check, which only `required: true` arms. Comment text only, plus a patch changeset for @objectstack/spec. Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK Co-authored-by: Claude --- .changeset/24187-docblock-mechanisms.md | 14 ++++++++++++++ packages/spec/src/conversions/registry.ts | 17 ++++++++++++++--- packages/spec/src/ui/action.zod.ts | 9 +++++++-- 3 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 .changeset/24187-docblock-mechanisms.md diff --git a/.changeset/24187-docblock-mechanisms.md b/.changeset/24187-docblock-mechanisms.md new file mode 100644 index 00000000000..bdad0148c14 --- /dev/null +++ b/.changeset/24187-docblock-mechanisms.md @@ -0,0 +1,14 @@ +--- +'@objectstack/spec': patch +--- + +Clause-②: no + +The `ActionParam` `options` docblock in `src/ui/action.zod.ts` no longer quotes objectui's inline +param branch as `options: param.options`. At the console pin (`.objectui-sha` `5e15928b4631`) that +branch of `resolveActionParam` reads `options: authoredOptions`, where `authoredOptions` is +`resolveOptionLabels(param.options, ctx.locale)`: the authored array itself when every `label` is +already a string, and otherwise only the entries with an inline per-locale `label` re-spread with +that label resolved. The docblock's point is unchanged: an inline param's options, `visibleWhen` +included, reach the widget as authored. No key, schema, export, accepted value or message text +changes. diff --git a/packages/spec/src/conversions/registry.ts b/packages/spec/src/conversions/registry.ts index f8c9af2fa52..00db5e5f238 100644 --- a/packages/spec/src/conversions/registry.ts +++ b/packages/spec/src/conversions/registry.ts @@ -14461,9 +14461,20 @@ const pageAssignedProfilesRemoved: MetadataConversion = { * `master_detail` under this sharing model into `required: true` * (`forceCbpMasterDetailRequired`, `data/object.zod.ts`), so the converted row * is exactly the object the builder would have produced from the same source. - * The relation it resolves is the one the second tier resolved, and the write - * the row could not legally take (an insert without its master, an update - * nulling it) was refused before as well, by the security gate. + * The relation it resolves is the one the second tier resolved. Of the two + * writes the row could not legally take, the insert without its master was + * refused before as well, by the security gate: on an insert + * `packages/plugins/plugin-security/src/security-plugin.ts#assertControlledByParentWrite` + * reads the master FK from the incoming body. The update nulling it was not. + * On a by-id update that gate reads the STORED row's FK, never the incoming + * value, so the write was accepted and left a detail row with a null master + * FK, which the `controlled_by_parent` read filter (`masterFK IN …`) never + * matches. The refusal for that update is the record validator's ADR-0113 + * check, a PATCH may not null out a required field (the update branch of + * `packages/objectql/src/validation/record-validator.ts#validateRecordInScope`), + * and only `required: true` arms it. So the converted row loses no write the + * contract allows: it gains the refusal the builder's force already gives an + * authored object. * * **Deliberately narrow — exactly one shape, nothing else.** * - An explicit `required: false` is NOT converted. The builder refuses it diff --git a/packages/spec/src/ui/action.zod.ts b/packages/spec/src/ui/action.zod.ts index 1652173322c..2364ed68f73 100644 --- a/packages/spec/src/ui/action.zod.ts +++ b/packages/spec/src/ui/action.zod.ts @@ -247,8 +247,13 @@ export const ActionParamSchema = lazySchema(() => strictObject( * guessed at here, and #5016 answered it **per key, on measurement**: * * - **`visibleWhen` — opened.** The reader is on this exact path and it works - * today. An inline param's `options` are lowered VERBATIM (objectui - * `resolveActionParam`'s inline branch does `options: param.options`; + * today. An inline param's `options` are lowered VERBATIM (at the console + * pin, `.objectui-sha` `5e15928b4631`, objectui `resolveActionParam`'s + * inline branch does `options: authoredOptions`, and `authoredOptions` is + * `resolveOptionLabels(param.options, ctx.locale)`, which returns the + * authored array itself when every `label` is already a string and + * otherwise re-spreads only the entries whose `label` is an inline + * per-locale map, resolving that `label`; * `ActionParamDialog` re-spreads each entry to localise `label`; * `paramToField` passes the array straight into the widget's field * metadata), and `SelectField` / `MultiSelectField` / `RadioField` / From fe8a8888614bb169da3a3ed7f445c373dfd00fc0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Oct 2026 08:02:23 +0000 Subject: [PATCH 2/2] docs(spec): spell the cbp read filter the way the spec already does Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK Co-authored-by: Claude --- packages/spec/src/conversions/registry.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/spec/src/conversions/registry.ts b/packages/spec/src/conversions/registry.ts index 00db5e5f238..5dacc0aa3dc 100644 --- a/packages/spec/src/conversions/registry.ts +++ b/packages/spec/src/conversions/registry.ts @@ -14468,9 +14468,10 @@ const pageAssignedProfilesRemoved: MetadataConversion = { * reads the master FK from the incoming body. The update nulling it was not. * On a by-id update that gate reads the STORED row's FK, never the incoming * value, so the write was accepted and left a detail row with a null master - * FK, which the `controlled_by_parent` read filter (`masterFK IN …`) never - * matches. The refusal for that update is the record validator's ADR-0113 - * check, a PATCH may not null out a required field (the update branch of + * FK, which the `controlled_by_parent` read filter + * (`masterFK IN (accessible master ids)`) never matches. The refusal for that + * update is the record validator's ADR-0113 check, a PATCH may not null out a + * required field (the update branch of * `packages/objectql/src/validation/record-validator.ts#validateRecordInScope`), * and only `required: true` arms it. So the converted row loses no write the * contract allows: it gains the refusal the builder's force already gives an