Repository navigation
feat(spec): the position form picks permissionSets from the permission-set registry (ref-multi:permission), with the console pin moved to 4997995022f8 - #22794
Conversation
…n-set registry (ref-multi:permission) ADR-0131 D4 resolves a reference to a declared item by machine name, registry-first. The position form's permissionSets row was a free-text tags box; it now declares widget 'ref-multi:permission', the list form of the ref:<type> pickers, naming the permission-set registry (metadata type permission). The row drops type 'tags': an explicit widget outranks type inference. A pin in position.test.ts reads the served form registry. Claude-Session: https://claude.ai/code/session_01S3aAf11JjbW1mSGL1EhfFj Co-authored-by: Claude <noreply@anthropic.com>
…ket placeholder Claude-Session: https://claude.ai/code/session_01S3aAf11JjbW1mSGL1EhfFj Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 10 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 7 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 140 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 324015238e0e2d253b866cba3f9357232afd8225 && git checkout 324015238e0e2d253b866cba3f9357232afd8225
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1eff3224d7e7d28474152f12e01819fa0ab1fb3a 483fddb3fc8cd50900b86f87100e7e793948d819 && git checkout -B drift-repro 1eff3224d7e7d28474152f12e01819fa0ab1fb3a && git merge --no-ff 483fddb3fc8cd50900b86f87100e7e793948d819
node scripts/docs-audit/affected-docs.mjs --json 1eff3224d7e7d28474152f12e01819fa0ab1fb3a
|
|
Held in draft on purpose ·
|
…sition-permission-sets-picker
…ui#12136) Moves the bundled Console's objectui pin from 0df67f237c5a to 4997995022f8e65946f6362b7434685539cc72b7, objectui#12136's merge (objectui#12126): metadata-admin registers the ref-multi:permission widget, so the position form's permissionSets row renders a picker of declared permission sets. It is the nearest objectui main commit that carries the widget, 7 commits past the old pin (0 merges, 80 paths). The bump follows the repo's procedure: scripts/bump-objectui.sh writes the pin and the console changeset; pnpm objectui:build and node scripts/gen-sdui-manifest-node.mjs re-record the SDUI manifest; check-sdui-lockstep --update re-records the lockstep; the asserting objectui pin citations in packages/spec/src are re-measured at the new pin; gen:docs regenerates the one describe whose sha moved. sdui.manifest.json is byte-identical (sha256 9d87162a3ef6, 107 components); only its record names the new pin. No key, default, enum member or export moves: check:generated, check:api-surface and check:authorable-surface are green on this tree. Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
…sition-permission-sets-picker
Landing pre-checks at
|
Fixes #22682
Clause-②: no
Item 3 of #22682, the card's last
packages/specitem (items 1 and 2 landed with PR #22710 asb32ee3eaf3). ADR-0131 D4, verbatim: "References to declared items are by machine name, resolved registry-first." The position form is the one authoring surface for the position to permission-set binding, and itspermissionSetsrow (added by PR #22723 astype: 'tags', a free-text tag box) now names the permission-set registry picker, so the designer offers the declared set names instead of free text.This PR now also moves the bundled Console's objectui pin to
4997995022f8, objectui#12136's merge (objectui#12126), which registers theref-multi:permissionwidget. That is landing order B from seat 1's ACCEPT6106524871on #22682: the objectui widget first, then the pin, then this row. Triage's unlock6108789665put the pin bump into this PR.6105901038(seatdomain:spec#1), basee84aeb36ce, accepted at8996b566b5by ACCEPT6106524871. The three files of that round are unchanged here.6109075588(seatdomain:spec#3, sessionsession_01KNKBCRDJCu5tGy3TEbvtrF), branchclaude/issue-22682-position-permission-sets-pickercontinued from8996b566b5. It mergesmain(aa734f0924ontoefcbac73cc, then483fddb3fconto1eff3224d7, both throughscripts/pm/os-regen-merge.sh), and adds one pin-bump commit,aaa50c66ec.What changes
packages/spec/src/identity/position.form.ts):{ field: 'permissionSets', type: 'tags', colSpan: 2, helpText: … }becomes{ field: 'permissionSets', widget: 'ref-multi:permission', colSpan: 2, helpText: … }, with a comment saying why. The helpText is unchanged. No other row, section or form changes.packages/spec/src/identity/position.test.ts, a newdescribeat the end, 4 tests). It reads the position form throughMETADATA_FORM_REGISTRY, the registry/meta/typesserves, which holds the output ofdefineForm's parse, so a parse that droppedwidgetwould also turn it red. It asserts:permissionSetsrow, whosewidgetisref-multi:permission;permission, andgetMetadataTypeSchema('permission')isPermissionSetSchema;permissionSetsrefuses a bare string withinvalid_typeatpermissionSets, so the list-valued key sits on a list-valued field.@objectstack/specpatch,Clause-②: no.PositionSchemais untouched (permissionSetsstaysz.array(SnakeCaseIdentifierSchema).optional()), the approver bindings are untouched, andFormField.widgetstaysz.string().optional(): the spec has no form widget-key vocabulary, so no published vocabulary is added. No objectui file.aaa50c66ec, 20 files, +312 / -83):.objectui-shafrom0df67f237c5ato4997995022f8, with its regeneration. The next section has the details.The console pin:
0df67f237c5ato4997995022f8This moves the bundled Console's objectui pin from
0df67f237c5a(PR #22812) to4997995022f8e65946f6362b7434685539cc72b7, objectui#12136's merge (objectui#12126): metadata-admin registersref-multi:permission, so the position form'spermissionSetsrow renders a multi-picker of the declared permission sets instead of the raw-JSON fallback.This is not a release act. The Version Packages PR is untouched, and this diff edits neither
packages/console/CHANGELOG.mdnorpackages/console/package.json. The console's release input is the new.changeset/console-4997995022f8.md.Range
compareon objectstack-ai/objectui,0df67f237c5a...4997995022f8(read 2026-10-11T12:55:58Z):status: ahead,ahead_by: 7,behind_by: 0, 80 files, merge base0df67f237c5a.git merge-base --is-ancestor 4997995022 origin/mainexits 0 (objectuimainread at1d1cfe0c119e, 2026-10-11T12:39:18Z).0df67f237c5a..4997995022f8has 7 commits and 0 merges. Its 80 paths are 17 added, 62 modified, 1 renamed and 0 deleted.maincommit that carries the widget, as the dispatch ruled. objectuimainhas two more commits (9b75509,1d1cfe0). I measured no reason to take them, so this bump does not chase them.What each commit brings, and which regenerated change it causes
69bca0a(objectui#12081 item 3)ObjectGrid.tsx(+75 / -41) and theen/zh/depacks: hop sentences in the fourObjectGrid.tsxrecords and the calendar record; corpus counts802f0bc(objectui#5250 slice C, the strict authoring face)packages/types/examples/dashboard.tsonly. No record cites them. Its one regenerated effect is a corpus control:timeout1705 to 1704 in two migration entries1071393(objectui#12098)7525045(objectui#12082 leg A)ObjectGrid.tsx(+14 / -3: a bulkupdatedef needs the edit verdict) andObjectGantt.tsx(+27 / -4): hop sentences in five gantt records and the grid recordsc9fc10b(objectui#12121)form.tsx(+37 / -25): the form-layout record's call-site anchor moves:3246to:325866eb560(objectui#12131 phase 1, the strict authoring face)4997995(objectui#12126)ref-multi:permissionwidgetwidgets.tsx,ResourceEditPage.tsxand metadata-admini18n.ts. No record cites them; corpus countsNeither of the two strict-authoring-face commits touches a registered component, so neither moves the SDUI manifest.
What the regeneration changed (20 files)
.objectui-shascripts/bump-objectui.sh 4997995022f8… --no-commit.changeset/console-4997995022f8.mdminorcheck:objectui-changeset, Check Changesetscripts/sdui-manifest.record.jsonnode scripts/gen-sdui-manifest-node.mjsover.cache/objectui-4997995022f8check-sdui-manifest.mjspackages/sdui-parser/objectui-lockstep.jsonnode scripts/check-sdui-lockstep.mjs --update,OBJECTUI_ROOTat the built treecheck:sdui-locksteppackages/spec/src, and 6 in the generatedregistry.tscheck:objectui-pin-citations(and--verify-anchors).changeset/objectui-pin-citations-4997995022f8.md@objectstack/specpatch,Clause-②: nocontent/docs/references/ui/view.mdxpnpm --filter @objectstack/spec gen:docs, the one artifactcheck:generatednamed stalecheck:docssdui.manifest.jsonis byte-identical (cmpagainst the tracked file: equal). It keeps 107 components and sha2569d87162a3ef6…. Only its record moves:objectuiShaandmodulesRoot.0131f27cf86d), 25 diagnostic codes and containment predicate76c18fb95d1f. All are unchanged; objectui'spackages/sdui-parseris untouched by the range, so no port is owed. Onlyrev,revDateandrecordedAgainstPinmove.packages/spec/src/migrations/registry.tsis gitignored and regenerated (gen:migration-registry), not committed.How the citations were re-measured
0df67f237c5aand intersected with the range's 80 changed paths. A record whose cited files are all byte-identical (git diff --quiet) gains that sentence and no re-pointing: 29 records. The other 20 were re-read one by one.ObjectGrid.tsx,ObjectGantt.tsxandform.tsx, plus theen/zh/depacks, whosecalendar.configRequiredlines hash the same at both pins. Every cited line in them moved or held byte-identical, measured by exact-text search of each old range in the new file:ObjectGrid.tsx: the selection block4897-4924to4981-5008, still hashing toc88443302d40;:1786to:1787;:1850to:1851;:5097-5116to:5165-5184;:5132to:5200;:5570to:5615;:6587-6588to:6632-6633;:6604-6605to:6649-6650.ObjectGantt.tsx: +2 above the new grant block and +23 below it, for examplegetGanttConfig:610to:612and the refusal arm:2307to:2330. The gantt record's 25 read-point anchors are re-cited at this pin.form.tsx:spanLadderForheld at:274-301; its call site moved:3246to:3258.ObjectGrid.tsx(objectui#12082): a bulkupdatedef is now withheld unless the edit verdict allows it. That narrows the declared defs and adds no implicit one, so thesys_api_keyreading inapi-methods-batch-conformance.test.tsholds.ObjectGantt.tsx(objectui#12082): the in-chart write handlers are withheld without the grant. The renderer'sschema.*read set is the same 49 names at both pins, so no declared key set moves.keyboardNavigation: 15 hit lines against 3 forschema.editable, at both pins.ObjectKanban.tsx'squickAdd/onQuickAdd/onCardClick: 2 / 2 / 11. No'kanban-ui'literal in objectui's non-test sources at either pin.ElementDataSourceGateoccurrences in the fivesrc/index.tsxshells: 0, 3, 3, 4 and 3, all five shells byte-identical.git grep -o -F. That method first reproduced every0df67f237c5anumber: 8424 files,objectstack18248,@objectstack/spec7582,useState2645,timeout1705,RuntimeConfig347,resourceLimits2,window4596,period251,interval233,metrics456,Span517,SpanSchema57,TTL184 andtenant1357.objectstack18271,@objectstack/spec7587,useState2648,timeout1704,RuntimeConfig352,resourceLimits2 (the same twopackages/app-shellhits),window4604,period251,interval238,metrics456,Span517,SpanSchema57,TTL184 andtenant1362.plugin-lifecycle-advanced.zod.ts,tracing.zod.tsandmetrics.zod.ts, plus every named key). 96 read 0 at both pins;Span/SpanSchemaread 517 / 57 at both.Clause-②: the pin hop moves no published contract, measured
.zod.tsaccept sets, defaults and exports: none moves. Each of the 14 changed.tsfiles underpackages/spec/srcwas scanned with the TypeScript scanner ataa734f0924and at the head, comments skipped and every string, template and regular-expression literal replaced by one placeholder token. The token sequences are identical in 12 of the 14. The other two,18.system-metrics-jsdoc-durations-unit-in-key.tsand18.system-tracing-otel-exporter-durations-unit-in-key.ts, each gain one+joining one new string fragment (the older count) into areasonstring. No identifier, key, value or export moves. The one shippeddescribethat changes isFormField.span's, and only its sha moves.check:api-surface("public API surface + factory signatures unchanged") andcheck:authorable-surfaceare green, andcheck:generatedreads "All 14 generated artifacts are up to date".os validatecompile against is byte-identical, so no page source changes verdict.Clause-②: no, the same as this PR's line 2 and as both new changesets. No line in this body moves.The widget renders at the new pin
packages/app-shell/src/views/metadata-admin/widgets.tsx:3242isexport const WIDGETS = {, and:3246is'ref-multi:permission': RefMultiPermissionWidget,.WIDGET_LABELLINGdeclares it'group'at:3358. The key is absent fromwidgets.tsxat0df67f237c5a.ResourceEditPage.tsx:1001-1004loads thepermissioncatalog (client.list('permission')) only when the form on screen declares the widget, which the position form now does.The key:
ref-multi:permission, measuredobjectui readings are
git showin a read-only clone, at objectuiorigin/main206505cand at this repo's pin.objectui-sha20c6d351ad74; both read the same at every line cited.ref:spelling exist anywhere?ref:widget literals: this repo hasref:object,ref:datasetandref:componentonly; objectui has the same three. All three are single-valued.field-ref/field-multi,select/multiselect, andaction-multi. No key in either repo serves both arities.ref:widgets take an array?RefObjectWidget,RefComponentWidgetandRefDatasetWidget(widgets.tsx:464,:607,:684) each readString(value)and write one string. The registry also declares ONE labelling per key (WIDGET_LABELLING,widgets.tsx:3142): the singleref:pickers are'control', whilefield-multi,action-multiandmultiselectare'group'.permission, on both sides. Spec:MetadataTypeSchema(kernel/metadata-plugin.zod.ts:166), theDEFAULT_METADATA_TYPE_REGISTRYrow labelledPermission Set(:1117), andgetMetadataTypeSchema('permission')returnsPermissionSetSchema. objectui:registerMetadataResource({ type: 'permission', … })(PermissionMatrixEditor.tsx:28), andanchors.ts:374.type: 'tags'override an explicit widget?inferWidgetreturns an explicitfieldSpec.widgetfirst (SchemaForm.tsx:447).tagsmaps tostring-tagsonly when no widget is declared (:467). So the row dropstype, matching every otherref:and-multirow in the spec forms, none of which declares atype.So the key is a distinct list key in the
ref:family. The prefix carries the arity, and the token after the colon is the exact registry type, so the same split readsref:objectandref-multi:permission.Alternatives considered:
ref:permissionon the array field, with the arity read from the schema. It is one key per type, but no widget in the vocabulary is arity-polymorphic, a singleref:widget writes a string into a list, and one key would have to carry one labelling for two shapes.permission-multi, mirroringfield-multi. It drops theref:namespace the claim names.ref:permission-multi. The token after the colon would no longer be a registry type.What objectui rendered for this key before the pin moved, and the landing order
Round 1's reading, measured by code path at objectui
206505cand at the then pin20c6d351ad74:ResourceEditPage. Its form isentry?.formfrom/meta/types(ResourceEditPage.tsx:456,:457,:2943). Position registers only a preview (previews/index.ts:87), not an edit page. So the key reaches every console as soon as the server runs this spec.WIDGETS(widgets.tsx:3033) had noref-multi:permissionentry.resolveFieldFacetherefore sent an unregistered, non-passthrough hint to theraw-jsonface (SchemaForm.tsx:870), rendered at:2223: a JSON textarea under the notewidget ref-multi:permission — falling back to JSON until a custom renderer is registered.(i18n.ts:2166).ref:dataset, and the joined-block row in spec(ui):reportForm's "Joined blocks" row offers a block'sdatasetas free text with norequired, so Studio saves the block #21702 now refuses #21714 declaredref:datasetonly after the pin carried the renderer.Seat 1 set landing order B in ACCEPT
6106524871: the objectui companion first, then the pin, then this row. The companion was filed as objectui#12126 and landed as objectui#12136 (4997995022). This PR now carries the pin as well, so the row and the widget that renders it ship in one console build. The section above has the reading at the new pin.Tests and gates
This round's readings are at head
483fddb3fc(git rev-parse --short HEAD) unless a line says otherwise. Every build and test went throughscripts/pm/os-verify-lock.sh, and each result below is quoted from itsVERDICTline or from the gate's own verdict line. Exit codes were captured before any pipe.@objectstack/spec:pnpm --filter @objectstack/spec build: VERDICT command-exit 0. Its declaration stamp (9278c1ed04e9…) still matches at483fddb3fc; the second merge brought nopackages/specpath.pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2:Test Files 648 passed (648),Tests 19346 passed | 1 todo (19347), VERDICT command-exit 0. It read the same ataaa50c66ec, before the second merge.pnpm --filter @objectstack/spec typecheck(tsc --noEmit, the scripts program and the test layer): VERDICT command-exit 0. The test-layer line reads52 file(s) / 246 error(s) / 135 pinned signature(s) held.pnpm --filter @objectstack/spec check:generated: "All 14 generated artifacts are up to date". The one stale artifact beforegen:docswascontent/docs/references/**(check:docs), from theFormField.spandescribe.@objectstack/sdui-parser(its lockstep record moved):pnpm --filter @objectstack/sdui-parser test:Test Files 14 passed (14),Tests 225 passed (225); its typecheck exits 0.check-sdui-manifest.mjs: "present, intact (sha256 9d87162a3ef6…, 107 components) and recorded at the live objectui pin 4997995022f8…" (exit 0).check:sdui-lockstep: "OK — this copy is byte-identical to objectui@4997995022f8 (2026-10-11T10:56:33+00:00) over 214 grammar line(s) [blob 0131f27cf86d], agrees on all 25 diagnostic code(s) and on thenot-a-containerpredicate [blob 76c18fb95d1f]" (exit 0).check:objectui-pin-citations: "55 asserting objectui pin citation(s) match .objectui-sha (499799502), 145 historical citation(s) recorded and not checked, across 1983 spec source(s)." With--verify-anchors(OBJECTUI_ROOTat the scratch repository): "13 anchor content assertion(s) verified against objectui at 499799502" (exit 0).turbo run build --filter=@objectstack/client...,bash scripts/build-console.sh, the dist assertion,check:console-shaandcheck:console-injection --require-stamp. Run here, at the pin commit's tree:turbo run build --filter=@objectstack/client... --filter=@objectstack/sdui-parser --concurrency=2:Tasks: 34 successful, 34 total, VERDICT command-exit 0.bash scripts/build-console.sh: VERDICT command-exit 0 (lock held 8m18s). Its log reads "Bundle canary 'import/jobs' present", "Single-zod canary: exactly one zod version literal {major:4,minor:6,patch:5}", "Console bundle carries THIS tree's @objectstack/spec, and only it" and "@objectstack/console dist ready (64384 KB) from objectui@4997995022f8".index.html,assets/and a.objectui-shastamp reading4997995022f8e65946f6362b7434685539cc72b7.check:console-sha: "Console dist matches the objectui pin (objectui@4997995022f8)" (exit 0).check:console-injection --require-stamp: "the dist carries this tree's copy, and not the published one" (exit 0). Both also ran green at483fddb3fcinside the derived union.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, run with no paths at483fddb3fc, derives 126 commands from 23 changed paths against merge base1eff3224d. All 126 ran.--ranreconciliation reads "126 derived famil(ies) accounted for — 126 run, 0 NOT-MEASURED (a DERIVED zero — all 126 recorded an exit code and none of them is 3)".check:skill-examples,check:i18nandcheck:dual-build-cjs-loads. All three exit 0 after building their closures:pnpm --filter @objectstack/client-react build, then the CLI closurecheck:i18nnames (Tasks: 59 successful, restored from the turbo cache).check:i18nreads "9 package(s) — all bundles in sync".check:dual-build-cjs-loadsreads "107 published require entry point(s) across 66 package(s) load".aaa50c66ecwarned that the tree was behindorigin/mainon six gate files. That is whymainwas merged a second time; the derivation at483fddb3fcis current and names the same 126 commands.check-closing-target-claim.mjs,check-partof-closing-keyword.mjs,check-single-claim-paths.mjs) ran withPR_NUMBER=22794, this body asPR_BODY, the branch asPR_HEAD_REFand a read token, as their workflows wire them: all three exit 0.--no-inline-config) over the 16 changed TS files gives 0 errors and 0 warnings, 16 files by the--format jsoncount. The population is theeslint.config.mjsblock that lints everytsfile (line 971). The config never enables type-aware linting (line 328), so this diff cannot move the verdict on any file it does not touch. The repo-widepnpm lintis CI's run.Test Core,Temporal Conformance,Dogfood Regression Gate,Dogfood Verify CLI,Build Core,Build Docs,Console Pin Gate), the four workspace type-check lanes with no local invocation, and the six families that take a value from the workflow.Round 1, at
8996b566b5(unchanged files, so still the record for them):position.test.ts23 tests (19 + 4 new). An ablation throughscripts/ablation-replace.mjsreverted the row totype: 'tags'and turned exactly the widget pin and the registry-type pin red (Tests 2 failed | 21 passed (23)); the restore was proven blob-equal to HEAD and re-ran 23 of 23.Size: 23 files, +372 / -84 (456 changed lines) against
1eff3224d, under the 3,000-line threshold.Acceptance notes
Clause-②line of its own. Both new changesets sayClause-②: no. The console changeset isminorbecause objectui declaredminorfor three of its five entries.mainis two commits past the pin (9b75509, objectui#5250 slice B;1d1cfe0, objectui#12106). Neither is needed here..objectui-shais untouched; this PR moves only this repository's pin.git push(no force) of three commits; one body and title update and one assignee swap on this PR (zhuangjianguoreplacesos-project-manager, mirroring the card), both throughscripts/pm/; no label; no MCP write.Landing round written by
domain:specseat 3, sessionsession_01KNKBCRDJCu5tGy3TEbvtrF; round 1 by seat 1, sessionsession_01S3aAf11JjbW1mSGL1EhfFj.