Repository navigation
fix(plugin-sharing): the record-share grant names each non-user value's sharing-rule recipient, so group is sent to team - #23678
Conversation
…'s sharing-rule recipient, so group is sent to team grant() refuses every recipientType but user (ADR-0078) with one remedy, "group/position recipients are delivered via sharing rules", and the rule vocabulary has no group: a rule spells it team since the ADR-0090 D3 rename (the ADR-0087 entry sharing-rule-recipient-reconcile). Each declared non-user value now names its own rule recipient from a table typed against both vocabularies (keys: every non-user RecordShareRecipientType member; values: the authorable ShareRecipientType), and a value no record share declares is told so instead of being named a route. The code, the status, the accepted values and the guest prescription are unchanged. Claude-Session: https://claude.ai/code/session_017WLmd5in9RXu7AjyHR8fKQ Co-authored-by: Claude <noreply@anthropic.com>
…cord-share-remedy-text
📓 Docs Drift CheckThis PR changes 1 package(s): 7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also 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 498aaf8a0ff9332033281414000929dd96446d6c && git checkout 498aaf8a0ff9332033281414000929dd96446d6c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8d1fb1fb2ddbd12a0c492bdfe1dca3cbe8190461 6f42b8d08af1654145043820447270229bea1b49 && git checkout -B drift-repro 8d1fb1fb2ddbd12a0c492bdfe1dca3cbe8190461 && git merge --no-ff 6f42b8d08af1654145043820447270229bea1b49
node scripts/docs-audit/affected-docs.mjs --json 8d1fb1fb2ddbd12a0c492bdfe1dca3cbe8190461
|
席位核对
Verdict: ACCEPT at head
Generated by Claude Code |
Fixes #23325
Clause-②: no
What was wrong
ISharingService.grantwrites a record share for auseronly (ADR-0078). It refused every other declared value with one remedy:The sharing-rule vocabulary has no
group. Neither the authoringsharedWith.typeenum (ShareRecipientType,packages/spec/src/security/sharing.zod.ts:126) nor thesys_sharing_rule.recipient_typeselect (packages/plugins/plugin-sharing/src/objects/sys-sharing-rule.object.ts:172) offers it. A rule spells itteam: the v17 ADR-0087 entrysharing-rule-recipient-reconcile(packages/spec/src/migrations/entries/semantic/17.sharing-rule-recipient-reconcile.ts:11) records "group→team" as the ADR-0090 D3 rename. A caller or an AI that followed the remedy wrote a rule naming a recipient no rule takes. Aunit_and_subordinatescaller read a sentence that did not name its value.What changed
packages/plugins/plugin-sharing/src/sharing-service.ts:RULE_ROUTE_BY_RECORD_SHARE_RECIPIENTnames, for each declared non-uservalue, the rule recipient that delivers it. It is typed against both vocabularies rather than restating either as a second runtime list:RecordShareRecipientTypemember exceptuser(the union withuserexcluded, as a TypeScriptRecordkey type), so a record-share member added in the spec fails to compile here until it names its route;ShareRecipientType(the list thesys_sharing_ruleselect is held equal to byfield-recipient.test.ts), so the refusal can never name a recipient a rule cannot be written with. The reservedqueueis excluded by construction.VALIDATION_FAILEDprefix arm,packages/rest/src/rest-server.ts:11301) and its first clause. Only the remedy after the dash changes:grouppositionunit_and_subordinatesteam, say)guestkeeps its own prescription (ADR-0138 D8), byte for byte.grant()accepts (useronly), and none to the record-share vocabulary.content/docs/kernel/runtime-services/sharing-service.mdx(Typical Errors,VALIDATION_FAILED) stated the old remedy. It now states the per-value mapping. This file is outside the claim's declared file surface. The dispatch asked that everything in this repo stating the old remedy be updated, and this was the one other hit..changeset/23325-record-share-remedy-text.md:patchon@objectstack/plugin-sharing. The sentence ships: after the build,dist/index.jsanddist/index.mjseach hold the new undeclared-value text once and the old sentence zero times, with the guest prescription as positive control (1 each).files[]is["dist","README.md","CHANGELOG.md"].Measured before writing (on
origin/mainatcad343e201, re-read after merging5477020809)sharing-service.ts:1396-1397. The existing pinrefuses non-user recipient types instead of persisting rows no gate readsassertedVALIDATION_FAILEDonly, never the remedy.user,group,position,unit_and_subordinates(RecordShareRecipientType, and thesys_record_shareselect). The rule vocabulary isuser,team,business_unit,position,unit_and_subordinates,field.groupmaps toteamby the recorded rename. The other two keep their own names.git grepfor the sentence and its phrases hits onlysharing-service.tsand the docs line above in this repo. There are zero hits in the i18n bundles. Inobjectstack-ai/objectuiat the pinned.objectui-shab9814b334dthere are zero hits. The control wordrecipientTypedoes hit there, for examplepackages/fields/src/widgets/RecipientPickerField.tsx(8 hits). Nothing outside this repo states it.role→position, notgroup→team. D3's naming commandment reads "collaboration =team". The spec'sShareRecipientTypedocblock ("ADR-0090 D3 renamed the pre-D3groupvocabulary toteam") and the v17 entry attribute the rename to D3. The remedy cites "the ADR-0090 D3 rename", as triage directed, which matches the repository's own record.Pins
sharing-service.test.ts, describe[ADR-0111 D7] the refusal names the sharing-rule recipient that delivers each non-user value, following the pattern PR #23324 set forguest:covers exactly the non-user values a record-share row declares, and routes each to a rule recipient: the table's keys equal thesys_record_shareselect's non-useroptions. Each route is an option of thesys_sharing_ruleselect, andgroupis not.group → … 'team',position → … 'position',unit_and_subordinates → … 'unit_and_subordinates': the code prefix and the received value, the named rule recipient, the(ADR-0111 D7)ending, and that nothing is written.group's remedy cites the rename and never names a group rule recipient. The other two carry no rename clause.a value no record share declares is told so, and is named no route:team, the exact sentence, no "delivered via"./sharing rules/to/sharing rule/, so that it still catches a remedy naming a rule route under the new singular wording. The guest message itself is unchanged.Ablation (one-off, committed fix first; nothing left in the tree)
node scripts/ablation-replace.mjsswapped+ nonUserRecipientTypeRemedy(recipientType),for the old sentence. It recorded anchor x1 → x0, replacement x0 → x1, blob52704b19f94e→97ac1ab79692. It then ranvitest run src/sharing-service.test.ts -t 'inert grants', with the test importing./sharing-service.jsfrom source (no dist in the path):Received: "VALIDATION_FAILED: recipientType must be 'user' (received "team") — group/position recipients are delivered via sharing rules (ADR-0111 D7)".git checkout HEAD -- …. Blob after restore52704b19f94e= HEAD blob,git diff HEADempty. Old-sentence count 0, new-call count 1.Tests (at
6f42b8d08a, the head this PR opens with)pnpm --filter @objectstack/plugin-sharing exec vitest run --maxWorkers=2: 46 files, 1067 tests passed.pnpm --filter @objectstack/plugin-sharing typecheck(tsc, tsconfig.scripts.json,check:test-typecheck): exit 0.sharing-service.test.tsis in thetsconfig.test.jsonprogram (--listFilesOnly: 1 hit, 46 test files), with no debt entry.pnpm --filter '@objectstack/plugin-sharing^...' build: exit 0. It was rebuilt after the merge oforigin/main, which brought apackages/corechange.dist/index.jsloads (require()→ 62 exports).Gates
Derived with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat6f42b8d08a: 95 commands for 4 paths. Each was run byte for byte, with its exit captured before any pipe.pnpm --filter @objectstack/spec run check:skill-examplesneededclient/client-reactdist. Built, re-run: exit 0, "263 prose examples type-check across 3 surface(s)".pnpm check:i18nneeded the CLI's extract closure. Built with the command the gate prints, re-run: exit 0, "OK (10 package(s) — all bundles in sync, no undeclared authoring keys)".pnpm check:dual-build-cjs-loadsNOT MEASURED: it needs every package's dist (57 named missing). That whole-tree build is CI's. The one package this diff touches was measured by hand above: its require entry loads, and the diff adds only type-only imports, which are erased.--ranreconciliation: "95 derived, 94 run, 1 NOT-MEASURED, 0 UNRUN".UNRECOGNISEDline, verbatim, frompnpm check:engine-double-contract(a verdict, not a failure):UNRECOGNISED [engine-double-contract]: 22 construct(s) in packages, examples declare a scanned verb (delete, update, findOne) alongside engine siblings, and this gate could not read the implementation -- so they are in NEITHER the pinned population nor the ledger. 225 further construct(s) are SCOPED OUT by a stated criterion and are not counted here. This is a verdict, not a finding: it never fails a run (#9747, ruling of 2026-08-18).Acceptance notes
team,business_unit,field) is told the value is not a record-share recipient type. It is deliberately not routed: the card scopes the remedy to declared record-share values, and routing undeclared ones would mean a second read of the rule vocabulary at this door.SharingRuleService.defineRuledoes not check the recipient type itself; it relies on thesys_sharing_ruleselect at insert. This is read from source only and not measured at a public door, so it is not filed.Generated by Claude Code