From 2171d2657870f55ba90d1c2c71258a9ee3e2f740 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 21:41:57 +0000 Subject: [PATCH 1/2] fix(plugin-security): the boot report's unnamed-grant line says what 18.0 supports for a v17 database The error line for sys_user_permission_set grants whose permission_set is empty told the operator to plan with `os migrate organization-ownership --out`, then apply with `--apply --plan-file`. 18.0 refuses that command on every database. The line now ends with the sentence the other 18.0 refusals share: 18.0 supports fresh installs only and does not upgrade a v17 database; keep a v17 database on a 17.x release until the 18.x release that carries the upgrade path. Text only: the count, the empty-field fact, the ADR-0131 D4 reason, "Nothing was changed.", the structured meta, the log level and the report result are unchanged. Claude-Session: https://claude.ai/code/session_01LR1cD9JqgkeqY7k9rrZH8s Co-authored-by: Claude --- .changeset/24652-report-v18-upgrade-text.md | 11 +++++ .../src/catalog-reference-report.test.ts | 40 ++++++++++++++++++- .../src/catalog-reference-report.ts | 8 ++-- 3 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 .changeset/24652-report-v18-upgrade-text.md diff --git a/.changeset/24652-report-v18-upgrade-text.md b/.changeset/24652-report-v18-upgrade-text.md new file mode 100644 index 00000000000..49324004c82 --- /dev/null +++ b/.changeset/24652-report-v18-upgrade-text.md @@ -0,0 +1,11 @@ +--- +'@objectstack/plugin-security': patch +--- + +fix(plugin-security): the boot report's unnamed-grant line says what 18.0 supports for a v17 database, not a command 18.0 refuses + +Clause-②: no + +- **The error line for `sys_user_permission_set` grants whose `permission_set` is empty** used to end with a fix: plan with `os migrate organization-ownership --out plan.json`, then run `os migrate organization-ownership --apply --backup-taken --plan-file plan.json`, restart, and do not grant the set again by name before then. 18.0 refuses that command on every database, so the operator was sent to a refusal. The line now ends with the sentence the 18.0 refusals share: "18.0 supports fresh installs only and does not upgrade a v17 database: keep a v17 database on a 17.x release until the 18.x release that carries the upgrade path." +- **Unchanged:** the count, the "permission_set is empty" fact, the ADR-0131 D4 reason ("each confers nothing"), "Nothing was changed.", the structured meta, the log level and the report's result. Such a grant still confers nothing and is still listed. +- **Removed from the line:** the two command steps and the restart step, and the advice about granting the set again by name. They described the 18.x upgrade ceremony, which 18.0 does not carry. diff --git a/packages/plugins/plugin-security/src/catalog-reference-report.test.ts b/packages/plugins/plugin-security/src/catalog-reference-report.test.ts index c2c619a3fef..4905c05efcb 100644 --- a/packages/plugins/plugin-security/src/catalog-reference-report.test.ts +++ b/packages/plugins/plugin-security/src/catalog-reference-report.test.ts @@ -14,7 +14,9 @@ * name it stay. * * Every assertion reads the structured result, never the log's wording; the - * wiring pin finds the plugin's lines by their structured meta. + * wiring pin finds the plugin's lines by their structured meta. One pin reads + * wording on purpose: the unnamed-grant line is 18.0 release text an operator + * acts on, and that release says one sentence about a v17 database everywhere. */ import { describe, it, expect, afterEach, vi } from 'vitest'; @@ -320,6 +322,42 @@ describe('[ADR-0131 D4] the catalog reference report — references whose names }); }); +describe('[ADR-0131 D4] the unnamed-grant line says what 18.0 supports for a v17 database', () => { + // The one wording pin in this file. 18.0 supports fresh installs only and its + // `os migrate organization-ownership` refuses plan and apply on every + // database, so the line an operator reads at boot must not send them to it; + // it carries the sentence the drop-gate refusal, the boot refusal and the + // command's own refusal carry. Everything else in the line is the report's + // own: the count, the field, the D4 reason, and that nothing was changed. + const V17_SENTENCE = '18.0 supports fresh installs only and does not upgrade a v17 database: keep a v17 database on a ' + + '17.x release until the 18.x release that carries the upgrade path.'; + + it('names the count, the empty field, the reason and "Nothing was changed", then the 18.0 sentence, and prescribes no command', async () => { + const b = await boot(); + await insertUser(b, 'usr_a'); + await insertUser(b, 'usr_b'); + await insertUnnamedGrants(b, [ + { id: 'g_one', user_id: 'usr_a', organization_id: ORG }, + { id: 'g_two', user_id: 'usr_b', organization_id: ORG }, + ]); + const logger = { info: vi.fn(), warn: vi.fn(), error: vi.fn() }; + await reportCatalogReferences(b.engine as any, { catalog: b.catalog, logger }); + + // Anti-vacuity: the report logged exactly the unnamed-grant line, found by its meta. + expect(logger.error).toHaveBeenCalledTimes(1); + const [message, , meta] = logger.error.mock.calls[0] as [string, unknown, { count: number }]; + expect(meta.count).toBe(2); + + expect(message).toContain( + '[security] 2 sys_user_permission_set grant(s) name no permission set (permission_set is empty), ' + + 'so each confers nothing (ADR-0131 D4). Nothing was changed. ' + V17_SENTENCE, + ); + expect(message.endsWith(V17_SENTENCE), 'the sentence closes the line').toBe(true); + // The refused command and every step of it are gone, not merely qualified. + expect(message).not.toMatch(/organization-ownership|os migrate|--out|--apply|--plan-file|--backup-taken|plan\.json|ceremony/); + }); +}); + describe('[ADR-0131 D4] a removed declaration is reported; re-adding it clears the report', () => { /** A member granted the auditor set directly and assigned the field-lead position that binds it. */ const seedHolder = async (b: Booted) => { diff --git a/packages/plugins/plugin-security/src/catalog-reference-report.ts b/packages/plugins/plugin-security/src/catalog-reference-report.ts index 8683807b938..14fa2de8049 100644 --- a/packages/plugins/plugin-security/src/catalog-reference-report.ts +++ b/packages/plugins/plugin-security/src/catalog-reference-report.ts @@ -433,11 +433,9 @@ 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. Plan it ' + - 'first, os migrate organization-ownership --out plan.json, and review the file it writes; then run ' + - '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.', + 'empty), so each confers nothing (ADR-0131 D4). Nothing was changed. 18.0 supports fresh installs only and ' + + 'does not upgrade a v17 database: keep a v17 database on a 17.x release until the 18.x release that carries ' + + 'the upgrade path.', { count: unnamed.count, organizations: unnamed.organizations }, ); } From cf2ef195394507245b8528d1bd236f1bb8f33a0a Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 22:29:39 +0000 Subject: [PATCH 2/2] docs(plugin-security): name the drop-gate refusal as the source of the 18.0 sentence in the changeset and the pin's comment The sentence the unnamed-grant line now ends with is the one the drop-gate refusal of `os migrate apply --allow-destructive` carries; the boot refusal and the command's own refusal say the same in their own terms. The changeset and the pin's comment said "the 18.0 refusals share" it. The changeset also describes the removed grant-again warning as it was worded. No code or assertion moves. Claude-Session: https://claude.ai/code/session_01LR1cD9JqgkeqY7k9rrZH8s Co-authored-by: Claude --- .changeset/24652-report-v18-upgrade-text.md | 2 +- .../plugin-security/src/catalog-reference-report.test.ts | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.changeset/24652-report-v18-upgrade-text.md b/.changeset/24652-report-v18-upgrade-text.md index 49324004c82..49839d91ac2 100644 --- a/.changeset/24652-report-v18-upgrade-text.md +++ b/.changeset/24652-report-v18-upgrade-text.md @@ -6,6 +6,6 @@ fix(plugin-security): the boot report's unnamed-grant line says what 18.0 suppor Clause-②: no -- **The error line for `sys_user_permission_set` grants whose `permission_set` is empty** used to end with a fix: plan with `os migrate organization-ownership --out plan.json`, then run `os migrate organization-ownership --apply --backup-taken --plan-file plan.json`, restart, and do not grant the set again by name before then. 18.0 refuses that command on every database, so the operator was sent to a refusal. The line now ends with the sentence the 18.0 refusals share: "18.0 supports fresh installs only and does not upgrade a v17 database: keep a v17 database on a 17.x release until the 18.x release that carries the upgrade path." +- **The error line for `sys_user_permission_set` grants whose `permission_set` is empty** used to end with a fix: plan with `os migrate organization-ownership --out plan.json`, then run `os migrate organization-ownership --apply --backup-taken --plan-file plan.json`, restart, with a warning that granting the set again by name before it runs leaves a second grant for the ceremony to report. 18.0 refuses that command on every database, so the operator was sent to a refusal. The line now ends with the sentence the drop-gate refusal of `os migrate apply --allow-destructive` carries: "18.0 supports fresh installs only and does not upgrade a v17 database: keep a v17 database on a 17.x release until the 18.x release that carries the upgrade path." - **Unchanged:** the count, the "permission_set is empty" fact, the ADR-0131 D4 reason ("each confers nothing"), "Nothing was changed.", the structured meta, the log level and the report's result. Such a grant still confers nothing and is still listed. - **Removed from the line:** the two command steps and the restart step, and the advice about granting the set again by name. They described the 18.x upgrade ceremony, which 18.0 does not carry. diff --git a/packages/plugins/plugin-security/src/catalog-reference-report.test.ts b/packages/plugins/plugin-security/src/catalog-reference-report.test.ts index 4905c05efcb..20401e850bc 100644 --- a/packages/plugins/plugin-security/src/catalog-reference-report.test.ts +++ b/packages/plugins/plugin-security/src/catalog-reference-report.test.ts @@ -326,9 +326,11 @@ describe('[ADR-0131 D4] the unnamed-grant line says what 18.0 supports for a v17 // The one wording pin in this file. 18.0 supports fresh installs only and its // `os migrate organization-ownership` refuses plan and apply on every // database, so the line an operator reads at boot must not send them to it; - // it carries the sentence the drop-gate refusal, the boot refusal and the - // command's own refusal carry. Everything else in the line is the report's - // own: the count, the field, the D4 reason, and that nothing was changed. + // it carries the sentence the drop-gate refusal of `os migrate apply + // --allow-destructive` carries (the boot refusal and the command's own + // refusal say the same in their own terms). Everything else in the line is + // the report's own: the count, the field, the D4 reason, and that nothing was + // changed. const V17_SENTENCE = '18.0 supports fresh installs only and does not upgrade a v17 database: keep a v17 database on a ' + '17.x release until the 18.x release that carries the upgrade path.';