Repository navigation
Commit 99e4eaa
fix(plugin-sharing): a record share's Recipient column names the recipient through the recipient-picker face (#24831)
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 `6282390889` ordered (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:
```ts
widget: 'recipient-picker',
dependsOn: ['recipient_type'],
```
This is the display declaration `sys_sharing_rule.recipient_id` already
carries, without `object_name` (see M1 below). No stored column, spec
key, accepted value, list-view column or objectui file changes. The
field stays a required `text` column with `maxLength: 100`. The object
stays `engine-owned` with `enable.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/main` `a43405d59` (the branch point) and at the
console pin `.objectui-sha` = `12dc7b47c340`, which is unchanged on main
at `307ce4c4b`. The objectui source was read with `git show` and `git
grep` at that sha. No browser reading was taken.
- **P1 holds.** `all_shares` is the only view whose columns include
`recipient_type`. `granted_by_me`, `by_object`, `manual_grants` and
`rule_grants` show `recipient_id` without it. `granted_to_me` shows
neither.
- **P2 holds.** At the pin, `ObjectGrid.tsx` builds `extraFields` from
predicate, grouping, concurrency, row-colour, deadline and
`referenceVia` fields only. There is no `dependsOn` arm in that
projection. `ListView.tsx` has no `dependsOn` hit at all.
- **P3 holds.** The type-only cell renderer passes the whole row as
`dependentValues` (`ObjectGrid.tsx`, objectui#12613).
`useRecipientLabel` (`refLabelDirectory.ts`) builds an empty key when
the row has no `recipient_type`, so the entry stays `unasked`.
`RecipientCellFace` then falls through to `TextCellRenderer`, the stored
text, as today.
So at the pin, `all_shares` names 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's `dependsOn` siblings; draw a widget-hinted
`nameField` through its face) after this lands.
## The PM's mechanism assumptions, measured
- **M1: `['recipient_type']` is enough.** At the pin,
`useRecipientLabel` reads `object_name` only when the kind is `field`
(`FIELD_RECIPIENT_TYPE`). `sys_record_share.recipient_type` offers
`user`, `group`, `position` and `unit_and_subordinates`, and has no
`field` kind. The kinds map as follows: `user` and
`unit_and_subordinates` are exact-id record kinds
(`RECIPIENT_RECORD_KINDS`), `position` is a registry kind, and `group`
is in neither table, so it draws as text. `grant()` writes only `user`
(ADR-0111 D7). `object_name` was therefore not added. A pin derives
`dependsOn` from the select's kinds, and its control derives the
sibling's `['recipient_type', 'object_name']` from that object's `field`
kind.
- **M2: no write or edit path changes.** In this repository,
`field.widget` is read by the spec schema and the liveness ledger only,
and no runtime code reads it. The server-side `dependsOn` readers 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 is `engine-owned` with `apiMethods: ['get',
'list']` and no `userActions`. 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 open `userActions`, so no form draws the
picker as an editor. Fork case 2 does not apply.
- **Fork case 1 does not apply either.** No gate refuses a
`recipient-picker` whose `dependsOn` names only `recipient_type`.
`validateObjectFieldRefs` over the package's three objects at the branch
head gave 0 findings. Its control, with `dependsOn: ['recipient_kind']`,
gave 1 finding: `fields.recipient_id.dependsOn[0] "recipient_kind" is
not a field on object "sys_record_share"`.
- **M3: which gates read the declaration.** See the gate list below. No
derived family is specific to field `widget` or `dependsOn`. 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`:
1. The declaration, read off the object, is `recipient-picker`, the same
value as `sys_sharing_rule.recipient_id`.
2. `dependsOn` names exactly the siblings the face reads for the kinds
this object's select stores, and each one is a declared field.
3. Control: the same rule yields the sibling's `['recipient_type',
'object_name']`, because that object stores the `field` kind.
4. Negative: the declaration opens no write path. The stored column is
unchanged (`text`, required, `maxLength: 100`, no `reference` or
`referenceVia`), the object is `engine-owned`, `apiMethods` stays
`['get', 'list']`, and there are no `userActions`.
5. Control: the insert census over this package's non-test `src/` reads
the other inserts (`sys_share_link`, `sys_sharing_rule`), so it is never
empty.
6. Negative: every `insert` call 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.
7. Negative: the only insert of a `sys_record_share` row is `grant()` in
`sharing-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, updates
`source_id` and inserts nothing.
The existing `sharing-pointer-pairs.test.ts`,
`sys-sharing-rule.by-object.test.ts` and `field-recipient.test.ts` pins
stay green.
### Ablations
Each leg ran on the committed fix through `node
scripts/ablation-replace.mjs` in wrap mode. Each anchor hit exactly once
and the blob changed. A shell trap restored the files from `HEAD`. The
pin imports the object and reads `sharing-service.ts` by source path, so
no `dist/` sits between a mutation and the run, and no rebuild was
needed.
| Leg | Mutation | Red case(s) | Received |
|:--|:--|:--|:--|
| F | delete the two declaration lines (blob `ecfd40c1d865` to
`82c721526026`) | 1, 2 | `expected undefined to be 'recipient-picker'`;
`expected undefined to deeply equal [ 'recipient_type' ]` |
| N1 | `apiMethods: ['get', 'list', 'update']` | 4 | `expected [ 'get',
'list', 'update' ] to deeply equal [ 'get', 'list' ]` |
| N2 | add `this.engine.insert('sys_record_share', …)` in `revoke()` | 7
| `+ "sharing-service.ts#revoke"` |
| N2b | add `this.engine.insert(tableFromElsewhere(), …)` in `revoke()`
| 6 | `+ "sharing-service.ts#revoke: (unresolved:
tableFromElsewhere())"` |
Every other case stayed green in each leg (`Tests 2 failed / 5 passed`,
then `1 failed / 6 passed` three times). Restore was checked per file:
`sys-record-share.object.ts` blob `ecfd40c1d865` and
`sharing-service.ts` blob `52704b19f94e` both equal `HEAD`, and `git
diff HEAD` is empty on both.
## Verification
All on head `1dc0ab1bf` (the branch merged with `origin/main`
`307ce4c4b` before opening), each under `scripts/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-typecheck` reports the ledger unchanged (2 files, 3 errors,
3 signatures). `tsc --listFiles -p tsconfig.test.json` lists 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.
- 66 exited 0 on the first pass.
- `pnpm check:i18n` first exited 3 (prerequisite: no CLI build). Its
prescribed `turbo run build` closure 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-loads` exited 3, PREREQUISITE NOT MET: it
needs a whole-workspace build (57 packages without `dist/`). NOT
MEASURED, a declared narrowing. The diff changes no entry point, export
or build config, and CI's `Lint & Repo Gates` runs 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 Core` and the `Test Core` shards.
`check:dev-prereqs` and `check:skill-frame-freshness` were not run,
because the diff touches neither the spec build nor the frame files.
`check-platform-checklist` was not run, because no exported symbol is
renamed or deleted.
## Acceptance notes
- **The H1 and four list views are unchanged at this pin** (P2, P3).
They follow the two console rules the ruling assigns to objectui, which
the seat files after this lands. ⛔ No column was added to a view to feed
the face, and no objectui file is edited.
- **The `widget` liveness note is now incomplete.**
`packages/spec/liveness/field.json` says `widget` is "live on the
sys_sharing_rule trio alone". `sys_record_share.recipient_id` now
declares one too, so the note under-counts. The verdict (`live`) is
unchanged. It is not edited here, because this claim excludes
`packages/spec/**`. Carrier: none.
- **The HotCRM pass (`6283814156`) ran on 18.0.0-next.5**, published
2026-10-19T11:06Z. That is before `54159fd74` (#24611's fix,
2026-10-20T00:58Z), which took `record_id` out of `highlightFields` so
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 current
`main`. 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_grants` shows `source_id`, and `all_shares` does not.
Neither item is edited.
- `sys_record_share.recipient_type` still offers `group`, `position` and
`unit_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](https://claude.ai/code/session_01UmqzD97e14iws6hmVhgZ5y)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 7697ddc commit 99e4eaa
3 files changed
Lines changed: 194 additions & 0 deletions
File tree
- .changeset
- packages/plugins/plugin-sharing/src/objects
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
215 | 215 | | |
216 | 216 | | |
217 | 217 | | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
218 | 235 | | |
219 | 236 | | |
220 | 237 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
0 commit comments