Repository navigation
feat(spec): a metadata-form row each for object.access, highlightFields, requiredPermissions, searchableFields and permission.adminScope (#20349) - #20405
Conversation
…missions, searchableFields and permission.adminScope Five live keys the object and permission-set schemas declare had no form row, so their only door was the Source tab. Each gets one row mirroring a row a registered form already has: string-tags for the two field-name lists (the view form's searchableFields row), a composite over a select for access (the lifecycle row), and json for the requiredPermissions union and for adminScope (the validations row and the permission form's structured rows). Claude-Session: https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ Co-authored-by: Claude <noreply@anthropic.com>
…nslated in zh-CN, ja-JP and es-ES Claude-Session: https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ Co-authored-by: Claude <noreply@anthropic.com>
… of the provenance tables Claude-Session: https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ Co-authored-by: Claude <noreply@anthropic.com>
…ive new rows; changeset Claude-Session: https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ Co-authored-by: Claude <noreply@anthropic.com>
…a-object-permission-rows
…a-object-permission-rows
…the objectui pin the renderer reading is taken at Claude-Session: https://claude.ai/code/session_01ARcDurZ5j34RdqsGgc4jgH Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 33 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 9 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 136 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 6318287bb7e5c3fe4a8e566222de5534e2abe33a && git checkout 6318287bb7e5c3fe4a8e566222de5534e2abe33a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin df3ba164a588805c6a3b07ec8d91c94737c47b52 353e708aa7808c39a649d8254cf2895a59f9f648 && git checkout -B drift-repro df3ba164a588805c6a3b07ec8d91c94737c47b52 && git merge --no-ff 353e708aa7808c39a649d8254cf2895a59f9f648
node scripts/docs-audit/affected-docs.mjs --json df3ba164a588805c6a3b07ec8d91c94737c47b52
|
Contract reviewServed-tier: Inputs read: card #20349 (body and all 4 comments, the ① Derived judgmentsEach row against its key's schema shape at the head, the ruling's widget rule, and the row it claims to mirror:
Help-text claims, each against its runtime reader at the head — no row claims a reading that does not exist:
Accept set and public surface: no Ruling premise re-checked at the pin, both halves hold on my reading too: Residue 28 → 23: derived from the inputs, not run. The analysis table's object-rooted unexplained set at ② Semver levelChangeset ③ Boundary flagsDev report
Out-of-scope findings, each answered:
Card acceptance met in full: one row per key mirroring a same-shape row, four-locale catalogue rows in the same PR generated as Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #20349
Flight G1a of #19332 (ruling 5861442317).
Clause-②: no
What
Five live keys that the object and permission-set schemas declare had no form row, so an author could reach them only through the Source tab. Each now has exactly one row. Each row copies a row that a registered form already has for the same node shape. The four-locale catalogue rows are in the same PR.
object.highlightFieldsobject.form.ts, Basics (besidenameField)widget: 'string-tags'view.form.tssearchableFieldsobject.searchableFieldsobject.form.ts, Basicswidget: 'string-tags'view.form.tssearchableFieldsobject.accessobject.form.ts, Advanced (besidesharingModel)type: 'composite'over one declareddefaultselect (public/private)object.form.tslifecycleobject.requiredPermissionsobject.form.ts, Advancedwidget: 'json'object.form.tsvalidationspermission.adminScopepermission.form.ts, System Permissionswidget: 'json'permission.form.tsobjects/tabPermissions/rowLevelSecurityThe ruling's widget rules, as applied here:
requiredPermissionsis the one union in this flight:string[]or a strict{read, create, update, delete}map. It takesjsonand neverstring-tags.string-tags, as free text.The help text says what the runtime does with each value, including what absence resolves to. Each claim was checked against its reader:
accessabsent means public:security-plugin.tsreadsisPrivateasaccess?.default === 'private'.requiredPermissionsskips the capability gate:security-plugin.tsnormalizes it to empty buckets.highlightFieldsentry is refused aterroron the object publish door:validateObjectFieldRefs,runtimeTypes: ['flow', 'object'].searchableFieldsentry is refused aterror:validateSearchableFields, includingobject.No schema's accepted input changes, and no export changes. What changes is the form payload that
getMetaTypes()serves and the translation keys thatos i18n extractwalks.The ruling's objectui premise, re-checked at the
.objectui-shapinThe ruling carries this premise for the first form flight to re-check: "
string-tagsreads a non-array as[];field-multibinds nothing on an object draft". I read objectuif8a9d0fb0596f4521076628e2bbfe27e6ce67d52(the pin onmain) from source. No browser was run. Both halves hold.string-tags—packages/app-shell/src/views/metadata-admin/widgets.tsx:1290: the widget setstagsto the value when the value is an array, and to an empty array otherwise.addandremovethen writenext, which is built fromtags, back throughonChange. So a stored map would be replaced by a list on the first edit. HOLDS.field-multi—ResourceEditPage.tsx:889-893: the field catalogue's source object comes frominterfaceConfig.source,data.object,objectorobjectNameon the draft. The object type's served JSON schema declares none of those four (probe on this tree: 43 top-level keys, all four absent, lit controlnamepresent). So the picker would offer an empty list. HOLDS.Also read at the pin, because the new rows depend on it:
resolveFieldFaceinSchemaForm.tsx:667-750.jsonis not a registered widget. It is inKNOWN_PASSTHROUGH_WIDGETS, so the face comes from the node afterresolveUnionBranch.requiredPermissions, the served node is ananyOfof array-of-string and an object with four properties.adminScopeis served inline as an object with properties, so it renders as a nested form.setFieldspreads the stored value and writes only the one key, so no key the author did not touch is rewritten.adminScopestays absent.Residue of the reconciliation gate
The gate's own helper block (
metadata-form-zod-reconciliation.test.ts, the whole file copied into a scratch probe that was never committed) was run at the root coordinate over every object-rooted type. It counts offerable keys, minus offered keys, minus rootomitrows. The probe asserted two controls in the same run:field.accept, a G1b key, is in the residue on both trees;object.zzFabricated20349is in no residue.15bf186f5src/as head353e708a)Removed:
object.access,object.highlightFields,object.requiredPermissions,object.searchableFields,permission.adminScope. Added: none. Both probe runs: 1 file, 58 tests passed.Verification
All test runs went through
scripts/pm/os-verify-lock.sh, and each reportedVERDICT command-exit 0.pnpm --filter @objectstack/spec testpnpm --filter @objectstack/spec test:repopnpm --filter @objectstack/platform-objects testpnpm --filter @objectstack/spec typechecktsc --noEmit,check:scripts-typecheck,check:test-typecheckOK: 53 files, 255 errors, 142 signatures held by the ledger)pnpm --filter @objectstack/platform-objects typecheckcheck:test-typecheckOK: 1 file, 3 errors, 2 signatures held by the ledger)src/validate-predicate-path-refs.test.ts(reads the object form)test/i18n-coverage.test.ts(reads the form registry)pnpm check:i18n(after the closure build it names)pnpm --filter @objectstack/spec check:generatedGates:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 85 commands. All 85 were run, and each exit code was written to disk before it was read.--ranreports: 85 derived, 85 run, 0 NOT-MEASURED, 0 UNRUN.check:dual-build-cjs-loadsfirst exited 3 (PREREQUISITE NOT MET: nine packages had nodist/). The missingdist/directories then existed, and the re-run exited 0. The re-run's code is the one recorded.check:entry-nameabilityprints its standing structural NOT MEASURED line for two entries with no callable export. That line is independent of this diff.No ablation was run. This PR adds rows and moves population pins; it adds no guard. The residue before/after above is the measurement of the delta.
Hand-over account
This branch was resumed from
bc1d395d3, the previous dev run's pushed head. It was re-read hunk by hunk and re-verified in full.7ce01726(the five rows): kept. Every help-text claim was checked against its runtime reader, and every comment claim about the renderer was checked at the pin (above).3820f4a7and3ae44a78(catalogue rows and re-extract): kept. After the merge,pnpm check:i18nregenerates them byte-for-byte. The source-hash rows the first commit added were removed by the second, net zero.abbf9c7f(echo-decision pins and changeset): kept. The pins were already complete: the platform-objects suite passes on the merged tree with no further pin edit.353e708a(this run): the changeset lacked theClause-②: noline. It now carries that line, and it names the pin that the renderer description is read at.e505f724: merge oforigin/mainviascripts/pm/os-regen-merge.sh. No os-regen path conflicted, and nothing needed regenerating (check:generatedis green).Acceptance notes
os validate(the two lint rules above, aterror), not in the Zod parse:ObjectSchemadoes not judge field names. The changeset says so.metadata-form-zod-reconciliation.test.tshas a dark-control comment reading "object.accessis authorable and no form offers it". After this PR a form offers it. The assertions under that comment still hold, because they test ruled-class admission and not whether a key is offered. That file is the ledger, which is outside this flight's surface. Carrier: the #19188 split: 145 top-level zod-only keys need a RECORDED REASON, never a form row — and none can be recorded until the ledger learns a root path #19333 item 2 wiring, which edits that file.string-tagsacross the registry. A future row could fliprequiredPermissionsback without any test turning red. This is not a defect today. It is noted for the #19188 split: 145 top-level zod-only keys need a RECORDED REASON, never a form row — and none can be recorded until the ledger learns a root path #19333 wiring, which is where a registry-wide union check would live.adminScope, clearing every sub-field leaves an empty object, and the parse then refuses it loudly becausebusinessUnitis required. The failure is loud, it comes from the generic objectui renderer, and it is the same for every nested-form row. Carrier: none.Generated by Claude Code