Repository navigation
Commit e207b97
feat(plugin-security): a boot upgrade impact report for the non-access classes, raised to the administrator like the access upgrade report (#25512)
<!-- claude-projects-attribution: {"github_login":"os-sales"} -->
_Requested by **Sales** · [project
thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuUSKmaWjm1sW7kkQuwKewbx)_
Part of #24447
Clause-②: yes (widening)
Metadata-protocol: none
Claim 6367925751 (`domain:services` seat 1, #6021). This is the services
half of the card: the non-access upgrade report (items 2 and 4 of ruling
6259332195). The spec half, retiring
`packages/spec/src/kernel/package-upgrade.zod.ts` with its ADR-0087
conversion, belongs to the spec lane. It was asked for in comment
6367978063 and in pointer 6367986096 on #18549, and it has no card yet,
so the card stays open for it. The two halves have no ordering between
them. The branch is the harness-pinned `claude/project-thread-mfvys8`,
the same one #25489 (#25413) landed from. This PR calls the delivery
helper that PR added.
## What changed
An upgrade that renames, removes or collides with a name could leave the
customer's stored state meaning something else. Nothing said so beyond a
scattered log line. At every boot, right after the access upgrade
report, the security plugin now reads that state against the registry.
It reports what it finds and changes nothing.
- **New `plugin-security/src/upgrade-impact-report.ts`.** One finding
list per class.
- **Switches (flow, action, webhook ledger rows):**
- a row whose package no longer ships the name;
- a switched-off row whose package now ships names of that type that no
switch covers (a suspected rename that makes the item active again);
- a switched-off action whose name more than one object declares (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);
- a `sys_webhook` row holding a declared webhook's name (the declaration
is withheld for the whole deployment).
- **Overlay orphans:** an active `sys_metadata` row of a view,
dashboard, report, translation or email template, bound to an installed
code or marketplace package that no longer ships the item. Bundled types
are judged per member.
- **Object extensions:**
- a field an extension declares that the base layer (the overlay, else
the owner) or another extension also declares, where the last
declaration wins;
- a stored object row bound to a package that does not own the object,
which the boot refuses to serve;
- a navigation contribution whose group its app no longer has (read from
`getAppNavDiagnostics`).
- **Settings:** a stored `sys_setting` / `sys_platform_setting` key that
its registered namespace no longer declares. The value is never read,
and the scan does not select the value column.
- **Not judged, on purpose (named in the module header):**
- a package-less object row over a packaged object, which is ADR-0005's
sanctioned overlay and cannot be told from a stale one;
- rows bound to an authored, unknown or disabled package;
- an unregistered settings namespace;
- a contribution to an app that is missing (the registry does not list
those).
- **Who is told.** The findings go through `upgrade-report-delivery.ts`
from #25489, under their own topic `system.upgrade_impact` and a
`dedupKey` over the sorted finding set.
- Under `single`: the Default Organization's administrators and the
operators.
- Under a wall: the operator line. When `OS_PLATFORM_OWNER_ORGANIZATION`
is set, a copy also goes in-app in that organization.
- Each category logs one `error` with its fix. A clean boot logs one
`info` line.
- **Stops.** A store that cannot be read, or settings manifests that
cannot be read, stop the report for that boot with one `warn` naming
what was unreadable. An unread answer is never taken for "resolves
nowhere", and nobody is told.
- **`security-plugin.ts`:** one call in the `kernel:bootstrapped` hook
after the access report. It has its own `try`/`catch`, and the
`settings` and `messaging` services are looked up by name.
- **Changeset:** `patch` for `@objectstack/plugin-security`.
Not touched: `packages/spec`, the engine lane (objectql, core,
metadata-*, platform-objects), service-automation, plugin-webhooks,
service-settings, the ADR.
## Tests
- **`upgrade-impact-report.test.ts` (39 cases), every class with a
CONTROL.**
- The engine double's `find` refuses a combinator or an object value.
- The registry double answers as the real one does. `getArtifactItem`
prefers the named package, else any package. `listItems` hides disabled
packages. Contributors come back as own, overlay, extend.
- Covered: the renamed flow under `single` and `isolated`, a disabled
package, a tenant-authored flow, a row with no package, a switched-ON
orphan, plural type spellings, inactive rows, another package shipping
the same name, bundled members, the overlay as base layer, ADR-0005's
package-less overlay, missing navigation diagnostics, a layout-only
setting specifier, a missing settings service, four unreadable stores
and unreadable manifests, absent stores, no registry, a stable
`dedupKey`, and no write verb ever asked for.
- **`upgrade-impact-report.wiring.test.ts` (5 cases).** It uses a real
`ObjectQL`, `SqlDriver` and `SecurityPlugin`, with a vendor package
before and after its upgrade, an add-on with an object extension and a
navigation contribution, and real customer rows.
- `single` upgraded: all 9 categories, and one emit under the topic to
the operator and the Default Organization's administrator.
- `isolated` upgraded: the operator line and no emit.
- Both postures, CONTROL: the clean line.
- The logged lines replayed through the core logger: no value is
redacted, and the setting key is named.
## Verification
- **Package suite:** plugin-security `pnpm test`: exit 0, 217 files,
4529 passed and 45 skipped. `pnpm typecheck` (tsc, scripts and
test-typecheck): exit 0. eslint on the touched files: exit 0.
- **Dispatch gates:** on this head (`7e12df4c01`, after a full `pnpm
build`, 76 of 76 tasks), all 69 commands that `dispatch-gates
--commands` derived exit 0. `--ran` reconciles 69 of 69, with exit codes
recorded. Before #25489 merged, the same commit on a branch that also
carried #25489 derived 100 commands, and all 100 exit 0.
`check-governed-merges --pr 25512`: not governed.
- **Ablations** (committed source mutated with
`scripts/ablation-replace.mjs` and restored; `git diff HEAD` is empty
after each):
1. The `security-plugin.ts` call skipped: 5 red (every wiring case).
2. The base layer left out of the collision check: 5 red.
3. The overlay's own-package check dropped: 1 red.
4. The undeclared-key test off: 5 red.
5. The shared-action threshold raised from more than 1 object to more
than 99: 4 red.
6. A store read failure swallowed as an empty store: 4 red (the four
stop cases).
- **R5:** production `os serve` of the showcase on a persistent SQLite
file, with `OS_PLATFORM_OWNER_EMAIL=op@r4.example` and
`OS_AUTH_MEMBERSHIP_POLICY=invite-only`.
- The impact seed writes these rows directly:
- a switched-off ledger flow `showcase_recalc_estimate_old`;
- a dashboard overlay `showcase_ops_dashboard_old` bound to
`com.example.showcase`;
- a package-less stored flow `showcase_recalc_task_estimate`;
- a `sys_webhook` row `showcase_task_changed`;
- an object row `showcase_task` bound to `app.r5_custom`;
- a `sys_setting` `company.legacy_tagline`.
- The CONTROL seed writes the same rows under names that are still
shipped or not taken.
- The showcase ships no add-on extension, nav contribution or
second-object action, so those three categories are pinned by the wiring
test only.
| boot | single | isolated |
|:--|:--|:--|
| clean | the clean `info` line | same |
| impact | 6 category `error` lines, one `sys_notification`
(`system.upgrade_impact`) in the Default Organization | setting unset:
the 6 lines, then the operator line ("this log is where they are
raised"), with no notification. Setting `r4-platform-ops`: the 6 lines,
then the operator line with "A copy is raised in-app …" and
`organizationId: org_r4_platform`, one `sys_notification` in
`org_r4_platform`, and one `sys_inbox_message` for `usr_r4_operator` |
| impact again | `[messaging] emit: dedupKey … already emitted;
skipping` | same, still one row |
| CONTROL | the clean line, no new notification | same |
The R5 isolated boot caught one defect, fixed here before this PR
opened. The setting finding first carried the key as `key`. The core
logger redacts any field of that name, so the line read
`"key":"***REDACTED***"`. The field is now `name`, and the fifth wiring
case pins it through the real logger. The rig's only other `ERROR` under
`isolated` is `[auth] no_sign_in_account_at_boot`. The rig caused it by
inserting `sys_user` directly, and it appears on the CONTROL boot too.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01XHebWJmL3ZgbVpjM6JERNA)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01XHebWJmL3ZgbVpjM6JERNA
Co-authored-by: Claude <noreply@anthropic.com>1 parent 7d3c013 commit e207b97
5 files changed
Lines changed: 1832 additions & 0 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 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
| 51 | + | |
51 | 52 | | |
52 | 53 | | |
53 | 54 | | |
| |||
4770 | 4771 | | |
4771 | 4772 | | |
4772 | 4773 | | |
| 4774 | + | |
| 4775 | + | |
| 4776 | + | |
| 4777 | + | |
| 4778 | + | |
| 4779 | + | |
| 4780 | + | |
| 4781 | + | |
| 4782 | + | |
| 4783 | + | |
| 4784 | + | |
| 4785 | + | |
| 4786 | + | |
| 4787 | + | |
| 4788 | + | |
| 4789 | + | |
| 4790 | + | |
| 4791 | + | |
| 4792 | + | |
| 4793 | + | |
| 4794 | + | |
| 4795 | + | |
| 4796 | + | |
| 4797 | + | |
4773 | 4798 | | |
4774 | 4799 | | |
4775 | 4800 | | |
| |||
0 commit comments