From 559a578314ad8878032f7ad3883b061e4ef48105 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 05:20:46 +0000 Subject: [PATCH 1/5] fix(plugin-security,spec): the unnamed-grant boot line and the ups-permission-set-id-column-retired entry name the reviewed-plan ceremony, not a bare --apply `os migrate organization-ownership --apply` refuses without `--plan-file`. The catalog reference report's unnamed-grant error line and the ADR-0087 entry's `replacement` now name the plan step (`--out plan.json`), the review, then `--apply --plan-file plan.json`. Text only. Claude-Session: https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX Co-authored-by: Claude --- .changeset/23513-grant-remedy-plan-file.md | 15 +++++++++++++++ .../src/catalog-reference-report.ts | 9 +++++---- .../18.ups-permission-set-id-column-retired.ts | 9 +++++---- 3 files changed, 25 insertions(+), 8 deletions(-) create mode 100644 .changeset/23513-grant-remedy-plan-file.md diff --git a/.changeset/23513-grant-remedy-plan-file.md b/.changeset/23513-grant-remedy-plan-file.md new file mode 100644 index 00000000000..168fc406e89 --- /dev/null +++ b/.changeset/23513-grant-remedy-plan-file.md @@ -0,0 +1,15 @@ +--- +'@objectstack/plugin-security': patch +'@objectstack/spec': patch +--- + +fix(plugin-security,spec): the unnamed-grant boot line and the `ups-permission-set-id-column-retired` upgrade entry name the reviewed-plan ceremony, `--out plan.json` then `--apply --plan-file plan.json`, instead of a bare `--apply` the CLI refuses (ADR-0131 D10) + +Clause-②: no + +`os migrate organization-ownership --apply` refuses a run without `--plan-file`, the file a plan run wrote and the operator reviewed. Two remedy texts still told the operator to run `--apply` alone, which answers `plan-file-required` and writes nothing: + +- `@objectstack/plugin-security`: the `kernel:bootstrapped` catalog reference report's error line for grants that name no permission set (`permission_set` empty). +- `@objectstack/spec`: the `replacement` of the ADR-0087 entry `ups-permission-set-id-column-retired`, the row the upgrade guide prints for the retired `sys_user_permission_set.permission_set_id`. + +Both now name the two steps: plan first, `os migrate organization-ownership --out plan.json`, review the file it writes, then `os migrate organization-ownership --apply --plan-file plan.json`, which executes that plan; restart afterwards. The ceremony itself is unchanged: its rewrite names each legacy grant from the retired id, and its column-drops step drops the two retired indexes and then the column. Only the text changes; the line's level, its fields and the entry's `id` are as they were. diff --git a/packages/plugins/plugin-security/src/catalog-reference-report.ts b/packages/plugins/plugin-security/src/catalog-reference-report.ts index 66a31baf526..a895622dffd 100644 --- a/packages/plugins/plugin-security/src/catalog-reference-report.ts +++ b/packages/plugins/plugin-security/src/catalog-reference-report.ts @@ -433,10 +433,11 @@ function logReport(report: CatalogReferenceReport, logger?: CatalogReferenceRepo logError( logger, `[security] ${unnamed.count} ${GRANT_OBJECT} grant(s) name no permission set (${GRANT_SET_NAME_FIELD} is ` + - 'empty), so each confers nothing (ADR-0131 D4). Nothing was changed. Fix: run the v18 ceremony, ' + - 'os migrate organization-ownership --apply, which names each legacy grant from its retired id and lists ' + - 'the ones it cannot name; then restart. Granting the set again by name before it runs makes a second ' + - 'grant the ceremony will report, not name.', + 'empty), so each confers nothing (ADR-0131 D4). Nothing was changed. Fix: run the v18 ceremony. Plan it ' + + 'first, os migrate organization-ownership --out plan.json, and review the file it writes; then run ' + + 'os migrate organization-ownership --apply --plan-file plan.json, which executes that plan: it names each ' + + 'legacy grant from its retired id and lists the ones it cannot name. Then restart. Granting the set again ' + + 'by name before it runs makes a second grant the ceremony will report, not name.', { count: unnamed.count, organizations: unnamed.organizations }, ); } diff --git a/packages/spec/src/migrations/entries/semantic/18.ups-permission-set-id-column-retired.ts b/packages/spec/src/migrations/entries/semantic/18.ups-permission-set-id-column-retired.ts index 5b25121aa3d..ba0e0e27800 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ups-permission-set-id-column-retired.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ups-permission-set-id-column-retired.ts @@ -21,10 +21,11 @@ export const entry: SemanticMigration = { replacement: '`permission_set`, the machine name of the permission set the grant holds (ADR-0131 D4). Delete ' + 'the key from any authored `sys_user_permission_set` seed row or data-door write that still ' - + 'carries it, and name the set instead. Existing databases: run the v18 ceremony, ' - + '`os migrate organization-ownership --apply`, whose verified rewrite names every grant from the ' - + 'column and whose column-drops step then drops the two retired indexes and then the column; ' - + 'restart the server afterwards', + + 'carries it, and name the set instead. Existing databases: run the v18 ceremony. Plan it first, ' + + '`os migrate organization-ownership --out plan.json`, and review the file it writes; then ' + + '`os migrate organization-ownership --apply --plan-file plan.json` executes that plan: its verified ' + + 'rewrite names every grant from the column and its column-drops step then drops the two retired ' + + 'indexes and then the column. Restart the server afterwards', reason: 'ADR-0131 D4 makes every reference to a catalog item a name, and D3 retired the permission-set ' + 'catalog rows the id pointed at. Since then no writer fills the column and no reader confers ' From 02ab85b28bd4b168ad5c243f0c1006b005d4d4c9 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 05:22:13 +0000 Subject: [PATCH 2/5] docs(cli,plugin-auth,scripts): the grant key's NULL organization is the unique index's, the plugin-auth reader header drops the retired backfill, and the deleted backfill's callee leaves both durability vocabularies - organization-ownership-grants.ts / -plan.ts: a NULL organization is keyed as the `unique: 'organization'` index keys it (NULL-safe, ADR-0120 D3); the write door these comments named is retired. - organization-ownership-apply.test.ts: the NULL-organization duplicate test's title no longer says the index keys NULLs apart. - plugin-auth grant-set-name.ts: C7b, not C8, retired `permission_set_id`; no boot names a grant, the v18 ceremony's rewrite does. - check-durability-degradation-log-level.mjs and measure-durability-swallow-family.mjs: `persistGrantNameBackfillRecord` is removed from both vocabularies; its only definition was deleted with the boot backfill. The census's copied-vocabulary cross-check reads 24 names matching the gate's declaration. Comments, a test title and two script vocabularies only; no behaviour change. Claude-Session: https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX Co-authored-by: Claude --- .../utils/organization-ownership-apply.test.ts | 2 +- .../src/utils/organization-ownership-grants.ts | 6 +++++- .../src/utils/organization-ownership-plan.ts | 5 +++-- .../plugins/plugin-auth/src/grant-set-name.ts | 18 +++++++++--------- .../check-durability-degradation-log-level.mjs | 4 ---- scripts/measure-durability-swallow-family.mjs | 1 - 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/cli/src/utils/organization-ownership-apply.test.ts b/packages/cli/src/utils/organization-ownership-apply.test.ts index 7b3db21b643..2fb0ea98131 100644 --- a/packages/cli/src/utils/organization-ownership-apply.test.ts +++ b/packages/cli/src/utils/organization-ownership-apply.test.ts @@ -1979,7 +1979,7 @@ describe('ADR-0131 D10 --apply — the grant\'s retired id column (C7b, seat ans expect(idColumn()).toEqual([]); }); - it('a second platform-wide grant (organization NULL) is a duplicate too: GROUP BY keys NULLs together, unlike the index', async () => { + it('a second platform-wide grant (organization NULL) is a duplicate too: GROUP BY keys NULLs together, as the index\'s NULL-safe organization key part does', async () => { await onlyTemplates(`('tpl_none', NULL, 'crm.none', 'en-US', 'x', 0, 'package')`, async () => { db.exec(` CREATE TABLE sys_user_permission_set (id TEXT PRIMARY KEY, organization_id TEXT, user_id TEXT, diff --git a/packages/cli/src/utils/organization-ownership-grants.ts b/packages/cli/src/utils/organization-ownership-grants.ts index a20c0ee8f39..09af324ca63 100644 --- a/packages/cli/src/utils/organization-ownership-grants.ts +++ b/packages/cli/src/utils/organization-ownership-grants.ts @@ -59,7 +59,11 @@ export interface GrantRewritePlan { reasons: Record; } -/** The unique key of a grant; a NULL organization is one key, as the write door keys it. */ +/** + * The unique key of a grant; a NULL organization is one key, as the grant's + * `unique: 'organization'` index keys it (its organization key part is + * NULL-safe, ADR-0120 D3). + */ export function grantKey(userId: string | null, name: string, organizationId: string | null): string { return JSON.stringify([userId, name, organizationId]); } diff --git a/packages/cli/src/utils/organization-ownership-plan.ts b/packages/cli/src/utils/organization-ownership-plan.ts index 2e0d1b3e161..75d6ad71271 100644 --- a/packages/cli/src/utils/organization-ownership-plan.ts +++ b/packages/cli/src/utils/organization-ownership-plan.ts @@ -181,8 +181,9 @@ export interface MetadataConflict { /** * `sys_user_permission_set` (C7b): the populations the unique index on * (`user_id`, `permission_set`, `organization_id`) refuses, which the ceremony - * reports and never deletes. A NULL organization is keyed with NULL, as the - * write door keys it. + * reports and never deletes. A NULL organization is keyed with NULL, as that + * index keys it: it is declared `unique: 'organization'`, so its organization + * key part is NULL-safe (ADR-0120 D3). */ export interface GrantKeyPlan { /** `absent` on a 17.x database this release has not booted against yet: nothing can be counted. */ diff --git a/packages/plugins/plugin-auth/src/grant-set-name.ts b/packages/plugins/plugin-auth/src/grant-set-name.ts index 161466271df..fbd07359538 100644 --- a/packages/plugins/plugin-auth/src/grant-set-name.ts +++ b/packages/plugins/plugin-auth/src/grant-set-name.ts @@ -3,21 +3,21 @@ /** * [ADR-0131 D4] How this package's grant readers read which permission set a * `sys_user_permission_set` grant holds: BY NAME, from its `permission_set` - * column — the reference the grant keeps once ADR-0131 C8 drops + * column — the grant's one reference since ADR-0131 C7b retired * `permission_set_id`. * * Every grant writer stores the name and nothing else (this package's * self-registration grant among them); `plugin-security` refuses a non-system - * grant naming a set the security catalog does not hold, and a one-time - * backfill names the grants written before the column existed. Internal: not - * re-exported from the package entry. + * grant naming a set the security catalog does not hold. No boot names a + * grant. Internal: not re-exported from the package entry. * * A grant whose name is `NULL` or blank names no set. That is a grant written - * before the column existed on an upgraded deployment's first boot, before the - * backfill (`kernel:bootstrapped`) names it, or one the backfill could not name - * (an id with no set row, or a set row of another organization). Such a grant - * confers nothing through a by-name reader here; each reader states what it - * does with one in the direction that fails closed. + * before the column existed, on a database upgraded from such a release, until + * the v18 ceremony's verified rewrite (`os migrate organization-ownership + * --apply`, ADR-0131 D10) names it from the retired id, or one the rewrite does + * not name (it lists each, with the reason). Such a grant confers nothing + * through a by-name reader here; each reader states what it does with one in + * the direction that fails closed. */ /** The grant's NAME reference to its permission set (ADR-0131 D4). */ diff --git a/scripts/check-durability-degradation-log-level.mjs b/scripts/check-durability-degradation-log-level.mjs index e92845fec29..099d780f7f3 100644 --- a/scripts/check-durability-degradation-log-level.mjs +++ b/scripts/check-durability-degradation-log-level.mjs @@ -408,10 +408,6 @@ const DURABILITY_CRITICAL_CALLEES = new Map([ 'persistLedgerDecisionRow', "A one-time membership decision (the ADR-0093 D6 backfill, or the default organization's owner bind) was carried out but its `sys_migration` record was never written. Every log line reads clean and the memberships it wrote stand, while the record that makes the decision ONE-TIME is absent, so the next boot decides it again — re-deciding membership for users whose membership was already decided at creation (ADR-0093 D7).", ], - [ - 'persistGrantNameBackfillRecord', - "The one-time grant-name backfill (ADR-0131 D4) named the grants it could, but its `sys_migration` verdict row was never written. Every log line reads clean and the names it wrote stand, while the record that makes the pass ONE-TIME is absent, so every later boot scans the whole grant table again and re-reports the grants it cannot name.", - ], [ 'runWideningAlters', "The widening ALTER never ran — the MySQL column keeps its legacy zero-precision type (`TIMESTAMP` for a `Field.datetime`, `TIME` for a `Field.time`) while the object stays registered and served, so every subsequent write silently drops the milliseconds the canonical storage form promises are always present: a `TIMESTAMP` truncates them, and a `TIME(0)` ROUNDS a fractional literal, changing the wall clock it was asked to store. Reads come back looking clean because the value that was stored is the value that is returned, and nothing else reports the column is still un-widened (#9609).", diff --git a/scripts/measure-durability-swallow-family.mjs b/scripts/measure-durability-swallow-family.mjs index 22619cb53f5..e8753b5ebb8 100644 --- a/scripts/measure-durability-swallow-family.mjs +++ b/scripts/measure-durability-swallow-family.mjs @@ -331,7 +331,6 @@ const WRITE_SHAPED_CALLEES = new Map([ ['persistPackageCommitRow', 'gate-vocabulary'], ['persistSeedTenancyReceiptRow', 'gate-vocabulary'], ['persistLedgerDecisionRow', 'gate-vocabulary'], - ['persistGrantNameBackfillRecord', 'gate-vocabulary'], ['recordLog', 'gate-vocabulary'], ['runWideningAlters', 'gate-vocabulary'], ['applyConfigPatch', 'gate-vocabulary'], From 55725c1d79fc4f4cefd8b4d8af2bed06a63fadb9 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 06:16:07 +0000 Subject: [PATCH 3/5] docs(changeset): the pending C7b S3 note names the reviewed-plan ceremony, not a bare --apply and a --plan flag the CLI does not have `.changeset/23430-grant-permission-set-id-retired.md` is still pending (in `.changeset/`, not `.changeset/pre/`), so it is corrected before the next version consumes it. Two sentences only: - the backfill's FROM -> TO now names `--out plan.json`, the review, then `--apply --plan-file plan.json` (bare `--apply` is refused: plan-file-required); - the grants block is reported by the plan run, `--out plan.json` (the CLI answers `--plan` with exit 2, Nonexistent flag). Claude-Session: https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX Co-authored-by: Claude --- .changeset/23430-grant-permission-set-id-retired.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/23430-grant-permission-set-id-retired.md b/.changeset/23430-grant-permission-set-id-retired.md index 4ab6d2854e1..b1951818381 100644 --- a/.changeset/23430-grant-permission-set-id-retired.md +++ b/.changeset/23430-grant-permission-set-id-retired.md @@ -18,13 +18,13 @@ FROM → TO: - `sys_user_permission_set.permission_set_id` → `permission_set`, the machine name of the set the grant holds. A seed row or data-door write that still carries `permission_set_id` is refused with `400 INVALID_FIELD`. Fix: delete the key and name the set, `{ user_id, permission_set: '', organization_id }`. - The unique index (`user_id`, `permission_set_id`, `organization_id`) and the (`permission_set_id`) index → one unique index on (`user_id`, `permission_set`, `organization_id`), declared `unique: 'organization'`: its organization key part is NULL-safe (ADR-0120 D3), so it refuses a second grant of one set to one user in one organization AND a second platform-wide grant (organization NULL), for every writer and every write path — batches, multi-row updates, concurrent writes and raw SQL included. The data doors answer `409 UNIQUE_VIOLATION` (the engine throws `DuplicateRecordError`, `DUPLICATE_RECORD`). Fix: keep the grant that exists; to change it, update that row. -- The boot-time grant-name backfill (`runOneTimeGrantPermissionSetNameBackfill`, the `sys_migration` entry it recorded at `kernel:bootstrapped`) → `os migrate organization-ownership --apply`. Its verified rewrite names every grant from the physical `permission_set_id` column by raw SQL, verifies each name against the security catalog, and its `column-drops` step then drops the column through the schema-drift ceremony (schema sync is additive, so an undeclared column stays until it is dropped). Fix: run the ceremony on every database upgraded from a release before the `permission_set` column existed, then restart the server: the boot's organization-administrator sweep revokes, by name, a newly named grant whose pair no longer qualifies, and a server left running across `--apply` serves it until then. +- The boot-time grant-name backfill (`runOneTimeGrantPermissionSetNameBackfill`, the `sys_migration` entry it recorded at `kernel:bootstrapped`) → the v18 ceremony: plan it with `os migrate organization-ownership --out plan.json`, review the file it writes, then run `os migrate organization-ownership --apply --plan-file plan.json`. Its verified rewrite names every grant from the physical `permission_set_id` column by raw SQL, verifies each name against the security catalog, and its `column-drops` step then drops the column through the schema-drift ceremony (schema sync is additive, so an undeclared column stays until it is dropped). Fix: run the ceremony on every database upgraded from a release before the `permission_set` column existed, then restart the server: the boot's organization-administrator sweep revokes, by name, a newly named grant whose pair no longer qualifies, and a server left running across `--apply` serves it until then. On a 17.x database every grant names nothing until `--apply` names it, so between the first v18 boot and `--apply` no grant confers anything, the platform administrator's included. What else changes: -- `os migrate organization-ownership --plan` reports a `grants` block on `sys_user_permission_set`, keyed like the new index (a NULL organization keyed with NULL): +- The plan run, `os migrate organization-ownership --out plan.json`, reports a `grants` block on `sys_user_permission_set`, keyed like the new index (a NULL organization keyed with NULL): - `duplicates`: groups of named grants holding one set for one user in one organization. `--apply`'s `column-drops` step does not drop `permission_set_id` while one group remains. - `rewriteCollisions`: legacy grants whose derived name their user already holds in the same organization. `--apply`'s rewrite names none of them and blocks, naming both ids. - On a database without the `permission_set` column it counts zero and names the remedy (boot this release once), instead of refusing the plan. From a618e1735cf83544253f93adf02726bde9fe6c33 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 06:51:38 +0000 Subject: [PATCH 4/5] fix(plugin-security,spec): the apply step in the three corrected remedy texts carries --backup-taken, as S4's entry, note and CLI examples spell it Without a TTY `--apply` refuses with backup-unacknowledged unless `--backup-taken` is passed. The unnamed-grant boot line, the ups-permission-set-id-column-retired entry's replacement and the pending C7b S3 note now write `os migrate organization-ownership --apply --backup-taken --plan-file plan.json`. One flag inserted in each; nothing else changes. Claude-Session: https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX Co-authored-by: Claude --- .changeset/23430-grant-permission-set-id-retired.md | 2 +- .../plugins/plugin-security/src/catalog-reference-report.ts | 6 +++--- .../semantic/18.ups-permission-set-id-column-retired.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.changeset/23430-grant-permission-set-id-retired.md b/.changeset/23430-grant-permission-set-id-retired.md index b1951818381..ebfb0555370 100644 --- a/.changeset/23430-grant-permission-set-id-retired.md +++ b/.changeset/23430-grant-permission-set-id-retired.md @@ -18,7 +18,7 @@ FROM → TO: - `sys_user_permission_set.permission_set_id` → `permission_set`, the machine name of the set the grant holds. A seed row or data-door write that still carries `permission_set_id` is refused with `400 INVALID_FIELD`. Fix: delete the key and name the set, `{ user_id, permission_set: '', organization_id }`. - The unique index (`user_id`, `permission_set_id`, `organization_id`) and the (`permission_set_id`) index → one unique index on (`user_id`, `permission_set`, `organization_id`), declared `unique: 'organization'`: its organization key part is NULL-safe (ADR-0120 D3), so it refuses a second grant of one set to one user in one organization AND a second platform-wide grant (organization NULL), for every writer and every write path — batches, multi-row updates, concurrent writes and raw SQL included. The data doors answer `409 UNIQUE_VIOLATION` (the engine throws `DuplicateRecordError`, `DUPLICATE_RECORD`). Fix: keep the grant that exists; to change it, update that row. -- The boot-time grant-name backfill (`runOneTimeGrantPermissionSetNameBackfill`, the `sys_migration` entry it recorded at `kernel:bootstrapped`) → the v18 ceremony: plan it with `os migrate organization-ownership --out plan.json`, review the file it writes, then run `os migrate organization-ownership --apply --plan-file plan.json`. Its verified rewrite names every grant from the physical `permission_set_id` column by raw SQL, verifies each name against the security catalog, and its `column-drops` step then drops the column through the schema-drift ceremony (schema sync is additive, so an undeclared column stays until it is dropped). Fix: run the ceremony on every database upgraded from a release before the `permission_set` column existed, then restart the server: the boot's organization-administrator sweep revokes, by name, a newly named grant whose pair no longer qualifies, and a server left running across `--apply` serves it until then. +- The boot-time grant-name backfill (`runOneTimeGrantPermissionSetNameBackfill`, the `sys_migration` entry it recorded at `kernel:bootstrapped`) → the v18 ceremony: plan it with `os migrate organization-ownership --out plan.json`, review the file it writes, then run `os migrate organization-ownership --apply --backup-taken --plan-file plan.json`. Its verified rewrite names every grant from the physical `permission_set_id` column by raw SQL, verifies each name against the security catalog, and its `column-drops` step then drops the column through the schema-drift ceremony (schema sync is additive, so an undeclared column stays until it is dropped). Fix: run the ceremony on every database upgraded from a release before the `permission_set` column existed, then restart the server: the boot's organization-administrator sweep revokes, by name, a newly named grant whose pair no longer qualifies, and a server left running across `--apply` serves it until then. On a 17.x database every grant names nothing until `--apply` names it, so between the first v18 boot and `--apply` no grant confers anything, the platform administrator's included. diff --git a/packages/plugins/plugin-security/src/catalog-reference-report.ts b/packages/plugins/plugin-security/src/catalog-reference-report.ts index a895622dffd..8683807b938 100644 --- a/packages/plugins/plugin-security/src/catalog-reference-report.ts +++ b/packages/plugins/plugin-security/src/catalog-reference-report.ts @@ -435,9 +435,9 @@ function logReport(report: CatalogReferenceReport, logger?: CatalogReferenceRepo `[security] ${unnamed.count} ${GRANT_OBJECT} grant(s) name no permission set (${GRANT_SET_NAME_FIELD} is ` + 'empty), so each confers nothing (ADR-0131 D4). Nothing was changed. Fix: run the v18 ceremony. Plan it ' + 'first, os migrate organization-ownership --out plan.json, and review the file it writes; then run ' + - 'os migrate organization-ownership --apply --plan-file plan.json, which executes that plan: it names each ' + - 'legacy grant from its retired id and lists the ones it cannot name. Then restart. Granting the set again ' + - 'by name before it runs makes a second grant the ceremony will report, not name.', + 'os migrate organization-ownership --apply --backup-taken --plan-file plan.json, which executes that plan: ' + + 'it names each legacy grant from its retired id and lists the ones it cannot name. Then restart. Granting ' + + 'the set again by name before it runs makes a second grant the ceremony will report, not name.', { count: unnamed.count, organizations: unnamed.organizations }, ); } diff --git a/packages/spec/src/migrations/entries/semantic/18.ups-permission-set-id-column-retired.ts b/packages/spec/src/migrations/entries/semantic/18.ups-permission-set-id-column-retired.ts index ba0e0e27800..96e5da404d4 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ups-permission-set-id-column-retired.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ups-permission-set-id-column-retired.ts @@ -23,7 +23,7 @@ export const entry: SemanticMigration = { + 'the key from any authored `sys_user_permission_set` seed row or data-door write that still ' + 'carries it, and name the set instead. Existing databases: run the v18 ceremony. Plan it first, ' + '`os migrate organization-ownership --out plan.json`, and review the file it writes; then ' - + '`os migrate organization-ownership --apply --plan-file plan.json` executes that plan: its verified ' + + '`os migrate organization-ownership --apply --backup-taken --plan-file plan.json` executes that plan: its verified ' + 'rewrite names every grant from the column and its column-drops step then drops the two retired ' + 'indexes and then the column. Restart the server afterwards', reason: From 9f50f1887b0f47ed778b3bff7f261d23831569a2 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 07:00:17 +0000 Subject: [PATCH 5/5] docs(changeset): this PR's note spells the apply step with --backup-taken, as the three texts it describes now do The title line and the "Both now name the two steps" sentence of .changeset/23513-grant-remedy-plan-file.md now give `--apply --backup-taken --plan-file plan.json`. One flag inserted in each. Claude-Session: https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX Co-authored-by: Claude --- .changeset/23513-grant-remedy-plan-file.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/23513-grant-remedy-plan-file.md b/.changeset/23513-grant-remedy-plan-file.md index 168fc406e89..818ad75cf92 100644 --- a/.changeset/23513-grant-remedy-plan-file.md +++ b/.changeset/23513-grant-remedy-plan-file.md @@ -3,7 +3,7 @@ '@objectstack/spec': patch --- -fix(plugin-security,spec): the unnamed-grant boot line and the `ups-permission-set-id-column-retired` upgrade entry name the reviewed-plan ceremony, `--out plan.json` then `--apply --plan-file plan.json`, instead of a bare `--apply` the CLI refuses (ADR-0131 D10) +fix(plugin-security,spec): the unnamed-grant boot line and the `ups-permission-set-id-column-retired` upgrade entry name the reviewed-plan ceremony, `--out plan.json` then `--apply --backup-taken --plan-file plan.json`, instead of a bare `--apply` the CLI refuses (ADR-0131 D10) Clause-②: no @@ -12,4 +12,4 @@ Clause-②: no - `@objectstack/plugin-security`: the `kernel:bootstrapped` catalog reference report's error line for grants that name no permission set (`permission_set` empty). - `@objectstack/spec`: the `replacement` of the ADR-0087 entry `ups-permission-set-id-column-retired`, the row the upgrade guide prints for the retired `sys_user_permission_set.permission_set_id`. -Both now name the two steps: plan first, `os migrate organization-ownership --out plan.json`, review the file it writes, then `os migrate organization-ownership --apply --plan-file plan.json`, which executes that plan; restart afterwards. The ceremony itself is unchanged: its rewrite names each legacy grant from the retired id, and its column-drops step drops the two retired indexes and then the column. Only the text changes; the line's level, its fields and the entry's `id` are as they were. +Both now name the two steps: plan first, `os migrate organization-ownership --out plan.json`, review the file it writes, then `os migrate organization-ownership --apply --backup-taken --plan-file plan.json`, which executes that plan; restart afterwards. The ceremony itself is unchanged: its rewrite names each legacy grant from the retired id, and its column-drops step drops the two retired indexes and then the column. Only the text changes; the line's level, its fields and the entry's `id` are as they were.