Repository navigation
Commit c245986
fix(plugin-sharing): the record-share grant names each non-user value's sharing-rule recipient, so
Fixes #23325
Clause-②: no
## What was wrong
`ISharingService.grant` writes a record share for a `user` only
(ADR-0078). It refused every other declared value with one remedy:
> VALIDATION_FAILED: recipientType must be 'user' (received "group") —
group/position recipients are delivered via sharing rules (ADR-0111 D7)
The sharing-rule vocabulary has no `group`. Neither the authoring
`sharedWith.type` enum (`ShareRecipientType`,
`packages/spec/src/security/sharing.zod.ts:126`) nor the
`sys_sharing_rule.recipient_type` select
(`packages/plugins/plugin-sharing/src/objects/sys-sharing-rule.object.ts:172`)
offers it. A rule spells it `team`: the v17 ADR-0087 entry
`sharing-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.
A `unit_and_subordinates` caller read a sentence that did not name its
value.
## What changed
`packages/plugins/plugin-sharing/src/sharing-service.ts`:
- `RULE_ROUTE_BY_RECORD_SHARE_RECIPIENT` names, for each declared
non-`user` value, the rule recipient that delivers it. It is typed
against both vocabularies rather than restating either as a second
runtime list:
- keys are every `RecordShareRecipientType` member except `user` (the
union with `user` excluded, as a TypeScript `Record` key type), so a
record-share member added in the spec fails to compile here until it
names its route;
- values are the authorable `ShareRecipientType` (the list the
`sys_sharing_rule` select is held equal to by
`field-recipient.test.ts`), so the refusal can never name a recipient a
rule cannot be written with. The reserved `queue` is excluded by
construction.
- The refusal keeps its code, its 400 (the `VALIDATION_FAILED` prefix
arm, `packages/rest/src/rest-server.ts:11301`) and its first clause.
Only the remedy after the dash changes:
| received | remedy, TO |
|---|---|
| `group` | a group recipient is delivered via a sharing rule with
recipient type 'team', its name since the ADR-0090 D3 rename (ADR-0111
D7) |
| `position` | a position recipient is delivered via a sharing rule with
recipient type 'position' (ADR-0111 D7) |
| `unit_and_subordinates` | a unit_and_subordinates recipient is
delivered via a sharing rule with recipient type 'unit_and_subordinates'
(ADR-0111 D7) |
| a value no record share declares (an untyped REST body sending `team`,
say) | "team" is not a record-share recipient type (ADR-0111 D7) |
- The retired `guest` keeps its own prescription (ADR-0138 D8), byte for
byte.
- ⛔ No change to which values `grant()` accepts (`user` only), 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`: `patch` on
`@objectstack/plugin-sharing`. The sentence ships: after the build,
`dist/index.js` and `dist/index.mjs` each 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/main` at `cad343e201`, re-read
after merging `5477020809`)
- **Premise holds.** The sentence was at `sharing-service.ts:1396-1397`.
The existing pin `refuses non-user recipient types instead of persisting
rows no gate reads` asserted `VALIDATION_FAILED` only, never the remedy.
- **No declared value lacks a rule route.** The record-share vocabulary
is `user`, `group`, `position`, `unit_and_subordinates`
(`RecordShareRecipientType`, and the `sys_record_share` select). The
rule vocabulary is `user`, `team`, `business_unit`, `position`,
`unit_and_subordinates`, `field`. `group` maps to `team` by the recorded
rename. The other two keep their own names.
- **Where the old remedy was stated or asserted:** `git grep` for the
sentence and its phrases hits only `sharing-service.ts` and the docs
line above in this repo. There are zero hits in the i18n bundles. In
`objectstack-ai/objectui` at the pinned `.objectui-sha` `b9814b334d`
there are zero hits. The control word `recipientType` does hit there,
for example `packages/fields/src/widgets/RecipientPickerField.tsx` (8
hits). Nothing outside this repo states it.
- **On the citation.** ADR-0090 D3's own rename table lists `role` →
`position`, not `group` → `team`. D3's naming commandment reads
"collaboration = `team`". The spec's `ShareRecipientType` docblock
("ADR-0090 D3 renamed the pre-D3 `group` vocabulary to `team`") 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 for `guest`:
- `covers exactly the non-user values a record-share row declares, and
routes each to a rule recipient`: the table's keys equal the
`sys_record_share` select's non-`user` options. Each route is an option
of the `sys_sharing_rule` select, and `group` is 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".
- The guest pin's guard widens from `/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.mjs` swapped `+
nonUserRecipientTypeRemedy(recipientType),` for the old sentence. It
recorded anchor x1 → x0, replacement x0 → x1, blob `52704b19f94e` →
`97ac1ab79692`. It then ran `vitest run src/sharing-service.test.ts -t
'inert grants'`, with the test importing `./sharing-service.js` from
source (no dist in the path):
- **Mutated: 5 failed.** The three per-value cases, the rename case, and
the undeclared-value case. For example: `Received: "VALIDATION_FAILED:
recipientType must be 'user' (received "team") — group/position
recipients are delivered via sharing rules (ADR-0111 D7)"`.
- **Green under the mutation, as predicted:** the vocabulary-coverage
case (it reads the selects, not the message), the pre-existing refusal
pin, and the guest pin.
- **Restored:** `git checkout HEAD -- …`. Blob after restore
`52704b19f94e` = HEAD blob, `git diff HEAD` empty. 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.ts` is in the `tsconfig.test.json` program
(`--listFilesOnly`: 1 hit, 46 test files), with no debt entry.
- Dependency closure `pnpm --filter '@objectstack/plugin-sharing^...'
build`: exit 0. It was rebuilt after the merge of `origin/main`, which
brought a `packages/core` change.
- The require entry `dist/index.js` loads (`require()` → 62 exports).
## Gates
Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at `6f42b8d08a`: 95 commands for 4 paths.
Each was run byte for byte, with its exit captured before any pipe.
- **92 exit 0 on the first pass.** 3 exit 3 (PREREQUISITE NOT MET):
- `pnpm --filter @objectstack/spec run check:skill-examples` needed
`client`/`client-react` dist. Built, re-run: exit 0, "263 prose examples
type-check across 3 surface(s)".
- `pnpm check:i18n` needed 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-loads` **NOT 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.
- **`--ran` reconciliation:** "95 derived, 94 run, 1 NOT-MEASURED, 0
UNRUN".
- **One `UNRECOGNISED` line, verbatim, from `pnpm
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).`
- CI's own families are read from this PR's check-runs.
## Acceptance notes
- A caller that sends a rule spelling to the record-share door (`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.
- A sharing rule shares the records its criteria match. A caller who
meant one record for a team needs a criteria naming that record. The
remedy names the rule recipient only, as the card asks.
- `SharingRuleService.defineRule` does not check the recipient type
itself; it relies on the `sys_sharing_rule` select at insert. This is
read from source only and not measured at a public door, so it is not
filed.
---
_Generated by [Claude
Code](https://claude.ai/code/session_017WLmd5in9RXu7AjyHR8fKQ)_
Co-authored-by: Claude <noreply@anthropic.com>group is sent to team (#23678)1 parent 09dd537 commit c245986
4 files changed
Lines changed: 149 additions & 7 deletions
File tree
- .changeset
- content/docs/kernel/runtime-services
- packages/plugins/plugin-sharing/src
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
59 | | - | |
| 59 | + | |
60 | 60 | | |
61 | 61 | | |
62 | 62 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
| 9 | + | |
8 | 10 | | |
9 | 11 | | |
10 | 12 | | |
| |||
1111 | 1113 | | |
1112 | 1114 | | |
1113 | 1115 | | |
1114 | | - | |
1115 | | - | |
| 1116 | + | |
| 1117 | + | |
1116 | 1118 | | |
1117 | 1119 | | |
1118 | 1120 | | |
| |||
1122 | 1124 | | |
1123 | 1125 | | |
1124 | 1126 | | |
1125 | | - | |
| 1127 | + | |
1126 | 1128 | | |
1127 | 1129 | | |
1128 | 1130 | | |
| 1131 | + | |
| 1132 | + | |
| 1133 | + | |
| 1134 | + | |
| 1135 | + | |
| 1136 | + | |
| 1137 | + | |
| 1138 | + | |
| 1139 | + | |
| 1140 | + | |
| 1141 | + | |
| 1142 | + | |
| 1143 | + | |
| 1144 | + | |
| 1145 | + | |
| 1146 | + | |
| 1147 | + | |
| 1148 | + | |
| 1149 | + | |
| 1150 | + | |
| 1151 | + | |
| 1152 | + | |
| 1153 | + | |
| 1154 | + | |
| 1155 | + | |
| 1156 | + | |
| 1157 | + | |
| 1158 | + | |
| 1159 | + | |
| 1160 | + | |
| 1161 | + | |
| 1162 | + | |
| 1163 | + | |
| 1164 | + | |
| 1165 | + | |
| 1166 | + | |
| 1167 | + | |
| 1168 | + | |
| 1169 | + | |
| 1170 | + | |
| 1171 | + | |
| 1172 | + | |
| 1173 | + | |
| 1174 | + | |
| 1175 | + | |
| 1176 | + | |
| 1177 | + | |
| 1178 | + | |
| 1179 | + | |
| 1180 | + | |
| 1181 | + | |
| 1182 | + | |
| 1183 | + | |
| 1184 | + | |
| 1185 | + | |
| 1186 | + | |
| 1187 | + | |
| 1188 | + | |
| 1189 | + | |
| 1190 | + | |
| 1191 | + | |
| 1192 | + | |
| 1193 | + | |
| 1194 | + | |
| 1195 | + | |
| 1196 | + | |
| 1197 | + | |
| 1198 | + | |
| 1199 | + | |
| 1200 | + | |
| 1201 | + | |
| 1202 | + | |
1129 | 1203 | | |
1130 | 1204 | | |
1131 | 1205 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| 10 | + | |
10 | 11 | | |
11 | 12 | | |
12 | 13 | | |
| |||
15 | 16 | | |
16 | 17 | | |
17 | 18 | | |
| 19 | + | |
18 | 20 | | |
19 | 21 | | |
20 | 22 | | |
| |||
101 | 103 | | |
102 | 104 | | |
103 | 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 | + | |
104 | 150 | | |
105 | 151 | | |
106 | 152 | | |
| |||
1383 | 1429 | | |
1384 | 1430 | | |
1385 | 1431 | | |
1386 | | - | |
1387 | | - | |
| 1432 | + | |
| 1433 | + | |
| 1434 | + | |
| 1435 | + | |
| 1436 | + | |
1388 | 1437 | | |
1389 | 1438 | | |
1390 | 1439 | | |
| |||
1394 | 1443 | | |
1395 | 1444 | | |
1396 | 1445 | | |
1397 | | - | |
| 1446 | + | |
1398 | 1447 | | |
1399 | 1448 | | |
1400 | 1449 | | |
| |||
0 commit comments