Repository navigation
fix(plugin-sharing): a record share's Recipient column names the recipient through the recipient-picker face - #24831
Conversation
…pient-picker face sys_record_share.recipient_id now declares widget 'recipient-picker' and dependsOn ['recipient_type'], the display declaration the sibling sys_sharing_rule.recipient_id carries, less object_name, which the face reads only for the field recipient kind this object does not offer. No stored column, spec key or accepted value moves; the object stays engine-owned and read-only through the data API. Pins read the declaration off the object, derive dependsOn from the kinds the select can store, and hold that the declaration opens no write path and that grant() stays the package's one insert of a share row. Claude-Session: https://claude.ai/code/session_01UmqzD97e14iws6hmVhgZ5y Co-authored-by: Claude <noreply@anthropic.com>
… face Claude-Session: https://claude.ai/code/session_01UmqzD97e14iws6hmVhgZ5y Co-authored-by: Claude <noreply@anthropic.com>
…cord-share-recipient-face
📓 Docs Drift CheckThis PR changes 1 package(s): ⛔ 2 release-owned page(s) name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 9 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 b4434cbf680f74aea61f1ff5a289da6d7273dd5f && git checkout b4434cbf680f74aea61f1ff5a289da6d7273dd5f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 87c09ebb7ac768b43d137996296d042a3cee1c97 1dc0ab1bfc7aebd7350b8f9e91634e1ccadd00e5 && git checkout -B drift-repro 87c09ebb7ac768b43d137996296d042a3cee1c97 && git merge --no-ff 1dc0ab1bfc7aebd7350b8f9e91634e1ccadd00e5
node scripts/docs-audit/affected-docs.mjs --json 87c09ebb7ac768b43d137996296d042a3cee1c97
|
席位核对ACCEPT · #24701 half (b) ·
Generated by Claude Code |
Fixes #24701
Clause-②: no
Half (b) of the card: the record share's Recipient column draws the recipient's raw id. Half (a) landed earlier as PR #24733 ("Part of"). This PR does what the seat's ruling
6282390889ordered (route B, a display declaration only).What changes
sys_record_share.recipient_id(packages/plugins/plugin-sharing/src/objects/sys-record-share.object.ts) now declares:This is the display declaration
sys_sharing_rule.recipient_idalready carries, withoutobject_name(see M1 below). No stored column, spec key, accepted value, list-view column or objectui file changes. The field stays a requiredtextcolumn withmaxLength: 100. The object staysengine-ownedwithenable.apiMethods: ['get', 'list'].The other changed files are a new pin,
src/objects/sys-record-share.recipient-face.test.ts(7 cases), and a patch changeset,.changeset/24701-record-share-recipient-face.md.Ruling premises, re-checked before the edit
Re-checked on
origin/maina43405d59(the branch point) and at the console pin.objectui-sha=12dc7b47c340, which is unchanged on main at307ce4c4b. The objectui source was read withgit showandgit grepat that sha. No browser reading was taken.all_sharesis the only view whose columns includerecipient_type.granted_by_me,by_object,manual_grantsandrule_grantsshowrecipient_idwithout it.granted_to_meshows neither.ObjectGrid.tsxbuildsextraFieldsfrom predicate, grouping, concurrency, row-colour, deadline andreferenceViafields only. There is nodependsOnarm in that projection.ListView.tsxhas nodependsOnhit at all.dependentValues(ObjectGrid.tsx, objectui#12613).useRecipientLabel(refLabelDirectory.ts) builds an empty key when the row has norecipient_type, so the entry staysunasked.RecipientCellFacethen falls through toTextCellRenderer, the stored text, as today.So at the pin,
all_sharesnames the user, and the other four views and the H1 draw what they draw today. The seat files the two console rules (project a faced column'sdependsOnsiblings; draw a widget-hintednameFieldthrough its face) after this lands.The PM's mechanism assumptions, measured
['recipient_type']is enough. At the pin,useRecipientLabelreadsobject_nameonly when the kind isfield(FIELD_RECIPIENT_TYPE).sys_record_share.recipient_typeoffersuser,group,positionandunit_and_subordinates, and has nofieldkind. The kinds map as follows:userandunit_and_subordinatesare exact-id record kinds (RECIPIENT_RECORD_KINDS),positionis a registry kind, andgroupis in neither table, so it draws as text.grant()writes onlyuser(ADR-0111 D7).object_namewas therefore not added. A pin derivesdependsOnfrom the select's kinds, and its control derives the sibling's['recipient_type', 'object_name']from that object'sfieldkind.field.widgetis read by the spec schema and the liveness ledger only, and no runtime code reads it. The server-sidedependsOnreaders are the lint reference check (validate-object-field-refs.ts, which judges that a named sibling exists) and the FLS reference masking. Neither is a write path. The object isengine-ownedwithapiMethods: ['get', 'list']and nouserActions. At the pin, the docblocks of the console's write gates (plugin-form/src/fieldWriteGate.ts,plugin-grid/src/rowCrudAffordances.ts) say engine-owned objects resolve closed unless they openuserActions, so no form draws the picker as an editor. Fork case 2 does not apply.recipient-pickerwhosedependsOnnames onlyrecipient_type.validateObjectFieldRefsover the package's three objects at the branch head gave 0 findings. Its control, withdependsOn: ['recipient_kind'], gave 1 finding:fields.recipient_id.dependsOn[0] "recipient_kind" is not a field on object "sys_record_share".widgetordependsOn. The ones that read the changed object file are the general census and source families.Pins
src/objects/sys-record-share.recipient-face.test.ts:recipient-picker, the same value assys_sharing_rule.recipient_id.dependsOnnames exactly the siblings the face reads for the kinds this object's select stores, and each one is a declared field.['recipient_type', 'object_name'], because that object stores thefieldkind.text, required,maxLength: 100, noreferenceorreferenceVia), the object isengine-owned,apiMethodsstays['get', 'list'], and there are nouserActions.src/reads the other inserts (sys_share_link,sys_sharing_rule), so it is never empty.insertcall in the package names its table in a form the census reads: a string literal, or an identifier bound to a same-file string constant.sys_record_sharerow isgrant()insharing-service.ts. The census is a TypeScript-AST walk scoped to this package's sources, not a whole-tree scan. The one writer measured outside the package, the CLI's organization-ownership apply, updatessource_idand inserts nothing.The existing
sharing-pointer-pairs.test.ts,sys-sharing-rule.by-object.test.tsandfield-recipient.test.tspins stay green.Ablations
Each leg ran on the committed fix through
node scripts/ablation-replace.mjsin wrap mode. Each anchor hit exactly once and the blob changed. A shell trap restored the files fromHEAD. The pin imports the object and readssharing-service.tsby source path, so nodist/sits between a mutation and the run, and no rebuild was needed.ecfd40c1d865to82c721526026)expected undefined to be 'recipient-picker';expected undefined to deeply equal [ 'recipient_type' ]apiMethods: ['get', 'list', 'update']expected [ 'get', 'list', 'update' ] to deeply equal [ 'get', 'list' ]this.engine.insert('sys_record_share', …)inrevoke()+ "sharing-service.ts#revoke"this.engine.insert(tableFromElsewhere(), …)inrevoke()+ "sharing-service.ts#revoke: (unresolved: tableFromElsewhere())"Every other case stayed green in each leg (
Tests 2 failed / 5 passed, then1 failed / 6 passedthree times). Restore was checked per file:sys-record-share.object.tsblobecfd40c1d865andsharing-service.tsblob52704b19f94eboth equalHEAD, andgit diff HEADis empty on both.Verification
All on head
1dc0ab1bf(the branch merged withorigin/main307ce4c4bbefore opening), each underscripts/pm/os-verify-lock.sh, with the exit code captured before any pipe:pnpm --filter '@objectstack/plugin-sharing...' build: VERDICT command-exit 0.pnpm --filter @objectstack/plugin-sharing test: exit 0,Test Files 52 passed (52),Tests 1199 passed (1199).pnpm --filter @objectstack/plugin-sharing typecheck: exit 0.check:test-typecheckreports the ledger unchanged (2 files, 3 errors, 3 signatures).tsc --listFiles -p tsconfig.test.jsonlists the new pin, and the only errors are the 3 ledgered ones.Gates on head
1dc0ab1bf:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, with no paths, derived 68 commands over the 3 changed files. Each was run with its exit code captured before any pipe.pnpm check:i18nfirst exited 3 (prerequisite: no CLI build). Its prescribedturbo run buildclosure gave VERDICT command-exit 0 (61 tasks). The rerun exited 0:check-i18n-bundles: OK (10 package(s) — all bundles in sync, no undeclared authoring keys).pnpm check:dual-build-cjs-loadsexited 3, PREREQUISITE NOT MET: it needs a whole-workspace build (57 packages withoutdist/). NOT MEASURED, a declared narrowing. The diff changes no entry point, export or build config, and CI'sLint & Repo Gatesruns it.--ran:Run reconciliation — 68 derived, 67 run, 1 NOT-MEASURED, 0 UNRUN, exit 0.Declared to CI and not run locally:
test:repo,pnpm lint, the type-check lanes,Build Coreand theTest Coreshards.check:dev-prereqsandcheck:skill-frame-freshnesswere not run, because the diff touches neither the spec build nor the frame files.check-platform-checklistwas not run, because no exported symbol is renamed or deleted.Acceptance notes
widgetliveness note is now incomplete.packages/spec/liveness/field.jsonsayswidgetis "live on the sys_sharing_rule trio alone".sys_record_share.recipient_idnow declares one too, so the note under-counts. The verdict (live) is unchanged. It is not edited here, because this claim excludespackages/spec/**. Carrier: none.6283814156) ran on 18.0.0-next.5, published 2026-10-19T11:06Z. That is before54159fd74(plugin-sharing / plugin-audit: record titles, highlight strips and theby_objectgrouping of share and audit records name the target object by its API name (object_name), outside the list columns #24501 covers #24611's fix, 2026-10-20T00:58Z), which tookrecord_idout ofhighlightFieldsso the Details tab draws it as the pointer card. Its "Record shows a raw id on the record overlay" reading is therefore not a reading of currentmain. It is NOT MEASURED here (no browser reading). Its "no column names the granting rule" reading is a product question, not a defect class:rule_grantsshowssource_id, andall_sharesdoes not. Neither item is edited.sys_record_share.recipient_typestill offersgroup,positionandunit_and_subordinates, which no writer produces. That is ruling C's protocol question, which the seat kept off this card. Not touched.Generated by Claude Code