Repository navigation
Commit 21301a3
fix(plugin-security): the delegated walk counts the permission set of the position's own name (#24793)
Fixes #24736
Clause-②: no
## What changes
The delegated-administration gate's position walk,
`setsBoundToPosition`, now takes its set names from
`setsCarriedByPosition`. That is the helper the organization-level
branch already reads. So a delegate's position assignment is judged on
every permission set the assignment grants. ADR-0090 D12, as amended on
2026-10-17, lists them: the names the position's definition lists and,
while the resolver folds held position names into the requested set
names, the permission set of the position's own name when the catalog
holds one.
The walk has three callers on `main`, and all three now judge that set:
- `assertAssignmentWrite` handles a `sys_user_position` insert, update
or delete by an `adminScope` holder. Both the allowlist check and strict
containment now see the own-name set.
- `describeDelegableScope`, delegated branch: a position whose own-name
set is outside every placing scope's allowlist is no longer offered.
`GET /api/v1/security/my-delegable-scope` now agrees with the write.
- `assertSelfDelegation` rule 6 (ADR-0091 D3): a delegatable position
whose own-name set carries an `adminScope` can no longer be
self-delegated.
A refusal names the set and says it is the set of the position's own
name, because the position's definition need not list it. The envelope
is unchanged: `403 PERMISSION_DENIED`.
Not changed: the organization-level branch, tenant-level administrators,
the resolver's fold in `security-plugin.ts`, `packages/spec`, and the
shipped permission sets. The change only narrows what a delegate may do.
Nothing a delegate was refused before is admitted now.
## Reachability, measured on `origin/main` `795ff708d8`
1. **Shipped and example catalogs hold no permission set named like a
position.** This is a source census of the declarations in `packages/**`
and `examples/**`, test fixtures excluded. Positions: the six built-ins
(`builtin-positions.ts`), three in `app-crm` and ten in `app-showcase`,
19 in all. Permission sets: the platform's eight
(`default-permission-sets.ts`), two in `app-crm` and nine in
`app-showcase`, all prefixed `showcase_`. The two name lists share 0
names.
2. **A named real producer holds the shape.** The vendored HotCRM 17.1
artifact,
`packages/metadata/src/__fixtures__/hotcrm-17.1-built-permissions.artifact.json`,
carries the permission sets `sales_manager` and `sales_rep`. HotCRM
declares positions of the same names, and #24757 measured that set in
force through the fold on a booted HotCRM.
3. **On a real stack, the gap was reachable.** The new posture suite was
run against the unmodified gate, with only the test file added. A
delegate's allowlist covered the position's listed set but not its
own-name set, and its assignment was answered `200` in all three
postures (`single`, `group`, `isolated`). The suite's ground-truth case
shows the assignee's resolved sets then include the own-name set; that
case is green before and after. The same held for an own-name set that
carries an `adminScope`, and the delegable-scope report offered both
positions.
The triage note on this card says a reachable shape makes it **p1**.
## Tests, at head `d110c15116`
The branch merged `origin/main` `7d45c8e29a`, which touches
`plugin-security`'s shipped sets. The closure was rebuilt and the
package run again on the merged head:
- `pnpm --filter @objectstack/plugin-security exec vitest run
--maxWorkers=2`: `Test Files 192 passed (192)`, `Tests 3995 passed | 45
skipped (4040)`. Before the merge, at `a3fc36f503`: `191 passed (191)`,
`3961 passed | 45 skipped`.
- `pnpm --filter @objectstack/plugin-security typecheck`: exit 0 at both
heads. Both new test files are in the `tsconfig.test.json` program; `tsc
-p tsconfig.test.json --listFilesOnly` lists both.
- `pnpm --filter @objectstack/plugin-security build`: exit 0.
- New unit pins in `src/delegated-admin-gate.test.ts`, in the describe
block on the permission set of the position's own name:
- a refusal asserting `code` `PERMISSION_DENIED` and `status` 403 that
names the set;
- the control, admitted;
- a position with no own-name set, unchanged;
- a removal, judged on the same sets;
- strict containment on an own-name set that carries an `adminScope`;
- the listing compared with the write, position by position;
- the self-delegation refusal, with its control.
- New real-stack suite,
`src/delegated-admin-own-name-set.posture.test.ts`: a real `ObjectQL`,
`SqlDriver` and `SecurityPlugin` in three postures. Each posture runs
the ground truth, the 403 refusal naming the set, the control, the
unchanged no-own-name case, strict containment, and the listing compared
with the write.
## Ablation
Taken at `a3fc36f503`, before the merge, which did not touch the gate or
its tests. The change was committed first. Then the old walk, which read
`position.definition.permissionSets` only, was put back through
`scripts/ablation-replace.mjs`: anchor 1 to 0, blob `2a202d1153e1` to
`2e45318f6351`. Both files were run:
- `Tests 14 failed | 125 passed (139)`.
- Red: the five new unit pins, and per posture the refusal, strict
containment and the listing (nine cases).
- Green: the controls, the no-own-name cases and the ground truth.
Restored with `git checkout HEAD -- PATH`: the blob is back to
`2a202d1153e1`, the same as HEAD, `git diff HEAD` is empty, and the
marker count is 0. The tests import the subject through relative source
paths (`./delegated-admin-gate`, `./security-plugin.js`), not a `dist/`,
so the ablation needed no rebuild.
## Gates
Local subset, at head `d110c15116`: `node scripts/pm/dispatch-gates.mjs
--commands --repo objectstack-ai/objectstack`, with no paths, derived 67
commands from the merge base `7d45c8e29a`. All 67 were run one by one,
each exit captured before any pipe, and all 67 exited 0. `--ran`
printed: `Run reconciliation — 67 derived, 67 run, 0 NOT-MEASURED, 0
UNRUN.`
On the first head (`a3fc36f503`), `check:dual-build-cjs-loads` and
`check:i18n` refused with `PREREQUISITE NOT MET` (exit 3): nothing was
measured. After the build that `check:i18n` names, `check:i18n` passed
on that head, and both passed in the battery on the merged head.
Every family CI runs is read from this PR's check-runs. None of them was
run again here.
## Acceptance notes
- The dispatch expected a binding-write blast-radius consumer. There is
none on `main`: the binding authoring rows retired with ADR-0131 D3. The
walk's three callers are the ones listed above.
- Both branches now share one answer to "which sets does a position
carry" (`setsCarriedByPosition`). `setsBoundToPosition` only adds each
set's `adminScope`.
- Own-name counting follows the catalog. When no set of the position's
name exists, nothing extra is judged. A set authored later under a
position's name begins granting through existing assignments without a
further write. Authoring a set is tenant-level work, so no delegate can
bring that about.
- The fold's retirement is not touched here; #13419 records it as the
maintainer's call. #24757 reads the same fold from Setup and is not
addressed here.
- Out of scope, reported to the seat in the dev report: the delegated
branch of `describeDelegableScope` offers the four built-in identity
positions (`platform_admin`, `org_owner`, `org_admin`, `org_member`).
The write refuses each of them for every writer with `400
VALIDATION_FAILED`. This was measured on the real stack in all three
postures. The organization-level branch already skips them
(`isReservedIdentityName`).
- Context: #24136 and #24732, where the organization-level branch's
helper landed.
---
_Generated by [Claude
Code](https://claude.ai/code/session_011TdZ8GjixNZFLz1xJD9zzK)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent 307ce4c commit 21301a3
4 files changed
Lines changed: 429 additions & 19 deletions
File tree
- .changeset
- packages/plugins/plugin-security/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1349 | 1349 | | |
1350 | 1350 | | |
1351 | 1351 | | |
| 1352 | + | |
| 1353 | + | |
| 1354 | + | |
| 1355 | + | |
| 1356 | + | |
| 1357 | + | |
| 1358 | + | |
| 1359 | + | |
| 1360 | + | |
| 1361 | + | |
| 1362 | + | |
| 1363 | + | |
| 1364 | + | |
| 1365 | + | |
| 1366 | + | |
| 1367 | + | |
| 1368 | + | |
| 1369 | + | |
| 1370 | + | |
| 1371 | + | |
| 1372 | + | |
| 1373 | + | |
| 1374 | + | |
| 1375 | + | |
| 1376 | + | |
| 1377 | + | |
| 1378 | + | |
| 1379 | + | |
| 1380 | + | |
| 1381 | + | |
| 1382 | + | |
| 1383 | + | |
| 1384 | + | |
| 1385 | + | |
| 1386 | + | |
| 1387 | + | |
| 1388 | + | |
| 1389 | + | |
| 1390 | + | |
| 1391 | + | |
| 1392 | + | |
| 1393 | + | |
| 1394 | + | |
| 1395 | + | |
| 1396 | + | |
| 1397 | + | |
| 1398 | + | |
| 1399 | + | |
| 1400 | + | |
| 1401 | + | |
| 1402 | + | |
| 1403 | + | |
| 1404 | + | |
| 1405 | + | |
| 1406 | + | |
| 1407 | + | |
| 1408 | + | |
| 1409 | + | |
| 1410 | + | |
| 1411 | + | |
| 1412 | + | |
| 1413 | + | |
| 1414 | + | |
| 1415 | + | |
| 1416 | + | |
| 1417 | + | |
| 1418 | + | |
| 1419 | + | |
| 1420 | + | |
| 1421 | + | |
| 1422 | + | |
| 1423 | + | |
| 1424 | + | |
| 1425 | + | |
| 1426 | + | |
| 1427 | + | |
| 1428 | + | |
| 1429 | + | |
| 1430 | + | |
| 1431 | + | |
| 1432 | + | |
| 1433 | + | |
| 1434 | + | |
| 1435 | + | |
| 1436 | + | |
| 1437 | + | |
| 1438 | + | |
| 1439 | + | |
| 1440 | + | |
| 1441 | + | |
| 1442 | + | |
| 1443 | + | |
| 1444 | + | |
| 1445 | + | |
| 1446 | + | |
| 1447 | + | |
| 1448 | + | |
| 1449 | + | |
| 1450 | + | |
| 1451 | + | |
| 1452 | + | |
| 1453 | + | |
| 1454 | + | |
| 1455 | + | |
| 1456 | + | |
| 1457 | + | |
| 1458 | + | |
| 1459 | + | |
| 1460 | + | |
| 1461 | + | |
| 1462 | + | |
| 1463 | + | |
| 1464 | + | |
| 1465 | + | |
| 1466 | + | |
| 1467 | + | |
| 1468 | + | |
| 1469 | + | |
| 1470 | + | |
| 1471 | + | |
| 1472 | + | |
| 1473 | + | |
| 1474 | + | |
| 1475 | + | |
| 1476 | + | |
| 1477 | + | |
| 1478 | + | |
| 1479 | + | |
| 1480 | + | |
| 1481 | + | |
| 1482 | + | |
| 1483 | + | |
| 1484 | + | |
| 1485 | + | |
| 1486 | + | |
| 1487 | + | |
| 1488 | + | |
| 1489 | + | |
| 1490 | + | |
| 1491 | + | |
| 1492 | + | |
| 1493 | + | |
| 1494 | + | |
| 1495 | + | |
| 1496 | + | |
| 1497 | + | |
| 1498 | + | |
| 1499 | + | |
| 1500 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
28 | | - | |
| 28 | + | |
| 29 | + | |
29 | 30 | | |
30 | 31 | | |
31 | 32 | | |
| |||
261 | 262 | | |
262 | 263 | | |
263 | 264 | | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
264 | 276 | | |
265 | 277 | | |
266 | 278 | | |
| |||
598 | 610 | | |
599 | 611 | | |
600 | 612 | | |
601 | | - | |
602 | | - | |
| 613 | + | |
| 614 | + | |
603 | 615 | | |
604 | 616 | | |
605 | 617 | | |
| |||
792 | 804 | | |
793 | 805 | | |
794 | 806 | | |
| 807 | + | |
| 808 | + | |
| 809 | + | |
795 | 810 | | |
796 | 811 | | |
797 | 812 | | |
| |||
1006 | 1021 | | |
1007 | 1022 | | |
1008 | 1023 | | |
1009 | | - | |
| 1024 | + | |
| 1025 | + | |
1010 | 1026 | | |
1011 | 1027 | | |
1012 | 1028 | | |
1013 | | - | |
| 1029 | + | |
1014 | 1030 | | |
1015 | 1031 | | |
1016 | 1032 | | |
| |||
1100 | 1116 | | |
1101 | 1117 | | |
1102 | 1118 | | |
1103 | | - | |
| 1119 | + | |
| 1120 | + | |
1104 | 1121 | | |
1105 | 1122 | | |
1106 | | - | |
| 1123 | + | |
1107 | 1124 | | |
1108 | 1125 | | |
1109 | 1126 | | |
| |||
1387 | 1404 | | |
1388 | 1405 | | |
1389 | 1406 | | |
1390 | | - | |
1391 | | - | |
1392 | | - | |
1393 | | - | |
| 1407 | + | |
| 1408 | + | |
| 1409 | + | |
| 1410 | + | |
| 1411 | + | |
| 1412 | + | |
| 1413 | + | |
| 1414 | + | |
| 1415 | + | |
1394 | 1416 | | |
1395 | | - | |
1396 | | - | |
1397 | | - | |
| 1417 | + | |
| 1418 | + | |
| 1419 | + | |
| 1420 | + | |
| 1421 | + | |
1398 | 1422 | | |
1399 | 1423 | | |
1400 | 1424 | | |
| |||
1409 | 1433 | | |
1410 | 1434 | | |
1411 | 1435 | | |
1412 | | - | |
1413 | | - | |
1414 | | - | |
1415 | | - | |
| 1436 | + | |
1416 | 1437 | | |
1417 | | - | |
| 1438 | + | |
1418 | 1439 | | |
1419 | 1440 | | |
1420 | 1441 | | |
| |||
0 commit comments