Skip to content

fix(plugin-sharing): a record share's Recipient column names the recipient through the recipient-picker face - #24831

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-24701-record-share-recipient-face
Oct 20, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-24701-record-share-recipient-face

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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:

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 (plugin-sharing / plugin-audit: record titles, highlight strips and the by_object grouping 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 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

…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>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-sharing, touching 2 documentable anchor(s).

⛔ 2 release-owned page(s) name something this change touched. These are read-only:

  • content/docs/releases/v15.mdx (via recipient_type (literal, a string literal in SysRecordShare))
  • content/docs/releases/v17/17-1.mdx (via recipient_type (literal, a string literal in SysRecordShare))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

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): node scripts/docs-audit/affected-docs.mjs --json 87c09ebb7ac768b43d137996296d042a3cee1c97 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from b4434cbf680f74aea61f1ff5a289da6d7273dd5f — the merge of head 1dc0ab1bfc7aebd7350b8f9e91634e1ccadd00e5 into base 87c09ebb7ac768b43d137996296d042a3cee1c97, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 87c09ebb7ac768b43d137996296d042a3cee1c97 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Oct 20, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · #24701 half (b) · domain:services seat 2 · os-support-ai · session_01UmqzD97e14iws6hmVhgZ5y · 2026-10-20T08:44Z · head 1dc0ab1bfc, the review of record for claim 6285537325.

  1. Form:
    • Draft, base main. The first line is Fixes #24701; the seat's grep of the body finds no other closing keyword beside an issue number.
    • Clause-②: no at line start in the body and in the changeset.
    • One patch changeset for @objectstack/plugin-sharing (published: no private, files ships dist).
    • 3 files, +194/−0, all inside the claim's file surface.
  2. Ruling and premise:
    • The diff, read by the seat. sys_record_share.recipient_id gains exactly widget: 'recipient-picker' and dependsOn: ['recipient_type'], plus a comment. Its type, required, maxLength and the object's apiMethods are unchanged. No list-view column, packages/spec file or objectui file moves. This is ruling 6282390889's route B to the letter.
    • M1 (object_name left out). The seat's read of origin/main: the sibling's comment says object_name serves only the field kind, and this object's recipient_type select has no field value. The pin derives dependsOn from the select's kinds, with sys_sharing_rule as the control.
    • Fork cases. Neither applies, by the dev's measurement: validateObjectFieldRefs gives 0 findings, and 1 under a bad-name control. The object stays engine-owned with ['get', 'list'].
    • Pins and ablation. Seven cases. The declaration, the derived dependsOn, the unchanged write shape, and an AST census showing grant() is the only sys_record_share insert. The census has a control and refuses an unresolved table. The fix and three negative legs were each ablated red and restored to the HEAD blob.
  3. Gates:
    • CI on 1dc0ab1bfc at this read: 38 check-runs, 17 success, 7 skipped, 14 in progress, 0 failed. Not yet green, so not yet ready.
    • Local on the head: 68 derived, 67 run, 1 NOT-MEASURED (check:dual-build-cjs-loads, which needs a whole-workspace build; CI runs it), 0 UNRUN.
    • Package suites: plugin-sharing 52 files, 1199 tests; typecheck exit 0 with the test-typecheck ledger unchanged.
    • typescript (imported by the new pin) is already a plugin-sharing devDependency (^6.0.3).
  4. Accepted deviations:
  5. Out of scope (the PR's Acceptance notes; none is a filing-gate class with a public-door reach:):
  6. Landing: ordinary queue landing (check-governed-merges --pr 24831: not governed, 194 lines). When every check on the current head is green, the seat sends pr_ready + automerge_enable through the relay, confirms MERGED, then files the two objectui rules.

Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants