Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .changeset/24447-upgrade-impact-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'@objectstack/plugin-security': patch
---

feat: a boot upgrade impact report for the non-access classes (flows, actions, webhooks, overlays, object extensions, navigation contributions, settings), raised to the administrator like the access upgrade report

Clause-②: yes (widening: a new boot report and a new notification topic, `system.upgrade_impact`; nothing is written or refused, and no existing path changes)
Metadata-protocol: none

A package upgrade that renames, removes or collides with a name could leave the customer's stored state meaning something else, with nothing said beyond a scattered log line. At every boot, beside the access upgrade report, the security plugin now reads that state against the registry and lists:

- **Flow, action and webhook switches:** an activation ledger row whose name no package ships any more, and a switched-off one whose package now ships unswitched names of the same type (a suspected rename that makes the item active again). Also a switched-off action whose name more than one object now declares, because the one row switches every one of them off.
- **Stored rows a package now overrides:** a stored flow whose name a package ships (the packaged flow runs, the stored one never does), and a `sys_webhook` row holding the name of a webhook a package declares (the declaration is withheld for the whole deployment).
- **Overlay orphans:** a customization of a view, dashboard, report, translation or email template bound to an installed package that no longer ships the item. It keeps serving as an item of its own.
- **Object extensions:** a field an extension declares that the object it extends (or another package's extension) also declares, where the last declaration silently wins. Also a stored object row bound to a package that does not own the object, because another package now ships the name, which the boot refuses to serve. And a navigation contribution whose group its app no longer has.
- **Settings:** a stored value whose namespace no longer declares its key. The value is never read, and the default applies. The report never reads a value.

Each category logs one `error` with its fix. The finding set is raised through the same delivery as the access report, under its own topic and `dedupKey`. Under `single` it goes to the Default Organization's administrators. Under a wall it goes to the operator alone, and in-app in the organization `OS_PLATFORM_OWNER_ORGANIZATION` names when that is set. A clean boot logs one `info` line. A store that cannot be read stops the report for that boot, and it says so.
25 changes: 25 additions & 0 deletions packages/plugins/plugin-security/src/security-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
type DeclaredSharingRuleReader,
} from './access-upgrade-report.js';
import { ACCESS_UPGRADE_CHECK_SERVICE, createAccessUpgradeCheck } from './access-upgrade-check.js';
import { reportUpgradeImpact, SETTINGS_SERVICE, type SettingsManifestReader } from './upgrade-impact-report.js';
import {
explainAccessForAsker,
buildContextForUser,
Expand Down Expand Up @@ -4759,6 +4760,30 @@ export class SecurityPlugin implements Plugin {
{ error: (e as Error)?.message },
);
}
// [#24447] The upgrade impact report: the same comparison for the
// non-access classes — flow, action and webhook switches, stored
// overlays and object rows, object extensions, navigation
// contributions and stored settings — raised the same way. It writes
// and refuses nothing. See `upgrade-impact-report.ts`.
try {
const optional = <T,>(name: string): T | undefined => {
try { return ctx.getService<T>(name) ?? undefined; } catch { return undefined; }
};
const settings = optional<Partial<SettingsManifestReader>>(SETTINGS_SERVICE);
const messaging = optional<Partial<AccessUpgradeNotifier>>('messaging');
await reportUpgradeImpact(ql as any, {
settings: typeof settings?.listManifests === 'function' ? (settings as SettingsManifestReader) : undefined,
messaging: typeof messaging?.emit === 'function' ? (messaging as AccessUpgradeNotifier) : undefined,
walled: postureEnforcesWall(this.tenancyPosture),
logger: ctx.logger,
});
} catch (e) {
ctx.logger.warn(
'[security] the upgrade impact report did not run — a switch, stored row, extension or setting an ' +
'upgrade left behind goes unreported for this boot',
{ error: (e as Error)?.message },
);
}
});
}

Expand Down
Loading
Loading