Uh oh!
There was an error while loading. Please reload this page.
feat(spec): ActionParamSchema.carryOver — seeded from the row, rendered read-only, submitted verbatim (#11992) - #12614
Conversation
…ed read-only, submitted verbatim (#11992) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012xGvxcwPRTJfA7RfjXEYA4
…tionparam-carryover-key
📓 Docs Drift CheckThis PR changes 2 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 129 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin fde0e515b92cf59ec66f4c133a9fb9665e1c1c31 && git checkout fde0e515b92cf59ec66f4c133a9fb9665e1c1c31
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d7b3963c2d4f2b8f043f04fa708d54f642bd97e9 3986bafead79b57041d6e33c7344005bd6608d1f && git checkout -B drift-repro d7b3963c2d4f2b8f043f04fa708d54f642bd97e9 && git merge --no-ff 3986bafead79b57041d6e33c7344005bd6608d1f
node scripts/docs-audit/affected-docs.mjs --json d7b3963c2d4f2b8f043f04fa708d54f642bd97e9
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#11992
Spec half of the #11753 ruling (maintainer 2026-08-25, verbatim 「同意」 on recommendation A; full record = #11753 comment 5404887360). Parent #11753 remains open (tracking); the objectui renderer leg is its downstream card and is not addressed here.
Pre-measurements (both named by the card; both gate the design)
1. Can objectui's
ActionParamDialogcarry a read-only-summary render for a param it must still submit? — YES, no renderer surgery. Measured read-only in the local objectui checkout (586cfc2baa4e, zero objectui edits):packages/app-shell/src/utils/resolveActionParams.tsresolvesdefaultFromRowintodefaultValue(defaultValue: rowDefault ?? param.defaultValue), andActionParamDialogseeds itsvaluesstate fromparam.defaultValueon open.valuesSTATE, never the DOM:handleSubmitresolvesserializeParamValues(visibleParams, values). A param rendered as a read-only summary instead of an editable widget keeps its seeded value invaluesand is submitted verbatim.resolveActionParam(its output object is explicit-key, not a spread) plus a render branch in the dialog, plus the dialog-level render pin the ruling names.packages/types/src/ui-action.tsderives its authoring keys by reference fromz.inputof the spec'sActionParamSchema, so the new key flows into the authoring type automatically.Fallback B (objectui-only collapsed summary) is NOT taken — the measurement does not point there.
2. Population sweep — every shipped
defaultFromRowparam bound to a JSON-serialized column (identities, not counts). Swept every shipped file declaringdefaultFromRowparams (repo-wide grep: 2 in plugin-security, 9 in platform-objects/identity; plugin-approvals declares none — config-borrow only):sys_permission_set.clone_permission_set:object_permissions,field_permissions,system_permissions,row_level_security,tab_permissions— all five areField.textareacolumns written withJSON.stringifybypermissionSetRowFields(). This is the entire population.defaultFromRowparam binds a scalar column:sys_permission_set.description+sys_position.description(text),sys_account.accountId(id),sys_invitation.email/role,sys_member.role,sys_oauth_application.client_id(4 actions),sys_organization.name/slug/logo(url),sys_sso_provider.providerId/domain,sys_team_member.teamId/userId,sys_team.name,sys_user.name/image(url).sys_approval_requestdeclares nodefaultFromRowparam (config-borrow only).So the exemplar set below is exactly the population the key serves today.
What this PR does
ActionParamSchema.carryOver(packages/spec/src/ui/action.zod.ts): one CLOSED optional boolean expressing the ruled contract — seed from the row, render as a non-editable summary, submit verbatim — stated in full in the key's.describe(). Measured constraint restated from the parent:visible: falseis NOT this contract (it omits the param from the submission entirely).carryOver: truerefuses withoutdefaultFromRow: true(pathcarryOver; the message carries the repair and thebodyExtraalternative). A carry-over with no row seed would render an empty locked control and submit nothing — the Theclone_permission_setaction copies only 2 of the 6 definition facets, so a clone silently drops system permissions, RLS and tab permissions #11703 silent-drop shape reintroduced through the key added to close it.readonly/disabled(the words option A itself used, borrowed fromFieldSchema/ widget vocabulary) are refused with guidance namingcarryOver— a field'sreadonlymeans write-path strip, exactly the wrong half here.packages/plugins/plugin-security/src/objects/sys-permission-set.object.ts): the fiveclone_permission_setJSON facet params declarecarryOver: true, riding this PR per the card.descriptiondeliberately stays editable — it is prose, not a permission facet; the objectui leg / The Clone dialog for a permission set now offers five raw JSON blobs as editable inputs —defaultFromRowhas no non-editable carry-over #11753 can revisit if the ruling's "two ordinary inputs" is read to include it.packages/spec/src/ui/action-param-carryover.test.ts(9 tests): accept set + parsed-output carry, thedefaultFromRowco-requirement (both directions, message content), alias prescriptions, and the.describe()three-semantics contract.packages/plugins/plugin-security/src/objects/clone-carryover.test.ts(7 tests): the five facets BY NAME declare the key,descriptiondoes not, and every clone param parses underActionParamSchema.clone_permission_setaction copies only 2 of the 6 definition facets, so a clone silently drops system permissions, RLS and tab permissions #11703 pin 6 (send-side) stays green untouched —packaged-permission-set-lock.test.tspasses unmodified;carryOverchanges what the dialog renders, never what it sends (itsclonePayloadreads onlyfield/name/defaultFromRow).packages/spec/authorable-surface/ui.jsongainsui/ActionParam:carryOver(viagen:schemain the spec build);content/docs/references/ui/action.mdxregenerated (gen:docs).check:livenessgreen with no ledger edit —action/paramsis a RECORDED undrilled container (child keys inherit its blanket verdict);gen:liveness-countsproduced no diff.@objectstack/specminor (new authorable key — accept-set expansion, adr-0087 not-required, same class as theonSuccessprecedent07e630e58),@objectstack/plugin-securitypatch (metadata declaration on an existing action, same class as Theclone_permission_setaction copies only 2 of the 6 definition facets, so a clone silently drops system permissions, RLS and tab permissions #11703's changeset).Verification
All at final head
3986bafea(branch includes a clean merge of currentorigin/main=d7b3963c2, i.e. after landing-watch #12612/#12611 — disjoint files; spec rebuilt at the merged head before every reading below):pnpm --filter @objectstack/spec build(gen:schema + gen:openapi + tsup + dts) — exit 0,check-dts-emitted: 34/34.3986bafea: specsrc/ui/action-param-carryover.test.ts+src/shared/alias-integrity.test.ts+src/ui/action-on-success.test.ts—Tests 48 passed (48); plugin-securitysrc/objects/clone-carryover.test.ts+src/packaged-permission-set-lock.test.ts+src/objects/rbac-objects.test.ts—Tests 46 passed (46).3986bafea: both packages exit 0 (spec's includes itscheck:test-typecheckhalf;tsc -p tsconfig.test.json --listFilescounts the new spec test file in that program — 1 hit).tscstayed GREEN (plugin-security's tsconfig excludes**/*.test.tsand its action literals do not surface excess-property checks for this key — the compile-time gate does not police this surface); the STRICT PARSE gate went RED exactly as required — plantingcarryOverX: trueon one exemplar param (mutation proven on disk: anchoredgrep -c0→1) failedclone-carryover.test.tsat module load withunrecognized_keysnamingcarryOverXand suggestingcarryOver. So declared=enforced is carried by the schema at object-definition time, not by tsc. Restore viagit checkout HEAD -- FILEPATH(absolute path), proven by emptygit diff HEAD+ grep 0.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, derived at3986bafea): 45 path-derived + convention-triggered families run locally — all exit 0 with their own verdict lines (check:authorable-surface,check:generated"All 14 generated artifacts are up to date",check:liveness,check:i18n"OK (9 packages)",check:skill-examples"260 prose examples type-check",check:nul-bytes"OK", alias/docs/changeset families,check:engine-double-contract,check:where-matcher,check:cross-package-test-inputs,check:type-check-coveragestructural half, …), except two declared narrowings:check:type-check-debt --re-measurerefuses on this worktree (39 workspace deps of OTHER ledger entries unbuilt — its own designed refusal, NOT MEASURED ≠ red). Narrowed measurement taken instead: the only ledger entry this diff can move is TEST_DEBT@objectstack/plugin-security; with tests included in that package's own tsconfig program, error count is 13 with and 13 without the new test file (0 lines name it) — delta 0, no upward drift possible from this diff. The spec.d.tschange is an additive optional key (can only remove excess-property errors, never add).node scripts/check-dev-prereqs.mjs— prerequisite not met (11 unrelated packages, studio/connectors, unbuilt in this worktree); it measures checkout build state, not the diff; CI's lint job builds the workspace before it.check:livenessneeds no ledger edit:action/paramsis a RECORDED undrilled container (its baseline covers new child keys); gate green, counts unchanged.Clause-② (contract-review tier): this PR widens the authorable public surface — staying DRAFT for maintainer review; no ready-flip, no auto-merge.
🤖 Generated with Claude Code
Generated by Claude Code
Generated by Claude Code