Repository navigation
Commit bbdfc05
feat(spec)!: retire the permission set's authored managedBy, a provenance key nothing read (#23989)
Fixes #23891
Clause-②: yes
Retires the AUTHORED key `PermissionSetSchema.managedBy` (remedy 1 of
the card; the liveness ledger's `dead` verdict of 2026-10-11, ADR-0049
enforce-or-remove) with the full `spec-property-retirement` kit. The
key's Setup form row, its help text, its three option labels and their
translations in four locales leave with it. The
`sys_permission_set.managed_by` COLUMN, its seeders and the ownership
ceremony are untouched, and so is `ObjectSchema.managedBy`.
## What changes for an author
- `managedBy` on a permission set is a `retiredKey()` tombstone: `tsc`
fails at the authoring site, and `definePermissionSet()`, `defineStack({
permissions })` and `PUT /api/v1/meta/permission/:name` refuse it at its
own path with the prescription (delete the key; a set's provenance is
the package that ships it, decided from that package's artifact and
recorded as `packageId`).
- A stored `permission` row (what Setup's form row wrote) and an
installed package's built artifact still load. The rehydration seams
(`applyConversionsToStoredItem`, `applyArtifactForwardConversions`)
replay the D2 conversion `permission-set-managed-by-removed` and strip
the key, with a notice that names the set. Only the authoring, save and
publish doors refuse it. This is the existing ADR-0087 mechanism
(`retiredFromLoadPath: true`, replayed with `includeRetired` at the
data-at-rest seams), so no kernel built from an installed package or a
stored row stops on it. #23904 landed after this head as PR #23962
(`d050a8fc63`); its `ObjectQL.convertPackageBodyForward` door runs the
same conversion table, so this entry rides it with no second mechanism.
## Measured before removal (base `6d1660dc3b`)
- **Readers of the authored key:** none. The retired
`bootstrapDeclaredPermissions` stamped the column and never read
`ps.managedBy`. `packaged-permission-set-lock.ts` decides provenance
from the artifact. `database-loader.ts:906` maps the `sys_metadata`
record envelope's `managed_by`, not a set body.
`permissionSetDefinitionFromRow` (core) excludes the column from the
body.
- **Writers in a permission-set body:** the example apps: 0.
`plugin-security`'s default sets: 0. Every `defineStack` fixture: 0 (the
tombstone typed every authoring site, and spec's typecheck and the
platform-objects typecheck are green). HotCRM 4.0.1 at `1d7148bf2d` (the
`v4.0.1` tag): 0 in its seven `*.profile.ts` sets, against the control
`objects:` at 7. The one writer was Setup's form row, into stored rows,
which the D2 strip heals.
- **objectui at the pin `daeb77fc84`** (`.objectui-sha` unchanged since
dispatch): `PermissionMatrixEditor.tsx:1365` and `:1606` read
`draft.managedBy` for the Platform / Package / Custom badge;
`permission-slice.ts:62` declares `managedBy?: string` on objectui's OWN
`PermissionSetDraft` type; `permission-set-clone.ts:13` lists it in a
comment; `PermissionMatrixEditor.badge.test.tsx:56-74` and
`PermissionMatrixEditor.provenanceBadge-7611.test.tsx` test the badge.
None imports the spec's type for that field, so the pinned objectui
build does not break (Post-Task Checklist step 4). Its reader is a
display, and its other two inputs (`packageId`, `codeIsArtifact`) are
the real provenance. No objectui file is touched; the follow-up card is
the seat's to file at landing.
## The kit
- `packages/spec/src/security/permission.zod.ts`: the tombstone
`managedBy: retiredKey(PERMISSION_SET_MANAGED_BY_REMOVED)` with its
TSDoc; the `packageId` TSDoc no longer cites the key.
- `packages/spec/src/security/permission.form.ts`: the `managedBy` row
and its decision-record bullet leave, and a one-line note stays in their
place.
- D2 `permission-set-managed-by-removed` (`conversions/registry.ts`,
`MAJOR_18_CONVERSIONS` order 67, defined above
`recordChatterPositionVocabulary` per the merge rule); D3
`permission-set-managed-by-retired` (`migrations/entries/semantic/`);
`RETIRED_KEYS_BY_MAJOR[18]` `security/PermissionSet:managedBy`
(`migrations/entries/retired-keys/`); a `STEP18_RATIONALE` fragment
(order 99) in `registry.ts.template`; a JOIN row in
`retired-key-removal-version.pin.test.ts`.
- `liveness/permission.json`: the row stays (`dead`, a REMOVED note,
`verifiedAt: 2026-10-16`). The status does not move, so the README's
per-type counts do not either.
- Generated: `authorable-surface/security.json` (one row becomes
`[RETIRED]`) and `content/docs/references/security/permission.mdx`
(`gen:docs`). The four `*.metadata-forms.generated.ts` bundles were
regenerated by `node scripts/check-i18n-bundles.mjs --write`, which
drops 4 lines per locale, all generator-owned. Nothing generated was
hand-edited.
- Hand-written: `content/docs/permissions/permission-sets.mdx` (the
clone door's dropped-key list no longer names `managedBy`). In
`platform-objects`, two pins follow the bundle:
`setup-copy-vocabulary.test.ts` drops the leaf from its help-text
population and dark leg, and
`object-lifecycle-panel-echo-decisions.test.ts` moves its catalog count
from 674 to 673 for the one label that left.
- `packages/spec/vitest.repo-tests.json`: the new pin's tree walk reads
outside the package, so it runs under the `repo` project
(`check:cross-package-test-inputs`).
- `.changeset/23891-permission-set-managed-by-retired.md`:
`@objectstack/spec` minor (BREAKING, FROM → TO, the one-line fix) and
`@objectstack/platform-objects` patch, with the ADR-0087 `registered`
marker.
## The four ratchets: what the route predicts, and what was read
On the tombstone route, the key stays in the walked shape and no def is
deleted. The route predicts a `[RETIRED]` mark on one authorable-surface
row and no change to the other three. Read on `076958cb`:
`authorable-surface` +1/−1 (the mark); `api-surface`,
`api-surface-signatures` and `json-schema.manifest` unchanged;
`check:generated` reports all 14 artifacts up to date.
## Pins, and the ablations that name them
`packages/spec/src/security/permission-set-managed-by-retirement.test.ts`
(17 cases): the refusal at the key's path with the prescription, for
every value; the `permission` write door; the `defineStack` envelope
(`STACK_SCHEMA_INVALID`, 422); a `@ts-expect-error` at an authoring
site; the form offers no row; a stored row replays clean through
`applyConversionsToStoredItem`; an installed package's artifact is
refused before the conversion and accepted after it, with a lit control;
strip, idempotence, and retirement from the load path; ADR-0087
registration; and a tree-scoped absence walk. Because
`ObjectSchema.managedBy` is live, the walk flags `managedBy` only in an
object literal that also carries a key only a permission set declares,
and it states that bound.
Ablations (`scripts/ablation-replace.mjs`, each restored to the HEAD
blob with `git diff HEAD` empty):
- **The key restored** as
`z.enum(['package','platform','user']).optional()`: 7 tests go red (the
parse refusal, every value, the write door, `defineStack`, the tsc-site
parse leg, the artifact before/after, and the removal-version JOIN row).
- **The D2 conversion unwired** from `MAJOR_18_CONVERSIONS`: 4 go red
(the stored row, the artifact, strip/idempotence, chain wiring).
- **The `@ts-expect-error` deleted:** `check:test-typecheck` goes red,
naming the file. The type channel is really compiled.
## Verification (on `076958cb`, after merging `main`)
- `@objectstack/spec`: `build` exit 0; `typecheck` exit 0;
`check:generated` exit 0; `check:liveness`, `check:empty-state`,
`check:exported-any`, `check:dual-source-exports` and
`check:variant-docs` exit 0. Vitest runs: `src/` in four batches, 681
files and 20,447 tests, all passing. After the merge: `src/security`,
`src/conversions`, `src/migrations` and `src/shared` plus the two
major-18 merge tests and `scripts/liveness`, 83 files and 2,445 tests,
all passing.
- `@objectstack/platform-objects`: vitest 73 files and 1,136 tests, all
passing; `typecheck` exit 0. `@objectstack/runtime`
`permission-set-clone.test.ts`: 23 passing. `check:i18n` exit 0.
- `dispatch-gates --commands` on this diff: 113 families; `--ran`: 113
accounted, 110 run, 3 NOT MEASURED. **NOT MEASURED** (prerequisites of
the local tree, not readings of this diff): `check:plugin-teardown-shape
--self-test` (exit 3, shallow clone), `check:skill-examples` (exit 3,
`client-react` declarations not built), `check:dual-build-cjs-loads`
(exit 3, packages never built here). `check:dts-closure` exited 1 on 54
packages missing `.d.ts`, which an earlier `OS_SKIP_DTS=1` closure build
left in this tree. None of these is a package this diff changes, and CI
checks out fresh.
## Acceptance notes
- `packages/runtime/src/domains/permission-set-clone.ts` still lists
`managedBy` in `PERMISSION_SET_CLONE_DROPPED_KEYS`. After this PR, no
set body that reaches it can carry the key, so the entry is inert, not
wrong. It is outside this card's file surface and is left alone.
Carrier: none.
- objectui's provenance badge reader (the sites listed above) is the
seat's follow-up card at landing.
- The out-of-repo consumer population of `@objectstack/spec` beyond
HotCRM 4.0.1 is NOT MEASURED.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01D3Ur6XgfkkaDUCCTkviXAr)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01D3Ur6XgfkkaDUCCTkviXAr
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent e18b23f commit bbdfc05
20 files changed
Lines changed: 685 additions & 47 deletions
File tree
- .changeset
- content/docs
- permissions
- references/security
- packages
- platform-objects/src/apps
- translations
- spec
- authorable-surface
- liveness
- src
- conversions
- migrations
- entries
- retired-keys
- semantic
- security
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
429 | 429 | | |
430 | 430 | | |
431 | 431 | | |
432 | | - | |
| 432 | + | |
433 | 433 | | |
434 | 434 | | |
435 | 435 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
118 | 118 | | |
119 | 119 | | |
120 | 120 | | |
121 | | - | |
| 121 | + | |
122 | 122 | | |
123 | 123 | | |
124 | 124 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
181 | 181 | | |
182 | 182 | | |
183 | 183 | | |
184 | | - | |
185 | 184 | | |
186 | 185 | | |
187 | 186 | | |
| |||
209 | 208 | | |
210 | 209 | | |
211 | 210 | | |
212 | | - | |
213 | 211 | | |
214 | 212 | | |
215 | 213 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2332 | 2332 | | |
2333 | 2333 | | |
2334 | 2334 | | |
2335 | | - | |
2336 | | - | |
2337 | | - | |
2338 | | - | |
2339 | 2335 | | |
2340 | 2336 | | |
2341 | 2337 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2332 | 2332 | | |
2333 | 2333 | | |
2334 | 2334 | | |
2335 | | - | |
2336 | | - | |
2337 | | - | |
2338 | | - | |
2339 | 2335 | | |
2340 | 2336 | | |
2341 | 2337 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2332 | 2332 | | |
2333 | 2333 | | |
2334 | 2334 | | |
2335 | | - | |
2336 | | - | |
2337 | | - | |
2338 | | - | |
2339 | 2335 | | |
2340 | 2336 | | |
2341 | 2337 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1199 | 1199 | | |
1200 | 1200 | | |
1201 | 1201 | | |
1202 | | - | |
| 1202 | + | |
| 1203 | + | |
| 1204 | + | |
| 1205 | + | |
1203 | 1206 | | |
1204 | 1207 | | |
1205 | 1208 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2332 | 2332 | | |
2333 | 2333 | | |
2334 | 2334 | | |
2335 | | - | |
2336 | | - | |
2337 | | - | |
2338 | | - | |
2339 | 2335 | | |
2340 | 2336 | | |
2341 | 2337 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
121 | 121 | | |
122 | 122 | | |
123 | 123 | | |
124 | | - | |
| 124 | + | |
125 | 125 | | |
126 | 126 | | |
127 | 127 | | |
| |||
0 commit comments